pull in additional changes from 2.0 branch.
[monitor.git] / monitor / database / zabbixapi / model.py
1 import pkg_resources
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
14
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
19
20 options_defaults['autosetup'] = False
21
22 from elixir.statements import Statement
23 from sqlalchemy import Sequence
24
25 try:
26         import defines
27 except:
28         print "WARNING: no defines.py available"
29
30 from monitor.database.dborm import zab_metadata, zab_session
31
32 __metadata__ = zab_metadata
33 __session__  = zab_session
34
35 # TODO:
36 #   - declare association between Media and MediaType so that look ups can
37 #       occur on 'description'
38
39 class ZabbixSerialize(object):
40
41         @classmethod
42         def xmlDeserialize(cls, xml):
43                 d = cls.xml2dict(xml)
44                 return cls.dict2object(d)
45
46         def xmlSerialize(self, elem=None):
47                 dict = self.convert_dict(self.to_dict())
48
49                 if hasattr(self, 'deepcopy'):
50                         for val in self.deepcopy:
51                                 dict[val] = getattr(self, val)
52
53                 skip_keys = [self._descriptor.auto_primarykey]
54                 if hasattr(self, 'skip_keys'):
55                         skip_keys += self.skip_keys
56
57                 return self.xmlMessage(dict, skip_keys, elem)
58
59         @classmethod
60         def xmlMessage(cls, dict=None, skip_keys=[], use_elem=None):
61
62                 elem = ElementTree.Element(cls.classname())
63
64                 if isinstance(dict, type({})):
65                         for key, value in dict.items():
66                                 if key in skip_keys:
67                                         continue
68
69                                 if isinstance(value, type(0)):
70                                         ElementTree.SubElement(elem, key, type="int").text = str(value)
71
72                                 elif isinstance(value, type(0L)):
73                                         ElementTree.SubElement(elem, key, type="long").text = str(value)
74
75                                 elif isinstance(value, type([])):
76                                         if len(value) > 0:
77                                                 e = ElementTree.SubElement(elem, key, type="list") 
78                                                 for obj in value:
79                                                         d = obj.convert_dict(obj.to_dict())
80                                                         obj.xmlSerialize(e) 
81                                 else:
82                                         ElementTree.SubElement(elem, key).text = value
83
84                 elif isinstance(dict, type([])):
85                         if len(dict) > 0:
86                                 o = dict[0]
87                                 key = "%s_list" % o.__class__.__name__.lower()
88                                 e = ElementTree.SubElement(elem, key, type="list") 
89                                 for obj in dict:
90                                         d = obj.convert_dict(obj.to_dict())
91                                         obj.xmlSerialize(e) 
92
93                 if use_elem is not None:
94                         use_elem.append(elem)
95                                 
96                 return ElementTree.tostring(elem)
97
98         @classmethod
99         def xml2dict(cls, message, elem=None):
100                 em = get_zabbix_entitymap()
101
102                 if message and elem is None:
103                         elem = ElementTree.XML(message)
104                 elif elem is None:
105                         raise Exception("Cannot proceed with empty xml, and no elem")
106
107                 #print "tag: %s : classname : %s" % (elem.tag, cls.classname())
108                 if cls is not ZabbixSerialize:
109                         assert elem.tag == cls.classname()
110                 dict = {}
111                 for elem in elem:
112                         if elem.get("type") == "int":
113                                 dict[elem.tag] = int(elem.text)
114                         elif elem.get("type") == "long":
115                                 dict[elem.tag] = long(elem.text)
116                         elif elem.get("type") == "list":
117                                 if cls is not ZabbixSerialize:
118                                         assert elem.tag in cls.deepcopy, "List (%s) in XML is not a recognized type for this object (%s)" % (elem.tag, cls.classname())
119                                 dict[elem.tag] = []
120                                 for e in elem:
121                                         dict[elem.tag].append( em[e.tag].xml2dict(None, e) )
122                         elif elem.text is None:
123                                 dict[elem.tag] = ""
124                         else:
125                                 dict[elem.tag] = elem.text
126                 return dict
127
128         @classmethod
129         def dict2object(cls, dict):
130                 em = get_zabbix_entitymap()
131                 if cls is ZabbixSerialize:
132                         # note: assume that there's only one type of class
133                         retdict = {}
134                         for key in dict.keys():
135                                 clsobj = get_zabbix_class_from_name(key)
136                                 retdict[key] = [ clsobj.dict2object(data) for data in dict[key] ]
137                         return retdict
138
139                 # take deepcopy values out of dict.
140                 backup = {}
141                 if hasattr(cls, 'deepcopy'):
142                         for val in cls.deepcopy:
143                                 if val in dict:
144                                         backup[val] = dict[val]
145                                         del dict[val]
146
147                 # instantiate them
148                 # for each deepcopy object, convert all values in list
149                 for k in backup.keys():
150                         clsobj = get_zabbix_class_from_name(k)
151                         l = [ clsobj.dict2object(data) for data in backup[k] ]
152                         backup[k] = l
153
154                 # find or create the primary object
155                 obj = cls.find_or_create(**dict)
156                 #if cls is DiscoveryCheck or \
157                 #       cls is ActionCondition or \
158                 #       cls is ActionOperation:
159                 #       # NOTE: Some objects should always be created. like DiscoveryCheck
160                 #       obj = None
161                 #else:
162                 #       obj = cls.get_by(**dict)
163 #
164 #               if obj is None:
165 #                       print "CREATING NEW %s" % cls.classname()
166 #                       obj = cls(**dict)
167 #               else:
168 #                       print "FOUND EXISTING OBJECT: %s"% obj
169
170                 # add deepcopy values to primary object
171                 for k in backup.keys():
172                         print type(backup[k][0])
173
174                         if isinstance(obj, User) and isinstance(backup[k][0], UsrGrp):
175                                 print "adding groups to user"
176                                 for g in backup[k]:
177                                         obj.append_group(g)
178
179                         elif isinstance(obj, User) and isinstance(backup[k][0], Media):
180                                 print "adding media to user"
181                                 for g in backup[k]:
182                                         obj.media_list.append(g)
183
184                         elif isinstance(obj, UsrGrp) and isinstance(backup[k][0], HostGroup):
185                                 print "adding hostgroup to usergroup"
186                                 print "NOT IMPLEMENTED!!!"
187                                 for g in backup[k]:
188                                         obj.append_hostgroup(g)
189                                         pass
190
191                         elif isinstance(obj, Action) and isinstance(backup[k][0], ActionCondition):
192                                 print "adding actionconditon to action"
193                                 for g in backup[k]:
194                                         obj.actioncondition_list.append(g)
195
196                         elif isinstance(obj, Action) and isinstance(backup[k][0], ActionOperation):
197                                 print "adding actionoperation to action"
198                                 for g in backup[k]:
199                                         obj.actionoperation_list.append(g)
200
201                         elif isinstance(obj, ActionOperation) and \
202                                  isinstance(backup[k][0], OperationCondition):
203                                 print "adding operationcondition to actionoperation"
204                                 for g in backup[k]:
205                                         obj.operationcondition_list.append(g)
206
207                         elif isinstance(obj, DiscoveryRule) and isinstance(backup[k][0], DiscoveryCheck):
208                                 print "adding discoverycheck to discoveryrule"
209                                 for v in backup[k]:
210                                         obj.discoverycheck_list.append(v)
211
212                 return obj
213
214         def convert_dict(self, d):
215                 rd = {}
216                 for key in d.keys():
217                         if type(d[key]) == type([]):
218                                 rd[str(key)] = [ self.convert_dict(v) for v in d[key] ]
219                         else:
220                                 rd[str(key)] = d[key]
221                 return rd
222
223         @classmethod
224         def classname(cls):
225                 return cls.__name__
226
227         def prettyserialize(self):
228                 xml = self.xmlSerialize()
229                 d = FromXml(xml)
230                 PrettyPrint(d)
231         
232 class ZabbixEntity(ZabbixSerialize):
233         __metaclass__ = EntityMeta
234
235         def __init__(self, **kwargs):
236                 print "__INIT__ %s" % self.classname()
237                 tablename = self._descriptor.tablename
238                 fieldname = self._descriptor.auto_primarykey
239                 index = IDs.get_by(table_name=tablename, field_name=fieldname)
240                 if not index:
241                         print "NEW IDs index INSIDE INIT"
242                         index = IDs(table_name=tablename, field_name=fieldname, nodeid=0, nextid=10)
243                         index.flush()
244                 index.nextid = index.nextid + 1
245                 kwargs[fieldname] = index.nextid
246                 self.set(**kwargs)
247
248         def __repr__(self):
249                 rd = {}
250                 if hasattr(self, 'deepcopy'):
251                         for k in self.deepcopy:
252                                 rd[k] = [ str(v) for v in getattr(self, k) ]
253
254                 rd.update(self.to_dict())
255                 val = ""
256                 for k in rd.keys():
257                         val += k
258                         val += "="
259                         val += str(rd[k])
260                         val += ", "
261                 return self.classname() + "(" + val + ")"
262
263         @classmethod
264         def classname(cls):
265                 return cls.__name__
266
267         def set(self, **kwargs):
268                 for key, value in kwargs.iteritems():
269                         setattr(self, key, value)
270         
271         @classmethod
272         def find_or_create(cls, exec_if_new=None, set_if_new={}, **kwargs):
273                 if cls is DiscoveryCheck or cls is ActionCondition or \
274                         cls is ActionOperation:
275                         # NOTE: Some objects should always be created. like DiscoveryCheck
276                         obj = None
277                 else:
278                         # NOTE: ignore *_list items
279                         query = {}
280                         for key in kwargs:
281                                 if "_list" not in key:
282                                         query[key] = kwargs[key]
283                         print "SEARCHING USING %s" % query
284                         obj = cls.get_by(**query)
285
286                 if obj is None:
287                         print "CREATING NEW %s" % cls.classname()
288                         print "USING %s" % kwargs
289                         obj = cls(**kwargs)
290                         obj.set(**set_if_new)
291                         if exec_if_new:
292                                 exec_if_new(obj)
293                 else:
294                         print "FOUND EXISTING OBJECT: %s"% obj
295
296                 return obj
297
298         def update_or_create(cls, data, surrogate=True):
299                 pk_props = cls._descriptor.primary_key_properties
300
301                 # if all pk are present and not None
302                 if not [1 for p in pk_props if data.get(p.key) is None]:
303                         pk_tuple = tuple([data[prop.key] for prop in pk_props])
304                         record = cls.query.get(pk_tuple)
305                         if record is None:
306                                 if surrogate:
307                                         raise Exception("cannot create surrogate with pk")
308                                 else:
309                                         record = cls()
310                 else:
311                         if surrogate:
312                                 record = cls()
313                         else:
314                                 raise Exception("cannot create non surrogate without pk")
315                 record.from_dict(data)
316                 return record
317         update_or_create = classmethod(update_or_create)
318
319         def from_dict(self, data):
320                 """
321                 Update a mapped class with data from a JSON-style nested dict/list
322                 structure.
323                 """
324                 # surrogate can be guessed from autoincrement/sequence but I guess
325                 # that's not 100% reliable, so we'll need an override
326
327                 mapper = sqlalchemy.orm.object_mapper(self)
328
329                 for key, value in data.iteritems():
330                         if isinstance(value, dict):
331                                 dbvalue = getattr(self, key)
332                                 rel_class = mapper.get_property(key).mapper.class_
333                                 pk_props = rel_class._descriptor.primary_key_properties
334
335                                 # If the data doesn't contain any pk, and the relationship
336                                 # already has a value, update that record.
337                                 if not [1 for p in pk_props if p.key in data] and \
338                                    dbvalue is not None:
339                                         dbvalue.from_dict(value)
340                                 else:
341                                         record = rel_class.update_or_create(value)
342                                         setattr(self, key, record)
343                         elif isinstance(value, list) and \
344                                  value and isinstance(value[0], dict):
345
346                                 rel_class = mapper.get_property(key).mapper.class_
347                                 new_attr_value = []
348                                 for row in value:
349                                         if not isinstance(row, dict):
350                                                 raise Exception(
351                                                                 'Cannot send mixed (dict/non dict) data '
352                                                                 'to list relationships in from_dict data.')
353                                         record = rel_class.update_or_create(row)
354                                         new_attr_value.append(record)
355                                 setattr(self, key, new_attr_value)
356                         else:
357                                 setattr(self, key, value)
358
359         def to_dict(self, deep={}, exclude=[]):
360                 """Generate a JSON-style nested dict/list structure from an object."""
361                 col_prop_names = [p.key for p in self.mapper.iterate_properties \
362                                                                           if isinstance(p, ColumnProperty)]
363                 data = dict([(name, getattr(self, name))
364                                          for name in col_prop_names if name not in exclude])
365                 for rname, rdeep in deep.iteritems():
366                         dbdata = getattr(self, rname)
367                         #FIXME: use attribute names (ie coltoprop) instead of column names
368                         fks = self.mapper.get_property(rname).remote_side
369                         exclude = [c.name for c in fks]
370                         if isinstance(dbdata, list):
371                                 data[rname] = [o.to_dict(rdeep, exclude) for o in dbdata]
372                         else:
373                                 data[rname] = dbdata.to_dict(rdeep, exclude)
374                 return data
375
376         # session methods
377         def flush(self, *args, **kwargs):
378                 return object_session(self).flush([self], *args, **kwargs)
379
380         def delete(self, *args, **kwargs):
381                 return object_session(self).delete(self, *args, **kwargs)
382
383         def expire(self, *args, **kwargs):
384                 return object_session(self).expire(self, *args, **kwargs)
385
386         def refresh(self, *args, **kwargs):
387                 return object_session(self).refresh(self, *args, **kwargs)
388
389         def expunge(self, *args, **kwargs):
390                 return object_session(self).expunge(self, *args, **kwargs)
391
392         # This bunch of session methods, along with all the query methods below
393         # only make sense when using a global/scoped/contextual session.
394         def _global_session(self):
395                 return self._descriptor.session.registry()
396         _global_session = property(_global_session)
397
398         def merge(self, *args, **kwargs):
399                 return self._global_session.merge(self, *args, **kwargs)
400
401         def save(self, *args, **kwargs):
402                 return self._global_session.save(self, *args, **kwargs)
403
404         def update(self, *args, **kwargs):
405                 return self._global_session.update(self, *args, **kwargs)
406
407         # only exist in SA < 0.5
408         # IMO, the replacement (session.add) doesn't sound good enough to be added
409         # here. For example: "o = Order(); o.add()" is not very telling. It's
410         # better to leave it as "session.add(o)"
411         def save_or_update(self, *args, **kwargs):
412                 return self._global_session.save_or_update(self, *args, **kwargs)
413
414         # query methods
415         def get_by(cls, *args, **kwargs):
416                 return cls.query.filter_by(*args, **kwargs).first()
417         get_by = classmethod(get_by)
418
419         def get(cls, *args, **kwargs):
420                 return cls.query.get(*args, **kwargs)
421         get = classmethod(get)
422
423 class IDs(Entity):
424         using_options(
425                 tablename='ids',
426                 autoload=True,
427         )
428
429 class Escalation(ZabbixEntity):
430         using_options(
431                 tablename='escalations',
432                 autoload=True,
433                 auto_primarykey='escalationid'
434         )
435
436 class Event(ZabbixEntity):
437         using_options(
438                 tablename='events',
439                 autoload=True,
440                 auto_primarykey='eventid'
441         )
442
443 class Item(ZabbixEntity):
444         using_options(
445                 tablename='items',
446                 autoload=True,
447                 auto_primarykey='itemid'
448         )
449
450 class Acknowledge(ZabbixEntity):
451         using_options(
452                 tablename='acknowledges',
453                 autoload=True,
454                 auto_primarykey='acknowledgeid'
455         )
456
457 class Trigger(ZabbixEntity):
458         using_options(
459                 tablename='triggers',
460                 autoload=True,
461                 auto_primarykey='triggerid'
462         )
463         
464
465 class Right(ZabbixEntity):
466         # rights of a usergroup to interact with hosts of a hostgroup
467         using_options(
468                 tablename='rights',
469                 autoload=True,
470                 auto_primarykey='rightid',
471         )
472         # column groupid is an index to usrgrp.usrgrpid
473         # column id is an index into the host-groups.groupid
474         # permission is 3=rw, 2=ro, 1=r_list, 0=deny
475
476         # TODO: NOTE: When serialization occurs, the 'permissions' field is lost,
477         # currently since the rights table is merely treated as an intermediate
478         # table for the m2m between usrgrp and groups.
479
480 rights = Table('rights', __metadata__, autoload=True)
481 hostsgroups = Table('hosts_groups', __metadata__, autoload=True)
482 hoststemplates = Table('hosts_templates', __metadata__, autoload=True)
483
484         
485 # m2m table between hosts and groups below
486 class HostsGroups(ZabbixEntity):
487         using_options(
488                 tablename='hosts_groups',
489                 autoload=True,
490                 auto_primarykey='hostgroupid',
491         )
492
493 class HostsTemplates(ZabbixEntity):
494         using_options(
495                 tablename='hosts_templates',
496                 autoload=True,
497                 auto_primarykey='hosttemplateid',
498         )
499
500 class Host(ZabbixEntity):
501         using_options(
502                 tablename='hosts',
503                 autoload=True,
504                 auto_primarykey='hostid',
505         )
506         hostgroup_list = ManyToMany(
507                 'HostGroup',
508                 table=hostsgroups,
509                 foreign_keys=lambda: [hostsgroups.c.groupid, hostsgroups.c.hostid],
510                 primaryjoin=lambda: Host.hostid==hostsgroups.c.hostid,
511                 secondaryjoin=lambda: HostGroup.groupid==hostsgroups.c.groupid,
512         )
513         template_list = ManyToMany(
514                 'Host',
515                 table=hoststemplates,
516                 foreign_keys=lambda: [hoststemplates.c.hostid, hoststemplates.c.templateid],
517                 primaryjoin=lambda: Host.hostid==hoststemplates.c.hostid,
518                 secondaryjoin=lambda: Host.hostid==hoststemplates.c.templateid,
519         )
520
521         def append_template(self, template):
522                 row = HostsTemplates(hostid=self.hostid, templateid=template.hostid)
523                 return template
524
525         def remove_template(self, template):
526                 row = HostsTemplates.get_by(hostid=self.hostid, templateid=template.hostid)
527                 if row is not None:
528                         row.delete()
529
530         def delete(self):
531                 # NOTE: media objects are automatically handled.
532                 hosts_templates_match = HostsTemplates.query.filter_by(hostid=self.hostid).all()
533                 for row in hosts_templates_match:
534                         row.delete()
535
536                 hosts_groups_match = HostsGroups.query.filter_by(hostid=self.hostid).all()
537                 for row in hosts_groups_match:
538                         row.delete()
539                 super(Host, self).delete()
540
541 class HostGroup(ZabbixEntity):
542         using_options(
543                 tablename='groups',
544                 autoload=True,
545                 auto_primarykey='groupid',
546         )
547         usrgrp_list = ManyToMany(
548                 'UsrGrp',
549                 table=rights,
550                 foreign_keys=lambda: [rights.c.groupid, rights.c.id],
551                 primaryjoin=lambda: HostGroup.groupid==rights.c.id,
552                 secondaryjoin=lambda: UsrGrp.usrgrpid==rights.c.groupid,
553         )
554         host_list = ManyToMany(
555                 'Host',
556                 table=hostsgroups,
557                 foreign_keys=lambda: [hostsgroups.c.groupid, hostsgroups.c.hostid],
558                 primaryjoin=lambda: HostGroup.groupid==hostsgroups.c.groupid,
559                 secondaryjoin=lambda: Host.hostid==hostsgroups.c.hostid,
560         )
561         def delete(self):
562                 # NOTE: media objects are automatically handled.
563                 hosts_groups_match = HostsGroups.query.filter_by(groupid=self.groupid).all()
564                 for row in hosts_groups_match:
565                         row.delete()
566                 super(HostGroup, self).delete()
567
568 class UsersGroups(ZabbixEntity):
569         using_options(
570                 tablename='users_groups',
571                 autoload=True,
572                 auto_primarykey='id',
573         )
574
575 class MediaType(ZabbixEntity):
576         using_options(
577                 tablename='media_type',
578                 autoload=True,
579                 auto_primarykey='mediatypeid',
580         )
581
582 class Script(ZabbixEntity):
583         using_options(
584                 tablename='scripts',
585                 autoload=True,
586                 auto_primarykey='scriptid',
587         )
588
589
590 # DISCOVERY ################################################3
591
592 class DiscoveryCheck(ZabbixEntity):
593         using_options(
594                 tablename='dchecks',
595                 autoload=True,
596                 auto_primarykey='dcheckid',
597         )
598         skip_keys = ['druleid']
599         discoveryrule = ManyToOne('DiscoveryRule', 
600                                         primaryjoin=lambda: DiscoveryCheck.druleid == DiscoveryRule.druleid,
601                                         foreign_keys=lambda: [DiscoveryCheck.druleid],
602                                         ondelete='cascade') 
603
604 class DiscoveryRule(ZabbixEntity):  # parent of dchecks
605         using_options(
606                 tablename='drules',
607                 autoload=True,
608                 auto_primarykey='druleid',
609         )
610         deepcopy = ['discoverycheck_list']
611         discoverycheck_list = OneToMany('DiscoveryCheck', cascade='all, delete-orphan',
612                                         primaryjoin=lambda: DiscoveryCheck.druleid == DiscoveryRule.druleid,
613                                         foreign_keys=lambda: [DiscoveryCheck.druleid])
614
615         discoveredhost_list = OneToMany('DiscoveredHost', cascade='all, delete-orphan',
616                                         primaryjoin=lambda: DiscoveredHost.druleid == DiscoveryRule.druleid,
617                                         foreign_keys=lambda: [DiscoveredHost.druleid])
618
619 class DiscoveredHost(ZabbixEntity):
620         using_options(
621                 tablename='dhosts',
622                 autoload=True,
623                 auto_primarykey='dhostid',
624         )
625         discoveryrule = ManyToOne('DiscoveryRule',
626                                         primaryjoin=lambda: DiscoveredHost.druleid == DiscoveryRule.druleid,
627                                         foreign_keys=lambda: [DiscoveredHost.druleid],
628                                         ondelete='cascade') 
629
630         discoveryservice_list = OneToMany('DiscoveryService', cascade='all, delete-orphan',
631                                         primaryjoin=lambda: DiscoveryService.dhostid== DiscoveredHost.dhostid,
632                                         foreign_keys=lambda: [DiscoveryService.dhostid],) 
633
634 class DiscoveryService(ZabbixEntity):
635         using_options(
636                 tablename='dservices',
637                 autoload=True,
638                 auto_primarykey='dserviceid',
639         )
640         discoveryrule = ManyToOne('DiscoveredHost',
641                                         primaryjoin=lambda: DiscoveryService.dhostid== DiscoveredHost.dhostid,
642                                         foreign_keys=lambda: [DiscoveryService.dhostid],
643                                         ondelete='cascade') 
644                                                 
645
646 # ACTIONS ################################################3
647
648 class ActionOperation(ZabbixEntity):
649         using_options(
650                 tablename='operations', autoload=True, auto_primarykey='operationid',
651         )
652         deepcopy = ['operationcondition_list']
653         skip_keys = ['actionid']
654         action = ManyToOne('Action', ondelete='cascade',
655                                         primaryjoin=lambda: ActionOperation.actionid == Action.actionid,
656                                         foreign_keys=lambda: [ActionOperation.actionid])
657                                         
658         operationcondition_list = OneToMany('OperationCondition', cascade='all, delete-orphan',
659                                         primaryjoin=lambda: OperationCondition.operationid == ActionOperation.operationid,
660                                         foreign_keys=lambda: [OperationCondition.operationid])
661
662 class OperationCondition(ZabbixEntity):
663         using_options(
664                 tablename='opconditions', autoload=True, auto_primarykey='opconditionid',
665         )
666         skip_keys = ['operationid']
667         actionoperation = ManyToOne('ActionOperation', ondelete='cascade',
668                                         primaryjoin=lambda: OperationCondition.operationid == ActionOperation.operationid,
669                                         foreign_keys=lambda: [OperationCondition.operationid])
670
671 class ActionCondition(ZabbixEntity):
672         using_options(
673                 tablename='conditions', autoload=True, auto_primarykey='conditionid',
674         )
675         skip_keys = ['actionid']
676         action = ManyToOne('Action', ondelete='cascade',
677                                         primaryjoin=lambda: ActionCondition.actionid == Action.actionid,
678                                         foreign_keys=lambda: [ActionCondition.actionid])
679
680 class Action(ZabbixEntity):
681         using_options(
682                 tablename='actions', autoload=True, auto_primarykey='actionid',
683         )
684         deepcopy = ['actionoperation_list', 'actioncondition_list']
685         actionoperation_list = OneToMany('ActionOperation', cascade='all, delete-orphan',
686                                         primaryjoin=lambda: ActionOperation.actionid == Action.actionid,
687                                         foreign_keys=lambda: [ActionOperation.actionid])
688                                         
689         actioncondition_list = OneToMany('ActionCondition', cascade='all, delete-orphan',
690                                         primaryjoin=lambda: ActionCondition.actionid == Action.actionid,
691                                         foreign_keys=lambda: [ActionCondition.actionid])
692
693 # USERS & EMAIL MEDIA ################################################3
694
695 class Media(ZabbixEntity):
696         using_options(
697                 tablename='media',
698                 autoload=True,
699                 auto_primarykey='mediaid',
700         )
701         skip_keys = ['userid']
702         user = ManyToOne('User', 
703                                         primaryjoin=lambda: Media.userid == User.userid,
704                                         foreign_keys=lambda: [Media.userid],
705                                         ondelete='cascade') 
706
707 users_groups = Table('users_groups', __metadata__, autoload=True)
708
709 class User(ZabbixEntity): # parent of media
710         using_options(
711                 tablename='users',
712                 autoload=True,
713                 auto_primarykey='userid',
714         )
715         deepcopy = ['media_list', 'usrgrp_list']
716         media_list = OneToMany('Media', 
717                                           primaryjoin=lambda: Media.userid == User.userid,
718                                           foreign_keys=lambda: [Media.userid],
719                                           cascade='all, delete-orphan')
720
721         # READ-ONLY: do not append or remove groups here.
722         usrgrp_list = ManyToMany('UsrGrp',
723                                 table=users_groups,
724                                 foreign_keys=lambda: [users_groups.c.userid, users_groups.c.usrgrpid],
725                                 primaryjoin=lambda: User.userid==users_groups.c.userid,
726                                 secondaryjoin=lambda: UsrGrp.usrgrpid==users_groups.c.usrgrpid)
727
728         def delete(self):
729                 # NOTE: media objects are automatically handled.
730                 users_groups_match = UsersGroups.query.filter_by(userid=self.userid).all()
731                 for row in users_groups_match:
732                         row.delete()
733                 super(User, self).delete()
734                 
735         def append_group(self, group):
736                 ug_row = UsersGroups(usrgrpid=group.usrgrpid, userid=self.userid)
737                 return group
738
739         def remove_group(self, group):
740                 ug_row = UsersGroups.get_by(usrgrpid=group.usrgrpid, userid=self.userid)
741                 if ug_row is not None:
742                         ug_row.delete()
743                 return
744                 
745 class UsrGrp(ZabbixEntity):
746         using_options(
747                 tablename='usrgrp',
748                 autoload=True,
749                 auto_primarykey='usrgrpid',
750         )
751         deepcopy= ['hostgroup_list']
752
753         user_list = ManyToMany(
754                 'User',
755                 table=users_groups,
756                 foreign_keys=lambda: [users_groups.c.userid, users_groups.c.usrgrpid],
757                 secondaryjoin=lambda: User.userid==users_groups.c.userid,
758                 primaryjoin=lambda: UsrGrp.usrgrpid==users_groups.c.usrgrpid,
759         )
760
761         hostgroup_list = ManyToMany(
762                 'HostGroup',
763                 table=rights,
764                 foreign_keys=lambda: [rights.c.groupid, rights.c.id],
765                 primaryjoin=lambda: UsrGrp.usrgrpid==rights.c.groupid,
766                 secondaryjoin=lambda: HostGroup.groupid==rights.c.id,
767         )
768
769         def delete(self):
770                 rights_match = Right.query.filter_by(groupid=self.usrgrpid).all()
771                 for row in rights_match:
772                         row.delete()
773
774                 users_groups_match = UsersGroups.query.filter_by(usrgrpid=self.usrgrpid).all()
775                 for row in users_groups_match:
776                         row.delete()
777
778                 super(UsrGrp, self).delete()
779
780         def append_hostgroup(self, hg):
781                 # NOTE: I know it looks wrong, but this is how the keys are mapped.
782                 print "APPENDING HOSTGROUP %s!!!!!!!!!!" % hg.name
783                 ug_row = Right(groupid=self.usrgrpid, id=hg.groupid, permission=3)
784                 ug_row.save()
785                 return
786
787         def append_user(self, user):
788                 ug_row = UsersGroups(userid=user.userid, usrgrpid=self.usrgrpid)
789                 ug_row.save()
790                 return
791
792         def remove_user(self, user):
793                 ug_row = UsersGroups.get_by(userid=user.userid, usrgrpid=self.usrgrpid)
794                 if ug_row is not None:
795                         ug_row.delete()
796                 return
797
798 def confirm_ids():
799         fields = {
800                 'scripts' : 'scriptid',
801                 'usrgrp' : 'usrgrpid',
802                 'users' : 'userid',
803                 'media' : 'mediaid',
804                 'users_groups' : 'id',
805                 'groups' : 'groupid',
806                 'rights' : 'rightid',
807                 'drules' : 'druleid',
808                 'dchecks' : 'dcheckid',
809                 'actions' : 'actionid',
810                 'conditions' : 'conditionid',
811                 'operations' : 'operationid',
812                 'opconditions' : 'opconditionid',
813         }
814         need_to_flush = False
815
816         for tablename in fields.keys():
817                 fieldname = fields[tablename]
818         
819                 index = IDs.get_by(table_name=tablename, field_name=fieldname)
820                 if not index:
821                         print "NEW IDs index INSIDE confirm_ids"
822                         index = IDs(table_name=tablename, field_name=fieldname, nodeid=0, nextid=10)
823                         index.flush()
824                         need_to_flush=True
825
826         if need_to_flush:
827                 zab_session.flush()
828         
829
830 setup_all()
831 confirm_ids()
832
833 def get_zabbix_class_from_name(name):
834         em = get_zabbix_entitymap()
835         cls = None
836         if "_list" in name:
837                 name=name[:-5]  # strip off the _list part.
838
839         for k in em.keys():
840                 if name == k.lower():
841                         cls = em[k]
842         return cls
843         
844 def get_zabbix_entitymap():
845         entity_map = {}
846         for n,c in zip([ u.__name__ for u in entities], entities): 
847                 entity_map[n] = c
848         return entity_map
849
850 # COMMON OBJECT TYPES
851 class OperationConditionNotAck(object):
852         def __new__(cls):
853                 o = OperationCondition(
854                                 conditiontype=defines.CONDITION_TYPE_EVENT_ACKNOWLEDGED, 
855                                 operator=defines.CONDITION_OPERATOR_EQUAL, 
856                                 value=0 ) # NOT_ACK
857                 return  o