2 pkg_resources.require("SQLAlchemy>=0.3.10")
3 pkg_resources.require("Elixir>=0.4.0")
4 # import the basic Elixir classes and functions for declaring the data model
5 # (see http://elixir.ematia.de/trac/wiki/TutorialDivingIn)
6 from elixir import EntityMeta, Entity, Field, OneToMany, ManyToOne, ManyToMany
7 from elixir import options_defaults, using_options, setup_all, entities
8 # import some datatypes for table columns from Elixir
9 # (see http://www.sqlalchemy.org/docs/04/types.html for more)
10 from elixir import String, Unicode, Integer, DateTime
11 from sqlalchemy import ColumnDefault
12 from sqlalchemy import Table
13 from sqlalchemy.orm import ColumnProperty, object_session
15 from xml.marshal.generic import Marshaller
16 from xml.dom.ext import PrettyPrint
17 from xml.dom.ext.reader.Sax import FromXml
18 from elementtree import ElementTree
20 options_defaults['autosetup'] = False
22 from elixir.statements import Statement
23 from sqlalchemy import Sequence
27 from monitor.database.dborm import zab_metadata, zab_session
29 __metadata__ = zab_metadata
30 __session__ = zab_session
33 # - declare association between Media and MediaType so that look ups can
34 # occur on 'description'
36 class ZabbixSerialize(object):
39 def xmlDeserialize(cls, xml):
41 return cls.dict2object(d)
43 def xmlSerialize(self, elem=None):
44 dict = self.convert_dict(self.to_dict())
46 if hasattr(self, 'deepcopy'):
47 for val in self.deepcopy:
48 dict[val] = getattr(self, val)
50 skip_keys = [self._descriptor.auto_primarykey]
51 if hasattr(self, 'skip_keys'):
52 skip_keys += self.skip_keys
54 return self.xmlMessage(dict, skip_keys, elem)
57 def xmlMessage(cls, dict=None, skip_keys=[], use_elem=None):
59 elem = ElementTree.Element(cls.classname())
61 if isinstance(dict, type({})):
62 for key, value in dict.items():
66 if isinstance(value, type(0)):
67 ElementTree.SubElement(elem, key, type="int").text = str(value)
69 elif isinstance(value, type(0L)):
70 ElementTree.SubElement(elem, key, type="long").text = str(value)
72 elif isinstance(value, type([])):
74 e = ElementTree.SubElement(elem, key, type="list")
76 d = obj.convert_dict(obj.to_dict())
79 ElementTree.SubElement(elem, key).text = value
81 elif isinstance(dict, type([])):
84 key = "%s_list" % o.__class__.__name__.lower()
85 e = ElementTree.SubElement(elem, key, type="list")
87 d = obj.convert_dict(obj.to_dict())
90 if use_elem is not None:
93 return ElementTree.tostring(elem)
96 def xml2dict(cls, message, elem=None):
97 em = get_zabbix_entitymap()
99 if message and elem is None:
100 elem = ElementTree.XML(message)
102 raise Exception("Cannot proceed with empty xml, and no elem")
104 #print "tag: %s : classname : %s" % (elem.tag, cls.classname())
105 if cls is not ZabbixSerialize:
106 assert elem.tag == cls.classname()
109 if elem.get("type") == "int":
110 dict[elem.tag] = int(elem.text)
111 elif elem.get("type") == "long":
112 dict[elem.tag] = long(elem.text)
113 elif elem.get("type") == "list":
114 if cls is not ZabbixSerialize:
115 assert elem.tag in cls.deepcopy, "List (%s) in XML is not a recognized type for this object (%s)" % (elem.tag, cls.classname())
118 dict[elem.tag].append( em[e.tag].xml2dict(None, e) )
119 elif elem.text is None:
122 dict[elem.tag] = elem.text
126 def dict2object(cls, dict):
127 em = get_zabbix_entitymap()
128 if cls is ZabbixSerialize:
129 # note: assume that there's only one type of class
131 for key in dict.keys():
132 clsobj = get_zabbix_class_from_name(key)
133 retdict[key] = [ clsobj.dict2object(data) for data in dict[key] ]
136 # take deepcopy values out of dict.
138 if hasattr(cls, 'deepcopy'):
139 for val in cls.deepcopy:
141 backup[val] = dict[val]
145 # for each deepcopy object, convert all values in list
146 for k in backup.keys():
147 clsobj = get_zabbix_class_from_name(k)
148 l = [ clsobj.dict2object(data) for data in backup[k] ]
151 # find or create the primary object
152 obj = cls.find_or_create(**dict)
153 #if cls is DiscoveryCheck or \
154 # cls is ActionCondition or \
155 # cls is ActionOperation:
156 # # NOTE: Some objects should always be created. like DiscoveryCheck
159 # obj = cls.get_by(**dict)
162 # print "CREATING NEW %s" % cls.classname()
165 # print "FOUND EXISTING OBJECT: %s"% obj
167 # add deepcopy values to primary object
168 for k in backup.keys():
169 print type(backup[k][0])
171 if isinstance(obj, User) and isinstance(backup[k][0], UsrGrp):
172 print "adding groups to user"
176 elif isinstance(obj, User) and isinstance(backup[k][0], Media):
177 print "adding media to user"
179 obj.media_list.append(g)
181 elif isinstance(obj, UsrGrp) and isinstance(backup[k][0], HostGroup):
182 print "adding hostgroup to usergroup"
183 print "NOT IMPLEMENTED!!!"
185 obj.append_hostgroup(g)
188 elif isinstance(obj, Action) and isinstance(backup[k][0], ActionCondition):
189 print "adding actionconditon to action"
191 obj.actioncondition_list.append(g)
193 elif isinstance(obj, Action) and isinstance(backup[k][0], ActionOperation):
194 print "adding actionoperation to action"
196 obj.actionoperation_list.append(g)
198 elif isinstance(obj, ActionOperation) and \
199 isinstance(backup[k][0], OperationCondition):
200 print "adding operationcondition to actionoperation"
202 obj.operationcondition_list.append(g)
204 elif isinstance(obj, DiscoveryRule) and isinstance(backup[k][0], DiscoveryCheck):
205 print "adding discoverycheck to discoveryrule"
207 obj.discoverycheck_list.append(v)
211 def convert_dict(self, d):
214 if type(d[key]) == type([]):
215 rd[str(key)] = [ self.convert_dict(v) for v in d[key] ]
217 rd[str(key)] = d[key]
224 def prettyserialize(self):
225 xml = self.xmlSerialize()
229 class ZabbixEntity(ZabbixSerialize):
230 __metaclass__ = EntityMeta
232 def __init__(self, **kwargs):
233 print "__INIT__ %s" % self.classname()
234 tablename = self._descriptor.tablename
235 fieldname = self._descriptor.auto_primarykey
236 index = IDs.get_by(table_name=tablename, field_name=fieldname)
238 index = IDs(table_name=tablename, field_name=fieldname, nodeid=0, nextid=10)
240 index.nextid = index.nextid + 1
241 kwargs[fieldname] = index.nextid
246 if hasattr(self, 'deepcopy'):
247 for k in self.deepcopy:
248 rd[k] = [ str(v) for v in getattr(self, k) ]
250 rd.update(self.to_dict())
257 return self.classname() + "(" + val + ")"
263 def set(self, **kwargs):
264 for key, value in kwargs.iteritems():
265 setattr(self, key, value)
268 def find_or_create(cls, exec_if_new=None, set_if_new={}, **kwargs):
269 if cls is DiscoveryCheck or cls is ActionCondition or \
270 cls is ActionOperation:
271 # NOTE: Some objects should always be created. like DiscoveryCheck
274 # NOTE: ignore *_list items
277 if "_list" not in key:
278 query[key] = kwargs[key]
279 print "SEARCHING USING %s" % query
280 obj = cls.get_by(**query)
283 print "CREATING NEW %s" % cls.classname()
284 print "USING %s" % kwargs
286 obj.set(**set_if_new)
290 print "FOUND EXISTING OBJECT: %s"% obj
294 def update_or_create(cls, data, surrogate=True):
295 pk_props = cls._descriptor.primary_key_properties
297 # if all pk are present and not None
298 if not [1 for p in pk_props if data.get(p.key) is None]:
299 pk_tuple = tuple([data[prop.key] for prop in pk_props])
300 record = cls.query.get(pk_tuple)
303 raise Exception("cannot create surrogate with pk")
310 raise Exception("cannot create non surrogate without pk")
311 record.from_dict(data)
313 update_or_create = classmethod(update_or_create)
315 def from_dict(self, data):
317 Update a mapped class with data from a JSON-style nested dict/list
320 # surrogate can be guessed from autoincrement/sequence but I guess
321 # that's not 100% reliable, so we'll need an override
323 mapper = sqlalchemy.orm.object_mapper(self)
325 for key, value in data.iteritems():
326 if isinstance(value, dict):
327 dbvalue = getattr(self, key)
328 rel_class = mapper.get_property(key).mapper.class_
329 pk_props = rel_class._descriptor.primary_key_properties
331 # If the data doesn't contain any pk, and the relationship
332 # already has a value, update that record.
333 if not [1 for p in pk_props if p.key in data] and \
335 dbvalue.from_dict(value)
337 record = rel_class.update_or_create(value)
338 setattr(self, key, record)
339 elif isinstance(value, list) and \
340 value and isinstance(value[0], dict):
342 rel_class = mapper.get_property(key).mapper.class_
345 if not isinstance(row, dict):
347 'Cannot send mixed (dict/non dict) data '
348 'to list relationships in from_dict data.')
349 record = rel_class.update_or_create(row)
350 new_attr_value.append(record)
351 setattr(self, key, new_attr_value)
353 setattr(self, key, value)
355 def to_dict(self, deep={}, exclude=[]):
356 """Generate a JSON-style nested dict/list structure from an object."""
357 col_prop_names = [p.key for p in self.mapper.iterate_properties \
358 if isinstance(p, ColumnProperty)]
359 data = dict([(name, getattr(self, name))
360 for name in col_prop_names if name not in exclude])
361 for rname, rdeep in deep.iteritems():
362 dbdata = getattr(self, rname)
363 #FIXME: use attribute names (ie coltoprop) instead of column names
364 fks = self.mapper.get_property(rname).remote_side
365 exclude = [c.name for c in fks]
366 if isinstance(dbdata, list):
367 data[rname] = [o.to_dict(rdeep, exclude) for o in dbdata]
369 data[rname] = dbdata.to_dict(rdeep, exclude)
373 def flush(self, *args, **kwargs):
374 return object_session(self).flush([self], *args, **kwargs)
376 def delete(self, *args, **kwargs):
377 return object_session(self).delete(self, *args, **kwargs)
379 def expire(self, *args, **kwargs):
380 return object_session(self).expire(self, *args, **kwargs)
382 def refresh(self, *args, **kwargs):
383 return object_session(self).refresh(self, *args, **kwargs)
385 def expunge(self, *args, **kwargs):
386 return object_session(self).expunge(self, *args, **kwargs)
388 # This bunch of session methods, along with all the query methods below
389 # only make sense when using a global/scoped/contextual session.
390 def _global_session(self):
391 return self._descriptor.session.registry()
392 _global_session = property(_global_session)
394 def merge(self, *args, **kwargs):
395 return self._global_session.merge(self, *args, **kwargs)
397 def save(self, *args, **kwargs):
398 return self._global_session.save(self, *args, **kwargs)
400 def update(self, *args, **kwargs):
401 return self._global_session.update(self, *args, **kwargs)
403 # only exist in SA < 0.5
404 # IMO, the replacement (session.add) doesn't sound good enough to be added
405 # here. For example: "o = Order(); o.add()" is not very telling. It's
406 # better to leave it as "session.add(o)"
407 def save_or_update(self, *args, **kwargs):
408 return self._global_session.save_or_update(self, *args, **kwargs)
411 def get_by(cls, *args, **kwargs):
412 return cls.query.filter_by(*args, **kwargs).first()
413 get_by = classmethod(get_by)
415 def get(cls, *args, **kwargs):
416 return cls.query.get(*args, **kwargs)
417 get = classmethod(get)
425 class Escalation(ZabbixEntity):
427 tablename='escalations',
429 auto_primarykey='escalationid'
432 class Event(ZabbixEntity):
436 auto_primarykey='eventid'
439 class Item(ZabbixEntity):
443 auto_primarykey='itemid'
446 class Acknowledge(ZabbixEntity):
448 tablename='acknowledges',
450 auto_primarykey='acknowledgeid'
453 class Trigger(ZabbixEntity):
455 tablename='triggers',
457 auto_primarykey='triggerid'
461 class Right(ZabbixEntity):
462 # rights of a usergroup to interact with hosts of a hostgroup
466 auto_primarykey='rightid',
468 # column groupid is an index to usrgrp.usrgrpid
469 # column id is an index into the host-groups.groupid
470 # permission is 3=rw, 2=ro, 1=r_list, 0=deny
472 # TODO: NOTE: When serialization occurs, the 'permissions' field is lost,
473 # currently since the rights table is merely treated as an intermediate
474 # table for the m2m between usrgrp and groups.
476 rights = Table('rights', __metadata__, autoload=True)
477 hostsgroups = Table('hosts_groups', __metadata__, autoload=True)
478 hoststemplates = Table('hosts_templates', __metadata__, autoload=True)
481 # m2m table between hosts and groups below
482 class HostsGroups(ZabbixEntity):
484 tablename='hosts_groups',
486 auto_primarykey='hostgroupid',
489 class HostsTemplates(ZabbixEntity):
491 tablename='hosts_templates',
493 auto_primarykey='hosttemplateid',
496 class Host(ZabbixEntity):
500 auto_primarykey='hostid',
502 hostgroup_list = ManyToMany(
505 foreign_keys=lambda: [hostsgroups.c.groupid, hostsgroups.c.hostid],
506 primaryjoin=lambda: Host.hostid==hostsgroups.c.hostid,
507 secondaryjoin=lambda: HostGroup.groupid==hostsgroups.c.groupid,
509 template_list = ManyToMany(
511 table=hoststemplates,
512 foreign_keys=lambda: [hoststemplates.c.hostid, hoststemplates.c.templateid],
513 primaryjoin=lambda: Host.hostid==hoststemplates.c.hostid,
514 secondaryjoin=lambda: Host.hostid==hoststemplates.c.templateid,
517 def append_template(self, template):
518 row = HostsTemplates(hostid=self.hostid, templateid=template.hostid)
521 def remove_template(self, template):
522 row = HostsTemplates.get_by(hostid=self.hostid, templateid=template.hostid)
527 # NOTE: media objects are automatically handled.
528 hosts_templates_match = HostsTemplates.query.filter_by(hostid=self.hostid).all()
529 for row in hosts_templates_match:
532 hosts_groups_match = HostsGroups.query.filter_by(hostid=self.hostid).all()
533 for row in hosts_groups_match:
535 super(Host, self).delete()
537 class HostGroup(ZabbixEntity):
541 auto_primarykey='groupid',
543 usrgrp_list = ManyToMany(
546 foreign_keys=lambda: [rights.c.groupid, rights.c.id],
547 primaryjoin=lambda: HostGroup.groupid==rights.c.id,
548 secondaryjoin=lambda: UsrGrp.usrgrpid==rights.c.groupid,
550 host_list = ManyToMany(
553 foreign_keys=lambda: [hostsgroups.c.groupid, hostsgroups.c.hostid],
554 primaryjoin=lambda: HostGroup.groupid==hostsgroups.c.groupid,
555 secondaryjoin=lambda: Host.hostid==hostsgroups.c.hostid,
558 # NOTE: media objects are automatically handled.
559 hosts_groups_match = HostsGroups.query.filter_by(groupid=self.groupid).all()
560 for row in hosts_groups_match:
562 super(HostGroup, self).delete()
564 class UsersGroups(ZabbixEntity):
566 tablename='users_groups',
568 auto_primarykey='id',
571 class MediaType(ZabbixEntity):
573 tablename='media_type',
575 auto_primarykey='mediatypeid',
578 class Script(ZabbixEntity):
582 auto_primarykey='scriptid',
586 # DISCOVERY ################################################3
588 class DiscoveryCheck(ZabbixEntity):
592 auto_primarykey='dcheckid',
594 skip_keys = ['druleid']
595 discoveryrule = ManyToOne('DiscoveryRule',
596 primaryjoin=lambda: DiscoveryCheck.druleid == DiscoveryRule.druleid,
597 foreign_keys=lambda: [DiscoveryCheck.druleid],
600 class DiscoveryRule(ZabbixEntity): # parent of dchecks
604 auto_primarykey='druleid',
606 deepcopy = ['discoverycheck_list']
607 discoverycheck_list = OneToMany('DiscoveryCheck', cascade='all, delete-orphan',
608 primaryjoin=lambda: DiscoveryCheck.druleid == DiscoveryRule.druleid,
609 foreign_keys=lambda: [DiscoveryCheck.druleid])
611 discoveredhost_list = OneToMany('DiscoveredHost', cascade='all, delete-orphan',
612 primaryjoin=lambda: DiscoveredHost.druleid == DiscoveryRule.druleid,
613 foreign_keys=lambda: [DiscoveredHost.druleid])
615 class DiscoveredHost(ZabbixEntity):
619 auto_primarykey='dhostid',
621 discoveryrule = ManyToOne('DiscoveryRule',
622 primaryjoin=lambda: DiscoveredHost.druleid == DiscoveryRule.druleid,
623 foreign_keys=lambda: [DiscoveredHost.druleid],
626 discoveryservice_list = OneToMany('DiscoveryService', cascade='all, delete-orphan',
627 primaryjoin=lambda: DiscoveryService.dhostid== DiscoveredHost.dhostid,
628 foreign_keys=lambda: [DiscoveryService.dhostid],)
630 class DiscoveryService(ZabbixEntity):
632 tablename='dservices',
634 auto_primarykey='dserviceid',
636 discoveryrule = ManyToOne('DiscoveredHost',
637 primaryjoin=lambda: DiscoveryService.dhostid== DiscoveredHost.dhostid,
638 foreign_keys=lambda: [DiscoveryService.dhostid],
642 # ACTIONS ################################################3
644 class ActionOperation(ZabbixEntity):
646 tablename='operations', autoload=True, auto_primarykey='operationid',
648 deepcopy = ['operationcondition_list']
649 skip_keys = ['actionid']
650 action = ManyToOne('Action', ondelete='cascade',
651 primaryjoin=lambda: ActionOperation.actionid == Action.actionid,
652 foreign_keys=lambda: [ActionOperation.actionid])
654 operationcondition_list = OneToMany('OperationCondition', cascade='all, delete-orphan',
655 primaryjoin=lambda: OperationCondition.operationid == ActionOperation.operationid,
656 foreign_keys=lambda: [OperationCondition.operationid])
658 class OperationCondition(ZabbixEntity):
660 tablename='opconditions', autoload=True, auto_primarykey='opconditionid',
662 skip_keys = ['operationid']
663 actionoperation = ManyToOne('ActionOperation', ondelete='cascade',
664 primaryjoin=lambda: OperationCondition.operationid == ActionOperation.operationid,
665 foreign_keys=lambda: [OperationCondition.operationid])
667 class ActionCondition(ZabbixEntity):
669 tablename='conditions', autoload=True, auto_primarykey='conditionid',
671 skip_keys = ['actionid']
672 action = ManyToOne('Action', ondelete='cascade',
673 primaryjoin=lambda: ActionCondition.actionid == Action.actionid,
674 foreign_keys=lambda: [ActionCondition.actionid])
676 class Action(ZabbixEntity):
678 tablename='actions', autoload=True, auto_primarykey='actionid',
680 deepcopy = ['actionoperation_list', 'actioncondition_list']
681 actionoperation_list = OneToMany('ActionOperation', cascade='all, delete-orphan',
682 primaryjoin=lambda: ActionOperation.actionid == Action.actionid,
683 foreign_keys=lambda: [ActionOperation.actionid])
685 actioncondition_list = OneToMany('ActionCondition', cascade='all, delete-orphan',
686 primaryjoin=lambda: ActionCondition.actionid == Action.actionid,
687 foreign_keys=lambda: [ActionCondition.actionid])
689 # USERS & EMAIL MEDIA ################################################3
691 class Media(ZabbixEntity):
695 auto_primarykey='mediaid',
697 skip_keys = ['userid']
698 user = ManyToOne('User',
699 primaryjoin=lambda: Media.userid == User.userid,
700 foreign_keys=lambda: [Media.userid],
703 users_groups = Table('users_groups', __metadata__, autoload=True)
705 class User(ZabbixEntity): # parent of media
709 auto_primarykey='userid',
711 deepcopy = ['media_list', 'usrgrp_list']
712 media_list = OneToMany('Media',
713 primaryjoin=lambda: Media.userid == User.userid,
714 foreign_keys=lambda: [Media.userid],
715 cascade='all, delete-orphan')
717 # READ-ONLY: do not append or remove groups here.
718 usrgrp_list = ManyToMany('UsrGrp',
720 foreign_keys=lambda: [users_groups.c.userid, users_groups.c.usrgrpid],
721 primaryjoin=lambda: User.userid==users_groups.c.userid,
722 secondaryjoin=lambda: UsrGrp.usrgrpid==users_groups.c.usrgrpid)
725 # NOTE: media objects are automatically handled.
726 users_groups_match = UsersGroups.query.filter_by(userid=self.userid).all()
727 for row in users_groups_match:
729 super(User, self).delete()
731 def append_group(self, group):
732 ug_row = UsersGroups(usrgrpid=group.usrgrpid, userid=self.userid)
735 def remove_group(self, group):
736 ug_row = UsersGroups.get_by(usrgrpid=group.usrgrpid, userid=self.userid)
737 if ug_row is not None:
742 class UsrGrp(ZabbixEntity):
746 auto_primarykey='usrgrpid',
748 deepcopy= ['hostgroup_list']
750 user_list = ManyToMany(
753 foreign_keys=lambda: [users_groups.c.userid, users_groups.c.usrgrpid],
754 secondaryjoin=lambda: User.userid==users_groups.c.userid,
755 primaryjoin=lambda: UsrGrp.usrgrpid==users_groups.c.usrgrpid,
758 hostgroup_list = ManyToMany(
761 foreign_keys=lambda: [rights.c.groupid, rights.c.id],
762 primaryjoin=lambda: UsrGrp.usrgrpid==rights.c.groupid,
763 secondaryjoin=lambda: HostGroup.groupid==rights.c.id,
767 rights_match = Right.query.filter_by(groupid=self.usrgrpid).all()
768 for row in rights_match:
771 users_groups_match = UsersGroups.query.filter_by(usrgrpid=self.usrgrpid).all()
772 for row in users_groups_match:
775 super(UsrGrp, self).delete()
777 def append_hostgroup(self, hg):
778 # NOTE: I know it looks wrong, but this is how the keys are mapped.
779 print "APPENDING HOSTGROUP %s!!!!!!!!!!" % hg.name
780 ug_row = Right(groupid=self.usrgrpid, id=hg.groupid, permission=3)
784 def append_user(self, user):
785 ug_row = UsersGroups(userid=user.userid, usrgrpid=self.usrgrpid)
789 def remove_user(self, user):
790 ug_row = UsersGroups.get_by(userid=user.userid, usrgrpid=self.usrgrpid)
791 if ug_row is not None:
798 def get_zabbix_class_from_name(name):
799 em = get_zabbix_entitymap()
802 name=name[:-5] # strip off the _list part.
805 if name == k.lower():
809 def get_zabbix_entitymap():
811 for n,c in zip([ u.__name__ for u in entities], entities):
815 # COMMON OBJECT TYPES
816 class OperationConditionNotAck(object):
818 o = OperationCondition(
819 conditiontype=defines.CONDITION_TYPE_EVENT_ACKNOWLEDGED,
820 operator=defines.CONDITION_OPERATOR_EQUAL,
825 #u = User(alias="stephen.soltesz@gmail.com", name="stephen.soltesz@gmail.com", surname="", passwd=md5.md5("test").hexdigest(), url="", autologin=0, autologout=900, lang="en_gb", refresh=30, type=1, theme="default.css")