search

Classes and Relationships

Classes and relationships form the model that forms the basis for everything Zenoss does. Classes are things like DeviceFileSystemIpInterface and OSProcess. Relationships state things like a Device contains many FileSystems. You will need to extend this model when the standard classes and relationships don’t adequately represent the model of a target your ZenPack is attempting to monitor. For example, a XenServer ZenPack needs to represent concepts like pools, storage repositories and virtual machines.

Contents

Standard Classes

The standard classes exist on all Zenoss systems. If these are the only types of things you care to model and monitor then you may not need to create your own classes or relationships.

  • Device
    • DeviceHW (hw)
      • CPU (hw/cpus)
      • ExpansionCard (hw/cards)
      • Fan (hw/fans)
      • HardDisk (hw/harddisks)
      • PowerSupply (hw/powersupplies)
      • TemperatureSensor (hw/temperaturesensors)
    • OperatingSystem (os)
      • FileSystem (os/filesystems)
      • IpInterface (os/interfaces)
      • IpRouteEntry (os/routes)
      • OSProcess (os/processes)
      • IpService (os/ipservices)
      • WinService (os/winservices)

Classes

If you need more than the standard classes provide, you will need to extend one of the following base classes provided by zenpacklib.

  • zenpacklib.Device
  • zenpacklib.Component
    • zenpacklib.HWComponent
      • zenpacklib.CPU
      • zenpacklib.ExpansionCard
      • zenpacklib.Fan
      • zenpacklib.HardDisk
      • zenpacklib.PowerSupply
      • zenpacklib.TemperatureSensor
    • zenpacklib.OSComponent
      • zenpacklib.FileSystem
      • zenpacklib.IpInterface
      • zenpacklib.IpRouteEntry
      • zenpacklib.OSProcess
      • zenpacklib.Service
        • zenpacklib.IpService
        • zenpacklib.WinService

You use zenpacklib.Device to create new device types of which instances will appear on the Infrastructure screen. You use zenpacklib.Component to create new component types of which instances will appear under Components on a device’s left navigation pane. Frequently when ZenPacks need to add new classes, they will add a single new device type with many new components types. For example, a XenServer ZenPack would add a new device type called Endpoint which represents the XenAPI management interface. That Endpoint device type would have many components of types such as PoolStorageRepository and VirtualMachine.

The other supported classes are proxies for their platform equivalents, and are to be used when you want to extend one of the platform component types rather than creating a totally new component type.

Relationships

Relationships are Zenoss’ way of saying objects are related to each other. For example, the DeviceHW class contains many CPUs of the CPU class. You must also declare relationships between classes in your ZenPack. If you only declare types based on zenpacklib.Device you don’t have to do this because they’ll automatically have a relationship to their containing device class among other things. However, you must define at least a containing relationship for every type based on zenpacklib.Component you create. This is because components aren’t contained in any relationship by default, and every object in Zenoss must be contained somewhere.

zenpacklib supports the following types of relationships.

  • One-to-Many Containing (1:MC)
  • One-to-Many (1:M)
  • Many-to-Many (M:M)
  • One-to-One (1:1)

It’s important to understand the different between containing and non-containing relationships. Each component type must be contained by exactly one relationship. Beyond that a device or component type may have as many non- containing relationships as you like. This is because every object in Zenoss has a single primary path that describes where it is stored in the tree that is the Zenoss object database.

A simplified version of XenServer’s classes and relationships provides for a good example. The following list of relationship states the following: An endpoint contains zero or more pools, each pool contains zero or more storage repositories and virtual machines, and each storage repository is related to zero or more virtual machines.

  • Endpoint 1:MC Pool
  • Pool 1:MC StorageRepository
  • Pool 1:MC VirtualMachine
  • StorageRepository M:M VirtualMachine


Once a ZenPack has been installed in a Zenoss environment, additional relationships can be added, but existing relationships should never be removed or renamed. Doing so can cause problems in your Zenoss environment (e.g., errors when attempting to delete devices). Therefore, please carefully consider the naming and organization of your custom relationships before deploying your ZenPack to a Production environment.

Adding Classes and Relationships

To add classes and relationships to zenpack.yaml you add entries to the top-level classes and class_relationships fields. The following example shows a XenServer Endpoint device type along with PoolStorageRepository, and VirtualMachine component types.

zenpack.yaml
name: ZenPacks.example.XenServer

