Classes and relationships
Classes and relationships form the model that forms the basis for
everything Zenoss does. Classes are things
like Device
, FileSystem
, IpInterface
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.
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)
- DeviceHW (hw)
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
- zenpacklib.HWComponent
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 Pool
, StorageRepository
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 (for example, 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 Pool
, StorageRepository
, and VirtualMachine
component types.
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:
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.
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.
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:
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:
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__.pyimport 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.yamlname: 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.yamlclass_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.
name
(required, string, default = implied from key in classes map)- Name (e.g. XenServerEndpoint). Must be a valid Python class name.
base
(optional, list, default =[zenpacklib.Component]
)- List of base classes to extend.
meta_type
(optional, string, default =name
)- Globally unique name for the class.
label
(optional, string, default =meta_type
)- Human-friendly label for the class.
plural_label
(optional, string, default =label
withs
appended)- Plural form of
label
. short_label
(optional, string, default =label
)- Short form of
label
. Used when display space is limited. plural_short_label
(optional, string, default =short_label
withs
appended)- Plural form of
short_label
. icon
(optional, string, default =name
with a.png
suffix inresources/icon/
)- Filename (in
resources/
) for icon. label_width
(optional, integer, default = 80)- Width of
label
text in pixels. plural_label_width
(optional, integer, default =label_width
+ 7)- Width of
plural_label
text in pixels. content_width
(optional, integer, default =label_width
)- Expected width of object's
title
in pixels. auto_expand_column
(optional, string, default =name
)- Column (property) to auto-expand in component grid.
initial_sort_column
(optional, string, default =name
)- Column (property) by which component grid is initially sorted.
order
(optional, integer, default = 100)- Order to display this class among other classes. See Order field.
filter_display
(optional, boolean, default = true)- Will related components be filterable by components of this type?
filter_hide_from
(optional, list, default =[]
(empty list))- Classes for which this class should not show in the filter dropdown.
monitoring_templates
(optional, list, default =[]
(empty list))- List of monitoring template names to bind to components of this type.
properties
(optional, map, default ={}
(empty map))- Properties for this class.
relationships
(optional, map, default ={}
(empty map))- Relationship overrides for this class.
impacts
(optional, list, default =[]
(empty list))- Relationship or method names that return an object, or objects. The returned objects are "impacted by" objects of this class.
impacted_by
(optional, list, default =[]
(empty list))- Relationship or method names that return an object, or objects. The returned objects "impact" objects of this class.
impact_triggers
(optional, map, default ={}
(empty map))- Impact trigger policy definitions for this class.
dynamicview_views
(optional, list, default =[service_view]
)- Names of Dynamic Views objects of this class can appear in.
dynamicview_group
(optional, string, default =plural_short_label
)- Dynamic View group name for objects of this class.
Can be overridden by implementing
getDynamicViewGroup()
. dynamicview_weight
(optional, integer, default = (order
* 10) + 1000))- Dynamic View weight for objects of this class.
Higher numbers are further to the right.
Can be overridden by implementing
getDynamicViewWeight()
. dynamicview_relations
(optional, map, default ={}
(empty map))- Map of Dynamic View relationship types for this class.
Map values are lists like those in
impacts
andimpacted_by
. extra_paths
(optional, list, default =[]
(empty list))- Extra paths under which objects of this class will be indexed.
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:
- Containing components
- Properties
- 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.
name
(required, string, default = implied from key in relationships map)- Name (e.g. ha_enabled). Must be a valid Python variable name.
type
(optional, string, default =string
)- Type of property:
string
,int
,float
,boolean
,lines
,password
, orentity
. All types are strictly enforced exceptentity
. default
(optional)- Default value of property.
label
(optional, string, default =name
)- Human-friendly label for the property.
short_label
(optional, string, default =label
)- Short form of label. Used as a column header where space is limited.
label_width
(optional, integer, default = 80)- Width of label text in pixels.
content_width
(optional, integer, default =label_width
)- Expected width of property's value in pixels.
display
(optional, boolean, default = true)- Should this property be shown as a column and in details?
details_display
(optional, boolean, default = true)- Should this property be shown in details?
grid_display
(optional, boolean, default = true)- Should this property be shown as a column?
order
(optional, integer, default= 100)- Order to display this property among other properties (1-100).
editable
(optional, boolean, default = false)- Should this property be editable in details?
renderer
(optional, string, default = None (renders value as-is))- JavaScript renderer for relationship value.
api_only
(optional, boolean, default = false)- Should this property be for the API only? The property or method (see
api_backendtype
) must be manually implemented if this is set to true. api_backendtype
(optional, string, default =property
)- Implementation style for the property if
api_only
is true. Must beproperty
ormethod
. enum
(optional, map, default ={}
)- Enumeration map for property. Set to something like
{1: OK, 2: ERROR}
to provide text labels for property values. datapoint
(optional, string, default = None)- The datasource_datapoint value to use as the value for this property.
datapoint_default
(optional, any, default = None)- Default value for property if datapoint is set, but no data exists.
datapoint_cached
(optional, boolean, default = true)- Should the value for datapoint be cached for a limited time? Can improve UI performance.
index_type
(optional, string, default = None (no indexing))- Type of indexing for the property:
field
orkeyword
. index_scope
(optional, string, default =device
)- Scope of index:
device
orglobal
. Only applies ifindex_type
is set.
Relationship override fields
The following fields are valid for a relationship override entry.
name
(required, string, default = implied from key in relationships map)- Name (e.g. xenServerPools). Must match a relationship name defined in class_relationships.
label
(optional, string, default =label
of class to which the relationship refers)- Human-friendly label for the relationship.
short_label
(optional, string, default =label
orshort_label
of the referred class)- Short form of label. Used where space is limited.
label_width
(optional, integer, default =label_width
of the referred class)- Width of label text in pixels.
content_width
(optional, integer, default = varies depending on relationship type)- Expected width of relationship's value in pixels.
display
(optional, boolean, default = true)- Should this relationship be shown as a column and in details?
details_display
(optional, boolean, default = true)- Should this relationship be shown in details?
grid_display
(optional, boolean, default = true)- Should this relationship be shown as a column?
order
(optional, integer, default = 100)- Order to display this relationship among other relationships and properties (1-100).
renderer
(optional, string, default = None)- JavaScript renderer for relationship value.
render_with_type
(optional, boolean, default = false)- Should related object be rendered with its type?
Impact trigger fields
The following fields are valid for an Impact trigger entry.
name
(required, string, default = implied from key in properties map)- Name (e.g. avail_pct_5). Must be a valid Python variable name.
policy
(optional, string, default =AVAILABILITY
)- Policy type:
AVAILABILITY
orPERFORMANCE
trigger
(optional, string, default =policyPercentageTrigger
)- Trigger type:
policyPercentageTrigger
,policyThresholdTrigger
,negativeThresholdTrigger
threshold
(optional, integer, default = 50)- Numerical boundary for the trigger.
state
(optional, string, default =UNKNOWN
)- State of this object when trigger criteria met (see note).
dependent_state
(optional, string, default =UNKNOWN
)- State of dependent objects meeting trigger criteria (see note).
Note
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