pako 发表于 2013-1-29 09:16:03

如何创建一个zenpack(翻译)

 
如何创建一个zenpack 来添加新属性来扩展device
 
这篇文章你能获取到什么信息
这篇文章解释了如果创建一个zenpack来扩展device 增加 一个componet。如何增加一个新的snmp oid 到zenoss中,如何在gui上看到新的component(其实gui的部分坐着没写完)。
 
一个新的zenpack会包括:
1)一个device的子类
2)一个DeviceComponent的子类(里面包括了新的字段)
3)一个modeler 来获取新的oid 提供给 DeviceComponent
4)一个zope 模板来生成 试图展现在前端页面上(zeoss3.0 使用javascript来写了)
 
背景介绍
 
我已经阅读了zenoss的开发者文档和admin文档,但是任何地方都没发现一段描述创建一个zenpack完整的流程的章节。我搜索了网上能找到的代码,但是没有一个zenpack中的modeler会生成一些新的属性到device里。因为网上能找到的发布的zenpack里都没有扩展device和生成一个modeler。我在zenoos的论坛上找到了一篇名为《 Custom ZenPack Rough Guide》的帖子描述的内容正是我想要的,但是这个帖子唯一的问题是作者仅仅建议修改变量名,类名,和oid。这篇文章的另个问题是它正对的是老版本的zenpack,所以一些类名,包名,必须要修改才能在当前的系统中运行
 
Zenpack的目录结构
Zenpacks 必须有3个python包名来组成,用“.”来分开(例如ZenPacks.test.ZPool) 第一个包名一般都是Zenpack。文件之后会自动生成。
 
 
对于一个名叫Zenpacks.test.ZPool的zenpack来说目录结构一般为
<Zenoss home>/zenoss/ZenPacks/
    ZenPacks.test.ZPool/  <Zenpack root directory>
        ZenPacks.test.ZPool.egg-info
        ZenPacks              <The following directories will reflect the Zenpack name as python packages>
            __init__.py
            test
                __init__.py
                ZPool     <任何model对象都应该放在此目录下>
                    __init__.py
                    daemons
                    datasources
                    lib
                    migrate
                    modeler          <存放modeler module 通过oid来收集一些snmp信息>
                        __init__.py
                        plugins
                            __init__.py
                            ZPoolDeviceDetailsModeler.py   <modeler module(collector)>
                    objects
                    reports
                    skins           <存放视图模板,用在GUI上展示数据>
                        __init__.py
                        ZenPacks.test.ZPool  <name of Zenpack>
                            ZPoolDeviceDetailTemplate.pt
 
Model objects
 
model object 应该被存放在 zenpack名最后一个点后面那个名字的文件夹中。(Zenpack.test.ZPool的话就是在ZPool文件夹)
增加一个component到device中,我们需要做如下事:
1)创建一个继承device的对象(它增加一个新的关系到device中),在这个例子中,我命名它为ZPoolDevice。
2)创建一个对象 继承DeviceComponent和ManagedEntity (里面同时定义了一个新的关系链接到ZPoolDevice),命名为ZPoolComponet。
 
ZPoolDevice 内容

from Globals import InitializeClassfrom Products.ZenRelations.RelSchema import *from Products.ZenModel.Device import Devicefrom Products.ZenModel.ZenossSecurity import ZEN_VIEWfrom copy import deepcopyclass ZPoolDevice(Device):    "An APC Switched PDU"    #增加一组关系,将ZPoolChild和ZPoolParent绑定起来,ZPoolParent是component    _relations = Device._relations + (      ('ZPoolChild', ToManyCont(ToOne,            'ZenPacks.test.ZPool.ZPoolComponent', 'ZPoolParent')),      )    # add to Device relations the relation to ZPoolComponent    # The relation is initialized with 5 params, relation name from this side, relation type(ToOne, ToManyCont... etc), component module, and the    # relation name from the other side of the realtion    # next we will define a new tab in the Gui which will preview zope template    factory_type_information = deepcopy(Device.factory_type_information)    factory_type_information['actions'] += (            { 'id'            : 'ZPoolChild'#tab id            , 'name'            : 'ZPool'      #tab name (appears from the device status tab)            , 'action'          : 'ZPoolDeviceDetailTemplate'    #the zope template to call when this tab is selected            , 'permissions'   : (ZEN_VIEW, ) },            )    def __init__(self, *args, **kw):      Device.__init__(self, *args, **kw)      self.buildRelations()InitializeClass(ZPoolDevice)   #must initialize class  
 
ZPoolComponent 内容
 