classes:
  DEFAULTS:
    base: [zenpacklib.Component]

  XenServerEndpoint:
    base: [zenpacklib.Device]
    label: Endpoint

  XenServerPool:
    label: Pool

    properties:
      ha_enabled:
        type: boolean
        label: HA Enabled
        short_label: HA

      ha_allow_overcommit:
        type: boolean
        label: HA Allow Overcommit
        short_label: Overcommit

  XenServerStorageRepository:
    label: Storage Repository

    properties:
      physical_size:
        type: int
        label: Physical Size
        short_label: Size

  XenServerVirtualMachine:
    label: Virtual Machine

    properties:
      vcpus_at_startup:
        type: int
        label: vCPUs at Startup
        short_label: vCPUs

class_relationships:
  - XenServerEndpoint 1:MC XenServerPool
  - XenServerPool 1:MC XenServerStorageRepository
  - XenServerPool 1:MC XenServerVirtualMachine
  - XenServerStorageRepository M:M XenServerVirtualMachine


DEFAULTS can be used in classes just like in zProperties to avoid repetitively setting the same field for many entries. Note specifically how XenServerPool, XenServerStorageRepository and XenServerVirtualMachine will inherit the default while XenServerEndpoint overrides it.

Classes and their properties allow for a wide range of control. See the following section for details.

Extending ZenPackLib Classes

Occasionally, you may wish to add your own custom methods to your YAML-defined classes or otherwise extend their functionality beyond ZenPackLib’s current capabilities. Doing so requires creating a Python file that imports and overrides the class you wish to modify, and this is relatively straightforward.

Suppose we have a component class called “BasicComponent”, and we want to provide a method called “hello world” that, when called, will return the string “Hello World” and display it in the component grid.

Our YAML file looks like this:

zenpack.yaml
name: ZenPacks.zenoss.BasicZenPack

class_relationships:
  - BasicDevice 1:MC BasicComponent

classes:
  BasicDevice:
    base: [zenpacklib.Device]

  BasicComponent:
    base: [zenpacklib.Component]
    properties:
      hello_world:
        # this will appear as the column header
        # in the component grid
        label: Hello World
        # this should be displayed in the component grid
        grid_display: true
        # tells ZenPackLib that this isn't a typical
        # property like a string, integer, boolean, etc...
        api_only: true
        # this is the type of property
        api_backendtype: method

First, the ZenPack’s init file (ZenPacks/zenoss/BasicZenPack/__init__.py) should contain the following content.

_init_.py
from ZenPacks.zenoss.ZenPackLib import zenpacklib

CFG = zenpacklib.load_yaml()
schema = CFG.zenpack_module.schema

Next, we create a file for the component extension (ZenPacks/zenoss/BasicZenPack/BasicComponent.py) with the following content.

BasicComponent.py
from . import schema

class BasicComponent(schema.BasicComponent):
    """Class override for BasisComponent"""

    def hello_world(self):
        return 'Hello World!'

The “Hello World” column will now display in the component grid, and the string “Hello World!” will be printed in each row of component output.

We can also override ZenPackLib’s built-in methods, but must be careful doing so to avoid undesirable results. Supposing that our YAML specifies some monitoring templates (not defined here) for BasicComponent, and for some reason we want to randomly choose which ones are displayed in the GUI. To do so, we need to override the “getRRDTemplates” method.

Our YAML file is modified:

zenpack.yaml
name: ZenPacks.zenoss.BasicZenPack

class_relationships:
  - BasicDevice 1:MC BasicComponent

classes:
  BasicDevice:
    base: [zenpacklib.Device]

  BasicComponent:
    base: [zenpacklib.Component]
    properties:
      hello_world:
        label: Hello World
        api_only: true
        api_backendtype: method
        grid_display: true

    monitoring_templates: [ThisTemplate, ThatTemplate]

And we further modify our BaseComponent.py as follows:

BasicComponent.py
import random
from . import schema

class BasicComponent(schema.BasicComponent):
    """Class override for BasisComponent"""

    def hello_world(self):
        return 'Hello World!'

    def getRRDTemplates(self):
        """Return randomly chosen templates."""
        templates = []
        # make sure we call the base method when we override it
        for template in super(BasicComponent, self).getRRDTemplates():
            # rolling the dice
            if bool(random.randint(0,1)):
                templates.append(template)
        return templates

The key point to remember here is the call to super(BasicComponent, self).getRRDTemplates(). This instructs Python to use the original method before we modify its output. Similar care must be exercised when overriding built-in methods and properties, assuming a safer method cannot be found.

Support for Multiple YAML Files

