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 { 'id' : 'viewHistory'
127 , 'name' : 'Modifications'
128 , 'action' : 'viewHistory'
129 , 'permissions' : (ZEN_VIEW_MODIFICATIONS,)
130 },
131 )
132 },
133 )
134
135 security = ClassSecurityInfo()
136
137
138 - def __init__(self, id, netmask=24, description='', version=4):
147
148 security.declareProtected('Change Network', 'manage_addIpNetwork')
150 """
151 From the GUI, create a new subnet (if necessary)
152 """
153 net = self.createNet(newPath)
154 if REQUEST is not None:
155 REQUEST['RESPONSE'].redirect(net.absolute_url())
156
162
163
171
172
174 """
175 Return and create if necessary network. netip is in the form
176 1.1.1.0/24 or with netmask passed as parameter. Subnetworks created
177 based on the zParameter zDefaulNetworkTree.
178 Called by IpNetwork.createIp and IpRouteEntry.setTarget
179 If the netmask is invalid, then a netmask of 24 is assumed.
180
181 @param netip: network IP address start
182 @type netip: string
183 @param netmask: network mask
184 @type netmask: integer
185 @todo: investigate IPv6 issues
186 """
187 if '/' in netip:
188 netip, netmask = netip.split("/",1)
189
190 checkip(netip)
191 ipobj = IPAddress(ipunwrap_strip(netip))
192 try:
193 netmask = int(netmask)
194 except (TypeError, ValueError):
195 netmask = 24
196 netmask = netmask if netmask < ipobj.max_prefixlen else 24
197
198
199 netroot = self.getNetworkRoot(ipobj.version)
200 netobj = netroot.getNet(netip)
201 if netmask == 0:
202 raise ValueError("netip '%s' without netmask" % netip)
203 if netobj and netobj.netmask >= netmask:
204 return netobj
205
206 ipNetObj = IPNetwork(netip)
207 if ipNetObj.version == 4:
208 netip = getnetstr(netip, netmask)
209 netTree = getattr(self, 'zDefaultNetworkTree', defaultNetworkTree)
210 netTree = map(int, netTree)
211 if ipobj.max_prefixlen not in netTree:
212 netTree.append(ipobj.max_prefixlen)
213 else:
214
215 netip = getnetstr(netip, 64)
216 netmask = 64
217
218 netTree = (48,)
219
220 if netobj:
221
222 netTree = [ m for m in netTree if m > netobj.netmask ]
223 else:
224
225 netobj = netroot
226
227 for treemask in netTree:
228 if treemask >= netmask:
229 netobjParent = netobj
230 netobj = netobj.addSubNetwork(netip, netmask)
231 self.rebalance(netobjParent, netobj)
232 break
233 else:
234 supnetip = getnetstr(netip, treemask)
235 netobjParent = netobj
236 netobj = netobj.addSubNetwork(supnetip, treemask)
237 self.rebalance(netobjParent, netobj)
238
239 return netobj
240
241
243 """
244 Look for children of the netobj at this level and move them to the
245 right spot.
246 """
247 moveList = []
248 for subnetOrIp in netobjParent.children():
249 if subnetOrIp == netobj:
250 continue
251 if netobj.hasIp(subnetOrIp.id):
252 moveList.append(subnetOrIp.id)
253 if moveList:
254 netobjPath = netobj.getOrganizerName()[1:]
255 netobjParent.moveOrganizer(netobjPath, moveList)
256
257 - def findNet(self, netip, netmask=0):
258 """
259 Find and return the subnet of this IpNetwork that matches the requested
260 netip and netmask.
261 """
262 if netip.find("/") >= 0:
263 netip, netmask = netip.split("/", 1)
264 netmask = int(netmask)
265 for subnet in [self] + self.getSubNetworks():
266 if netmask == 0 and subnet.id == netip:
267 return subnet
268 if subnet.id == netip and subnet.netmask == netmask:
269 return subnet
270 return None
271
272
274 """Return the net starting form the Networks root for ip.
275 """
276 return self._getNet(ipunwrap(ip))
277
278
280 """Recurse down the network tree to find the net of ip.
281 """
282
283
284 brains = self.ipSearch(id=ip)
285 path = self.getPrimaryUrlPath()
286 for brain in brains:
287 bp = brain.getPath()
288 if bp.startswith(path):
289 try:
290 return self.unrestrictedTraverse('/'.join(bp.split('/')[:-2]))
291 except KeyError:
292 pass
293
294
295 for net in self.children():
296 if net.hasIp(ip):
297 if len(net.children()):
298 subnet = net._getNet(ip)
299 if subnet:
300 return subnet
301 else:
302 return net
303 else:
304 return net
305
306
308 """Return an ip and create if nessesary in a hierarchy of
309 subnetworks based on the zParameter zDefaulNetworkTree.
310 """
311 ipobj = self.findIp(ip)
312 if ipobj: return ipobj
313 netobj = self.createNet(ip, netmask)
314 ipobj = netobj.addIpAddress(ip,netmask)
315 return ipobj
316
317
319 """Number of free Ips left in this network.
320 """
321 freeips = 0
322 try:
323 net = IPNetwork(ipunwrap(self.id))
324 freeips = int(math.pow(2, net.max_prefixlen - self.netmask) - self.countIpAddresses())
325 if self.netmask > net.max_prefixlen:
326 return freeips
327 return freeips - 2
328 except ValueError:
329 for net in self.children():
330 freeips += net.freeIps()
331 return freeips
332
333
342
344 """Return a list of all IPs in this network.
345 """
346 net = IPNetwork(ipunwrap(self.id))
347 if (self.netmask == net.max_prefixlen): return [self.id]
348 ipnumb = long(int(net))
349 maxip = math.pow(2, net.max_prefixlen - self.netmask)
350 start = int(ipnumb+1)
351 end = int(ipnumb+maxip-1)
352 return map(strip, range(start,end))
353
354
361
362
364 """Return the ip of the default router for this network.
365 It is based on zDefaultRouterNumber which specifies the sequence
366 number that locates the router in this network. If:
367 zDefaultRouterNumber==1 for 10.2.1.0/24 -> 10.2.1.1
368 zDefaultRouterNumber==254 for 10.2.1.0/24 -> 10.2.1.254
369 zDefaultRouterNumber==1 for 10.2.2.128/25 -> 10.2.2.129
370 zDefaultRouterNumber==126 for 10.2.2.128/25 -> 10.2.2.254
371 """
372 roffset = getattr(self, "zDefaultRouterNumber", 1)
373 return strip((numbip(self.id) + roffset))
374
375
377 """return the full network name of this network"""
378 return "%s/%d" % (self.id, self.netmask)
379
380
381 security.declareProtected('View', 'primarySortKey')
383 """
384 Sort by the IP numeric
385
386 >>> net = dmd.Networks.addSubNetwork('1.2.3.0', 24)
387 >>> net.primarySortKey()
388 16909056L
389 """
390 return numbip(self.id)
391
392
393 security.declareProtected('Change Network', 'addSubNetwork')
402
403
404 security.declareProtected('View', 'getSubNetwork')
406 """get an ip on this network"""
407 return self._getOb(ipwrap(ip), None)
408
409
411 """Return all network objects below this one.
412 """
413 nets = self.children()
414 for subgroup in self.children():
415 nets.extend(subgroup.getSubNetworks())
416 return nets
417
418 security.declareProtected('Change Network', 'addIpAddress')
424
425
426 security.declareProtected('View', 'getIpAddress')
430
431 security.declareProtected('Change Network', 'manage_deleteIpAddresses')
440
441
442 security.declareProtected('View', 'countIpAddresses')
457
458 security.declareProtected('View', 'countDevices')
459 countDevices = countIpAddresses
460
461
463 """Count all devices within a device group and get the
464 ping and snmp counts as well"""
465 unused(devrel)
466 counts = [
467 self.ipaddresses.countObjects(),
468 self._status("Ping", "ipaddresses"),
469 self._status("Snmp", "ipaddresses"),
470 ]
471 for group in self.children():
472 sc = group.getAllCounts()
473 for i in range(3): counts[i] += sc[i]
474 return counts
475
476
481
482
487
488
492
493
495 """Find an ipAddress.
496 """
497 searchCatalog = self.getNetworkRoot().ipSearch
498 ret = searchCatalog(dict(id=ipwrap(ip)))
499 if not ret: return None
500 if len(ret) > 1:
501 raise IpAddressConflict( "IP address conflict for IP: %s" % ip )
502 return ret[0].getObject()
503
504
506 if self.version == 6:
507 nets = self.getDmdRoot("IPv6Networks")
508 else:
509 nets = self.getDmdRoot("Networks")
510 if getattr(aq_base(nets), "zDefaultNetworkTree", False):
511 return
512 nets._setProperty("zDefaultNetworkTree", (64,128) if nets.id == "IPv6Networks" else (24,32), type="lines")
513 nets._setProperty("zDrawMapLinks", True, type="boolean")
514 nets._setProperty("zAutoDiscover", True, type="boolean")
515 nets._setProperty("zPingFailThresh", 168, type="int")
516 nets._setProperty("zIcon", "/zport/dmd/img/icons/network.png")
517 nets._setProperty("zPreferSnmpNaming", False, type="boolean")
518 nets._setProperty("zSnmpStrictDiscovery", False, type="boolean")
519
520
528
529
531 """make the catalog for device searching"""
532 from Products.ZCatalog.ZCatalog import manage_addZCatalog
533
534
535 manage_addZCatalog(self, self.default_catalog,
536 self.default_catalog)
537 zcat = self._getOb(self.default_catalog)
538 cat = zcat._catalog
539 cat.addIndex('id', makeCaseInsensitiveFieldIndex('id'))
540 zcat.addColumn('getPrimaryId')
541
542
543 fieldIndexes = ['getInterfaceName', 'getDeviceName', 'getInterfaceDescription', 'getInterfaceMacAddress']
544 for indexName in fieldIndexes:
545 zcat._catalog.addIndex(indexName, makeCaseInsensitiveFieldIndex(indexName))
546 zcat._catalog.addIndex('allowedRolesAndUsers', makeCaseSensitiveKeywordIndex('allowedRolesAndUsers'))
547 zcat._catalog.addIndex('ipAddressAsInt', makeCaseSensitiveFieldIndex('ipAddressAsInt'))
548 zcat._catalog.addIndex('path', makeMultiPathIndex('path'))
549 zcat.addColumn('details')
550
551
557
559 """
560 Load a device into the database connecting its major relations
561 and collecting its configuration.
562 """
563 xmlrpc = isXmlRpc(REQUEST)
564
565 if not organizerPaths:
566 if xmlrpc: return 1
567 return self.callZenScreen(REQUEST)
568
569 zDiscCommand = "empty"
570
571 from Products.ZenUtils.ZenTales import talesEval
572
573 orgroot = self.getNetworkRoot()
574 for organizerName in organizerPaths:
575 organizer = orgroot.getOrganizer(organizerName)
576 if organizer is None:
577 if xmlrpc: return 1
578 log.error("Couldn't obtain a network entry for '%s' "
579 "-- does it exist?" % organizerName)
580 continue
581
582 zDiscCommand = getattr(organizer, "zZenDiscCommand", None)
583 if zDiscCommand:
584 cmd = talesEval('string:' + zDiscCommand, organizer).split(" ")
585 else:
586 cmd = ["zendisc", "run", "--net", organizer.getNetworkName()]
587 if getattr(organizer, "zSnmpStrictDiscovery", False):
588 cmd += ["--snmp-strict-discovery"]
589 if getattr(organizer, "zPreferSnmpNaming", False):
590 cmd += ["--prefer-snmp-naming"]
591 zd = binPath('zendisc')
592 zendiscCmd = [zd] + cmd[1:]
593 status = self.dmd.JobManager.addJob(ShellCommandJob, zendiscCmd)
594
595 log.info('Done')
596
597 if REQUEST and not xmlrpc:
598 REQUEST.RESPONSE.redirect('/zport/dmd/JobManager/joblist')
599
600 if xmlrpc: return 0
601
602
604 """setup logging package to send to browser"""
605 from logging import StreamHandler, Formatter
606 root = logging.getLogger()
607 self._v_handler = StreamHandler(response)
608 fmt = Formatter("""<tr class="tablevalues">
609 <td>%(asctime)s</td><td>%(levelname)s</td>
610 <td>%(name)s</td><td>%(message)s</td></tr>
611 """, "%Y-%m-%d %H:%M:%S")
612 self._v_handler.setFormatter(fmt)
613 root.addHandler(self._v_handler)
614 root.setLevel(10)
615
616
618 alog = logging.getLogger()
619 if getattr(self, "_v_handler", False):
620 alog.removeHandler(self._v_handler)
621
622
629
630 security.declareProtected('View', 'getXMLEdges')
637
639 """ gets icon """
640 try:
641 return self.primaryAq().zIcon
642 except AttributeError:
643 return '/zport/dmd/img/icons/noicon.png'
644
645
646 - def urlLink(self, text=None, url=None, attrs={}):
647 """
648 Return an anchor tag if the user has access to the remote object.
649 @param text: the text to place within the anchor tag or string.
650 Defaults to the id of this object.
651 @param url: url for the href. Default is getPrimaryUrlPath
652 @type attrs: dict
653 @param attrs: any other attributes to be place in the in the tag.
654 @return: An HTML link to this object
655 @rtype: string
656 """
657 if not text:
658 text = "%s/%d" % (self.id, self.netmask)
659 if not self.checkRemotePerm("View", self):
660 return text
661 if not url:
662 url = self.getPrimaryUrlPath()
663 if len(attrs):
664 return '<a href="%s" %s>%s</a>' % (url,
665 ' '.join('%s="%s"' % (x,y) for x,y in attrs.items()),
666 text)
667 else:
668 return '<a href="%s">%s</a>' % (url, text)
669
670 InitializeClass(IpNetwork)
671
672
674 """
675 Job encapsulating autodiscovery over a set of IP addresses.
676
677 Accepts a list of strings describing networks OR a list of strings
678 specifying IP ranges, not both. Also accepts a set of zProperties to be
679 set on devices that are discovered.
680 """
681 - def __init__(self, jobid, nets=(), ranges=(), zProperties=()):
682
683 self.nets = nets
684 self.ranges = ranges
685 self.zProperties = zProperties
686
687
688 super(AutoDiscoveryJob, self).__init__(jobid, '')
689
691 transaction.commit()
692 log = self.getStatus().getLog()
693
694
695 if self.zProperties:
696 self.getStatus().setZProperties(**self.zProperties)
697 transaction.commit()
698
699
700 cmd = [binPath('zendisc')]
701 cmd.extend(['run', '--now',
702 '--monitor', 'localhost',
703 '--deviceclass', '/Discovered',
704 '--parallel', '8',
705 '--job', self.getUid()
706 ])
707 if not self.nets and not self.ranges:
708
709 log.write("ERROR: Must pass in a network or a range.")
710 self.finished(FAILURE)
711 elif self.nets and self.ranges:
712
713 log.write("ERROR: Must pass in either networks or ranges, "
714 "not both.")
715 self.finished(FAILURE)
716 else:
717 if self.nets:
718 for net in self.nets:
719 cmd.extend(['--net', net])
720 elif self.ranges:
721 for iprange in self.ranges:
722 cmd.extend(['--range', iprange])
723 self.cmd = cmd
724 super(AutoDiscoveryJob, self).run(r)
725
727 if self.nets:
728 details = 'networks %s' % ', '.join(self.nets)
729 elif self.ranges:
730 details = 'IP ranges %s' % ', '.join(self.ranges)
731 if r==SUCCESS:
732 JobMessenger(self).sendToUser(
733 'Discovery Complete',
734 'Discovery of %s has completed successfully.' % details
735 )
736 elif r==FAILURE:
737 JobMessenger(self).sendToUser(
738 'Discovery Failed',
739 'An error occurred discovering %s.' % details,
740 priority=messaging.WARNING
741 )
742 super(AutoDiscoveryJob, self).finished(r)
743
744
746
748 """out is the output stream to print to"""
749 self._out = out
750
751
752 -class TextIpNetworkPrinter(IpNetworkPrinter):
753 """
754 Prints out IpNetwork hierarchy as text with indented lines.
755 """
756
757 - def printIpNetwork(self, net):
758 """
759 Print out the IpNetwork and IpAddress hierarchy under net.
760 """
761 self._printIpNetworkLine(net)
762 self._printTree(net)
763
764 - def _printTree(self, net, indent=" "):
765 for child in net.children():
766 self._printIpNetworkLine(child, indent)
767 self._printTree(child, indent + " ")
768 for ipaddress in net.ipaddresses():
769 args = (indent, ipaddress, ipaddress.__class__.__name__)
770 self._out.write("%s%s (%s)\n" % args)
771
772 - def _printIpNetworkLine(self, net, indent=""):
773 args = (indent, net.id, net.netmask, net.__class__.__name__)
774 self._out.write("%s%s/%s (%s)\n" % args)
775
776
778 """
779 Prints out the IpNetwork hierarchy as a python dictionary.
780 """
781
783 """
784 Print out the IpNetwork and IpAddress hierarchy under net.
785 """
786 tree = {}
787 self._createTree(net, tree)
788 from pprint import pformat
789 self._out.write("%s\n" % pformat(tree))
790
797
803
804
806 """
807 Prints out the IpNetwork hierarchy as XML.
808 """
809
811 """
812 Print out the IpNetwork and IpAddress hierarchy under net.
813 """
814 self._doc = minidom.parseString('<root/>')
815 root = self._doc.documentElement
816 self._createTree(net, root)
817 self._out.write(self._doc.toprettyxml())
818
824
828
830 node = self._doc.createElement(child.__class__.__name__)
831 node.setAttribute("id", child.id)
832 node.setAttribute("netmask", str(child.netmask))
833 tree.appendChild(node)
834 return node
835
836
838
843
845 if format in self._printerFactories:
846 factory = self._printerFactories[format]
847 return factory(out)
848 else:
849 args = (format, self._printerFactories.keys())
850 raise Exception("Invalid format '%s' must be one of %s" % args)
851