import localefrom Globals import DTMLFilefrom Globals import InitializeClassfrom Products.ZenUtils.Utils import convToUnitsfrom Products.ZenRelations.RelSchema import *from Products.ZenModel.ZenossSecurity import ZEN_VIEW, ZEN_CHANGE_SETTINGSfrom Products.ZenModel.DeviceComponent import DeviceComponentfrom Products.ZenModel.ManagedEntity import ManagedEntityfrom Products.ZenUtils.Utils import prepIdclass ZPoolComponent(DeviceComponent, ManagedEntity):    #define the type (name to be used in zenoss, most probably as a database table name)    portal_type = meta_type = 'ZPoolComponent'    zPoolName = ""    health = ""    #define component fields#新类的属性,zPoolName和health    _properties = (      {'id':'zPoolName', 'type':'string', 'mode':''},      {'id':'health', 'type':'string', 'mode':''}      )    #define relation the same as in the Device but only inverted    _relations = (      ("ZPoolParent", ToOne(ToManyCont,            "ZenPacks.test.ZPool.ZPoolDevice", "ZPoolChild")),      )    #some getters to be called from the zope template    def viewName(self):      return self.zPoolName    def getZPoolName(self):               return self.zPoolName    def isOnline(self):    #用过modeler收集到的health属性来判断状态      return self.health == 'ONLINE'    #return a new 'Relation' object (the object will be populated at runtime)    def device(self):      return self.ZPoolParent()    def getRRDNames(self):      return []InitializeClass(ZPoolComponent) 
Modeler
modeler 的作用是获取snmp结果,然后存储字段信息。它应该存放在<Zenosshome>/ZenPacks.test.ZPool/ZenPacks/test/ZPool/modeler/plugins/下。modeler有2中模式,一种是映射 snmp 表,另一种是映射定义好的oid。在这个例子中我使用第二种方式。
 
在这个modeler中,它的oid是.1.3.6.1.4.1.2021.55.1,是自己定义的snmp脚本,会返回字符串“data1 ONLINE|data2 DEGRADED|rpool ONLINE” 。
 
modeler代码:

from Products.DataCollector.plugins.CollectorPlugin import SnmpPlugin, GetTableMap, GetMapfrom Products.DataCollector.plugins.DataMaps import ObjectMapclass ZPoolDeviceDetailsModeler(SnmpPlugin):    relname = "ZPoolChild"#The ralation name from the Device side    modname = "ZenPacks.test.ZPool.ZPoolComponent"   #the module of the component to be populated    #list of oids to get, and their respective name, here zenoss will call that oid and will call process() with a parameter result containing map ['zPoolHealth': <value returned>]    snmpGetMap = GetMap({'.1.3.6.1.4.1.2021.55.1' : 'zPoolHealth',                         })    #for the case of one to may relation process should return a list of relations mappings relMap    def process(self, device, results, log):      """collect snmp information from this device      @param device: device that is currently modeled      @param results: tuple of (non snmp table data, snmp table data) both as dict      @param log: logger"""      log.info('processing %s for device %s', self.name(), device.id)      getdata, tabledata = results      rm = self.relMap()      zPoolHealth = getdata['zPoolHealth']      # create a different object model for each relation model and append it to relation model list      for zPool in zPoolHealth.split('|'):            om = self.objectMap()            zpoolFields = zPool.split()            om.zPoolName = zpoolFields            om.health = zpoolFields            rm.append(om)            om.id = self.prepId(str(om.zPoolName))            log.info('Health for ZPool %s Collected health is %s'%(om.zPoolName, om.health))      return rm 此时在zenpack的日志里可以看到‘Health for ZPool %s Collected health is %s’,的内容,但是还不能通过GUI看到。(因为作者没有完成GUI部分的代码)
 
其实我觉得翻的还是比较烂的,大家看的不舒服可以看原文。
原文出处:http://georgefakhri.wordpress.com/2009/07/15/how-to-create-a-zenpack/
 
我跟作者开始写这篇文章时的感受一样,在zenoss的开发文档中没有一段比较简单的讲述一个最基本的zenpack的开发流程,它将很多功能拆分开来将, 而且代码也没很详细的解释,大多数都是贴出其中的一段。最后在作者的这篇文章的评论中发现了一份相对详细的zenpack的文档地址:http://community.zenoss.org/docs/DOC-10268,我看完之后我自己对zenpack大致上有了粗略的一些了解。
 
zenpack最基本的开发如上所说的就4部分
1)device文件,用来创建一个对象,链接到device,此例中是ZPoolDevice。
2)component文件,新device的新属性在此文件中定义,GUI中页面的下拉选项也在次定于,还有一些函数用来当调用数据库中对象属性时初始化用(我自己还没找到modeler中获取到的数据存在哪里了,很苦恼)见ZPoolComponent.py
3)modeler 文件及时一个collector,用过snmp来获取数据,用process()方法来解析,处理,存储数据,见ZPoolDeviceDetailModeler.py
4)最后需要一个文件来将获取的数据展示到页面上,在zenoss3之前是用过skin中的。pt文件,是html写的,zenoss3开始通过使用javascript来写。
 