For particularly complex ZenPacks the YAML file can grow to be quite large, potentially making management cumbersome. To address this concern, ZenPackLib supports splitting the zenpack.yaml files into multiple files. The following conditions should be observed when using multiple files:

  • The YAML files should have a .yaml extension.

  • The “load_yaml” method will detect and load yaml files automatically. This behavior can be overridden by calling load_yaml(yaml_doc=[doc1, doc2]). In this case the full file paths will need to be specified:

    _init_.py
    import os
    files = ['file1.yaml', 'file2.yaml']
    YAML_DOCS = [os.path.join(os.path.dirname(__file__), f) for f in files]
    from ZenPacks.zenoss.ZenPackLib import zenpacklib
    CFG = zenpacklib.load_yaml(yaml_doc=YAML_DOCS)
    schema = CFG.zenpack_module.schema
  • The name parameter (ZenPack name), if used in multiple files, should be identical between them.

  • If a given YAML section (device_classes, classes, device_classes, etc) is split between files, then each file should give the complete path to the defined objects.

    file1.yaml
    name: ZenPacks.zenoss.BasicZenPack
    
    class_relationships:
      - BaseComponent 1:MC AuxComponent
    
    classes:
      BasicDevice:
        base: [zenpacklib.Device]
        monitoring_templates: [BasicDevice]
    
      BasicComponent:
        base: [zenpacklib.Component]
        monitoring_templates: [BasicComponent]
    file2.yaml
    class_relationships:
      - BaseDevice 1:MC BaseComponent
    
    classes:
      SubComponent:
        base: [BasicComponent]
        monitoring_templates: [SubComponent]
    
      AuxComponent:
        base: [SubComponent]
        monitoring_templates: [AuxComponent]
  • Using conflicting parameters (like setting different DEFAULTS for the same entity in different files) will likely lead to undesirable results.

Class Fields

The following fields are valid for a class entry.

NameDescriptionRequiredTypeDefault Value
nameName (e.g. XenServerEndpoint). Must be a valid Python class name.yesstring(implied from key in classes map)
baseList of base classes to extend. See Classes.nolist<classname>

[zenpacklib.Component]

meta_typeGlobally unique name for the class.nostring(same as name)
labelHuman-friendly label for the class.nostring(same as meta_type)
plural_labelPlural form of label.nostring(same as label with an "s" suffix)
short_labelShort form of label. Used when display space is limited.nostring(same as label)
plural_short_labelPlural form of short_label.nostring(same as short_label with an "s" suffix)
iconFilename (in resources/) for icon.nostring(same as name with a ".png" suffix in resources/icon/)
label_widthWidth of label text in pixels.nointeger80
plural_label_widthWidth of plural_label text in pixels.nointeger(same as label_width + 7)
content_widthExpected width of object's title in pixels.nointeger(same as label_width)
auto_expand_columnColumn (property) to auto-expand in component grid.nostringname
initial_sort_columnColumn (property) by which component grid is initially sorted.nostringname
orderOrder to display this class among other classes.nointeger (1-100)100 (See Order Field)
filter_displayWill related components be filterable by components of this type?nobooleantrue
filter_hide_fromClasses for which this class should not show in the filter dropdown.nolist<classname>

[] (empty list)

monitoring_templatesList of monitoring template names to bind to components of this type.nolist<string>[] (empty list)
propertiesProperties for this class.nomap<name, Class Property>{} (empty map)
relationshipsRelationship overrides for this class.nomap<name, Relationship Override>{} (empty map)
impactsRelationship or method names that return an object, or objects.
The returned objects are "impacted by" objects of this class.
nolist<relationship_or_method_name>[] (empty list)
impacted_byRelationship or method names that return an object, or objects.
The returned objects "impact" objects of this class.
nolist<relationship_or_method_name>[] (empty list)
impact_triggersImpact trigger policy definitions for this class.nomap<name, Impact Trigger>{} (empty map)
dynamicview_viewsNames of Dynamic Views objects of this class can appear in.nolist<dynamicview_view_name>[service_view]
dynamicview_groupDynamic View group name for objects of this class.
Can be overridden by implementing getDynamicViewGroup().
nostring(same as plural_short_label)
dynamicview_weightDynamic View weight for objects of this class.
Higher numbers are further to the right.
Can be overridden by implementing getDynamicViewWeight().
nointeger(order * 10) + 1000
dynamicview_relationsMap of Dynamic View relationship types for this class.
Map values are lists like those in impacts and impacted_by.
nomap<type, list<relation_or_method>>{} (empty map)
extra_pathsExtra paths under which objects of this class will be indexed.nolist<list<regexp>>
See Extra Paths Field.
[] (empty list)

Order Field

The order field takes any integer value between 1 and 100. However, its behavior depends on whether it applies to a Class, a Property, or a Relationship. For a relationship, order behavior can further depend on the type of relationship.

There is an overall clustering for like items in the GUI component grid, following this order:

  1. Containing Components
  2. Properties
  3. Contained Components

With containing relationships listed before visible properties and finally any non-containing relationships.

Earlier (pre-2.0) versions of ZenPackLib accepted float arguments for order. However, ZenPackLib now normalizes these values behind the scenes to integers between 1 and 100.

