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

# Source Code for Module Products.ZenModel.IpNetwork

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