diff --git a/docs/jsonformat.rst b/docs/jsonformat.rst index 5541535..d0b7e46 100644 --- a/docs/jsonformat.rst +++ b/docs/jsonformat.rst @@ -24,7 +24,8 @@ Json Options for a ZenPack:: "organizers": [], "zProperties": [], "deviceClasses": [], - "relationships": [] + "relationships": []. + "discoveryMappings": [] } * id: A string defining the unique name of your Zenpack. [required] @@ -39,6 +40,7 @@ Json Options for a ZenPack:: * zproperties: An Array of :ref:`zproperty` elements. [optional] * deviceClasses: An Array of :ref:`deviceClass` elements. [optional] * relationships: An Array of :ref:`relationship` elements. [optional] +* discoveryMappings: An Array of :ref:`discoveryMapping` elements. [optional] * organizers: An Array of :ref:`organizer` elements. [optional] .. _zproperties: @@ -258,6 +260,21 @@ Json Options for a DeviceType:: * impactedBy: An Array of components that this component is impactedBy. * The component can be in :ref:`shorthand` notation. +.. _discoveryMapping: + +discoveryMapping {} +------------------- + +Json Options for a DiscoveryMapping:: + + "discoveryMappings": [{ + "oid": '1.3.6.1.5', + "deviceClass": 'Network/Switch/Cisco/Foo' + }] + +* oid: a devices SnmpOid to match +* deviceClass: The destination device Class + .. _properties: properties [] diff --git a/examples/autoclassification.json b/examples/autoclassification.json new file mode 100644 index 0000000..d083ba2 --- /dev/null +++ b/examples/autoclassification.json @@ -0,0 +1,9 @@ +{ + "deviceClasses": [ { "path": "Device" } ], + "discoveryMappings": [ + { "oid": "1.2.3.4.5", + "deviceClass": "Network/Example"}, + { "oid": "1.2.3.4.5.6", + "deviceClass": "Network/Example"}], + "id": "ZenPacks.training.AutoClassification" +} diff --git a/setup.py b/setup.py index e653d24..7e7b913 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ 'Mock', 'nose', 'pep8', + 'argparse' ] diff --git a/zpg/AutoClassificationZcml.py b/zpg/AutoClassificationZcml.py new file mode 100644 index 0000000..f4b5f28 --- /dev/null +++ b/zpg/AutoClassificationZcml.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +############################################################################## +# +# Copyright (C) Zenoss, Inc. 2013, all rights reserved. +# +# This content is made available according to terms specified in the LICENSE +# file at the top-level directory of this package. +# +############################################################################## + +from ._defaults import Defaults +from ._zenoss_utils import KlassExpand, zpDir +from .Template import Template +defaults = Defaults() + + +class AutoClassificationZcml(Template): + """Build the autoclassification.zcml file""" + + def __init__(self, + zenpack, + ): + '''Args: + zenpack: ZenPack class instance + + ''' + + super(AutoClassificationZcml, self).__init__(zenpack) + self.source_template = 'autoclassification.zcml.tmpl' + self.zenpack = zenpack + self.components = zenpack.components + + self.dest_file = "%s/autoclassification.zcml" % zpDir(zenpack) + + def discoveryMappings(self): + if self.zenpack.discoveryMappings: + return True + return False + + def write(self): + '''Write the file''' + + if self.discoveryMappings(): + self.processTemplate() diff --git a/zpg/Component.py b/zpg/Component.py index ddc9fad..598f48a 100644 --- a/zpg/Component.py +++ b/zpg/Component.py @@ -312,12 +312,13 @@ def displayInfo(self): imports = "from Products.Zuul.infos.device import DeviceInfo" else: imports = "from Products.Zuul.infos.component import ComponentInfo" - if self.properties: - self.imports.append(imports) - return True - if self.ManyRelationships(): - self.imports.append(imports) - return True + if self.componentInZenPackNameSpace(): + if self.properties: + self.imports.append(imports) + return True + if self.ManyRelationships(): + self.imports.append(imports) + return True return False def displayIInfo(self): @@ -327,13 +328,14 @@ def displayIInfo(self): imports = "from %s import IDeviceInfo" % name else: imports = "from %s.component import IComponentInfo" % name - for p in self.properties.values(): - if p.detailDisplay: + if self.componentInZenPackNameSpace(): + for p in self.properties.values(): + if p.detailDisplay: + self.imports.append(imports) + return True + if self.ManyRelationships(): self.imports.append(imports) return True - if self.ManyRelationships(): - self.imports.append(imports) - return True return False @classmethod @@ -445,9 +447,16 @@ def impactSingle(self, impactee): return True return False + def componentInZenPackNameSpace(self): + 'return true if the component id startswith the zenpack id' + return self.id.startswith(self.zenpack.id) + def write(self): """Write the component files""" self.updateImports() self.findUpdateComponents() self.convertImpactStringsToRealComponents() - self.processTemplate() + + # Only write components that are prefixed in this zenpacks namespace. + if self.componentInZenPackNameSpace(): + self.processTemplate() diff --git a/zpg/ComponentJS.py b/zpg/ComponentJS.py index 7419d5d..c008b0c 100644 --- a/zpg/ComponentJS.py +++ b/zpg/ComponentJS.py @@ -23,8 +23,9 @@ def __init__(self, deviceClass): self.deviceClass = deviceClass self.zenpack = self.deviceClass.zenpack self.zPythonClass = self.deviceClass.zPythonClass - self.ConfigureComponent = "%s.%s" % ( - self.zPythonClass, self.zPythonClass.split('.')[-1]) + self.ConfigureComponent = self.zPythonClass + #self.ConfigureComponent = "%s.%s" % ( + # self.zPythonClass, self.zPythonClass.split('.')[-1]) self.source_template = 'component.js.tmpl' self.dest_file = "%s/resources/js/%s.js" % ( diff --git a/zpg/Configure.py b/zpg/Configure.py index 31aeb33..e93691c 100644 --- a/zpg/Configure.py +++ b/zpg/Configure.py @@ -43,6 +43,11 @@ def impactAdapters(self): return True return False + def discoveryMappings(self): + if self.zenpack.discoveryMappings: + return True + return False + # TODO # Router and facade # custom device loaders diff --git a/zpg/DiscoveryMapping.py b/zpg/DiscoveryMapping.py new file mode 100644 index 0000000..e01080c --- /dev/null +++ b/zpg/DiscoveryMapping.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# +# Copyright (C) Zenoss, Inc. 2013, all rights reserved. +# +# This content is made available according to terms specified in the LICENSE +# file at the top-level directory of this package. +# +# + +from .colors import error, warn, debug, info, green, red, yellow + + +class DiscoveryMapping(object): + ''' Discovery Mapping Object ''' + + discoveryMappings = {} + + def __init__(self, + zenpack, + oid, + deviceClass, + *args, + **kwargs + ): + """Args: + zenpack: the containing zenpack + oid: a devices snmpoid to match + deviceClass: destination device class to move the + discovered device + """ + self.zenpack = zenpack + self.oid = oid + self.deviceClass = deviceClass + + for key in kwargs: + do_not_warn = False + clsname = self.__class__.__name__ + layer = "%s:%s" % (clsname, self.name) + msg = "WARNING: [%s] unknown keyword ignored in file: '%s'" + margs = (layer, key) + if not do_not_warn: + warn(self.logger, yellow(msg) % margs) + + DiscoveryMapping.discoveryMappings[self.oid] = self + self.zenpack.registerDiscoveryMapping(self) diff --git a/zpg/ImpactPy.py b/zpg/ImpactPy.py index cbdfc08..b434190 100644 --- a/zpg/ImpactPy.py +++ b/zpg/ImpactPy.py @@ -50,8 +50,17 @@ def updateImports(self): if istring not in self.imports: self.imports.append(istring) + def impactAdapters(self): + '''Return true if there are any impact relationships''' + for c in self.components.values(): + if c.hasImpact(): + return True + return False + def write(self): '''Write the impact file''' self.updateImports() - self.processTemplate() + + if self.impactAdapters(): + self.processTemplate() diff --git a/zpg/ImpactZcml.py b/zpg/ImpactZcml.py index 71b3e9c..0019d7d 100644 --- a/zpg/ImpactZcml.py +++ b/zpg/ImpactZcml.py @@ -29,8 +29,16 @@ def __init__(self, self.dest_file = "%s/impact.zcml" % zpDir(zenpack) + def impactAdapters(self): + '''Return true if there are any impact relationships''' + for c in self.components.values(): + if c.hasImpact(): + return True + return False + def write(self): '''Write the impact file''' #self.updateImports() - self.processTemplate() + if self.impactAdapters(): + self.processTemplate() diff --git a/zpg/Templates/autoclassification.zcml.tmpl b/zpg/Templates/autoclassification.zcml.tmpl new file mode 100644 index 0000000..ed4c2e0 --- /dev/null +++ b/zpg/Templates/autoclassification.zcml.tmpl @@ -0,0 +1,28 @@ + + + + + + + + +#for $discoveryMapping in $zenpack.discoveryMappings.values + + +#end for + + + + diff --git a/zpg/Templates/configure.zcml.tmpl b/zpg/Templates/configure.zcml.tmpl index 6954e59..137e592 100644 --- a/zpg/Templates/configure.zcml.tmpl +++ b/zpg/Templates/configure.zcml.tmpl @@ -81,4 +81,11 @@ --> #end if +#if $discoveryMappings + +#end if diff --git a/zpg/ZenPack.py b/zpg/ZenPack.py index fe16c79..ea02542 100644 --- a/zpg/ZenPack.py +++ b/zpg/ZenPack.py @@ -22,6 +22,7 @@ from .ComponentJS import ComponentJS from .Configure import Configure from .DirLayout import DirLayout +from .DiscoveryMapping import DiscoveryMapping from .DeviceClass import DeviceClass from .License import License from .ObjectsXml import ObjectsXml @@ -33,6 +34,7 @@ from .ZenPackUI import ZenPackUI from .ImpactPy import ImpactPy from .ImpactZcml import ImpactZcml +from .AutoClassificationZcml import AutoClassificationZcml defaults = Defaults() @@ -58,6 +60,7 @@ def __init__(self, zProperties=None, deviceClasses=None, relationships=None, + discoveryMappings=None, opts=None, *args, **kwargs @@ -70,6 +73,7 @@ def __init__(self, self.deviceClasses = {} self.components = {} self.relationships = {} + self.discoveryMappings = {} self.organizers = {} self.componentJSs = {} self.zproperties = {} @@ -107,16 +111,24 @@ def __init__(self, self.zenpackUI = ZenPackUI(self) self.objects_xml = ObjectsXml(self) self.impact_zcml = ImpactZcml(self) + self.autoclassification_zcml = AutoClassificationZcml(self) self.impact = ImpactPy(self) if zProperties: for zp in zProperties: self.addZProperty(**zp) + if deviceClasses: for dc in deviceClasses: self.addDeviceClass(**dc) + if relationships: for rel in relationships: self.addRelation(**rel) + + if discoveryMappings: + for mapping in discoveryMappings: + self.addDiscoveryMapping(**mapping) + # Make sure we create the organizers after the deviceClasses # because we look up the zPythonClasses out of the deviceClasses if organizers: @@ -143,11 +155,33 @@ def addRelation(self, *args, **kwargs): r = Relationship(self, *args, **kwargs) return r + def addDiscoveryMapping(self, *args, **kwargs): + r = DiscoveryMapping(self, *args, **kwargs) + return r + def addOrganizer(self, *args, **kwargs): o = Organizer(self, *args, **kwargs) return o - def addZProperty(self, name, type_='string', default='', Category=None): + def addZProperty(self, name, type_='string', default='', + Category=None, **kwargs): + + for key in kwargs: + do_not_warn = False + layer = self.__class__.__name__ + msg = "WARNING: JSON keyword ignored in layer '%s': '%s'" + margs = (layer, key) + if key == "Type": + msg = "WARNING: JSON keyword deprecated in '%s' layer. "\ + "'%s' is now '%s'." + margs = (layer, key, key.lower()) + self.type_ = kwargs[key] + elif key == "type": + self.type_ = type_ = kwargs[key] + do_not_warn = True + if not do_not_warn: + warn(self.logger, yellow(msg) % margs) + if type_ == 'string': if not default.startswith('\''): default = '\'' + default @@ -155,6 +189,7 @@ def addZProperty(self, name, type_='string', default='', Category=None): default = default + '\'' if not default.endswith('\''): default = default + '\'' + self.zproperties[name] = (name, default, type_, Category) def registerComponent(self, component): @@ -172,6 +207,9 @@ def registerDeviceClass(self, deviceClass): def registerOrganizer(self, organizer): self.organizers[organizer.id] = organizer + def registerDiscoveryMapping(self, discoveryMapping): + self.discoveryMappings[discoveryMapping.oid] = discoveryMapping + def __repr__(self): return "%s \n\tAUTHOR: %s\n\tVERSION: %s\n\tLICENSE: %s" \ % (self.id, self.author, self.version, self.license) @@ -226,4 +264,7 @@ def write(self, verbose=False): # Create the impact.py self.impact.write() + # Create the autoclassification.zcml + self.autoclassification_zcml.write() + self.updateGitTemplates() diff --git a/zpg/_defaults.py b/zpg/_defaults.py index 6b48e25..682dd02 100644 --- a/zpg/_defaults.py +++ b/zpg/_defaults.py @@ -26,7 +26,7 @@ 'author': 'ZenossLabs ', 'author_email': 'labs@zenoss.com', 'description': 'A tool to assist building zenpacks.', - 'version': '1.0.12', + 'version': '1.0.13', 'license': 'GPLv2', 'component_classes': [ 'Products.ZenModel.DeviceComponent.DeviceComponent', diff --git a/zpg/tests/test_Component.py b/zpg/tests/test_Component.py index 1e7198d..9aab5fa 100644 --- a/zpg/tests/test_Component.py +++ b/zpg/tests/test_Component.py @@ -301,19 +301,19 @@ class TestDisplayInfo(SimpleSetup): def test_properties(self): dc = self.zp.addDeviceClass('Device', zPythonClass='a.b.c.d.Device') - e = dc.addComponentType('a.Enclosure') + e = dc.addComponentType('Enclosure') e.addProperty('foo') self.assertEqual(e.displayInfo(), True) def test_trueManyRelationships(self): dc = self.zp.addDeviceClass('Device', zPythonClass='a.b.c.d.Device') - e2 = dc.addComponentType('a.Enclosure2') + e2 = dc.addComponentType('Enclosure2') e2.addComponentType('Drive2') self.assertEqual(e2.displayInfo(), True) def test_donotdisplayInfo(self): dc = self.zp.addDeviceClass('Device', zPythonClass='a.b.c.d.Device') - e = dc.addComponentType('a.Enclosure') + e = dc.addComponentType('Enclosure3') self.assertEqual(e.displayInfo(), False) @@ -321,19 +321,19 @@ class TestDisplayIInfo(SimpleSetup): def test_properties(self): dc = self.zp.addDeviceClass('Device', zPythonClass='a.b.c.d.Device') - e = dc.addComponentType('a.Enclosure') + e = dc.addComponentType('Enclosure') e.addProperty('foo') self.assertEqual(e.displayIInfo(), True) def test_trueManyRelationships(self): dc = self.zp.addDeviceClass('Device', zPythonClass='a.b.c.d.Device') - e2 = dc.addComponentType('a.Enclosure2') + e2 = dc.addComponentType('Enclosure2') e2.addComponentType('Drive2') self.assertEqual(e2.displayIInfo(), True) def test_donotdisplayIInfo(self): dc = self.zp.addDeviceClass('Device', zPythonClass='a.b.c.d.Device') - e = dc.addComponentType('a.Enclosure') + e = dc.addComponentType('Enclosure3') self.assertEqual(e.displayIInfo(), False)