0

This is a modified and much simpler version of my production program where I replicated the error I´m having. My program should load a list of IP addresses (for routers) and work with them by sending commands and collecting return information. The list is loaded with all IPs, but my program logic should skip the ones which are not online (online variable is False), that is, commands should not be run in those routers which are offline.

Class Router represents each router in the list of IPs. It has 3 object-level variables: addr for router address, online for online status (True or False) and infoBasic for a dict of information like {'Name': 'router name', 'Uptime': 'time router is up', 'OS': 'IOS version'} and so on. For routers which are not online, this dict should be empty ({}). The main program will never interact with this class as it is instantiated by the next class (RouterList)

class RouterList represents a list of all the Router objects, and this is the one that has methods to interact with the main. Initially it loads a list of IP addresses into routerList variable, then it instantiates each router and saves into a dict routerDict the IP address as the key, and the Router object as the value. To mix things up, I manually set the second IP as offline.

In RouterList, my getName method (and all the other methods) should loop through routerDict and run commands to get the router names and write that info in the Router object, in the dict variable infoBasic. In this case, getName calls getBasic passing the Router object, which calls the Router object runComm method passing the string "sh version". Don´t mind the silly logic here, in my real production program I´m using caching decorators to run a command only once and store it for later use.

In this same getName method inside the loop it should skip offline routers. At the end of the program I call getRouters, which should just print this dict of routerList and its inner information. Not only it populated the information in the router which was offline, but it also duplicated the same information in all the router objects (more specifically it looks like it applied the same thing for all items of the dict, so the last one was the one that overlapped at the end).

Here is my full program:

class Router:
  addr = None
  online = False
  infoBasic = {}

  def __init__(self, addr = None):
    self.addr = addr
    if self.addr == '192.168.10.50':
      self.online = False
    else:
      self.online = True

  def runComm(self, command = None):
    if command == None:
      return "Error"
    return "Return of command for router {}".format(self.addr)

class RouterList:
  routerList = None
  routerDict = {}

  def __init__(self, routerList = None):
    if routerList == None:
      self.routerList = ['192.168.10.10', '192.168.10.50', '192.168.10.100']
    else:
      self.routerList = routerList

    for routerAddr in self.routerList:
      router = Router(routerAddr)
      self.routerDict[routerAddr] = router
    print("Router list loaded")

  def getRouters(self):
    """ Return list of routers """
    for router in self.routerDict.values():
      # print below should show the 3 routers but only .10 and .100 with infoBasic populated
      print(router.addr + ', online = ' + str(router.online) + ' - ' + str(router.infoBasic))

  def getBasic(self, *args, **kwargs):
    """ Call router runComm method and return show version output """
    return args[0].runComm("sh version")

  def getName(self):
    """ Call router runComm method and return show version output """
    for router in self.routerDict.values():
      if router.online == True:
        print('Setting name for ' + router.addr)
        # below I tried to use the self dict to write the return of getBasic
        self.routerDict[router.addr].infoBasic['Name'] = self.getBasic(router)

  def getUptime(self):
    """ Return router uptime """
    for router in self.routerDict.values():
      if router.online == True:
        # below I tried to use the router variable from the for loop to write the return of getBasic
        router.infoBasic['Uptime'] = self.getBasic(router)

if __name__ == '__main__':
  rl = RouterList()
  rl.getName()
  rl.getUptime()

  rl.getRouters()

This is the output:

# python3 simulate-error.py
Router list loaded
Setting name for 192.168.10.10
Setting name for 192.168.10.100
192.168.10.10, online = True - {'Name': 'Return of command for router 192.168.10.100', 'Uptime': 'Return of command for router 192.168.10.100'}
192.168.10.50, online = False - {'Name': 'Return of command for router 192.168.10.100', 'Uptime': 'Return of command for router 192.168.10.100'}
192.168.10.100, online = True - {'Name': 'Return of command for router 192.168.10.100', 'Uptime': 'Return of command for router 192.168.10.100'}

As you can see, in the "Setting name for..." area, it looks like the routers which were offline really were skipped. But still in the end, all routers ended up with the same information. I know I may be overlooking something really foolish and obvious but I can´t find it no matter how many times I review. Can you help?

Adriano_Pinaffo
  • 1,429
  • 4
  • 23
  • 46
  • 2
    You are using class attributes (i.e. static attributes), you are sharing your dicts across instances. – juanpa.arrivillaga May 22 '19 at 17:25
  • 2
    `routerDict` and `infoBasic` are *class* attributes shared by every instance of the classes they belong to. You probably want them to be *instance* attributes like `routerList` (which *shadows* the class attribute `RouterList.routerList`). – chepner May 22 '19 at 17:25
  • 2
    Specifically, `self.routerDict[router.addr].infoBasic['Name'] = self.getBasic(router)` always mutates the same `infoBasic` dict. All `Router` instances share the same one. – Peter DeGlopper May 22 '19 at 17:27
  • Oh my, I really thought the class attributes were unique for each instantiation. It all makes sense now. Now I understand why decorators in fact work. Thank you. PS: I would suggest you guys answered the question so I could mark as accepted response, but @juanpa.arrivillaga already set it as duplicate. No problem. – Adriano_Pinaffo May 22 '19 at 19:19

0 Answers0