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