Fix for session.clear for newer versions of SQLAlchemy.
[monitor.git] / web / MonitorWeb / monitorweb / monitor_xmlrpc.py
1 import sys
2 import xmlrpclib
3 import cherrypy
4 import turbogears
5 from datetime import datetime, timedelta
6 import time
7 from monitor.wrapper import plc
8
9 try:
10         from monitor.database.info.model import *
11         from monitor.database.info.interface import *
12 except:
13         pass
14
15 try:
16     from PLC.Parameter import Parameter, Mixed
17 except:
18     def Parameter(a = None, b = None): pass
19     def Mixed(a = None, b = None, c = None): pass
20
21 def export_to_docbook(**kwargs):
22
23     keywords = {
24         "group" : "Monitor",
25         "status" : "current",
26         "name": None,
27         "args": None,
28         "roles": [],
29         "accepts": [],
30         "returns": [],
31     }
32     def export(method):
33         def args():
34             # Inspect method. Remove self from the argument list.
35             max_args = method.func_code.co_varnames[0:method.func_code.co_argcount]
36             defaults = method.func_defaults
37             if defaults is None:
38                 defaults = ()
39             min_args = max_args[0:len(max_args) - len(defaults)]
40
41             defaults = tuple([None for arg in min_args]) + defaults
42             return (min_args, max_args, defaults)
43
44         keywords['name'] = method.__name__
45         keywords['args'] = args
46         for arg in keywords:
47             method.__setattr__(arg, keywords[arg])
48
49         for arg in kwargs:
50             method.__setattr__(arg, kwargs[arg])
51         return method
52
53     return export
54
55
56 class MonitorXmlrpcServerMethods:
57         @cherrypy.expose
58         def listMethods(self):
59                 mod = MonitorXmlrpcServer()
60                 ret_list = []
61                 for f in dir(mod):
62                         if isinstance(mod.__getattribute__(f),type(mod.__getattribute__('addDowntime'))):
63                                 ret_list += [f]
64                 return ret_list
65
66 def convert_datetime(d, keys=None):
67         ret = d.copy()
68         n = datetime.now()
69         if keys == None:
70                 keys = d.keys()
71         for k in keys:
72                 if type(d[k]) == type(n):
73                         ret[k] = time.mktime(d[k].utctimetuple())
74         
75         return ret
76
77 class MonitorXmlrpcServer(object):
78
79         @cherrypy.expose
80         def listMethods(self):
81                 mod = MonitorXmlrpcServer()
82                 ret_list = []
83                 for f in dir(mod):
84                         if isinstance(mod.__getattribute__(f),type(mod.__getattribute__('addDowntime'))):
85                                 ret_list += [f]
86                 return ret_list
87
88         @turbogears.expose()
89         def XMLRPC(self):
90                 params, method = xmlrpclib.loads(cherrypy.request.body.read())
91                 try:
92                         if method == "xmlrpc":
93                                 # prevent recursion
94                                 raise AssertionError("method cannot be 'xmlrpc'")
95                         # Get the function and make sure it's exposed.
96                         method = getattr(self, method, None)
97                         # Use the same error message to hide private method names
98                         if method is None or not getattr(method, "exposed", False):
99                                 raise AssertionError("method does not exist")
100
101             try:
102                             session.expunge_all()
103             except AttributeError: # SQLAlchemy < 0.5.1
104                 session.clear()
105                         # Call the method, convert it into a 1-element tuple
106                         # as expected by dumps                                     
107                         response = method(*params)
108
109                         session.flush()
110                         response = xmlrpclib.dumps((response,), methodresponse=1, allow_none=1)
111                 except xmlrpclib.Fault, fault:
112                         # Can't marshal the result
113                         response = xmlrpclib.dumps(fault, allow_none=1)
114                 except:
115                         # Some other error; send back some error info
116                         response = xmlrpclib.dumps(
117                                 xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
118                                 )
119
120                 cherrypy.response.headers["Content-Type"] = "text/xml"
121                 return response
122
123         # User-defined functions must use cherrypy.expose; turbogears.expose
124         #       does additional checking of the response type that we don't want.
125         @cherrypy.expose
126         @export_to_docbook(roles=['tech', 'user', 'pi', 'admin'],
127                            accepts=[],
128                                            returns=Parameter(bool, 'True if successful'))
129         def upAndRunning(self,noop=None):
130                 """ This call can indicate to a script whether the server is up
131                 and running before trying any more sophisticated operations. """
132                 return True
133
134         # BOOTMAN SEQUENCE ------------------------------------------------------------
135
136         @cherrypy.expose
137         @export_to_docbook(roles=['admin'],
138                            accepts=[Parameter(dict, "Auth struct"), 
139                                                         Parameter(str, "The bootman sequence returned by MyOps"), 
140                                                         Parameter(str, "The action string that identifies what to do when this sequence occurs")],
141                                            returns=Parameter(bool, 'True on success.'))
142         def setBootmanSequence(self, auth, sequence, action):
143                 """ Using this call, you can set a new sequence to identify an Unknown
144                 Error sqeuence returned by MyOps and associate it with a pre-defined
145                 action, (i.e. reboot, reinstall, or others).  Please see the
146                 documentation for automated actions to see a list of supported
147                 actions. """
148                 api = plc.getAuthAPI()
149                 api.auth = auth
150                 if api.AuthCheck():
151                         bms = BootmanSequenceRecord.get_by(sequence=sequence)
152                         if not bms:
153                                 bms = BootmanSequenceRecord(sequence=sequence, action=action)
154                         else:
155                                 bms.action = action 
156
157                         bms.flush()
158                         return True
159                 else:
160                         return False
161
162         @cherrypy.expose
163         @export_to_docbook(roles=['admin'],
164                            accepts=[Parameter(dict, "Auth struct")], 
165                                            returns=Parameter(list, 'Array of bootman sequences'))
166         def getBootmanSequences(self, auth):
167                 """ Using this call, you can learn all currently defined bootman
168                 sequences and their associated actions. """
169                 api = plc.getAuthAPI()
170                 api.auth = auth
171                 if api.AuthCheck():
172                         ret_list = []
173                         bms = BootmanSequenceRecord.query.all()
174                         for q in bms:
175                                 d = q.to_dict()
176                                 d = convert_datetime(d, ['date_created'])
177                                 ret_list.append(d)
178                         return ret_list
179                 else:
180                         return []
181
182         # SITES ------------------------------------------------------------
183
184         @cherrypy.expose
185         @export_to_docbook(roles=['tech', 'user', 'pi', 'admin'],
186                            accepts=[Parameter(dict, "Auth struct")],
187                                            returns=Parameter(list, 'array of SiteStatus records'))
188         def getSiteStatus(self, auth):
189                 """ This call returns a list that includes the status and observations 
190                 of all sites, including those blacklisted.  At this time, there is no
191                 indication which sites are blacklisted from this list. """
192                 ret_list = []
193                 sites = HistorySiteRecord.query.all()
194                 for q in sites:
195                         d = q.to_dict(exclude=['timestamp', 'version', ])
196                         d = convert_datetime(d, ['last_checked', 'last_changed', 'message_created'])
197                         ret_list.append(d)
198                 return ret_list
199
200         @cherrypy.expose
201         @export_to_docbook(roles=['admin'],
202                            accepts=[Parameter(dict, "Auth struct")],
203                                            returns=Parameter(bool, 'True on success.'))
204         def clearSitePenalty(self, auth, loginbase):
205                 """ Rather than waiting for monitor to run automatically, this call
206                         will manually clear a site's penalties. """
207                 sitehist = SiteInterface.get_or_make(loginbase=loginbase)
208                 sitehist.clearPenalty()
209                 #sitehist.applyPenalty()
210                 #sitehist.sendMessage('clear_penalty')
211                 sitehist.closeTicket()
212                 return True
213
214         @cherrypy.expose
215         @export_to_docbook(roles=['admin'],
216                            accepts=[Parameter(dict, "Auth struct")],
217                                            returns=Parameter(bool, 'True on success.'))
218         def increaseSitePenalty(self, auth, loginbase):
219                 """ Rather than waiting for monitor to run automatically, this call
220                         will manually increase a site's penalties."""
221                 sitehist = SiteInterface.get_or_make(loginbase=loginbase)
222                 sitehist.increasePenalty()
223                 #sitehist.applyPenalty()
224                 #sitehist.sendMessage('increase_penalty')
225                 return True
226
227         # NODES ------------------------------------------------------------
228
229         @cherrypy.expose
230         @export_to_docbook(roles=['tech', 'user', 'pi', 'admin'],
231                            accepts=[Parameter(dict, "Auth struct")],
232                                            returns=Parameter(list, 'array of NodeStatus records.'))
233         def getNodeStatus(self, auth):
234                 """ This call returns a list of all nodes, including those
235                         blacklisted.  The current observation and recorded status of each node
236                         is returned."""
237                 ret_list = []
238                 sites = HistoryNodeRecord.query.all()
239                 for q in sites:
240                         d = q.to_dict(exclude=['timestamp', 'version', ])
241                         d = convert_datetime(d, ['last_checked', 'last_changed',])
242                         ret_list.append(d)
243                 return ret_list
244
245         @cherrypy.expose
246         @export_to_docbook(roles=['tech', 'user', 'pi', 'admin'],
247                            accepts=[Parameter(dict, "Auth struct")],
248                                            returns=Parameter(bool, 'True on success.'))
249         def getRecentActions(self, auth, loginbase=None, hostname=None):
250                 """ Monitor takes various actions on sites (such as applying
251                         penalties) and nodes (such as repairing a node installation via
252                         BootManager).  As well, it makes a log of every email message sent
253                         out, or believed to be sent.  This call returns a list of all actions,
254                         filtered on site or for a specific node. """
255                 ret_list = []
256                 return ret_list
257
258         # BLACKLIST ------------------------------------------------------------
259
260         @cherrypy.expose
261         @export_to_docbook(roles=['tech', 'user', 'pi', 'admin'],
262                            accepts=[Parameter(dict, "Auth struct")],
263                                            returns=Parameter(bool, 'True on success.'))
264         def getBlacklist(self, auth):
265                 """ Return a list of all nodes and sites that are excluded from
266                 penalties.  Currently there is no way to exclude a node or site 
267                 from being monitored. """
268                 bl = BlacklistRecord.query.all()
269                 ret_list = []
270                 for q in bl:
271                         d = q.to_dict(exclude=['timestamp', 'version', 'id', ])
272                         d = convert_datetime(d, ['date_created'])
273                         ret_list.append(d)
274
275                 return ret_list
276         
277         @cherrypy.expose
278         @export_to_docbook(roles=['admin'],
279                            accepts=[Parameter(dict, "Auth struct"), 
280                                                                 Parameter(str, "hostname"), 
281                                                                 Parameter(int, "expires number of seconds from time.now()")],
282                                            returns=Parameter(bool, 'True on success.'))
283         def addHostToBlacklist(self, auth, hostname, expires=0):
284                 """ Add a host to the blacklist, with an optional expiration time"""
285                 bl = BlacklistRecord.findby_or_create(hostname=hostname, expires=expires)
286                 return True
287
288         @cherrypy.expose
289         @export_to_docbook(roles=['admin'],
290                            accepts=[Parameter(dict, "Auth struct"),
291                                                                 Parameter(str, "loginbase"), 
292                                                                 Parameter(int, "expires number of seconds from time.now()")],
293                                            returns=Parameter(bool, 'True on success.'))
294         def addSiteToBlacklist(self, auth, loginbase, expires=0):
295                 """ Add a site to the blacklist, with an optional expiration time"""
296                 bl = BlacklistRecord.findby_or_create(hostname=hostname, expires=expires)
297                 return True
298
299         @cherrypy.expose
300         @export_to_docbook(roles=['admin'],
301                            accepts=[Parameter(dict, "Auth struct"),
302                                                                 Parameter(str, "loginbase"), 
303                                                                 Parameter(str, "hostname"),],
304                                            returns=Parameter(bool, 'True on success.'))
305         def deleteFromBlacklist(self, auth, loginbase=None, hostname=None):
306                 """ Remove a host or site from the blacklist """
307                 if (loginbase==None and hostname == None) or (loginbase != None and hostname != None):
308                         raise Exception("Please specify a single record to delete: either hostname or loginbase")
309                 elif loginbase != None:
310                         bl = BlacklistRecord.get_by(loginbase=loginbase)
311                         bl.delete()
312                 elif hostname != None:
313                         bl = BlacklistRecord.get_by(hostname=hostname)
314                         bl.delete()
315                 return True