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