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  import logging
     23  log = logging.getLogger('zen')
     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
     35
     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 ShellCommandJob, JobMessenger
     47  from Products.Jobber.status import SUCCESS, FAILURE
     48  from Products.ZenWidgets import messaging
     49
     50 -def manage_addIpNetwork(context, id, netmask=24, REQUEST = None):
     51      """make a IpNetwork"""
     52      net = IpNetwork(id, netmask=netmask)
     53      context._setObject(net.id, net)
     54      if id == "Networks":
     55          net = context._getOb(net.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 defaul 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      _properties = (
     86          {'id':'netmask', 'type':'int', 'mode':'w'},
     87          {'id':'description', 'type':'text', 'mode':'w'},
     88          )
     89
     90      _relations = DeviceOrganizer._relations + (
     91          ("ipaddresses", ToManyCont(ToOne, "Products.ZenModel.IpAddress", "network")),
     92          ("clientroutes", ToMany(ToOne,"Products.ZenModel.IpRouteEntry","target")),
     93          ("location", ToOne(ToMany, "Products.ZenModel.Location", "networks")),
     94          )
     95
     96      # Screen action bindings (and tab definitions)
     97      factory_type_information = (
     98          {
     99              'id'             : 'IpNetwork',
    100              'meta_type'      : 'IpNetwork',
    101              'description'    : """Arbitrary device grouping class""",
    102              'icon'           : 'IpNetwork_icon.gif',
    103              'product'        : 'ZenModel',
    104              'factory'        : 'manage_addIpNetwork',
    105              'immediate_view' : 'viewNetworkOverview',
    106              'actions'        :
    107              (
    108                  { 'id'            : 'overview'
    109                  , 'name'          : 'Overview'
    110                  , 'action'        : 'viewNetworkOverview'
    111                  , 'permissions'   : (
    112                    permissions.view, )
    113                  },
    114                  { 'id'            : 'zProperties'
    115                  , 'name'          : 'zProperties'
    116                  , 'action'        : 'zPropertyEdit'
    117                  , 'permissions'   : ("Manage DMD",)
    118                  },
    119                  { 'id'            : 'viewHistory'
    120                  , 'name'          : 'Modifications'
    121                  , 'action'        : 'viewHistory'
    122                  , 'permissions'   : (ZEN_VIEW_MODIFICATIONS,)
    123                  },
    124              )
    125            },
    126          )
    127
    128      security = ClassSecurityInfo()
    129
    130
    131 -    def __init__(self, id, netmask=24, description=''):
    132          if id.find("/") > -1: id, netmask = id.split("/",1)
    133          DeviceOrganizer.__init__(self, id, description)
    134          if id != "Networks":
    135              checkip(id)
    136          self.netmask = maskToBits(netmask)
    137          self.description = description
    138
    139
    140 -    def checkValidId(self, id, prep_id = False):
    141          """Checks a valid id
    142          """
    143          if id.find("/") > -1: id, netmask = id.split("/",1)
    144          return super(IpNetwork, self).checkValidId(id, prep_id)
    145
    146
    147 -    def getNetworkRoot(self):
    148          """This is a hook method do not remove!"""
    149          return self.dmd.getDmdRoot("Networks")
    150
    151
    152 -    def createNet(self, netip, netmask=24):
    153          """
    154          Return and create if necessary network.  netip is in the form
    155          1.1.1.0/24 or with netmask passed as parameter.  Subnetworks created
    156          based on the zParameter zDefaulNetworkTree.
    157          Called by IpNetwork.createIp and IpRouteEntry.setTarget
    158          If the netmask is invalid, then a netmask of 24 is assumed.
    159
    160          @param netip: network IP address start
    161          @type netip: string
    162          @param netmask: network mask
    163          @type netmask: integer
    164          @todo: investigate IPv6 issues
    165          """
    166          if netip.find("/") > -1: netip, netmask = netip.split("/",1)
    167          try:
    168              netmask = int(netmask)
    169          except (TypeError, ValueError):
    170              netmask = 24
    171          if netmask < 0 or netmask >= 64:
    172              netmask = 24
    173
    174          #hook method do not remove!
    175          netroot = self.getNetworkRoot()
    176          netobj = netroot.getNet(netip)
    177          if netobj and netobj.netmask == netmask: # Network already exists.
    178              return netobj
    179          if netmask == 0:
    180              raise ValueError("netip '%s' without netmask" % netip)
    181          netip = getnetstr(netip,netmask)
    182          netTree = getattr(self, 'zDefaultNetworkTree', defaultNetworkTree)
    183          netTree = map(int, netTree)
    184          if netobj:
    185              # strip irrelevant values from netTree if we're not starting at /0
    186              netTree = [ m for m in netTree if m > netobj.netmask ]
    187          else:
    188              # start at /Networks if no containing network was found
    189              netobj = netroot
    190
    191          for treemask in netTree:
    192              if treemask >= netmask:
    193                  netobj = netobj.addSubNetwork(netip, netmask)
    194                  break
    195              else:
    196                  supnetip = getnetstr(netip, treemask)
    197                  netobj = netobj.addSubNetwork(supnetip, treemask)
    198          return netobj
    199
    200
    201 -    def findNet(self, netip, netmask=0):
    202          """
    203          Find and return the subnet of this IpNetwork that matches the requested
    204          netip and netmask.
    205          """
    206          if netip.find("/") >= 0:
    207              netip, netmask = netip.split("/", 1)
    208              netmask = int(netmask)
    209          for subnet in [self] + self.getSubNetworks():
    210              if netmask == 0 and subnet.id == netip:
    211                  return subnet
    212              if subnet.id == netip and subnet.netmask == netmask:
    213                  return subnet
    214          return None
    215
    216
    217 -    def getNet(self, ip):
    218          """Return the net starting form the Networks root for ip.
    219          """
    220          return self._getNet(ip)
    221
    222
    223 -    def _getNet(self, ip):
    224          """Recurse down the network tree to find the net of ip.
    225          """
    226          for net in self.children():
    227              if net.hasIp(ip):
    228                  if len(net.children()):
    229                      subnet = net._getNet(ip)
    230                      if subnet:
    231                          return subnet
    232                      else:
    233                          return net
    234                  else:
    235                      return net
    236
    237
    238 -    def createIp(self, ip, netmask=24):
    239          """Return an ip and create if nessesary in a hierarchy of
    240          subnetworks based on the zParameter zDefaulNetworkTree.
    241          """
    242          ipobj = self.findIp(ip)
    243          if ipobj: return ipobj
    244          netobj = self.createNet(ip, netmask)
    245          ipobj = netobj.addIpAddress(ip,netmask)
    246          return ipobj
    247
    248
    249 -    def freeIps(self):
    250          """Number of free Ips left in this network.
    251          """
    252          freeips = int(math.pow(2,32-self.netmask)-(self.countIpAddresses()))
    253          if self.netmask >= 31:
    254              return freeips
    255          return freeips - 2
    256
    257
    258 -    def hasIp(self, ip):
    259          """Does network have (contain) this ip.
    260          """
    261          start = numbip(self.id)
    262          end = start + math.pow(2,32-self.netmask)
    263          return start <= numbip(ip) < end
    264
    265
    266 -    def fullIpList(self):
    267          """Return a list of all ips in this network.
    268          """
    269          if (self.netmask == 32): return [self.id]
    270          ipnumb = numbip(self.id)
    271          maxip = math.pow(2,32-self.netmask)
    272          start = int(ipnumb+1)
    273          end = int(ipnumb+maxip-1)
    274          return map(strip, range(start,end))
    275
    276
    277 -    def deleteUnusedIps(self):
    278          """Delete ips that are unused in this network.
    279          """
    280          for ip in self.ipaddresses():
    281              if ip.device(): continue
    282              self.ipaddresses.removeRelation(ip)
    283
    284
    285 -    def defaultRouterIp(self):
    286          """Return the ip of the default router for this network.
    287          It is based on zDefaultRouterNumber which specifies the sequence
    288          number that locates the router in this network.  If:
    289          zDefaultRouterNumber==1 for 10.2.1.0/24 -> 10.2.1.1
    290          zDefaultRouterNumber==254 for 10.2.1.0/24 -> 10.2.1.254
    291          zDefaultRouterNumber==1 for 10.2.2.128/25 -> 10.2.2.129
    292          zDefaultRouterNumber==126 for 10.2.2.128/25 -> 10.2.2.254
    293          """
    294          roffset = getattr(self, "zDefaultRouterNumber", 1)
    295          return strip((numbip(self.id) + roffset))
    296
    297
    298 -    def getNetworkName(self):
    299          """return the full network name of this network"""
    300          return "%s/%d" % (self.id, self.netmask)
    301
    302
    303      security.declareProtected('View', 'primarySortKey')
    304 -    def primarySortKey(self):
    305          """make sure that networks sort correctly"""
    306          return numbip(self.id)
    307
    308
    309      security.declareProtected('Change Network', 'addSubNetwork')
    310 -    def addSubNetwork(self, ip, netmask=24):
    311          """Return and add if nessesary subnetwork to this network.
    312          """
    313          netobj = self.getSubNetwork(ip)
    314          if not netobj:
    315              net = IpNetwork(ip, netmask)
    316              self._setObject(ip, net)
    317          return self.getSubNetwork(ip)
    318
    319
    320      security.declareProtected('View', 'getSubNetwork')
    321 -    def getSubNetwork(self, ip):
    322          """get an ip on this network"""
    323          return self._getOb(ip, None)
    324
    325
    326 -    def getSubNetworks(self):
    327          """Return all network objects below this one.
    328          """
    329          nets = self.children()
    330          for subgroup in self.children():
    331              nets.extend(subgroup.getSubNetworks())
    332          return nets
    333
    334      security.declareProtected('Change Network', 'addIpAddress')
    335 -    def addIpAddress(self, ip, netmask=24):
    336          """add ip to this network and return it"""
    337          ipobj = IpAddress(ip,netmask)
    338          self.ipaddresses._setObject(ip, ipobj)
    339          return self.getIpAddress(ip)
    340
    341
    342      security.declareProtected('View', 'getIpAddress')
    343 -    def getIpAddress(self, ip):
    344          """get an ip on this network"""
    345          return self.ipaddresses._getOb(ip, None)
    346
    347      security.declareProtected('Change Network', 'manage_deleteIpAddresses')
    348 -    def manage_deleteIpAddresses(self, ipaddresses=(), REQUEST=None):
    349          """Delete ipaddresses by id from this network.
    350          """
    351          for ipaddress in ipaddresses:
    352              ip = self.getIpAddress(ipaddress)
    353              self.ipaddresses.removeRelation(ip)
    354          if REQUEST:
    355              return self.callZenScreen(REQUEST)
    356
    357
    358      security.declareProtected('View', 'countIpAddresses')
    359 -    def countIpAddresses(self, inuse=False):
    360          """get an ip on this network"""
    361          if inuse:
    362              # When there are a large number of IPs this code is too slow
    363              # we either need to cache all /Status/Ping events before hand
    364              # and then integrate them with the list of IPs
    365              # or blow off the whole feature.  For now we just set the
    366              # default to not use this code.  -EAD
    367              count = len(filter(lambda x: x.getStatus() == 0,self.ipaddresses()))
    368          else:
    369              count = self.ipaddresses.countObjects()
    370          for net in self.children():
    371              count += net.countIpAddresses(inuse)
    372          return count
    373
    374      security.declareProtected('View', 'countDevices')
    375      countDevices = countIpAddresses
    376
    377
    378 -    def getAllCounts(self, devrel=None):
    379          """Count all devices within a device group and get the
    380          ping and snmp counts as well"""
    381          unused(devrel)
    382          counts = [
    383              self.ipaddresses.countObjects(),
    384              self._status("Ping", "ipaddresses"),
    385              self._status("Snmp", "ipaddresses"),
    386          ]
    387          for group in self.children():
    388              sc = group.getAllCounts()
    389              for i in range(3): counts[i] += sc[i]
    390          return counts
    391
    392
    393 -    def pingStatus(self, devrel=None):
    394          """aggregate ping status for all devices in this group and below"""
    395          unused(devrel)
    396          return DeviceOrganizer.pingStatus(self, "ipaddresses")
    397
    398
    399 -    def snmpStatus(self, devrel=None):
    400          """aggregate snmp status for all devices in this group and below"""
    401          unused(devrel)
    402          return DeviceOrganizer.snmpStatus(self, "ipaddresses")
    403
    404
    405 -    def getSubDevices(self, filter=None):
    406          """get all the devices under and instance of a DeviceGroup"""
    407          return DeviceOrganizer.getSubDevices(self, filter, "ipaddresses")
    408
    409
    410 -    def findIp(self, ip):
    411          """Find an ipAddress.
    412          """
    413          searchCatalog = self.getDmdRoot("Networks").ipSearch
    414          ret = searchCatalog(dict(id=ip))
    415          if not ret: return None
    416          if len(ret) > 1:
    417              raise IpAddressConflict( "IP address conflict for IP: %s" % ip )
    418          return ret[0].getObject()
    419
    420
    421 -    def buildZProperties(self):
    422          nets = self.getDmdRoot("Networks")
    423          if getattr(aq_base(nets), "zDefaultNetworkTree", False):
    424              return
    425          nets._setProperty("zDefaultNetworkTree", (24,32), type="lines")
    426          nets._setProperty("zDrawMapLinks", True, type="boolean")
    427          nets._setProperty("zAutoDiscover", True, type="boolean")
    428          nets._setProperty("zPingFailThresh", 168, type="int")
    429          nets._setProperty("zIcon", "/zport/dmd/img/icons/network.png")
    430
    431
    432 -    def reIndex(self):
    433          """Go through all ips in this tree and reindex them."""
    434          zcat = self._getCatalog()
    435          zcat.manage_catalogClear()
    436          transaction.savepoint()
    437          for net in self.getSubNetworks():
    438              for ip in net.ipaddresses():
    439                  ip.index_object()
    440              transaction.savepoint()
    441
    442
    443 -    def createCatalog(self):
    444          """make the catalog for device searching"""
    445          from Products.ZCatalog.ZCatalog import manage_addZCatalog
    446
    447          # XXX convert to ManagableIndex
    448          manage_addZCatalog(self, self.default_catalog,
    449                              self.default_catalog)
    450          zcat = self._getOb(self.default_catalog)
    451          cat = zcat._catalog
    452          cat.addIndex('id', makeCaseInsensitiveFieldIndex('id'))
    453          zcat.addColumn('getPrimaryId')
    454
    455
    456 -    def discoverNetwork(self, REQUEST=None):
    457          """
    458          """
    459          path = '/'.join(self.getPrimaryPath()[4:])
    460          return self.discoverDevices([path], REQUEST=REQUEST)
    461
    462 -    def discoverDevices(self, organizerPaths=None, REQUEST = None):
    463          """
    464          Load a device into the database connecting its major relations
    465          and collecting its configuration.
    466          """
    467          xmlrpc = isXmlRpc(REQUEST)
    468
    469          if not organizerPaths:
    470              if xmlrpc: return 1
    471              return self.callZenScreen(REQUEST)
    472
    473          zDiscCommand = "empty"
    474
    475          from Products.ZenUtils.ZenTales import talesEval
    476
    477          orgroot = self.getNetworkRoot()
    478          for organizerName in organizerPaths:
    479              organizer = orgroot.getOrganizer(organizerName)
    480              if organizer is None:
    481                  if xmlrpc: return 1 # XML-RPC error
    482                  log.error("Couldn't obtain a network entry for '%s' "
    483                              "-- does it exist?" % organizerName)
    484                  continue
    485
    486              zDiscCommand = getattr(organizer, "zZenDiscCommand", None)
    487              if zDiscCommand:
    488                  cmd = talesEval('string:' + zDiscCommand, organizer).split(" ")
    489              else:
    490                  cmd = ["zendisc", "run", "--net", organizer.getNetworkName()]
    491                  if getattr(organizer, "zSnmpStrictDiscovery", False):
    492                      cmd += ["--snmp-strict-discovery"]
    493                  if getattr(organizer, "zPreferSnmpNaming", False):
    494                      cmd += ["--prefer-snmp-naming"]
    495              zd = binPath('zendisc')
    496              zendiscCmd = [zd] + cmd[1:]
    497              status = self.dmd.JobManager.addJob(ShellCommandJob, zendiscCmd)
    498
    499          log.info('Done')
    500
    501          if REQUEST and not xmlrpc:
    502              REQUEST.RESPONSE.redirect('/zport/dmd/JobManager/joblist')
    503
    504          if xmlrpc: return 0
    505
    506
    507 -    def setupLog(self, response):
    508          """setup logging package to send to browser"""
    509          from logging import StreamHandler, Formatter
    510          root = logging.getLogger()
    511          self._v_handler = StreamHandler(response)
    512          fmt = Formatter("""<tr class="tablevalues">
    513          <td>%(asctime)s</td><td>%(levelname)s</td>
    514          <td>%(name)s</td><td>%(message)s</td></tr>
    515          """, "%Y-%m-%d %H:%M:%S")
    516          self._v_handler.setFormatter(fmt)
    517          root.addHandler(self._v_handler)
    518          root.setLevel(10)
    519
    520
    521 -    def clearLog(self):
    522          alog = logging.getLogger()
    523          if getattr(self, "_v_handler", False):
    524              alog.removeHandler(self._v_handler)
    525
    526
    527 -    def loaderFooter(self, response):
    528          """add navigation links to the end of the loader output"""
    529          response.write("""<tr class="tableheader"><td colspan="4">
    530              Navigate to network <a href=%s>%s</a></td></tr>"""
    531              % (self.absolute_url(), self.id))
    532          response.write("</table></body></html>")
    533
    534      security.declareProtected('View', 'getXMLEdges')
    535 -    def getXMLEdges(self, depth=1, filter='/', start=()):
    536          """ Gets XML """
    537          if not start: start=self.id
    538          edges = NetworkTree.get_edges(self, depth,
    539                                        withIcons=True, filter=filter)
    540          return edgesToXML(edges, start)
    541
    542 -    def getIconPath(self):
    543          """ gets icon """
    544          try:
    545              return self.primaryAq().zIcon
    546          except AttributeError:
    547              return '/zport/dmd/img/icons/noicon.png'
    548
    549
    550 -    def urlLink(self, text=None, url=None, attrs={}):
    551          """
    552          Return an anchor tag if the user has access to the remote object.
    553          @param text: the text to place within the anchor tag or string.
    554                       Defaults to the id of this object.
    555          @param url: url for the href. Default is getPrimaryUrlPath
    556          @type attrs: dict
    557          @param attrs: any other attributes to be place in the in the tag.
    558          @return: An HTML link to this object
    559          @rtype: string
    560          """
    561          if not text:
    562              text = "%s/%d" % (self.id, self.netmask)
    563          if not self.checkRemotePerm("View", self):
    564              return text
    565          if not url:
    566              url = self.getPrimaryUrlPath()
    567          if len(attrs):
    568              return '<a href="%s" %s>%s</a>' % (url,
    569                  ' '.join(['%s="%s"' % (x,y) for x,y in attrs.items()]),
    570                  text)
    571          else:
    572              return '<a href="%s">%s</a>' % (url, text)
    573
    574  InitializeClass(IpNetwork)
    575
    576
    577
    578 -class AutoDiscoveryJob(ShellCommandJob):
    579      """
    580      Job encapsulating autodiscovery over a set of IP addresses.
    581
    582      Accepts a list of strings describing networks OR a list of strings
    583      specifying IP ranges, not both. Also accepts a set of zProperties to be
    584      set on devices that are discovered.
    585      """
    586 -    def __init__(self, jobid, nets=(), ranges=(), zProperties=()):
    587          # Store the nets and ranges
    588          self.nets = nets
    589          self.ranges = ranges
    590          self.zProperties = zProperties
    591
    592          # Set up the job, passing in a blank command (gets set later)
    593          super(AutoDiscoveryJob, self).__init__(jobid, '')
    594
    595 -    def run(self, r):
    596          transaction.commit()
    597          log = self.getStatus().getLog()
    598
    599          # Store zProperties on the job
    600          if self.zProperties:
    601              self.getStatus().setZProperties(**self.zProperties)
    602              transaction.commit()
    603
    604          # Build the zendisc command
    605          cmd = [binPath('zendisc')]
    606          cmd.extend(['run', '--now',
    607                     '--monitor', 'localhost',
    608                     '--deviceclass', '/Discovered',
    609                     '--parallel', '8',
    610                     '--job', self.getUid()
    611                     ])
    612          if not self.nets and not self.ranges:
    613              # Gotta have something
    614              log.write("ERROR: Must pass in a network or a range.")
    615              self.finished(FAILURE)
    616          elif self.nets and self.ranges:
    617              # Can't have both
    618              log.write("ERROR: Must pass in either networks or ranges, "
    619                        "not both.")
    620              self.finished(FAILURE)
    621          else:
    622              if self.nets:
    623                  for net in self.nets:
    624                      cmd.extend(['--net', net])
    625              elif self.ranges:
    626                  for iprange in self.ranges:
    627                      cmd.extend(['--range', iprange])
    628              self.cmd = cmd
    629              super(AutoDiscoveryJob, self).run(r)
    630
    631 -    def finished(self, r):
    632          if self.nets:
    633              details = 'networks %s' % ', '.join(self.nets)
    634          elif self.ranges:
    635              details = 'IP ranges %s' % ', '.join(self.ranges)
    636          if r==SUCCESS:
    637              JobMessenger(self).sendToUser(
    638                  'Discovery Complete',
    639                  'Discovery of %s has completed successfully.' % details
    640              )
    641          elif r==FAILURE:
    642              JobMessenger(self).sendToUser(
    643                  'Discovery Failed',
    644                  'An error occurred discovering %s.' % details,
    645                  priority=messaging.WARNING
    646              )
    647          super(AutoDiscoveryJob, self).finished(r)
    648