Extra Paths Field

Each item in extra_paths is expressed as a tuple of regular expression patterns that are matched in order against the actual relationship path structure as it is traversed.

To facilitate matching, we construct a compiled set of regular expressions that can be matched against the entire path string, from root to leaf.

The following:

("orgComponent", "(parentOrg)+")

Is transformed into a “pattern stream”, which is a list of regular expressions that can be applied incrementally as we traverse the possible paths:

( re.compile(^orgComponent), re.compile(^orgComponent/(parentOrg)+), re.compile(^orgComponent/(parentOrg)+/?$’ )

Once traversal embarks upon a stream, these patterns are matched in order as the traversal proceeds, with the first one to fail causing recursion to stop. When the final one is matched, then the objects on that relation are matched. Note that the final one may match multiple times if recursive relationships are in play.

Class Property Fields

The following fields are valid for a class property entry.

NameDescriptionRequiredTypeDefault Value
nameName (e.g. ha_enabled). Must be a valid Python variable name.yesstring(implied from key in properties map)
typeType of property: string, int, float, boolean, lines, password or entity.
All types are strictly enforced except for entity.
nostringstring
defaultDefault value for property.no(varies depending on type)None
labelHuman-friendly label for the property.nostring(same as name)
short_labelShort form of label. Used as a column header where space is limited.nostring(same as label)
label_widthWidth of label text in pixels.nointeger80
content_widthExpected width of property’s value in pixels.nointeger(same as label_width)
displayShould this property be shown as a column and in details?nobooleantrue
details_displayShould this property be shown in details?nobooleantrue
grid_displayShould this property be shown as a column?nobooleantrue
orderOrder to display this property among other properties. (1-100)nointeger100
editableShould this property be editable in details?nobooleanfalse
rendererJavaScript renderer for property value.nostringNone (renders value as-is)
api_onlyShould this property be for the API only?
The property or method (see api_backendtype) must be manually implemented if this is set to true.
nobooleanfalse
api_backendtypeImplementation style for the property if api_only is true.
Must be one of: property or method.
nostringproperty
enumEnumeration map for property.
Set to something like {1: OK, 2: ERROR} to provide text labels for property values.
nomap<value, label>{} (empty map)
datapointdatasource_datapoint value to use as the value for this property.nostringNone
datapoint_defaultDefault value for property if datapoint is set, but no data exists.noanyNone
datapoint_cachedShould the value for datapoint be cached for a limited time? Can improve UI performance.nobooleantrue
index_typeType of indexing for the property: field or keyword.nostringNone (no indexing)
index_scopeScope of index: device or global. Only applies if index_type is set.nostringdevice

Relationship Override Fields

The following fields are valid for a relationship override entry.

NameDescriptionRequiredTypeDefault Value
nameName (e.g. xenServerPools). Must match a relationship name defined in class_relationships.yesstring(implied from key in relationships map)
labelHuman-friendly label for the relationship.nostring(label of class to which the relationship refers)
short_labelShort form of label. Used where space is limited.nostring(same as label or referred class' short_label)
label_widthWidth of label text in pixels.nointeger(same as referred class' label_width)
content_widthExpected width of relationship's value in pixels.nointeger(varies depending on relationship type)
displayShould this relationship be shown as a column and in details?nobooleantrue
details_displayShould this relationship be shown in details?nobooleantrue
grid_displayShould this relationship be shown as a column?nobooleantrue
orderOrder to display this relationship among other relationships and properties. (1-100)nointeger100
rendererJavaScript renderer for relationship value.nostringNone
render_with_typeShould related object be rendered with its type?nobooleanfalse

Impact Trigger Fields

The following fields are valid for an Impact trigger entry.

NameDescriptionRequiredTypeDefault Value
nameName (e.g. avail_pct_5). Must be a valid Python variable name.yesstring(implied from key in properties map)
policyPolicy type: AVAILABILITY or PERFORMANCEnostringAVAILABILITY
triggerTrigger type: policyPercentageTrigger, policyThresholdTrigger, negativeThresholdTriggernostring

policyPercentageTrigger

thresholdNumerical boundary for the trigger.nointeger50
stateState of this object when trigger criteria met (see note).nostringUNKNOWN
dependent_stateState of dependent objects meeting trigger criteria (see note).nostringUNKNOWN


Valid values for both state and dependent_state depend on the choice of policy parameter:

  • AVAILABILITY: DOWN, UP, DEGRADED, ATRISK, or UNKNOWN
  • PERFORMANCE: UNACCEPTABLE, DEGRADED, ACCEPTABLE, or UNKNOWN
  • CAPACITY: UNACCEPTABLE, REDUCED, ACCEPTABLE, or UNKNOWN