datacenter / acitoolkit

A basic toolkit for accessing the Cisco APIC
Other
350 stars 266 forks source link

Unable to get filter entries via FilterEntry class #192

Open russellpope opened 8 years ago

russellpope commented 8 years ago

Hey there,

I'm trying to get the filter entries via FilterEntry.get(session, parent=contract, tenant=tenant) but it seems to return empty lists.

Assuming I was doing something wrong I tried your sample her: samples/aci-show-filter-entries.py and I get the same empty output: Filter Entries Contract Tenant


Process finished with exit code 0

I'm logging in with the admin account on the APIC. When I use the API browser I see one that I expect: url: https://10.1.2.201/api/node/mo/uni/tn-kpsc/flt-https_inbound.json?query-target=children&target-subtree-class=vzEntry&subscription=yes response: {"totalCount":"1","subscriptionId":"72058006372483231","imdata":[{"vzEntry":{"attributes":{"applyToFrag":"no","arpOpc":"unspecified","childAction":"","dFromPort":"https","dToPort":"https","descr":"","dn":"uni/tn-kpsc/flt-https_inbound/e-https_in","etherT":"ip","icmpv4T":"unspecified","icmpv6T":"unspecified","lcOwn":"local","matchDscp":"unspecified","modTs":"2016-06-08T17:04:21.134+00:00","name":"https_in","prot":"tcp","sFromPort":"unspecified","sToPort":"unspecified","stateful":"no","status":"","tcpRules":"","uid":"15374"}}}]}

I'm certain I'm doing something wrong here but I can't seem to sort it out.

Any help would be appreciated!

editing to add information.

I debugged it and I see when I hit this URL with a REST client like postman I get: 'https://10.1.2.201/api/mo/uni/tn-kpsc.json?query-target=subtree&target-subtree-class=vzRsSubjFiltAtt'

