datacenter / acitoolkit

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

Unexpected objects behaviour #300

Open AlmogCohen opened 7 years ago

AlmogCohen commented 7 years ago

It seems like when calling AppProfile.get(session, tenant), the tenant object is being modified in some unknown way. Before I run this I can deep look into the tenant children, and after I run it all of the deep information is gone.

Unless there is a very good explanation, the practice of changing the object under the hood is very unexpected and quirky. Any thought? It took me a lot of time to debug, find and understand this problem!

Code example to reproduct:

from pprint import pprint

from acitoolkit.acisession import Session
from acitoolkit.acitoolkit import Tenant, EPG, AppProfile
from acitoolkit.acitoolkitlib import Credentials

def explore(tenant):
    apps = tenant.get_children(only_class=AppProfile)
    for app in apps:
        if app.name == 'Payroll':
            pprint(app.get_json())

def get_tenant(session):
    tenants = Tenant.get_deep(session)
    for i in tenants:
        if i.name == "Test":
            return i

if __name__ == "__main__":
    creds = Credentials().get()

    session = Session(creds.url, creds.login, creds.password, subscription_enabled=False)
    session.login()

    tenant = get_tenant(session)
    print "########## First Fetch ###############"
    explore(tenant)

    print "########## Second Fetch ###############"
    AppProfile.get(session, tenant)
    explore(tenant)

    print "############## Third Fetch ##############"
    new_tenant = get_tenant(session)
    explore(new_tenant)

Output:

########## First Fetch ###############
{'fvAp': {'attributes': {'name': 'Payroll'},
          'children': [{'fvAEPg': {'attributes': {'name': u'Web'},
                                   'children': [{'fvRsProv': {'attributes': {'tnVzBrCPName': 'web_connection'}}}]}},
                       {'fvAEPg': {'attributes': {'name': u'HR_zone'},
                                   'children': [{'fvRsCons': {'attributes': {'tnVzBrCPName': 'web_connection'}}},
                                                {'fvRsBd': {'attributes': {'tnFvBDName': 'bd1'}}}]}}]}}
########## Second Fetch ###############
{'fvAp': {'attributes': {'name': 'Payroll'}, 'children': []}}
############## Third Fetch ##############
{'fvAp': {'attributes': {'name': 'Payroll'},
          'children': [{'fvAEPg': {'attributes': {'name': u'Web'},
                                   'children': [{'fvRsProv': {'attributes': {'tnVzBrCPName': 'web_connection'}}}]}},
                       {'fvAEPg': {'attributes': {'name': u'HR_zone'},
                                   'children': [{'fvRsCons': {'attributes': {'tnVzBrCPName': 'web_connection'}}},
                                                {'fvRsBd': {'attributes': {'tnFvBDName': 'bd1'}}}]}}]}}

Notice how all of the children disappear! I would be happy to help with any additional input.

Busesz commented 7 years ago

Hello! I have a very similar problem, i am on the debugging phase now. In my code, i do the same, except that i only fetch once. I have a get_tenant method, just like you. Then I search for 2 epg.name, when i got them i give back the names, and the AppProfile name. After this, i want to find the contract.name between the 2 epg and the result is NoneType. It gives back a list like => apps = tenant.get_children(AppProfile) but when i foreach on the apps, i see that in the apps there is no AppProfilr object :/ i dont understand why, i think it has the same reason as you. So this is my code [...]

def get_contract_name(tenant, appname, epg1name, epg2name): apps = tenant.get_children(AppProfile) container1 = [ ] container2 = [ ] for app in apps: if app.name == appname: epgs = app.get_children(EPG) for epg in epgs: if epg.name == epg1name: container1.extend((epg.get_all_consumed)) elif epg.name == epg2name: container2.extend((epg.get_all_provided))

pile1 = set(container1) pile2 = set(container2)

section = pile1 & pile2 for each in section: return each

[...]

michsmit99 commented 7 years ago

The Tenant.get_deep() call will return the Tenant objects and their children. The AppProfile.get() call will only return the AppProfile objects and not the children. In general, get() returns just the objects requested and the parent objects but not the children. get_deep() returns the children in addition to what get() returns.

michsmit99 commented 7 years ago

