fixed the httpd conf issue with redirecting to /monitor/
[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 import defines
26
27 from monitor.database.dborm import zab_metadata, zab_session
28
29 __metadata__ = zab_metadata
30 __session__  = zab_session
31
32 # TODO:
33 #   - declare association between Media and MediaType so that look ups can
34 #       occur on 'description'
35
36 class ZabbixSerialize(object):
37
38         @classmethod
39         def xmlDeserialize(cls, xml):
40                 d = cls.xml2dict(xml)
41                 return cls.dict2object(d)
42
43         def xmlSerialize(self, elem=None):
44                 dict = self.convert_dict(self.to_dict())
45
46                 if hasattr(self, 'deepcopy'):
47                         for val in self.deepcopy:
48                                 dict[val] = getattr(self, val)
49
50                 skip_keys = [self._descriptor.auto_primarykey]
51                 if hasattr(self, 'skip_keys'):
52                         skip_keys += self.skip_keys
53
54                 return self.xmlMessage(dict, skip_keys, elem)
55
56         @classmethod
57         def xmlMessage(cls, dict=None, skip_keys=[], use_elem=None):
58
59                 elem = ElementTree.Element(cls.classname())
60
61                 if isinstance(dict, type({})):
62                         for key, value in dict.items():
63                                 if key in skip_keys:
64                                         continue
65
66                                 if isinstance(value, type(0)):
67                                         ElementTree.SubElement(elem, key, type="int").text = str(value)
68
69                                 elif isinstance(value, type(0L)):
70                                         ElementTree.SubElement(elem, key, type="long").text = str(value)
71
72                                 elif isinstance(value, type([])):
73                                         if len(value) > 0:
74                                                 e = ElementTree.SubElement(elem, key, type="list") 
75                                                 for obj in value:
76                                                         d = obj.convert_dict(obj.to_dict())
77                                                         obj.xmlSerialize(e) 
78                                 else:
79                                         ElementTree.SubElement(elem, key).text = value
80
81                 elif isinstance(dict, type([])):
82                         if len(dict) > 0:
83                                 o = dict[0]
84                                 key = "%s_list" % o.__class__.__name__.lower()
85                                 e = ElementTree.SubElement(elem, key, type="list") 
86                                 for obj in dict:
87                                         d = obj.convert_dict(obj.to_dict())
88                                         obj.xmlSerialize(e) 
89
90                 if use_elem is not None:
91                         use_elem.append(elem)
92                                 
93                 return ElementTree.tostring(elem)
94
95         @classmethod
96         def xml2dict(cls, message, elem=None):
97                 em = get_zabbix_entitymap()
98
99                 if message and elem is None:
100                         elem = ElementTree.XML(message)
101                 elif elem is None:
102                         raise Exception("Cannot proceed with empty xml, and no elem")
103
104                 #print "tag: %s : classname : %s" % (elem.tag, cls.classname())
105                 if cls is not ZabbixSerialize:
106                         assert elem.tag == cls.classname()
107                 dict = {}
108                 for elem in elem:
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())
116                                 dict[elem.tag] = []
117                                 for e in elem:
118                                         dict[elem.tag].append( em[e.tag].xml2dict(None, e) )
119                         elif elem.text is None:
120                                 dict[elem.tag] = ""
121                         else:
122                                 dict[elem.tag] = elem.text
123                 return dict
124
125         @classmethod
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
130                         retdict = {}
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] ]
134                         return retdict
135
136                 # take deepcopy values out of dict.
137                 backup = {}
138                 if hasattr(cls, 'deepcopy'):
139                         for val in cls.deepcopy:
140                                 if val in dict:
141                                         backup[val] = dict[val]
142                                         del dict[val]
143
144                 # instantiate them
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] ]
149                         backup[k] = l
150
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
157                 #       obj = None
158                 #else:
159                 #       obj = cls.get_by(**dict)
160 #
161 #               if obj is None:
162 #                       print "CREATING NEW %s" % cls.classname()
163 #                       obj = cls(**dict)
164 #               else:
165 #                       print "FOUND EXISTING OBJECT: %s"% obj
166
167                 # add deepcopy values to primary object
168                 for k in backup.keys():
169                         print type(backup[k][0])
170
171                         if isinstance(obj, User) and isinstance(backup[k][0], UsrGrp):
172                                 print "adding groups to user"
173                                 for g in backup[k]:
174                                         obj.append_group(g)
175
176                         elif isinstance(obj, User) and isinstance(backup[k][0], Media):
177                                 print "adding media to user"
178                                 for g in backup[k]:
179                                         obj.media_list.append(g)
180
181                         elif isinstance(obj, UsrGrp) and isinstance(backup[k][0], HostGroup):
182                                 print "adding hostgroup to usergroup"
183                                 print "NOT IMPLEMENTED!!!"
184                                 for g in backup[k]:
185                                         obj.append_hostgroup(g)
186                                         pass
187
188                         elif isinstance(obj, Action) and isinstance(backup[k][0], ActionCondition):
189                                 print "adding actionconditon to action"
190                                 for g in backup[k]:
191                                         obj.actioncondition_list.append(g)
192
193                         elif isinstance(obj, Action) and isinstance(backup[k][0], ActionOperation):
194                                 print "adding actionoperation to action"
195                                 for g in backup[k]:
196                                         obj.actionoperation_list.append(g)
197
198                         elif isinstance(obj, ActionOperation) and \
199                                  isinstance(backup[k][0], OperationCondition):
200                                 print "adding operationcondition to actionoperation"
201                                 for g in backup[k]:
202                                         obj.operationcondition_list.append(g)
203
204                         elif isinstance(obj, DiscoveryRule) and isinstance(backup[k][0], DiscoveryCheck):
205                                 print "adding discoverycheck to discoveryrule"
206                                 for v in backup[k]:
207                                         obj.discoverycheck_list.append(v)
208
209                 return obj
210
211         def convert_dict(self, d):
212                 rd = {}
213                 for key in d.keys():
214                         if type(d[key]) == type([]):
215                                 rd[str(key)] = [ self.convert_dict(v) for v in d[key] ]
216                         else:
217                                 rd[str(key)] = d[key]
218                 return rd
219
220         @classmethod
221         def classname(cls):
222                 return cls.__name__
223
224         def prettyserialize(self):
225                 xml = self.xmlSerialize()
226                 d = FromXml(xml)
227                 PrettyPrint(d)
228         
229 class ZabbixEntity(ZabbixSerialize):
230         __metaclass__ = EntityMeta
231
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)
237                 if not index:
238                         index = IDs(table_name=tablename, field_name=fieldname, nodeid=0, nextid=10)
239                         index.flush()
240                 index.nextid = index.nextid + 1
241                 kwargs[fieldname] = index.nextid
242                 self.set(**kwargs)
243
244         def __repr__(self):
245                 rd = {}
246                 if hasattr(self, 'deepcopy'):
247                         for k in self.deepcopy:
248                                 rd[k] = [ str(v) for v in getattr(self, k) ]
249
250                 rd.update(self.to_dict())
251                 val = ""
252                 for k in rd.keys():
253                         val += k
254                         val += "="
255                         val += str(rd[k])
256                         val += ", "
257                 return self.classname() + "(" + val + ")"
258
259         @classmethod
260         def classname(cls):
261                 return cls.__name__
262
263         def set(self, **kwargs):
264                 for key, value in kwargs.iteritems():
265                         setattr(self, key, value)
266         
267         @classmethod
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
272                         obj = None
273                 else:
274                         # NOTE: ignore *_list items
275                         query = {}
276                         for key in kwargs:
277                                 if "_list" not in key:
278                                         query[key] = kwargs[key]
279                         print "SEARCHING USING %s" % query
280                         obj = cls.get_by(**query)
281
282                 if obj is None:
283                         print "CREATING NEW %s" % cls.classname()
284                         print "USING %s" % kwargs
285                         obj = cls(**kwargs)
286                         obj.set(**set_if_new)
287                         if exec_if_new:
288                                 exec_if_new(obj)
289                 else:
290                         print "FOUND EXISTING OBJECT: %s"% obj
291
292                 return obj
293
294         def update_or_create(cls, data, surrogate=True):
295                 pk_props = cls._descriptor.primary_key_properties
296
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)
301                         if record is None:
302                                 if surrogate:
303                                         raise Exception("cannot create surrogate with pk")
304                                 else:
305                                         record = cls()
306                 else:
307                         if surrogate:
308                                 record = cls()
309                         else:
310                                 raise Exception("cannot create non surrogate without pk")
311                 record.from_dict(data)
312                 return record
313         update_or_create = classmethod(update_or_create)
314
315         def from_dict(self, data):
316                 """
317                 Update a mapped class with data from a JSON-style nested dict/list
318                 structure.
319                 """
320                 # surrogate can be guessed from autoincrement/sequence but I guess
321                 # that's not 100% reliable, so we'll need an override
322
323                 mapper = sqlalchemy.orm.object_mapper(self)
324
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
330
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 \
334                                    dbvalue is not None:
335                                         dbvalue.from_dict(value)
336                                 else:
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):
341
342                                 rel_class = mapper.get_property(key).mapper.class_
343                                 new_attr_value = []
344                                 for row in value:
345                                         if not isinstance(row, dict):
346                                                 raise Exception(
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)
352                         else:
353                                 setattr(self, key, value)
354
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]
368                         else:
369                                 data[rname] = dbdata.to_dict(rdeep, exclude)
370                 return data
371
372         # session methods
373         def flush(self, *args, **kwargs):
374                 return object_session(self).flush([self], *args, **kwargs)
375
376         def delete(self, *args, **kwargs):
377                 return object_session(self).delete(self, *args, **kwargs)
378
379         def expire(self, *args, **kwargs):
380                 return object_session(self).expire(self, *args, **kwargs)
381
382         def refresh(self, *args, **kwargs):
383                 return object_session(self).refresh(self, *args, **kwargs)
384
385         def expunge(self, *args, **kwargs):
386                 return object_session(self).expunge(self, *args, **kwargs)
387
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)
393
394         def merge(self, *args, **kwargs):
395                 return self._global_session.merge(self, *args, **kwargs)
396
397         def save(self, *args, **kwargs):
398                 return self._global_session.save(self, *args, **kwargs)
399
400         def update(self, *args, **kwargs):
401                 return self._global_session.update(self, *args, **kwargs)
402
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)
409
410         # query methods
411         def get_by(cls, *args, **kwargs):
412                 return cls.query.filter_by(*args, **kwargs).first()
413         get_by = classmethod(get_by)
414
415         def get(cls, *args, **kwargs):
416                 return cls.query.get(*args, **kwargs)
417         get = classmethod(get)
418
419 class IDs(Entity):
420         using_options(
421                 tablename='ids',
422                 autoload=True,
423         )
424
425 class Escalation(ZabbixEntity):
426         using_options(
427                 tablename='escalations',
428                 autoload=True,
429                 auto_primarykey='escalationid'
430         )
431
432 class Event(ZabbixEntity):
433         using_options(
434                 tablename='events',
435                 autoload=True,
436                 auto_primarykey='eventid'
437         )
438
439 class Item(ZabbixEntity):
440         using_options(
441                 tablename='items',
442                 autoload=True,
443                 auto_primarykey='itemid'
444         )
445
446 class Acknowledge(ZabbixEntity):
447         using_options(
448                 tablename='acknowledges',
449                 autoload=True,
450                 auto_primarykey='acknowledgeid'
451         )
452
453 class Trigger(ZabbixEntity):
454         using_options(
455                 tablename='triggers',
456                 autoload=True,
457                 auto_primarykey='triggerid'
458         )
459         
460
461 class Right(ZabbixEntity):
462         # rights of a usergroup to interact with hosts of a hostgroup
463         using_options(
464                 tablename='rights',
465                 autoload=True,
466                 auto_primarykey='rightid',
467         )
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
471
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.
475
476 rights = Table('rights', __metadata__, autoload=True)
477 hostsgroups = Table('hosts_groups', __metadata__, autoload=True)
478 hoststemplates = Table('hosts_templates', __metadata__, autoload=True)
479
480         
481 # m2m table between hosts and groups below
482 class HostsGroups(ZabbixEntity):
483         using_options(
484                 tablename='hosts_groups',
485                 autoload=True,
486                 auto_primarykey='hostgroupid',
487         )
488
489 class HostsTemplates(ZabbixEntity):
490         using_options(
491                 tablename='hosts_templates',
492                 autoload=True,
493                 auto_primarykey='hosttemplateid',
494         )
495
496 class Host(ZabbixEntity):
497         using_options(
498                 tablename='hosts',
499                 autoload=True,
500                 auto_primarykey='hostid',
501         )
502         hostgroup_list = ManyToMany(
503                 'HostGroup',
504                 table=hostsgroups,
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,
508         )
509         template_list = ManyToMany(
510                 'Host',
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,
515         )
516
517         def append_template(self, template):
518                 row = HostsTemplates(hostid=self.hostid, templateid=template.hostid)
519                 return template
520
521         def remove_template(self, template):
522                 row = HostsTemplates.get_by(hostid=self.hostid, templateid=template.hostid)
523                 if row is not None:
524                         row.delete()
525
526         def delete(self):
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:
530                         row.delete()
531
532                 hosts_groups_match = HostsGroups.query.filter_by(hostid=self.hostid).all()
533                 for row in hosts_groups_match:
534                         row.delete()
535                 super(Host, self).delete()
536
537 class HostGroup(ZabbixEntity):
538         using_options(
539                 tablename='groups',
540                 autoload=True,
541                 auto_primarykey='groupid',
542         )
543         usrgrp_list = ManyToMany(
544                 'UsrGrp',
545                 table=rights,
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,
549         )
550         host_list = ManyToMany(
551                 'Host',
552                 table=hostsgroups,
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,
556         )
557         def delete(self):
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:
561                         row.delete()
562                 super(HostGroup, self).delete()
563
564 class UsersGroups(ZabbixEntity):
565         using_options(
566                 tablename='users_groups',
567                 autoload=True,
568                 auto_primarykey='id',
569         )
570
571 class MediaType(ZabbixEntity):
572         using_options(
573                 tablename='media_type',
574                 autoload=True,
575                 auto_primarykey='mediatypeid',
576         )
577
578 class Script(ZabbixEntity):
579         using_options(
580                 tablename='scripts',
581                 autoload=True,
582                 auto_primarykey='scriptid',
583         )
584
585
586 # DISCOVERY ################################################3
587
588 class DiscoveryCheck(ZabbixEntity):
589         using_options(
590                 tablename='dchecks',
591                 autoload=True,
592                 auto_primarykey='dcheckid',
593         )
594         skip_keys = ['druleid']
595         discoveryrule = ManyToOne('DiscoveryRule', 
596                                         primaryjoin=lambda: DiscoveryCheck.druleid == DiscoveryRule.druleid,
597                                         foreign_keys=lambda: [DiscoveryCheck.druleid],
598                                         ondelete='cascade') 
599
600 class DiscoveryRule(ZabbixEntity):  # parent of dchecks
601         using_options(
602                 tablename='drules',
603                 autoload=True,
604                 auto_primarykey='druleid',
605         )
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])
610
611         discoveredhost_list = OneToMany('DiscoveredHost', cascade='all, delete-orphan',
612                                         primaryjoin=lambda: DiscoveredHost.druleid == DiscoveryRule.druleid,
613                                         foreign_keys=lambda: [DiscoveredHost.druleid])
614
615 class DiscoveredHost(ZabbixEntity):
616         using_options(
617                 tablename='dhosts',
618                 autoload=True,
619                 auto_primarykey='dhostid',
620         )
621         discoveryrule = ManyToOne('DiscoveryRule',
622                                         primaryjoin=lambda: DiscoveredHost.druleid == DiscoveryRule.druleid,
623                                         foreign_keys=lambda: [DiscoveredHost.druleid],
624                                         ondelete='cascade') 
625
626         discoveryservice_list = OneToMany('DiscoveryService', cascade='all, delete-orphan',
627                                         primaryjoin=lambda: DiscoveryService.dhostid== DiscoveredHost.dhostid,
628                                         foreign_keys=lambda: [DiscoveryService.dhostid],) 
629
630 class DiscoveryService(ZabbixEntity):
631         using_options(
632                 tablename='dservices',
633                 autoload=True,
634                 auto_primarykey='dserviceid',
635         )
636         discoveryrule = ManyToOne('DiscoveredHost',
637                                         primaryjoin=lambda: DiscoveryService.dhostid== DiscoveredHost.dhostid,
638                                         foreign_keys=lambda: [DiscoveryService.dhostid],
639                                         ondelete='cascade') 
640                                                 
641
642 # ACTIONS ################################################3
643
644 class ActionOperation(ZabbixEntity):
645         using_options(
646                 tablename='operations', autoload=True, auto_primarykey='operationid',
647         )
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])
653                                         
654         operationcondition_list = OneToMany('OperationCondition', cascade='all, delete-orphan',
655                                         primaryjoin=lambda: OperationCondition.operationid == ActionOperation.operationid,
656                                         foreign_keys=lambda: [OperationCondition.operationid])
657
658 class OperationCondition(ZabbixEntity):
659         using_options(
660                 tablename='opconditions', autoload=True, auto_primarykey='opconditionid',
661         )
662         skip_keys = ['operationid']
663         actionoperation = ManyToOne('ActionOperation', ondelete='cascade',
664                                         primaryjoin=lambda: OperationCondition.operationid == ActionOperation.operationid,
665                                         foreign_keys=lambda: [OperationCondition.operationid])
666
667 class ActionCondition(ZabbixEntity):
668         using_options(
669                 tablename='conditions', autoload=True, auto_primarykey='conditionid',
670         )
671         skip_keys = ['actionid']
672         action = ManyToOne('Action', ondelete='cascade',
673                                         primaryjoin=lambda: ActionCondition.actionid == Action.actionid,
674                                         foreign_keys=lambda: [ActionCondition.actionid])
675
676 class Action(ZabbixEntity):
677         using_options(
678                 tablename='actions', autoload=True, auto_primarykey='actionid',
679         )
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])
684                                         
685         actioncondition_list = OneToMany('ActionCondition', cascade='all, delete-orphan',
686                                         primaryjoin=lambda: ActionCondition.actionid == Action.actionid,
687                                         foreign_keys=lambda: [ActionCondition.actionid])
688
689 # USERS & EMAIL MEDIA ################################################3
690
691 class Media(ZabbixEntity):
692         using_options(
693                 tablename='media',
694                 autoload=True,
695                 auto_primarykey='mediaid',
696         )
697         skip_keys = ['userid']
698         user = ManyToOne('User', 
699                                         primaryjoin=lambda: Media.userid == User.userid,
700                                         foreign_keys=lambda: [Media.userid],
701                                         ondelete='cascade') 
702
703 users_groups = Table('users_groups', __metadata__, autoload=True)
704
705 class User(ZabbixEntity): # parent of media
706         using_options(
707                 tablename='users',
708                 autoload=True,
709                 auto_primarykey='userid',
710         )
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')
716
717         # READ-ONLY: do not append or remove groups here.
718         usrgrp_list = ManyToMany('UsrGrp',
719                                 table=users_groups,
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)
723
724         def delete(self):
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:
728                         row.delete()
729                 super(User, self).delete()
730                 
731         def append_group(self, group):
732                 ug_row = UsersGroups(usrgrpid=group.usrgrpid, userid=self.userid)
733                 return group
734
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:
738                         ug_row.delete()
739                         #ug_row.flush()
740                 return
741                 
742 class UsrGrp(ZabbixEntity):
743         using_options(
744                 tablename='usrgrp',
745                 autoload=True,
746                 auto_primarykey='usrgrpid',
747         )
748         deepcopy= ['hostgroup_list']
749
750         user_list = ManyToMany(
751                 'User',
752                 table=users_groups,
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,
756         )
757
758         hostgroup_list = ManyToMany(
759                 'HostGroup',
760                 table=rights,
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,
764         )
765
766         def delete(self):
767                 rights_match = Right.query.filter_by(groupid=self.usrgrpid).all()
768                 for row in rights_match:
769                         row.delete()
770
771                 users_groups_match = UsersGroups.query.filter_by(usrgrpid=self.usrgrpid).all()
772                 for row in users_groups_match:
773                         row.delete()
774
775                 super(UsrGrp, self).delete()
776
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)
781                 ug_row.save()
782                 return
783
784         def append_user(self, user):
785                 ug_row = UsersGroups(userid=user.userid, usrgrpid=self.usrgrpid)
786                 ug_row.save()
787                 return
788
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:
792                         ug_row.delete()
793                         #ug_row.flush()
794                 return
795
796 setup_all()
797
798 def get_zabbix_class_from_name(name):
799         em = get_zabbix_entitymap()
800         cls = None
801         if "_list" in name:
802                 name=name[:-5]  # strip off the _list part.
803
804         for k in em.keys():
805                 if name == k.lower():
806                         cls = em[k]
807         return cls
808         
809 def get_zabbix_entitymap():
810         entity_map = {}
811         for n,c in zip([ u.__name__ for u in entities], entities): 
812                 entity_map[n] = c
813         return entity_map
814
815 # COMMON OBJECT TYPES
816 class OperationConditionNotAck(object):
817         def __new__(cls):
818                 o = OperationCondition(
819                                 conditiontype=defines.CONDITION_TYPE_EVENT_ACKNOWLEDGED, 
820                                 operator=defines.CONDITION_OPERATOR_EQUAL, 
821                                 value=0 ) # NOT_ACK
822                 return  o
823
824 #import md5
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")
826 #u.flush()