Package Products :: Package ZenModel :: Module IpNetwork |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenModel.IpNetwork

      1  ###########################################################################
      2  #
      3  # This program is part of Zenoss Core, an open source monitoring platform.
      4  # Copyright (C) 2008, Zenoss Inc.
      5  #
      6  # This program is free software; you can redistribute it and/or modify it
      7  # under the terms of the GNU General Public License version 2 as published by
      8  # the Free Software Foundation.
      9  #
     10  # For complete information please visit: http://www.zenoss.com/oss/
     11  #
     12  ###########################################################################
     13
     14  __doc__ = """IpNetwork
     15
     16  IpNetwork represents an IP network which contains
     17  many IP addresses.
     18  """
     19
     20  import math
     21  import transaction
     22  from xml.dom import minidom
     23  import logging
     24  log = logging.getLogger('zen')
     25
     26  from Globals import DTMLFile
     27  from Globals import InitializeClass
     28  from Acquisition import aq_base
     29  from AccessControl import ClassSecurityInfo
     30  from AccessControl import Permissions as permissions
     31  from Products.ZenModel.ZenossSecurity import *
     32
     33  from Products.ZenUtils.IpUtil import *
     34  from Products.ZenRelations.RelSchema import *
     35  from Products.ZenUtils.Search import makeCaseInsensitiveFieldIndex
     36
     37  from IpAddress import IpAddress
     38  from DeviceOrganizer import DeviceOrganizer
     39
     40  from Products.ZenModel.Exceptions import *
     41
     42  from Products.ZenUtils.Utils import isXmlRpc, setupLoggingHeader, executeCommand
     43  from Products.ZenUtils.Utils import binPath, clearWebLoggingStream
     44  from Products.ZenUtils import NetworkTree
     45  from Products.ZenUtils.Utils import edgesToXML
     46  from Products.ZenUtils.Utils import unused
     47  from Products.Jobber.jobs import ShellCommandJob, JobMessenger
     48  from Products.Jobber.status import SUCCESS, FAILURE
     49  from Products.ZenWidgets import messaging
     50
     51 -def manage_addIpNetwork(context, id, netmask=24, REQUEST = None):
     52      """make a IpNetwork"""
     53      net = IpNetwork(id, netmask=netmask)
     54      context._setObject(net.id, net)
     55      if id == "Networks":
     56          net = context._getOb(net.id)
     57          net.buildZProperties()
     58          net.createCatalog()
     59          #manage_addZDeviceDiscoverer(context)
     60      if REQUEST is not None:
     61          REQUEST['RESPONSE'].redirect(context.absolute_url()+'/manage_main')
     62
     63
     64  addIpNetwork = DTMLFile('dtml/addIpNetwork',globals())
     65
     66
     67  # when an ip is added the defaul location will be
     68  # into class A->B->C network tree
     69  defaultNetworkTree = (32,)
     70
     71 -class IpNetwork(DeviceOrganizer):
     72      """IpNetwork object"""
     73
     74      isInTree = True
     75
     76      buildLinks = True
     77
     78      # Organizer configuration
     79      dmdRootName = "Networks"
     80
     81      # Index name for IP addresses
     82      default_catalog = 'ipSearch'
     83
     84      portal_type = meta_type = 'IpNetwork'
     85
     86      _properties = (
     87          {'id':'netmask', 'type':'int', 'mode':'w'},
     88          {'id':'description', 'type':'text', 'mode':'w'},
     89          )
     90
     91      _relations = DeviceOrganizer._relations + (
     92          ("ipaddresses", ToManyCont(ToOne, "Products.ZenModel.IpAddress", "network")),
     93          ("clientroutes", ToMany(ToOne,"Products.ZenModel.IpRouteEntry","target")),
     94          ("location", ToOne(ToMany, "Products.ZenModel.Location", "networks")),
     95          )
     96
     97      # Screen action bindings (and tab definitions)
     98      factory_type_information = (
     99          {
    100              'id'             : 'IpNetwork',
    101              'meta_type'      : 'IpNetwork',
    102              'description'    : """Arbitrary device grouping class""",
    103              'icon'           : 'IpNetwork_icon.gif',
    104              'product'        : 'ZenModel',
    105              'factory'        : 'manage_addIpNetwork',
    106              'immediate_view' : 'viewNetworkOverview',
    107              'actions'        :
    108              (
    109                  { 'id'            : 'overview'
    110                  , 'name'          : 'Overview'
    111                  , 'action'        : 'viewNetworkOverview'
    112                  , 'permissions'   : (
    113                    permissions.view, )
    114                  },
    115                  { 'id'            : 'zProperties'
    116                  , 'name'          : 'Configuration Properties'
    117                  , 'action'        : 'zPropertyEdit'
    118                  , 'permissions'   : ("Manage DMD",)
    119                  },
    120                  { 'id'            : 'viewHistory'
    121                  , 'name'          : 'Modifications'
    122                  , 'action'        : 'viewHistory'
    123                  , 'permissions'   : (ZEN_VIEW_MODIFICATIONS,)
    124                  },
    125              )
    126            },
    127          )
    128
    129      security = ClassSecurityInfo()
    130
    131
    132 -    def __init__(self, id, netmask=24, description=''):
    133          if id.find("/") > -1: id, netmask = id.split("/",1)
    134          DeviceOrganizer.__init__(self, id, description)
    135          if id != "Networks":
    136              checkip(id)
    137          self.netmask = maskToBits(netmask)
    138          self.description = description
    139
    140      security.declareProtected('Change Network', 'manage_addIpNetwork')
    141 -    def manage_addIpNetwork(self, newPath, REQUEST=None):
    142          """
    143          From the GUI, create a new subnet (if necessary)
    144          """
    145          net = self.createNet(newPath)
    146          if REQUEST is not None:
    147              REQUEST['RESPONSE'].redirect(net.absolute_url())
    148
    149 -    def checkValidId(self, id, prep_id = False):
    150          """Checks a valid id
    151          """
    152          if id.find("/") > -1: id, netmask = id.split("/",1)
    153          return super(IpNetwork, self).checkValidId(id, prep_id)
    154
    155
    156 -    def getNetworkRoot(self):
    157          """This is a hook method do not remove!"""
    158          return self.dmd.getDmdRoot("Networks")
    159
    160
    161 -    def createNet(self, netip, netmask=24):
    162          """
    163          Return and create if necessary network.  netip is in the form
    164          1.1.1.0/24 or with netmask passed as parameter.  Subnetworks created
    165          based on the zParameter zDefaulNetworkTree.
    166          Called by IpNetwork.createIp and IpRouteEntry.setTarget
    167          If the netmask is invalid, then a netmask of 24 is assumed.
    168
    169          @param netip: network IP address start
    170          @type netip: string
    171          @param netmask: network mask
    172          @type netmask: integer
    173          @todo: investigate IPv6 issues
    174          """
    175          if netip.find("/") > -1: netip, netmask = netip.split("/",1)
    176          try:
    177              netmask = int(netmask)
    178          except (TypeError, ValueError):
    179              netmask = 24
    180          if netmask < 0 or netmask >= 64:
    181              netmask = 24
    182
    183          #hook method do not remove!
    184          netroot = self.getNetworkRoot()
    185          netobj = netroot.getNet(netip)
    186          if netmask == 0:
    187              raise ValueError("netip '%s' without netmask" % netip)
    188          if netobj and netobj.netmask >= netmask: # Network already exists.
    189              return netobj
    190
    191          netip = getnetstr(netip,netmask)
    192          netTree = getattr(self, 'zDefaultNetworkTree', defaultNetworkTree)
    193          netTree = map(int, netTree)
    194          if netobj:
    195              # strip irrelevant values from netTree if we're not starting at /0
    196              netTree = [ m for m in netTree if m > netobj.netmask ]
    197          else:
    198              # start at /Networks if no containing network was found
    199              netobj = netroot
    200
    201          for treemask in netTree:
    202              if treemask >= netmask:
    203                  netobjParent = netobj
    204                  netobj = netobj.addSubNetwork(netip, netmask)
    205                  self.rebalance(netobjParent, netobj)
    206                  break
    207              else:
    208                  supnetip = getnetstr(netip, treemask)
    209                  netobjParent = netobj
    210                  netobj = netobj.addSubNetwork(supnetip, treemask)
    211                  self.rebalance(netobjParent, netobj)
    212
    213          return netobj
    214
    215
    216 -    def rebalance(self, netobjParent, netobj):
    217          """
    218          Look for children of the netobj at this level and move them to the
    219          right spot.
    220          """
    221          moveList = []
    222          for subnetOrIp in netobjParent.children():
    223              if subnetOrIp == netobj:
    224                  continue
    225              if netobj.hasIp(subnetOrIp.id):
    226                  moveList.append(subnetOrIp.id)
    227          if moveList:
    228              netobjPath = netobj.getOrganizerName()[1:]
    229              netobjParent.moveOrganizer(netobjPath, moveList)
    230
    231 -    def findNet(self, netip, netmask=0):
    232          """
    233          Find and return the subnet of this IpNetwork that matches the requested
    234          netip and netmask.
    235          """
    236          if netip.find("/") >= 0:
    237              netip, netmask = netip.split("/", 1)
    238              netmask = int(netmask)
    239          for subnet in [self] + self.getSubNetworks():
    240              if netmask == 0 and subnet.id == netip:
    241                  return subnet
    242              if subnet.id == netip and subnet.netmask == netmask:
    243                  return subnet
    244          return None
    245
    246
    247 -    def getNet(self, ip):
    248          """Return the net starting form the Networks root for ip.
    249          """
    250          return self._getNet(ip)
    251
    252
    253 -    def _getNet(self, ip):
    254          """Recurse down the network tree to find the net of ip.
    255          """
    256
    257          # If we can find the IP in the catalog, use it. This is fast.
    258          brains = self.ipSearch(id=ip)
    259          path = self.getPrimaryUrlPath()
    260          for brain in brains:
    261              bp = brain.getPath()
    262              if bp.startswith(path):
    263                  return self.unrestrictedTraverse('/'.join(bp.split('/')[:-2]))
    264
    265          # Otherwise we have to traverse the entire network hierarchy.
    266          for net in self.children():
    267              if net.hasIp(ip):
    268                  if len(net.children()):
    269                      subnet = net._getNet(ip)
    270                      if subnet:
    271                          return subnet
    272                      else:
    273                          return net
    274                  else:
    275                      return net
    276
    277
    278 -    def createIp(self, ip, netmask=24):
    279          """Return an ip and create if nessesary in a hierarchy of
    280          subnetworks based on the zParameter zDefaulNetworkTree.
    281          """
    282          ipobj = self.findIp(ip)
    283          if ipobj: return ipobj
    284          netobj = self.createNet(ip, netmask)
    285          ipobj = netobj.addIpAddress(ip,netmask)
    286          return ipobj
    287
    288
    289 -    def freeIps(self):
    290          """Number of free Ips left in this network.
    291          """
    292          freeips = int(math.pow(2,32-self.netmask)-(self.countIpAddresses()))
    293          if self.netmask >= 31:
    294              return freeips
    295          return freeips - 2
    296
    297
    298 -    def hasIp(self, ip):
    299          """Does network have (contain) this ip.
    300          """
    301          start = numbip(self.id)
    302          end = start + math.pow(2,32-self.netmask)
    303          return start <= numbip(ip) < end
    304
    305
    306 -    def fullIpList(self):
    307          """Return a list of all ips in this network.
    308          """
    309          if (self.netmask == 32): return [self.id]
    310          ipnumb = numbip(self.id)
    311          maxip = math.pow(2,32-self.netmask)
    312          start = int(ipnumb+1)
    313          end = int(ipnumb+maxip-1)
    314          return map(strip, range(start,end))
    315
    316
    317 -    def deleteUnusedIps(self):
    318          """Delete ips that are unused in this network.
    319          """
    320          for ip in self.ipaddresses():
    321              if ip.device(): continue
    322              self.ipaddresses.removeRelation(ip)
    323
    324
    325 -    def defaultRouterIp(self):
    326          """Return the ip of the default router for this network.
    327          It is based on zDefaultRouterNumber which specifies the sequence
    328          number that locates the router in this network.  If:
    329          zDefaultRouterNumber==1 for 10.2.1.0/24 -> 10.2.1.1
    330          zDefaultRouterNumber==254 for 10.2.1.0/24 -> 10.2.1.254
    331          zDefaultRouterNumber==1 for 10.2.2.128/25 -> 10.2.2.129
    332          zDefaultRouterNumber==126 for 10.2.2.128/25 -> 10.2.2.254
    333          """
    334          roffset = getattr(self, "zDefaultRouterNumber", 1)
    335          return strip((numbip(self.id) + roffset))
    336
    337
    338 -    def getNetworkName(self):
    339          """return the full network name of this network"""
    340          return "%s/%d" % (self.id, self.netmask)
    341
    342
    343      security.declareProtected('View', 'primarySortKey')
    344 -    def primarySortKey(self):
    345          """
    346          Sort by the IP numeric
    347
    348          >>> net = dmd.Networks.addSubNetwork('1.2.3.0', 24)
    349          >>> net.primarySortKey()
    350          16909056L
    351          """
    352          return numbip(self.id)
    353
    354
    355      security.declareProtected('Change Network', 'addSubNetwork')
    356 -    def addSubNetwork(self, ip, netmask=24):
    357          """Return and add if nessesary subnetwork to this network.
    358          """
    359          netobj = self.getSubNetwork(ip)
    360          if not netobj:
    361              net = IpNetwork(ip, netmask)
    362              self._setObject(ip, net)
    363          return self.getSubNetwork(ip)
    364
    365
    366      security.declareProtected('View', 'getSubNetwork')
    367 -    def getSubNetwork(self, ip):
    368          """get an ip on this network"""
    369          return self._getOb(ip, None)
    370
    371
    372 -    def getSubNetworks(self):
    373          """Return all network objects below this one.
    374          """
    375          nets = self.children()
    376          for subgroup in self.children():
    377              nets.extend(subgroup.getSubNetworks())
    378          return nets
    379
    380      security.declareProtected('Change Network', 'addIpAddress')
    381 -    def addIpAddress(self, ip, netmask=24):
    382          """add ip to this network and return it"""
    383          ipobj = IpAddress(ip,netmask)
    384          self.ipaddresses._setObject(ip, ipobj)
    385          return self.getIpAddress(ip)
    386
    387
    388      security.declareProtected('View', 'getIpAddress')
    389 -    def getIpAddress(self, ip):
    390          """get an ip on this network"""
    391          return self.ipaddresses._getOb(ip, None)
    392
    393      security.declareProtected('Change Network', 'manage_deleteIpAddresses')
    394 -    def manage_deleteIpAddresses(self, ipaddresses=(), REQUEST=None):
    395          """Delete ipaddresses by id from this network.
    396          """
    397          for ipaddress in ipaddresses:
    398              ip = self.getIpAddress(ipaddress)
    399              self.ipaddresses.removeRelation(ip)
    400          if REQUEST:
    401              return self.callZenScreen(REQUEST)
    402
    403
    404      security.declareProtected('View', 'countIpAddresses')
    405 -    def countIpAddresses(self, inuse=False):
    406          """get an ip on this network"""
    407          if inuse:
    408              # When there are a large number of IPs this code is too slow
    409              # we either need to cache all /Status/Ping events before hand
    410              # and then integrate them with the list of IPs
    411              # or blow off the whole feature.  For now we just set the
    412              # default to not use this code.  -EAD
    413              count = len(filter(lambda x: x.getStatus() == 0,self.ipaddresses()))
    414          else:
    415              count = self.ipaddresses.countObjects()
    416          for net in self.children():
    417              count += net.countIpAddresses(inuse)
    418          return count
    419
    420      security.declareProtected('View', 'countDevices')
    421      countDevices = countIpAddresses
    422
    423
    424 -    def getAllCounts(self, devrel=None):
    425          """Count all devices within a device group and get the
    426          ping and snmp counts as well"""
    427          unused(devrel)
    428          counts = [
    429              self.ipaddresses.countObjects(),
    430              self._status("Ping", "ipaddresses"),
    431              self._status("Snmp", "ipaddresses"),
    432          ]
    433          for group in self.children():
    434              sc = group.getAllCounts()
    435              for i in range(3): counts[i] += sc[i]
    436          return counts
    437
    438
    439 -    def pingStatus(self, devrel=None):
    440          """aggregate ping status for all devices in this group and below"""
    441          unused(devrel)
    442          return DeviceOrganizer.pingStatus(self, "ipaddresses")
    443
    444
    445 -    def snmpStatus(self, devrel=None):
    446          """aggregate snmp status for all devices in this group and below"""
    447          unused(devrel)
    448          return DeviceOrganizer.snmpStatus(self, "ipaddresses")
    449
    450
    451 -    def getSubDevices(self, filter=None):
    452          """get all the devices under and instance of a DeviceGroup"""
    453          return DeviceOrganizer.getSubDevices(self, filter, "ipaddresses")
    454
    455
    456 -    def findIp(self, ip):
    457          """Find an ipAddress.
    458          """
    459          searchCatalog = self.getDmdRoot("Networks").ipSearch
    460          ret = searchCatalog(dict(id=ip))
    461          if not ret: return None
    462          if len(ret) > 1:
    463              raise IpAddressConflict( "IP address conflict for IP: %s" % ip )
    464          return ret[0].getObject()
    465
    466
    467 -    def buildZProperties(self):
    468          nets = self.getDmdRoot("Networks")
    469          if getattr(aq_base(nets), "zDefaultNetworkTree", False):
    470              return
    471          nets._setProperty("zDefaultNetworkTree", (24,32), type="lines")
    472          nets._setProperty("zDrawMapLinks", True, type="boolean")
    473          nets._setProperty("zAutoDiscover", True, type="boolean")
    474          nets._setProperty("zPingFailThresh", 168, type="int")
    475          nets._setProperty("zIcon", "/zport/dmd/img/icons/network.png")
    476          nets._setProperty("zPreferSnmpNaming", False, type="boolean")
    477          nets._setProperty("zSnmpStrictDiscovery", False, type="boolean")
    478
    479
    480 -    def reIndex(self):
    481          """Go through all ips in this tree and reindex them."""
    482          zcat = self._getCatalog()
    483          zcat.manage_catalogClear()
    484          transaction.savepoint()
    485          for net in self.getSubNetworks():
    486              for ip in net.ipaddresses():
    487                  ip.index_object()
    488              transaction.savepoint()
    489
    490
    491 -    def createCatalog(self):
    492          """make the catalog for device searching"""
    493          from Products.ZCatalog.ZCatalog import manage_addZCatalog
    494
    495          # XXX convert to ManagableIndex
    496          manage_addZCatalog(self, self.default_catalog,
    497                              self.default_catalog)
    498          zcat = self._getOb(self.default_catalog)
    499          cat = zcat._catalog
    500          cat.addIndex('id', makeCaseInsensitiveFieldIndex('id'))
    501          zcat.addColumn('getPrimaryId')
    502
    503
    504 -    def discoverNetwork(self, REQUEST=None):
    505          """
    506          """
    507          path = '/'.join(self.getPrimaryPath()[4:])
    508          return self.discoverDevices([path], REQUEST=REQUEST)
    509
    510 -    def discoverDevices(self, organizerPaths=None, REQUEST = None):
    511          """
    512          Load a device into the database connecting its major relations
    513          and collecting its configuration.
    514          """
    515          xmlrpc = isXmlRpc(REQUEST)
    516
    517          if not organizerPaths:
    518              if xmlrpc: return 1
    519              return self.callZenScreen(REQUEST)
    520
    521          zDiscCommand = "empty"
    522
    523          from Products.ZenUtils.ZenTales import talesEval
    524
    525          orgroot = self.getNetworkRoot()
    526          for organizerName in organizerPaths:
    527              organizer = orgroot.getOrganizer(organizerName)
    528              if organizer is None:
    529                  if xmlrpc: return 1 # XML-RPC error
    530                  log.error("Couldn't obtain a network entry for '%s' "
    531                              "-- does it exist?" % organizerName)
    532                  continue
    533
    534              zDiscCommand = getattr(organizer, "zZenDiscCommand", None)
    535              if zDiscCommand:
    536                  cmd = talesEval('string:' + zDiscCommand, organizer).split(" ")
    537              else:
    538                  cmd = ["zendisc", "run", "--net", organizer.getNetworkName()]
    539                  if getattr(organizer, "zSnmpStrictDiscovery", False):
    540                      cmd += ["--snmp-strict-discovery"]
    541                  if getattr(organizer, "zPreferSnmpNaming", False):
    542                      cmd += ["--prefer-snmp-naming"]
    543              zd = binPath('zendisc')
    544              zendiscCmd = [zd] + cmd[1:]
    545              status = self.dmd.JobManager.addJob(ShellCommandJob, zendiscCmd)
    546
    547          log.info('Done')
    548
    549          if REQUEST and not xmlrpc:
    550              REQUEST.RESPONSE.redirect('/zport/dmd/JobManager/joblist')
    551
    552          if xmlrpc: return 0
    553
    554
    555 -    def setupLog(self, response):
    556          """setup logging package to send to browser"""
    557          from logging import StreamHandler, Formatter
    558          root = logging.getLogger()
    559          self._v_handler = StreamHandler(response)
    560          fmt = Formatter("""<tr class="tablevalues">
    561          <td>%(asctime)s</td><td>%(levelname)s</td>
    562          <td>%(name)s</td><td>%(message)s</td></tr>
    563          """, "%Y-%m-%d %H:%M:%S")
    564          self._v_handler.setFormatter(fmt)
    565          root.addHandler(self._v_handler)
    566          root.setLevel(10)
    567
    568
    569 -    def clearLog(self):
    570          alog = logging.getLogger()
    571          if getattr(self, "_v_handler", False):
    572              alog.removeHandler(self._v_handler)
    573
    574
    575 -    def loaderFooter(self, response):
    576          """add navigation links to the end of the loader output"""
    577          response.write("""<tr class="tableheader"><td colspan="4">
    578              Navigate to network <a href=%s>%s</a></td></tr>"""
    579              % (self.absolute_url(), self.id))
    580          response.write("</table></body></html>")
    581
    582      security.declareProtected('View', 'getXMLEdges')
    583 -    def getXMLEdges(self, depth=1, filter='/', start=()):
    584          """ Gets XML """
    585          if not start: start=self.id
    586          edges = NetworkTree.get_edges(self, depth,
    587                                        withIcons=True, filter=filter)
    588          return edgesToXML(edges, start)
    589
    590 -    def getIconPath(self):
    591          """ gets icon """
    592          try:
    593              return self.primaryAq().zIcon
    594          except AttributeError:
    595              return '/zport/dmd/img/icons/noicon.png'
    596
    597
    598 -    def urlLink(self, text=None, url=None, attrs={}):
    599          """
    600          Return an anchor tag if the user has access to the remote object.
    601          @param text: the text to place within the anchor tag or string.
    602                       Defaults to the id of this object.
    603          @param url: url for the href. Default is getPrimaryUrlPath
    604          @type attrs: dict
    605          @param attrs: any other attributes to be place in the in the tag.
    606          @return: An HTML link to this object
    607          @rtype: string
    608          """
    609          if not text:
    610              text = "%s/%d" % (self.id, self.netmask)
    611          if not self.checkRemotePerm("View", self):
    612              return text
    613          if not url:
    614              url = self.getPrimaryUrlPath()
    615          if len(attrs):
    616              return '<a href="%s" %s>%s</a>' % (url,
    617                  ' '.join(['%s="%s"' % (x,y) for x,y in attrs.items()]),
    618                  text)
    619          else:
    620              return '<a href="%s">%s</a>' % (url, text)
    621
    622  InitializeClass(IpNetwork)
    623
    624
    625
    626 -class AutoDiscoveryJob(ShellCommandJob):
    627      """
    628      Job encapsulating autodiscovery over a set of IP addresses.
    629
    630      Accepts a list of strings describing networks OR a list of strings
    631      specifying IP ranges, not both. Also accepts a set of zProperties to be
    632      set on devices that are discovered.
    633      """
    634 -    def __init__(self, jobid, nets=(), ranges=(), zProperties=()):
    635          # Store the nets and ranges
    636          self.nets = nets
    637          self.ranges = ranges
    638          self.zProperties = zProperties
    639
    640          # Set up the job, passing in a blank command (gets set later)
    641          super(AutoDiscoveryJob, self).__init__(jobid, '')
    642
    643 -    def run(self, r):
    644          transaction.commit()
    645          log = self.getStatus().getLog()
    646
    647          # Store zProperties on the job
    648          if self.zProperties:
    649              self.getStatus().setZProperties(**self.zProperties)
    650              transaction.commit()
    651
    652          # Build the zendisc command
    653          cmd = [binPath('zendisc')]
    654          cmd.extend(['run', '--now',
    655                     '--monitor', 'localhost',
    656                     '--deviceclass', '/Discovered',
    657                     '--parallel', '8',
    658                     '--job', self.getUid()
    659                     ])
    660          if not self.nets and not self.ranges:
    661              # Gotta have something
    662              log.write("ERROR: Must pass in a network or a range.")
    663              self.finished(FAILURE)
    664          elif self.nets and self.ranges:
    665              # Can't have both
    666              log.write("ERROR: Must pass in either networks or ranges, "
    667                        "not both.")
    668              self.finished(FAILURE)
    669          else:
    670              if self.nets:
    671                  for net in self.nets:
    672                      cmd.extend(['--net', net])
    673              elif self.ranges:
    674                  for iprange in self.ranges:
    675                      cmd.extend(['--range', iprange])
    676              self.cmd = cmd
    677              super(AutoDiscoveryJob, self).run(r)
    678
    679 -    def finished(self, r):
    680          if self.nets:
    681              details = 'networks %s' % ', '.join(self.nets)
    682          elif self.ranges:
    683              details = 'IP ranges %s' % ', '.join(self.ranges)
    684          if r==SUCCESS:
    685              JobMessenger(self).sendToUser(
    686                  'Discovery Complete',
    687                  'Discovery of %s has completed successfully.' % details
    688              )
    689          elif r==FAILURE:
    690              JobMessenger(self).sendToUser(
    691                  'Discovery Failed',
    692                  'An error occurred discovering %s.' % details,
    693                  priority=messaging.WARNING
    694              )
    695          super(AutoDiscoveryJob, self).finished(r)
    696
    697
    698 -class IpNetworkPrinter(object):
    699
    700 -    def __init__(self, out):
    701          """out is the output stream to print to"""
    702          self._out = out
    703
    704
    705 -class TextIpNetworkPrinter(IpNetworkPrinter):
    706      """
    707      Prints out IpNetwork hierarchy as text with indented lines.
    708      """
    709
    710 -    def printIpNetwork(self, net):
    711          """
    712          Print out the IpNetwork and IpAddress hierarchy under net.
    713          """
    714          self._printIpNetworkLine(net)
    715          self._printTree(net)
    716
    717 -    def _printTree(self, net, indent="  "):
    718          for child in net.children():
    719              self._printIpNetworkLine(child, indent)
    720              self._printTree(child, indent + "  ")
    721          for ipaddress in net.ipaddresses():
    722              args = (indent, ipaddress, ipaddress.__class__.__name__)
    723              self._out.write("%s%s (%s)\n" % args)
    724
    725 -    def _printIpNetworkLine(self, net, indent=""):
    726          args = (indent, net.id, net.netmask, net.__class__.__name__)
    727          self._out.write("%s%s/%s (%s)\n" % args)
    728
    729
    730 -class PythonIpNetworkPrinter(IpNetworkPrinter):
    731      """
    732      Prints out the IpNetwork hierarchy as a python dictionary.
    733      """
    734
    735 -    def printIpNetwork(self, net):
    736          """
    737          Print out the IpNetwork and IpAddress hierarchy under net.
    738          """
    739          tree = {}
    740          self._createTree(net, tree)
    741          from pprint import pformat
    742          self._out.write("%s\n" % pformat(tree))
    743
    744 -    def _walkTree(self, net, tree):
    745          for child in net.children():
    746              self._createTree(child, tree)
    747          for ip in net.ipaddresses():
    748              key = (ip.__class__.__name__, ip.id, ip.netmask)
    749              tree[key] = True
    750
    751 -    def _createTree(self, net, tree):
    752          key = (net.__class__.__name__, net.id, net.netmask)
    753          subtree = {}
    754          tree[key] = subtree
    755          self._walkTree(net, subtree)
    756
    757
    758 -class XmlIpNetworkPrinter(IpNetworkPrinter):
    759      """
    760      Prints out the IpNetwork hierarchy as XML.
    761      """
    762
    763 -    def printIpNetwork(self, net):
    764          """
    765          Print out the IpNetwork and IpAddress hierarchy under net.
    766          """
    767          self._doc = minidom.parseString('<root/>')
    768          root = self._doc.documentElement
    769          self._createTree(net, root)
    770          self._out.write(self._doc.toprettyxml())
    771
    772 -    def _walkTree(self, net, tree):
    773          for child in net.children():
    774              self._createTree(child, tree)
    775          for ip in net.ipaddresses():
    776              self._appendChild(tree, ip)
    777
    778 -    def _createTree(self, net, tree):
    779          node = self._appendChild(tree, net)
    780          self._walkTree(net, node)
    781
    782 -    def _appendChild(self, tree, child):
    783          node = self._doc.createElement(child.__class__.__name__)
    784          node.setAttribute("id", child.id)
    785          node.setAttribute("netmask", str(child.netmask))
    786          tree.appendChild(node)
    787          return node
    788
    789
    790 -class IpNetworkPrinterFactory(object):
    791
    792 -    def __init__(self):
    793          self._printerFactories = {'text': TextIpNetworkPrinter,
    794                                    'python': PythonIpNetworkPrinter,
    795                                    'xml': XmlIpNetworkPrinter}
    796
    797 -    def createIpNetworkPrinter(self, format, out):
    798          if format in self._printerFactories:
    799              factory = self._printerFactories[format]
    800              return factory(out)
    801          else:
    802              args = (format, self._printerFactories.keys())
    803              raise Exception("Invalid format '%s' must be one of %s" % args)
    804