1
2
3
4
5
6
7
8
9
10
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
65
66
67 addIpNetwork = DTMLFile('dtml/addIpNetwork',globals())
68
69
70
71
72 defaultNetworkTree = (32,)
73
75 """IpNetwork object"""
76
77 isInTree = True
78
79 buildLinks = True
80
81
82 dmdRootName = "Networks"
83
84
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
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):
142
143 security.declareProtected('Change Network', 'manage_addIpNetwork')
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
157
158
166
167
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
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:
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
210 netip = getnetstr(netip, 64)
211 netmask = 64
212
213 netTree = (48,)
214
215 if netobj:
216
217 netTree = [ m for m in netTree if m > netobj.netmask ]
218 else:
219
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
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
269 """Return the net starting form the Networks root for ip.
270 """
271 return self._getNet(ipunwrap(ip))
272
273
275 """Recurse down the network tree to find the net of ip.
276 """
277
278
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
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
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
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
337
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
356
357
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
372 """return the full network name of this network"""
373 return "%s/%d" % (self.id, self.netmask)
374
375
376 security.declareProtected('View', 'primarySortKey')
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')
397
398
399 security.declareProtected('View', 'getSubNetwork')
401 """get an ip on this network"""
402 return self._getOb(ipwrap(ip), None)
403
404
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')
419
420
421 security.declareProtected('View', 'getIpAddress')
425
426 security.declareProtected('Change Network', 'manage_deleteIpAddresses')
435
436
437 security.declareProtected('View', 'countIpAddresses')
452
453 security.declareProtected('View', 'countDevices')
454 countDevices = countIpAddresses
455
456
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
476
477
482
483
487
488
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
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
523
524
526 """make the catalog for device searching"""
527 from Products.ZCatalog.ZCatalog import manage_addZCatalog
528
529
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
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
552
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
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
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
613 alog = logging.getLogger()
614 if getattr(self, "_v_handler", False):
615 alog.removeHandler(self._v_handler)
616
617
624
625 security.declareProtected('View', 'getXMLEdges')
632
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
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
678 self.nets = nets
679 self.ranges = ranges
680 self.zProperties = zProperties
681
682
683 super(AutoDiscoveryJob, self).__init__(jobid, '')
684
686 transaction.commit()
687 log = self.getStatus().getLog()
688
689
690 if self.zProperties:
691 self.getStatus().setZProperties(**self.zProperties)
692 transaction.commit()
693
694
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
704 log.write("ERROR: Must pass in a network or a range.")
705 self.finished(FAILURE)
706 elif self.nets and self.ranges:
707
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
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
741
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
773 """
774 Prints out the IpNetwork hierarchy as a python dictionary.
775 """
776
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
792
798
799
801 """
802 Prints out the IpNetwork hierarchy as XML.
803 """
804
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
819
823
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
833
838
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