@Busesz, I'm not sure if you copied the code correctly into the comment. It looks like your call to epg.get_all_consumed has some misplaced brackets i.e.

container1.extend((epg.get_all_consumed))

should be

container1.extend(epg.get_all_consumed())

The same error is in the call to get_all_provided. The first case will add the actual function to your list, where the second case will call the function and add the returned result to the list.

AlmogCohen commented 7 years ago

I know. Please notice that I'm not printing anything about the get function. I call get_deep on the tenant and the contents of the tenant are changing completely after calling the get on a completely different object. This is a bug or a proper buggy design :)

On Apr 22, 2017 12:18, "Michael Smith" notifications@github.com wrote:

The Tenant.get_deep() call will return the Tenant objects and their children. The AppProfile.get() call will only return the AppProfile objects and not the children. In general, get() returns just the objects requested and the parent objects but not the children. get_deep() returns the children in addition to what get() returns.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/datacenter/acitoolkit/issues/300#issuecomment-296395382, or mute the thread https://github.com/notifications/unsubscribe-auth/ADtXZj5SkCf5oToYq891pABzMTiKVqUiks5rylKLgaJpZM4NE4uj .

michsmit99 commented 7 years ago

I know. Please notice that I'm not printing anything about the get function. I call get_deep on the tenant and the contents of the tenant are changing completely after calling the get on a completely different object. This is a bug or a proper buggy design :) …

On Apr 22, 2017 12:18, "Michael Smith" @.***> wrote: The Tenant.get_deep() call will return the Tenant objects and their children. The AppProfile.get() call will only return the AppProfile objects and not the children. In general, get() returns just the objects requested and the parent objects but not the children. get_deep() returns the children in addition to what get() returns.

Ok, I see what you're saying now. I think this falls into the category of proper buggy design :) The call to get() will go to the APIC, retrieve the AppProfile objects (without children since it is a get() function call), and stick them under the Tenant object which will essentially overwrite the existing AppProfile objects retrieved in the first call to get_deep().

Busesz commented 7 years ago

Yep, sorry, i made a mistake, i wrote it by heart, at 6 am. You are right! But my problem is that when the loop should start on the AppProfile list, it doesn't give back any ApplicationProfile, it seems that not get into the loop, i don't use any simple .get() function in my whole code, before that method, i get the app, and the epgs.

michsmit99 commented 7 years ago

Yep, sorry, i made a mistake, i wrote it by heart, at 6 am. You are right! But my problem is that when the loop should start on the AppProfile list, it doesn't give back any ApplicationProfile, it seems that not get into the loop, i don't use any simple .get() function in my whole code, before that method, i get the app, and the epgs.

@Busesz, how are you getting the tenant information ? Your code snippet is doesn't show the call to tenant.get_deep() which would populate the AppProfile objects.

Busesz commented 7 years ago

Actually i found the problem, when i pass an AppProfile list to a method, it doesn't contain any children, doesn't matter the .get_deep() method: This is my get_Tenant:

def _get_tenant(self, tenantn):
        tenants = Tenant.get_deep(self.client)
        for tenant in tenants:
            if tenant.name == tenantn:
                return tenant

So when i use this tenant, i look up the AppProfile list in an other method, that AppProfile List is passed to an other method, like this:

def _get_app_prof_list(self, tenant):
   Apps = tenant.get_children(AppProfile)
   return Apps

def _get_provided_contr(self, AppPro, epgn):
      for app in AppPro:
            for epg in app.get_children(EPG):
                  if epg.name == epgn:
                      return epg.get_all_provided()

def myCode(self):
  tenantname = 'XYZ'
  epgname = 'XYZEPG'
  tenant =  self._get_tenant(tenantname)
  AppPro = self._get_app_prof_list(tenant) 
  Contracts = self._get_provided_contr(AppPro, epgname) => So this AppPro doesn't contain any children element, so i wont get the Contracts which are provided by an epg. (I made a debug  on the AppPro json)

The only way to solve this problem, i have to pass the tenant for every method where i want to work with a Subobject (maybe this is an existing word), like EPGs, AppProfiles, Contracts, Filters, etc, and i have to run a lot of for loop on that. object every time. I hope my explanation was understandable. :)