It returns this JSON (I've truncated it for brevity):

{
  "totalCount": "5",
  "imdata": [
    {
      "vzRsSubjFiltAtt": {
        "attributes": {
          "childAction": "",
          "dn": "uni\/tn-kpsc\/brc-ICMP\/subj-ICMP\/rssubjFiltAtt-icmp",
          "forceResolve": "yes",
          "lcOwn": "local",
          "modTs": "2015-10-13T15:23:16.905+00:00",
          "monPolDn": "uni\/tn-common\/monepg-default",
          "rType": "mo",
          "state": "formed",
          "stateQual": "none",
          "status": "",
          "tCl": "vzFilter",
          "tContextDn": "",
          "tDn": "uni\/tn-common\/flt-icmp",
          "tRn": "flt-icmp",
          "tType": "name",
          "tnVzFilterName": "icmp",
          "uid": "15374"
        }
      }
    }
...

Which does include the filters I'd expect to see so I think my configuration on the device is fine.

russellpope commented 8 years ago

Just a quick update. I'm going over this function and I see that based on how the conditional is written I never actually get a True result because some of the things it's comparing are uppercase to lowercase. Also my parent object appears to not always be the tenant the contract/filter was defined in so it doesn't find the filterentries in the query_url.

Here's what I've hacked up so far. Debating adding logic to check the common tenant if the parent.name tenant value comes up empty.

   @classmethod
    def get(cls, session, parent, tenant):
        """
        To get all of acitoolkit style Filter Entries APIC class.

        :param session:  the instance of Session used for APIC communication
        :param parent:  Object to assign as the parent to the created objects.
        :param tenant:  Tenant object to assign the created objects.
        """

        apic_class = 'vzRsSubjFiltAtt'

        if isinstance(tenant, str):
            raise TypeError
        logging.debug('%s.get called', cls.__name__)
        if tenant is None:
            tenant_url = ''
        else:
            tenant_url = '/tn-%s' % tenant.name
            if parent is not None:
                tenant_url = tenant_url + parent._get_url_extension()
        query_url = ('/api/mo/uni%s.json?query-target=subtree&'
                     'target-subtree-class=%s' % (tenant_url, apic_class))
        ret = session.get(query_url)
        data = ret.json()['imdata']
        logging.debug('response returned %s', data)
        resp = []
        for object_data in data:
            dn = object_data['vzRsSubjFiltAtt']['attributes']['dn']
            tDn = object_data['vzRsSubjFiltAtt']['attributes']['tDn']
            tRn = object_data['vzRsSubjFiltAtt']['attributes']['tRn']
            if dn.split('/')[2][4:].upper() == parent.name.upper() and \
               dn.split('/')[4][len(apic_class) - 1:].upper() == dn.split('/')[3][5:].upper() and \
               dn.split('/')[3][5:].upper() == tDn.split('/')[2][4:].upper() and \
                            tDn.split('/')[2][4:].upper() == tRn[4:].upper():
                filter_name = str(object_data[apic_class]['attributes']['tRn'][4:])
                contract_name = filter_name[:len(parent.name)]
                entry_name = filter_name[:len(parent.name)]
                if contract_name.upper() == parent.name.upper() and entry_name != '':
                    query_url = ('/api/mo/uni%s/flt-%s.json?query-target=subtree&'
                                 'target-subtree-class=vzEntry&'
                                 'query-target-filter=eq(vzEntry.name,"%s")' % (tenant_url, filter_name, entry_name))
                    ret = session.get(query_url)
                    filter_data = ret.json()['imdata']
                    if len(filter_data) == 0:
                        continue
                    logging.debug('response returned %s', filter_data)
                    resp = []
                    obj = cls(entry_name, parent)
                    attribute_data = filter_data[0]['vzEntry']['attributes']
                    obj._populate_from_attributes(attribute_data)
                    resp.append(obj)
        return resp

Any thoughts? Has anyone else seen this behavior?

michsmit99 commented 8 years ago

The APIC object model uses vzBrCP, vzSubj, vzRsSubjFiltAtt, vzFilter, vzEntry classes to define the contract and filter entries. Originally, the ACI toolkit simplified this to only Contract and FilterEntry. These are mapped directly to vzBrCP and vzEntry respectively. The intermediate objects (vzSubj, vzRsSubjFiltAtt, vzFilter) were automatically created and named using a mashup of the FilterEntry name. The current FilterEntry.get() will only collect the FilterEntry objects that were created in this manner i.e. it only collects FilterEntry objects that were created by the ACI toolkit. This is likely the reason you are not seeing the entries.

Since then, the ACI toolkit has added the ContractSubject and Filter classes to map to vzSubj and vzFilter. It also continues to support placing the FilterEntry directly within the Contract in the previous manner. The FilterEntry.get() needs to be updated to support this and provide the ability to collect all of the filter entries in the APIC. We should also update the sample script so that it can collect all of the filter entries.

russellpope commented 8 years ago

Awesome, thanks for the update. I'm glad I'm not totally insane. I'll see what I can sort out based on your guidance. Looking forward to your next update. I'm actually enjoying working with the toolkit otherwise.

michsmit99 commented 8 years ago

Another way is to use the get_deep() function. I've added a new sample file to the repo showing how this can be done. https://github.com/datacenter/acitoolkit/blob/master/samples/aci-show-all-filter-entries.py

russellpope commented 8 years ago

Hrmm, I get an error:

Traceback (most recent call last):
  File "/Users/ldh/github.kovarus/aci-applications/aci-show-all-filter-entries.py", line 68, in <module>
    for aci_filter in tenant.get_children(ACI.Filter):
AttributeError: 'module' object has no attribute 'Filter'

I tried swapping it to ACI.FilterEntry and it still returns nothing. Going to try in a new clean venv in case I broke something.

michsmit99 commented 8 years ago

I just pulled a new clone on Ubuntu and the script is working in a fresh install. Is it possible you need to re-run "python setup.py install" ? Filter was added to the init.py file in February 2016. If your install was run before that, it will need to be re-run.

russellpope commented 8 years ago

Oops yeap I was just being dopey. New venv and a re-install sorted me.

nick00783 commented 8 years ago

I just meet the same issue as yours,then I make a little change to the Filter.get() function,an it worked...Here is the script: ` def get(cls, session, parent, tenant): """ To get all of acitoolkit style Filter Entries APIC class.

    :param session:  the instance of Session used for APIC communication
    :param parent:  Object to assign as the parent to the created objects.
    :param tenant:  Tenant object to assign the created objects.
    """

    apic_class = 'vzRsSubjFiltAtt'

    if isinstance(tenant, str):
        raise TypeError
    logging.debug('%s.get called', cls.__name__)
    if tenant is None:
        tenant_url = ''
    else:
        tenant_url = '/tn-%s' % tenant.name
        if parent is not None:
            tenant_url = tenant_url + parent._get_url_extension()
    query_url = ('/api/mo/uni%s.json?query-target=subtree&'
                 'target-subtree-class=%s' % (tenant_url, apic_class))
    ret = session.get(query_url)
    data = ret.json()['imdata']
    logging.debug('response returned %s', data)
    resp = []

    for object_data in data:
        dn = object_data['vzRsSubjFiltAtt']['attributes']['dn']
        tDn = object_data['vzRsSubjFiltAtt']['attributes']['tDn']
        tRn = object_data['vzRsSubjFiltAtt']['attributes']['tRn']
        if dn.split('/')[2][4:] == parent.name:
            filter_name = str(object_data[apic_class]['attributes']['tRn'][4:])
            contract_name = dn.split('/')[2][4:]
            if tDn.split('/')[1]=='tn-common':
                query_url=('/api/mo/uni/tn-common/flt-%s.json?query-target=subtree&'
                             'target-subtree-class=vzEntry' % filter_name)
            else:
                query_url = ('/api/mo/uni%s/flt-%s.json?query-target=subtree&'
                             'target-subtree-class=vzEntry' % (tenant_url, filter_name))
            ret = session.get(query_url)
            filter_data = ret.json()['imdata']
            logging.debug('response returned %s', filter_data)
            entry_name=filter_data[0]['vzEntry']['attributes']['name']
            obj = cls(entry_name, parent)
            attribute_data = filter_data[0]['vzEntry']['attributes']
            obj._populate_from_attributes(attribute_data)
            resp.append(obj)
    return resp`