1
2
3
4
5
6
7
8
9
10
11 __doc__ = """IpNetwork
12
13 IpNetwork represents an IP network which contains
14 many IP addresses.
15 """
16
17 import math
18 import transaction
19 from xml.dom import minidom
20 import logging
21 log = logging.getLogger('zen')
22
23 from ipaddr import IPAddress, IPNetwork
24
25 from Globals import DTMLFile
26 from Globals import InitializeClass
27 from Acquisition import aq_base
28 from AccessControl import ClassSecurityInfo
29 from AccessControl import Permissions as permissions
30 from Products.ZenModel.ZenossSecurity import *
31
32 from Products.ZenUtils.IpUtil import *
33 from Products.ZenRelations.RelSchema import *
34 from Products.ZenUtils.Search import makeCaseInsensitiveFieldIndex, makeMultiPathIndex, makeCaseSensitiveKeywordIndex\
35 , makeCaseSensitiveFieldIndex
36 from IpAddress import IpAddress
37 from DeviceOrganizer import DeviceOrganizer
38
39 from Products.ZenModel.Exceptions import *
40
41 from Products.ZenUtils.Utils import isXmlRpc, setupLoggingHeader, executeCommand
42 from Products.ZenUtils.Utils import binPath, clearWebLoggingStream
43 from Products.ZenUtils import NetworkTree
44 from Products.ZenUtils.Utils import edgesToXML
45 from Products.ZenUtils.Utils import unused
46 from Products.Jobber.jobs import SubprocessJob
47 from Products.ZenWidgets import messaging
48
61
62
63 addIpNetwork = DTMLFile('dtml/addIpNetwork',globals())
64
65
66
67
68 defaultNetworkTree = (32,)
69
71 """IpNetwork object"""
72
73 isInTree = True
74
75 buildLinks = True
76
77
78 dmdRootName = "Networks"
79
80
81 default_catalog = 'ipSearch'
82
83 portal_type = meta_type = 'IpNetwork'
84
85 version = 4
86
87 _properties = (
88 {'id':'netmask', 'type':'int', 'mode':'w'},
89 {'id':'description', 'type':'text', 'mode':'w'},
90 {'id':'version', 'type':'int', 'mode':'w'},
91 )
92
93 _relations = DeviceOrganizer._relations + (
94 ("ipaddresses", ToManyCont(ToOne, "Products.ZenModel.IpAddress", "network")),
95 ("clientroutes", ToMany(ToOne,"Products.ZenModel.IpRouteEntry","target")),
96 ("location", ToOne(ToMany, "Products.ZenModel.Location", "networks")),
97 )
98
99
100 factory_type_information = (
101 {
102 'id' : 'IpNetwork',
103 'meta_type' : 'IpNetwork',
104 'description' : """Arbitrary device grouping class""",
105 'icon' : 'IpNetwork_icon.gif',
106 'product' : 'ZenModel',
107 'factory' : 'manage_addIpNetwork',
108 'immediate_view' : 'viewNetworkOverview',
109 'actions' :
110 (
111 { 'id' : 'overview'
112 , 'name' : 'Overview'
113 , 'action' : 'viewNetworkOverview'
114 , 'permissions' : (
115 permissions.view, )
116 },
117 { 'id' : 'zProperties'
118 , 'name' : 'Configuration Properties'
119 , 'action' : 'zPropertyEdit'
120 , 'permissions' : ("Manage DMD",)
121 },
122 )
123 },
124 )
125
126 security = ClassSecurityInfo()
127
128
129 - def __init__(self, id, netmask=24, description='', version=4):
138
139 security.declareProtected('Change Network', 'manage_addIpNetwork')
141 """
142 From the GUI, create a new subnet (if necessary)
143 """
144 net = self.createNet(newPath)
145 if REQUEST is not None:
146 REQUEST['RESPONSE'].redirect(net.absolute_url())
147
153
154
162
163
165 """
166 Return and create if necessary network. netip is in the form
167 1.1.1.0/24 or with netmask passed as parameter. Subnetworks created
168 based on the zParameter zDefaulNetworkTree.
169 Called by IpNetwork.createIp and IpRouteEntry.setTarget
170 If the netmask is invalid, then a netmask of 24 is assumed.
171
172 @param netip: network IP address start
173 @type netip: string
174 @param netmask: network mask
175 @type netmask: integer
176 @todo: investigate IPv6 issues
177 """
178 if '/' in netip:
179 netip, netmask = netip.split("/",1)
180
181 checkip(netip)
182 ipobj = IPAddress(ipunwrap_strip(netip))
183 try:
184 netmask = int(netmask)
185 except (TypeError, ValueError):
186 netmask = 24
187 netmask = netmask if netmask < ipobj.max_prefixlen else 24
188
189
190 netroot = self.getNetworkRoot(ipobj.version)
191 netobj = netroot.getNet(netip)
192 if netmask == 0:
193 raise ValueError("netip '%s' without netmask" % netip)
194 if netobj and netobj.netmask >= netmask:
195 return netobj
196
197 ipNetObj = IPNetwork(netip)
198 if ipNetObj.version == 4:
199 netip = getnetstr(netip, netmask)
200 netTree = getattr(self, 'zDefaultNetworkTree', defaultNetworkTree)
201 netTree = map(int, netTree)
202 if ipobj.max_prefixlen not in netTree:
203 netTree.append(ipobj.max_prefixlen)
204 else:
205
206 netip = getnetstr(netip, 64)
207 netmask = 64
208
209 netTree = (48,)
210
211 if netobj:
212
213 netTree = [ m for m in netTree if m > netobj.netmask ]
214 else:
215
216 netobj = netroot
217
218 for treemask in netTree:
219 if treemask >= netmask:
220 netobjParent = netobj
221 netobj = netobj.addSubNetwork(netip, netmask)
222 self.rebalance(netobjParent, netobj)
223 break
224 else:
225 supnetip = getnetstr(netip, treemask)
226 netobjParent = netobj
227 netobj = netobj.addSubNetwork(supnetip, treemask)
228 self.rebalance(netobjParent, netobj)
229
230 return netobj
231
232
234 """
235 Look for children of the netobj at this level and move them to the
236 right spot.
237 """
238 moveList = []
239 for subnetOrIp in netobjParent.children():
240 if subnetOrIp == netobj:
241 continue
242 if netobj.hasIp(subnetOrIp.id):
243 moveList.append(subnetOrIp.id)
244 if moveList:
245 netobjPath = netobj.getOrganizerName()[1:]
246 netobjParent.moveOrganizer(netobjPath, moveList)
247
248 - def findNet(self, netip, netmask=0):
249 """
250 Find and return the subnet of this IpNetwork that matches the requested
251 netip and netmask.
252 """
253 if netip.find("/") >= 0:
254 netip, netmask = netip.split("/", 1)
255 netmask = int(netmask)
256 for subnet in [self] + self.getSubNetworks():
257 if netmask == 0 and subnet.id == netip:
258 return subnet
259 if subnet.id == netip and subnet.netmask == netmask:
260 return subnet
261 return None
262
263
265 """Return the net starting form the Networks root for ip.
266 """
267 return self._getNet(ipunwrap(ip))
268
269
271 """Recurse down the network tree to find the net of ip.
272 """
273
274
275 brains = self.ipSearch(id=ip)
276 path = self.getPrimaryUrlPath()
277 for brain in brains:
278 bp = brain.getPath()
279 if bp.startswith(path):
280 try:
281 return self.unrestrictedTraverse('/'.join(bp.split('/')[:-2]))
282 except KeyError:
283 pass
284
285
286 for net in self.children():
287 if net.hasIp(ip):
288 if len(net.children()):
289 subnet = net._getNet(ip)
290 if subnet:
291 return subnet
292 else:
293 return net
294 else:
295 return net
296
297
299 """Return an ip and create if nessesary in a hierarchy of
300 subnetworks based on the zParameter zDefaulNetworkTree.
301 """
302 ipobj = self.findIp(ip)
303 if ipobj: return ipobj
304 netobj = self.createNet(ip, netmask)
305 ipobj = netobj.addIpAddress(ip,netmask)
306 return ipobj
307
308
310 """Number of free Ips left in this network.
311 """
312 freeips = 0
313 try:
314 net = IPNetwork(ipunwrap(self.id))
315 freeips = int(math.pow(2, net.max_prefixlen - self.netmask) - self.countIpAddresses())
316 if self.netmask > net.max_prefixlen:
317 return freeips
318 return freeips - 2
319 except ValueError:
320 for net in self.children():
321 freeips += net.freeIps()
322 return freeips
323
324
333
335 """Return a list of all IPs in this network.
336 """
337 net = IPNetwork(ipunwrap(self.id))
338 if (self.netmask == net.max_prefixlen): return [self.id]
339 ipnumb = long(int(net))
340 maxip = math.pow(2, net.max_prefixlen - self.netmask)
341 start = int(ipnumb+1)
342 end = int(ipnumb+maxip-1)
343 return map(strip, range(start,end))
344
345
352
353
355 """Return the ip of the default router for this network.
356 It is based on zDefaultRouterNumber which specifies the sequence
357 number that locates the router in this network. If:
358 zDefaultRouterNumber==1 for 10.2.1.0/24 -> 10.2.1.1
359 zDefaultRouterNumber==254 for 10.2.1.0/24 -> 10.2.1.254
360 zDefaultRouterNumber==1 for 10.2.2.128/25 -> 10.2.2.129
361 zDefaultRouterNumber==126 for 10.2.2.128/25 -> 10.2.2.254
362 """
363 roffset = getattr(self, "zDefaultRouterNumber", 1)
364 return strip((numbip(self.id) + roffset))
365
366
368 """return the full network name of this network"""
369 return "%s/%d" % (self.id, self.netmask)
370
371
372 security.declareProtected('View', 'primarySortKey')
374 """
375 Sort by the IP numeric
376
377 >>> net = dmd.Networks.addSubNetwork('1.2.3.0', 24)
378 >>> net.primarySortKey()
379 16909056L
380 """
381 return numbip(self.id)
382
383
384 security.declareProtected('Change Network', 'addSubNetwork')
393
394
395 security.declareProtected('View', 'getSubNetwork')
397 """get an ip on this network"""
398 return self._getOb(ipwrap(ip), None)
399
400
402 """Return all network objects below this one.
403 """
404 nets = self.children()
405 for subgroup in self.children():
406 nets.extend(subgroup.getSubNetworks())
407 return nets
408
409 security.declareProtected('Change Network', 'addIpAddress')
415
416
417 security.declareProtected('View', 'getIpAddress')
421
422 security.declareProtected('Change Network', 'manage_deleteIpAddresses')
431
432
433 security.declareProtected('View', 'countIpAddresses')
448
449 security.declareProtected('View', 'countDevices')
450 countDevices = countIpAddresses
451
452
454 """Count all devices within a device group and get the
455 ping and snmp counts as well"""
456 unused(devrel)
457 counts = [
458 self.ipaddresses.countObjects(),
459 self._status("Ping", "ipaddresses"),
460 self._status("Snmp", "ipaddresses"),
461 ]
462 for group in self.children():
463 sc = group.getAllCounts()
464 for i in range(3): counts[i] += sc[i]
465 return counts
466
467
472
473
478
479
483
484
486 """Find an ipAddress.
487 """
488 searchCatalog = self.getNetworkRoot().ipSearch
489 ret = searchCatalog(dict(id=ipwrap(ip)))
490 if not ret: return None
491 if len(ret) > 1:
492 raise IpAddressConflict( "IP address conflict for IP: %s" % ip )
493 return ret[0].getObject()
494
495
497 if self.version == 6:
498 nets = self.getDmdRoot("IPv6Networks")
499 else:
500 nets = self.getDmdRoot("Networks")
501 if getattr(aq_base(nets), "zDefaultNetworkTree", False):
502 return
503 nets._setProperty("zDefaultNetworkTree", (64,128) if nets.id == "IPv6Networks" else (24,32), type="lines")
504 nets._setProperty("zDrawMapLinks", True, type="boolean")
505 nets._setProperty("zAutoDiscover", True, type="boolean")
506 nets._setProperty("zPingFailThresh", 168, type="int")
507 nets._setProperty("zIcon", "/zport/dmd/img/icons/network.png")
508 nets._setProperty("zPreferSnmpNaming", False, type="boolean")
509 nets._setProperty("zSnmpStrictDiscovery", False, type="boolean")
510
511
519
520
534
535
541
543 """
544 Load a device into the database connecting its major relations
545 and collecting its configuration.
546 """
547 xmlrpc = isXmlRpc(REQUEST)
548
549 if not organizerPaths:
550 if xmlrpc: return 1
551 return self.callZenScreen(REQUEST)
552
553 zDiscCommand = "empty"
554
555 from Products.ZenUtils.ZenTales import talesEval
556
557 orgroot = self.getNetworkRoot()
558 for organizerName in organizerPaths:
559 organizer = orgroot.getOrganizer(organizerName)
560 if organizer is None:
561 if xmlrpc: return 1
562 log.error("Couldn't obtain a network entry for '%s' "
563 "-- does it exist?" % organizerName)
564 continue
565
566 zDiscCommand = getattr(organizer, "zZenDiscCommand", None)
567 if zDiscCommand:
568 cmd = talesEval('string:' + zDiscCommand, organizer).split(" ")
569 else:
570 cmd = ["zendisc", "run", "--net", organizer.getNetworkName()]
571 if getattr(organizer, "zSnmpStrictDiscovery", False):
572 cmd += ["--snmp-strict-discovery"]
573 if getattr(organizer, "zPreferSnmpNaming", False):
574 cmd += ["--prefer-snmp-naming"]
575 zd = binPath('zendisc')
576 zendiscCmd = [zd] + cmd[1:]
577 status = self.dmd.JobManager.addJob(SubprocessJob,
578 description="Discover devices in network %s" % organizer.getNetworkName(),
579 args=(zendiscCmd,))
580
581 log.info('Done')
582
583 if REQUEST and not xmlrpc:
584 REQUEST.RESPONSE.redirect('/zport/dmd/JobManager/joblist')
585
586 if xmlrpc: return 0
587
588
590 """setup logging package to send to browser"""
591 from logging import StreamHandler, Formatter
592 root = logging.getLogger()
593 self._v_handler = StreamHandler(response)
594 fmt = Formatter("""<tr class="tablevalues">
595 <td>%(asctime)s</td><td>%(levelname)s</td>
596 <td>%(name)s</td><td>%(message)s</td></tr>
597 """, "%Y-%m-%d %H:%M:%S")
598 self._v_handler.setFormatter(fmt)
599 root.addHandler(self._v_handler)
600 root.setLevel(10)
601
602
604 alog = logging.getLogger()
605 if getattr(self, "_v_handler", False):
606 alog.removeHandler(self._v_handler)
607
608
615
616 security.declareProtected('View', 'getXMLEdges')
623
625 """ gets icon """
626 try:
627 return self.primaryAq().zIcon
628 except AttributeError:
629 return '/zport/dmd/img/icons/noicon.png'
630
631
632 - def urlLink(self, text=None, url=None, attrs={}):
633 """
634 Return an anchor tag if the user has access to the remote object.
635 @param text: the text to place within the anchor tag or string.
636 Defaults to the id of this object.
637 @param url: url for the href. Default is getPrimaryUrlPath
638 @type attrs: dict
639 @param attrs: any other attributes to be place in the in the tag.
640 @return: An HTML link to this object
641 @rtype: string
642 """
643 if not text:
644 text = "%s/%d" % (self.id, self.netmask)
645 if not self.checkRemotePerm("View", self):
646 return text
647 if not url:
648 url = self.getPrimaryUrlPath()
649 if len(attrs):
650 return '<a href="%s" %s>%s</a>' % (url,
651 ' '.join('%s="%s"' % (x,y) for x,y in attrs.items()),
652 text)
653 else:
654 return '<a href="%s">%s</a>' % (url, text)
655
656 InitializeClass(IpNetwork)
657
658
660 """
661 Job encapsulating autodiscovery over a set of IP addresses.
662
663 Accepts a list of strings describing networks OR a list of strings
664 specifying IP ranges, not both. Also accepts a set of zProperties to be
665 set on devices that are discovered.
666 """
667 - def _run(self, nets=(), ranges=(), zProperties=()):
668
669 self.nets = nets
670 self.ranges = ranges
671
672
673 if zProperties:
674 self.setProperties(**zProperties)
675
676 cmd = [binPath('zendisc')]
677 cmd.extend(['run', '--now',
678 '--monitor', 'localhost',
679 '--deviceclass', '/Discovered',
680 '--parallel', '8',
681 '--job', self.request.id
682 ])
683 if not self.nets and not self.ranges:
684
685 self.log.error("Must pass in either a network or a range.")
686 elif self.nets and self.ranges:
687
688 self.log.error("Must pass in either networks or ranges, not both")
689 else:
690 if self.nets:
691 for net in self.nets:
692 cmd.extend(['--net', net])
693 elif self.ranges:
694 for iprange in self.ranges:
695 cmd.extend(['--range', iprange])
696 SubprocessJob._run(self, cmd)
697
698
700
702 """out is the output stream to print to"""
703 self._out = out
704
705
706 -class TextIpNetworkPrinter(IpNetworkPrinter):
707 """
708 Prints out IpNetwork hierarchy as text with indented lines.
709 """
710
711 - def printIpNetwork(self, net):
712 """
713 Print out the IpNetwork and IpAddress hierarchy under net.
714 """
715 self._printIpNetworkLine(net)
716 self._printTree(net)
717
718 - def _printTree(self, net, indent=" "):
719 for child in net.children():
720 self._printIpNetworkLine(child, indent)
721 self._printTree(child, indent + " ")
722 for ipaddress in net.ipaddresses():
723 args = (indent, ipaddress, ipaddress.__class__.__name__)
724 self._out.write("%s%s (%s)\n" % args)
725
726 - def _printIpNetworkLine(self, net, indent=""):
727 args = (indent, net.id, net.netmask, net.__class__.__name__)
728 self._out.write("%s%s/%s (%s)\n" % args)
729
730
732 """
733 Prints out the IpNetwork hierarchy as a python dictionary.
734 """
735
737 """
738 Print out the IpNetwork and IpAddress hierarchy under net.
739 """
740 tree = {}
741 self._createTree(net, tree)
742 from pprint import pformat
743 self._out.write("%s\n" % pformat(tree))
744
751
757
758
760 """
761 Prints out the IpNetwork hierarchy as XML.
762 """
763
765 """
766 Print out the IpNetwork and IpAddress hierarchy under net.
767 """
768 self._doc = minidom.parseString('<root/>')
769 root = self._doc.documentElement
770 self._createTree(net, root)
771 self._out.write(self._doc.toprettyxml())
772
778
782
784 node = self._doc.createElement(child.__class__.__name__)
785 node.setAttribute("id", child.id)
786 node.setAttribute("netmask", str(child.netmask))
787 tree.appendChild(node)
788 return node
789
790
792
797
799 if format in self._printerFactories:
800 factory = self._printerFactories[format]
801 return factory(out)
802 else:
803 args = (format, self._printerFactories.keys())
804 raise Exception("Invalid format '%s' must be one of %s" % args)
805