我自己现在将作者这篇没写完的部分写好了,但是一直界面上看不到,很郁闷。国内,即使是国外zenpack的资料实在太少,官方文档也比较烂。。。没啥多说的。很郁闷。


by:pakoo
email:zealzpc@gmial.com


已经写完GUI部分顺利调试通过,过几天写份完整的开发介绍。
 
先附上GUI代码
info.py

__doc__="""info.pyRepresentation of Bridge components.$Id: info.py,v 1.2 2010/12/14 20:45:46 jc Exp $"""__version__ = "$Revision: 1.4 $"from zope.interface import implementsfrom Products.Zuul.infos import ProxyPropertyfrom Products.Zuul.infos.component import ComponentInfofrom Products.Zuul.decorators import info#from Products.ZenUtils.Utils import convToUnitsfrom ZenPacks.test.ZPool import interfacesclass ZPoolInterfaceInfo(ComponentInfo):    implements(interfaces.IBridgeInterfaceInfo)    zPoolName = ProxyProperty("zPoolName")    health = ProxyProperty("health")    @property    def ZPoolName(self):      return self._object.getZPoolName()    @property    def health(self):      return self._object.gethealth()    @property    def isOnline(self):      return self._object.isOnline() interfaces.py

__doc__="""interfacesdescribes the form field to the user interface.$Id: interfaces.py,v 1.2 2010/12/14 20:46:34 jc Exp $"""__version__ = "$Revision: 1.4 $"from Products.Zuul.interfaces import IComponentInfofrom Products.Zuul.form import schemafrom Products.Zuul.utils import ZuulMessageFactory as _tclass IBridgeInterfaceInfo(IComponentInfo):    """Info adapter for Bridge Interface component"""    zPoolName = schema.Text(title=u"zPoolName", readonly=True, group='Details')    health = schema.Text(title=u"health", readonly=True, group='Details') 
configure.zcml
 

<?xml version="1.0" encoding="utf-8"?><configure xmlns="http://namespaces.zope.org/zope"         xmlns:browser="http://namespaces.zope.org/browser"         xmlns:zcml="http://namespaces.zope.org/zcml">    <configure zcml:condition="installed Products.Zuul">      <adapter factory=".info.ZPoolInterfaceInfo"               for=".ZPoolComponent.ZPoolComponent"               provides=".interfaces.IBridgeInterfaceInfo"               />      <browser:resourceDirectory               name="ZPool"               directory="resources"               />      <browser:viewlet               name="js-ZPool"               paths="/++resource++ZPool/ZPool.js"               weight="10"               manager="Products.ZenUI3.browser.interfaces.IJavaScriptSrcManager"               class="Products.ZenUI3.browser.javascript.JavaScriptSrcBundleViewlet"               permission="zope2.Public"               />    </configure></configure> 
ZPool.js

(function(){var ZC = Ext.ns('Zenoss.component');function render_link(ob) {    if (ob && ob.uid) {      return Zenoss.render.link(ob.uid);    } else {      return ob;    }}ZC.ZPoolInterfacePanel = Ext.extend(ZC.ComponentGridPanel, {    constructor: function(config) {      config = Ext.applyIf(config||{}, {            componentType: 'ZPool',            fields: [                {name: 'zPoolName'},                {name: 'health'},                {name: 'uid'},                {name: 'name'},                {name: 'severity'},                {name: 'status'},                {name: 'hasMonitor'},                {name: 'monitor'},            ],            columns: [{                id: 'severity',                dataIndex: 'severity',                header: _t('Events'),                renderer: Zenoss.render.severity,                width: 60            },{                id: 'zPoolName',                dataIndex: 'zPoolName',                header: _t('zPoolName'),                sortable: true            },{                id: 'health',                dataIndex: 'health',                header: _t('health'),                sortable: true,            },{                id: 'name',                dataIndex: 'name',                header: _t('Name'),                width: 120,                sortable: true            },{id: 'monitored',dataIndex: 'monitored',header: _t('Monitored'),width: 60}]      });      ZC.ZPoolInterfacePanel.superclass.constructor.call(this, config);    }});Ext.reg('ZPoolComponent', ZC.ZPoolInterfacePanel);ZC.registerName('ZPoolComponent', _t('ZPool Component'), _t('ZPool Component'));})();
页: [1]
查看完整版本: 如何创建一个zenpack(翻译)