add areSlicesEnabled and isSiteEnabled convenience checks
[monitor.git] / monitor / wrapper / plc.py
1 #
2 # plc.py
3 #
4 # Helper functions that minipulate the PLC api.
5
6 # Faiyaz Ahmed <faiyaza@cs.princeton.edu
7 #
8 # $Id: plc.py,v 1.18 2007/08/29 17:26:50 soltesz Exp $
9 #
10
11 import xml, xmlrpclib
12 import logging
13 import time
14 import traceback
15 from datetime import datetime
16
17 # note: this needs to be consistent with the value in PLEWWW/planetlab/includes/plc_functions.php
18 PENDING_CONSORTIUM_ID = 0
19 # not used in monitor
20 #APPROVED_CONSORTIUM_ID = 999999
21
22 try:
23         from monitor import config
24         debug = config.debug
25         XMLRPC_SERVER=config.API_SERVER
26 except:
27         debug = False
28         # NOTE: this host is used by default when there are no auth files.
29         XMLRPC_SERVER="https://boot.planet-lab.org/PLCAPI/"
30
31 logger = logging.getLogger("monitor")
32         
33 class Auth:
34         def __init__(self, username=None, password=None, **kwargs):
35                 if 'session' in kwargs:
36                         self.auth= { 'AuthMethod' : 'session',
37                                         'session' : kwargs['session'] }
38                 else:
39                         if username==None and password==None:
40                                 self.auth = {'AuthMethod': "anonymous"}
41                         else:
42                                 self.auth = {'Username' : username,
43                                                         'AuthMethod' : 'password',
44                                                         'AuthString' : password}
45
46
47 # NOTE: by default, use anonymous access, but if auth files are 
48 #       configured, use them, with their auth definitions.
49 auth = Auth()
50 try:
51         from monitor import config
52         auth.auth = {'Username' : config.API_AUTH_USER,
53                      'AuthMethod' : 'password',
54                                  'AuthString' : config.API_AUTH_PASSWORD}
55         auth.server = config.API_SERVER
56 except:
57         try:
58                 import auth
59                 auth.server = auth.plc
60         except:
61                 auth = Auth()
62                 auth.server = XMLRPC_SERVER
63
64 global_error_count = 0
65
66 class PLC:
67         def __init__(self, auth, url):
68                 self.auth = auth
69                 self.url = url
70                 self.api = xmlrpclib.Server(self.url, verbose=False, allow_none=True)
71
72         def __getattr__(self, name):
73                 method = getattr(self.api, name)
74                 if method is None:
75                         raise AssertionError("method does not exist")
76
77                 try:
78                         return lambda *params : method(self.auth, *params)
79                 except xmlrpclib.ProtocolError:
80                         traceback.print_exc()
81                         global_error_count += 1
82                         if global_error_count >= 10:
83                                 print "maximum error count exceeded; exiting..."
84                                 sys.exit(1)
85                         else:
86                                 print "%s errors have occurred" % global_error_count
87                         raise Exception("ProtocolError continuing")
88
89         def __repr__(self):
90                 return self.api.__repr__()
91
92
93 api = PLC(auth.auth, auth.server)
94
95
96 def getAPI(url):
97         return xmlrpclib.Server(url, verbose=False, allow_none=True)
98
99 def getNodeAPI(session):
100         nodeauth = Auth(session=session)
101         return PLC(nodeauth.auth, auth.server)
102
103 def getAuthAPI(url=None):
104         if url:
105                 return PLC(auth.auth, url)
106         else:
107                 return PLC(auth.auth, auth.server)
108
109 def getCachedAuthAPI():
110         return CachedPLC(auth.auth, auth.server)
111
112 def getSessionAPI(session, server):
113         nodeauth = Auth(session=session)
114         return PLC(nodeauth.auth, server)
115 def getUserAPI(username, password, server):
116         auth = Auth(username,password)
117         return PLC(auth.auth, server)
118
119 def getTechEmails(loginbase):
120         """
121                 For the given site, return all user email addresses that have the 'tech' role.
122         """
123         api = getAuthAPI()
124         # get site details.
125         s = api.GetSites(loginbase)[0]
126         # get people at site
127         p = api.GetPersons(s['person_ids'])
128         # pull out those with the right role.
129         emails = []
130         for person in filter(lambda x: 'tech' in x['roles'], p):
131                 if not isPersonExempt(person['email']):
132                         emails.append(person['email'])
133         #emails = [ person['email'] for person in filter(lambda x: 'tech' in x['roles'], p) ]
134         return emails
135
136 def getPIEmails(loginbase):
137         """
138                 For the given site, return all user email addresses that have the 'tech' role.
139         """
140         api = getAuthAPI()
141         # get site details.
142         s = api.GetSites(loginbase)[0]
143         # get people at site
144         p = api.GetPersons(s['person_ids'])
145         # pull out those with the right role.
146         #emails = [ person['email'] for person in filter(lambda x: 'pi' in x['roles'], p) ]
147         emails = []
148         for person in filter(lambda x: 'pi' in x['roles'], p):
149                 if not isPersonExempt(person['email']):
150                         emails.append(person['email'])
151         return emails
152
153 def getSliceUserEmails(loginbase):
154         """
155                 For the given site, return all user email addresses that have the 'tech' role.
156         """
157         api = getAuthAPI()
158         # get site details.
159         s = api.GetSites(loginbase)[0]
160         # get people at site
161         slices = api.GetSlices(s['slice_ids'])
162         people = []
163         for slice in slices:
164                 people += api.GetPersons(slice['person_ids'])
165         # pull out those with the right role.
166         #emails = [ person['email'] for person in filter(lambda x: 'pi' in x['roles'], people) ]
167
168         emails = []
169         for person in people:
170                 if not isPersonExempt(person['email']):
171                         emails.append(person['email'])
172
173         unique_emails = [ x for x in set(emails) ]
174         return unique_emails
175
176 '''
177 Returns list of nodes in dbg as reported by PLC
178 '''
179 def nodesDbg():
180         dbgNodes = []
181         api = xmlrpclib.Server(auth.server, verbose=False)
182         anon = {'AuthMethod': "anonymous"}
183         for node in api.GetNodes(anon, {"boot_state":"dbg"},["hostname"]):
184                 dbgNodes.append(node['hostname'])
185         logger.info("%s nodes in debug according to PLC." %len(dbgNodes))
186         return dbgNodes
187
188
189 '''
190 Returns loginbase for given nodename
191 '''
192 def siteId(nodename):
193         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
194         site_id = api.GetNodes (auth.auth, {"hostname": nodename}, ['site_id'])
195         if len(site_id) == 1:
196                 loginbase = api.GetSites (auth.auth, site_id[0], ["login_base"])
197                 return loginbase[0]['login_base']
198         else:
199                 print "Not nodes returned!!!!"
200
201 '''
202 Returns list of slices for a site.
203 '''
204 def slices(loginbase):
205         siteslices = []
206         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
207         sliceids = api.GetSites (auth.auth, {"login_base" : loginbase}, ["slice_ids"])[0]['slice_ids']
208         for slice in api.GetSlices(auth.auth, {"slice_id" : sliceids}, ["name"]):
209                 siteslices.append(slice['name'])
210         return siteslices
211
212 '''
213 Returns dict of PCU info of a given node.
214 '''
215 def getpcu(nodename):
216         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
217         anon = {'AuthMethod': "anonymous"}
218         try:
219                 nodeinfo = api.GetNodes(auth.auth, {"hostname": nodename}, ["pcu_ids", "ports"])[0]
220         except IndexError:
221                 logger.info("Can not find node: %s" % nodename)
222                 return False
223         if nodeinfo['pcu_ids']:
224                 print nodeinfo
225                 sitepcu = api.GetPCUs(auth.auth, nodeinfo['pcu_ids'])[0]
226                 print sitepcu
227                 print nodeinfo["ports"]
228                 sitepcu[nodename] = nodeinfo["ports"][0]
229                 return sitepcu
230         else:
231                 logger.info("%s doesn't have PCU" % nodename)
232                 return False
233
234 def GetPCUs(filter=None, fields=None):
235         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
236         pcu_list = api.GetPCUs(auth.auth, filter, fields)
237         return pcu_list 
238
239 '''
240 Returns all site nodes for site id (loginbase).
241 '''
242 def getSiteNodes(loginbase, fields=None):
243         api = xmlrpclib.Server(auth.server, verbose=False)
244         nodelist = []
245         anon = {'AuthMethod': "anonymous"}
246         try:
247                 nodeids = api.GetSites(anon, {"login_base": loginbase}, fields)[0]['node_ids']
248                 for node in api.GetNodes(anon, {"node_id": nodeids}, ['hostname']):
249                         nodelist.append(node['hostname'])
250         except Exception, exc:
251                 logger.info("getSiteNodes:  %s" % exc)
252                 print "getSiteNodes:  %s" % exc
253         return nodelist
254
255
256 def getPersons(filter=None, fields=None):
257         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
258         persons = []
259         try:
260                 persons = api.GetPersons(auth.auth, filter, fields)
261         except Exception, exc:
262                 print "getPersons:  %s" % exc
263                 logger.info("getPersons:  %s" % exc)
264         return persons
265
266 def getSites(filter=None, fields=None):
267         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
268         sites = []
269         anon = {'AuthMethod': "anonymous"}
270         try:
271                 #sites = api.GetSites(anon, filter, fields)
272                 sites = api.GetSites(auth.auth, filter, fields)
273         except Exception, exc:
274                 traceback.print_exc()
275                 print "getSites:  %s" % exc
276                 logger.info("getSites:  %s" % exc)
277         return sites
278
279 def getSiteNodes2(loginbase):
280         api = xmlrpclib.Server(auth.server, verbose=False)
281         nodelist = []
282         anon = {'AuthMethod': "anonymous"}
283         try:
284                 nodeids = api.GetSites(anon, {"login_base": loginbase})[0]['node_ids']
285                 nodelist += getNodes({'node_id':nodeids})
286         except Exception, exc:
287                 logger.info("getSiteNodes2:  %s" % exc)
288         return nodelist
289
290 def getNodeNetworks(filter=None):
291         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
292         nodenetworks = api.GetInterfaces(auth.auth, filter, None)
293         return nodenetworks
294
295 def getNodes(filter=None, fields=None):
296         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
297         nodes = api.GetNodes(auth.auth, filter, fields) 
298                         #['boot_state', 'hostname', 
299                         #'site_id', 'date_created', 'node_id', 'version', 'interface_ids',
300                         #'last_updated', 'peer_node_id', 'ssh_rsa_key' ])
301         return nodes
302
303
304 # Check if the site is a pending site that needs to be approved.
305 def isPendingSite(loginbase):
306         api = xmlrpclib.Server(auth.server, verbose=False)
307         try:
308                 site = api.GetSites(auth.auth, loginbase)[0]
309         except Exception, exc:
310                 logger.info("ERROR: No site %s" % loginbase)
311                 return False
312
313         if not site['enabled'] and site['ext_consortium_id'] == PENDING_CONSORTIUM_ID:
314                 return True
315
316         return False
317
318
319 '''
320 Sets boot state of a node.
321 '''
322 def nodeBootState(nodename, state):
323         api = xmlrpclib.Server(auth.server, verbose=False)
324         try:
325                 return api.UpdateNode(auth.auth, nodename, {'boot_state': state})
326         except Exception, exc:
327                 logger.info("nodeBootState:  %s" % exc)
328
329 def updateNodeKey(nodename, key):
330         api = xmlrpclib.Server(auth.server, verbose=False)
331         try:
332                 return api.UpdateNode(auth.auth, nodename, {'key': key})
333         except Exception, exc:
334                 logger.info("updateNodeKey:  %s" % exc)
335
336 '''
337 Sends Ping Of Death to node.
338 '''
339 def nodePOD(nodename):
340         api = xmlrpclib.Server(auth.server, verbose=False)
341         logger.info("Sending POD to %s" % nodename)
342         try:
343                 if not debug:
344                         return api.RebootNode(auth.auth, nodename)
345         except Exception, exc:
346                         logger.info("nodePOD:  %s" % exc)
347
348 '''
349 Freeze all site slices.
350 '''
351 def suspendSiteSlices(loginbase):
352         if isPendingSite(loginbase):
353                 msg = "INFO: suspendSiteSlices: Pending Site (%s)" % loginbase
354                 print msg
355                 logger.info(msg)
356                 return
357
358         api = xmlrpclib.Server(auth.server, verbose=False)
359         for slice in slices(loginbase):
360                 logger.info("Suspending slice %s" % slice)
361                 try:
362                         if not debug:
363                             if not isSliceExempt(slice):
364                                     api.AddSliceAttribute(auth.auth, slice, "enabled", "0")
365                 except Exception, exc:
366                         logger.info("suspendSlices:  %s" % exc)
367
368 '''
369 Freeze all site slices.
370 '''
371 def suspendSlices(nodename):
372         loginbase = siteId(nodename)
373         suspendSiteSlices(loginbase)
374
375
376 def enableSiteSlices(loginbase):
377         if isPendingSite(loginbase):
378                 msg = "INFO: enableSiteSlices: Pending Site (%s)" % loginbase
379                 print msg
380                 logger.info(msg)
381                 return
382
383         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
384         for slice in slices(loginbase):
385                 logger.info("Enabling slices %s" % slice)
386                 try:
387                         if not debug:
388                                 slice_list = api.GetSlices(auth.auth, {'name': slice}, None)
389                                 if len(slice_list) == 0:
390                                         return
391                                 slice_id = slice_list[0]['slice_id']
392                                 l_attr = api.GetSliceAttributes(auth.auth, {'slice_id': slice_id}, None)
393                                 for attr in l_attr:
394                                         if "enabled" == attr['name'] and attr['value'] == "0":
395                                                 logger.info("Deleted enable=0 attribute from slice %s" % slice)
396                                                 api.DeleteSliceAttribute(auth.auth, attr['slice_attribute_id'])
397                 except Exception, exc:
398                         logger.info("enableSiteSlices: %s" % exc)
399                         print "exception: %s" % exc
400
401 def enableSlices(nodename):
402         loginbase = siteId(nodename)
403         enableSiteSlices(loginbase)
404
405
406 #I'm commenting this because this really should be a manual process.  
407 #'''
408 #Enable suspended site slices.
409 #'''
410 #def enableSlices(nodename, slicelist):
411 #       api = xmlrpclib.Server(auth.server, verbose=False)
412 #       for slice in  slices(siteId(nodename)):
413 #               logger.info("Suspending slice %s" % slice)
414 #               api.SliceAttributeAdd(auth.auth, slice, "plc_slice_state", {"state" : "suspended"})
415 #
416 def enableSiteSliceCreation(loginbase):
417         if isPendingSite(loginbase):
418                 msg = "INFO: enableSiteSliceCreation: Pending Site (%s)" % loginbase
419                 print msg
420                 logger.info(msg)
421                 return
422
423         api = xmlrpclib.Server(auth.server, verbose=False, allow_none=True)
424         try:
425                 logger.info("Enabling slice creation for site %s" % loginbase)
426                 if not debug:
427                         site = api.GetSites(auth.auth, loginbase)[0]
428                         if site['enabled'] == False:
429                                 logger.info("\tcalling UpdateSite(%s, enabled=True)" % loginbase)
430                                 api.UpdateSite(auth.auth, loginbase, {'enabled': True})
431         except Exception, exc:
432                 print "ERROR: enableSiteSliceCreation:  %s" % exc
433                 logger.info("ERROR: enableSiteSliceCreation:  %s" % exc)
434
435 def enableSliceCreation(nodename):
436         loginbase = siteId(nodename)
437         enableSiteSliceCreation(loginbase)
438
439 def areSlicesEnabled(site):
440
441         try:
442                 slice_list = api.GetSlices(slices(site))
443                 if len(slice_list) == 0:
444                         return None
445                 for slice in slice_list:
446                         slice_id = slice['slice_id']
447                         l_attr = api.GetSliceAttributes({'slice_id': slice_id})
448                         for attr in l_attr:
449                                 if "enabled" == attr['name'] and attr['value'] == "0":
450                                         return False
451
452         except Exception, exc:
453                 pass
454
455         return True
456         
457
458 def isSiteEnabled(site):
459     try:
460         site = api.GetSites(site)[0]
461         return site['enabled']
462     except:
463         pass
464
465     return True
466     
467
468 def isTagCurrent(tags):
469     if len(tags) > 0:
470         for tag in tags:
471             until = tag['value']
472             if datetime.strptime(until, "%Y%m%d") > datetime.now():
473                 # NOTE: the 'exempt_until' time is beyond current time
474                 return True
475     return False
476
477 def isPersonExempt(email):
478     tags = api.GetPersonTags({'email' : email, 'tagname' : 'exempt_person_until'})
479     return isTagCurrent(tags)
480
481 def isNodeExempt(hostname):
482     tags = api.GetNodeTags({'hostname' : hostname, 'tagname' : 'exempt_node_until'})
483     return isTagCurrent(tags)
484
485 def isSliceExempt(slicename):
486     tags = api.GetSliceTags({'name' : slicename, 'tagname' : 'exempt_slice_until'})
487     return isTagCurrent(tags)
488
489 def isSiteExempt(loginbase):
490     tags = api.GetSiteTags({'login_base' : loginbase, 'tagname' : 'exempt_site_until'})
491     return isTagCurrent(tags)
492
493 '''
494 Removes site's ability to create slices. Returns previous max_slices
495 '''
496 def removeSiteSliceCreation(loginbase):
497         #print "removeSiteSliceCreation(%s)" % loginbase
498
499         if isPendingSite(loginbase):
500                 msg = "INFO: removeSiteSliceCreation: Pending Site (%s)" % loginbase
501                 print msg
502                 logger.info(msg)
503                 return
504
505         api = xmlrpclib.Server(auth.server, verbose=False)
506         try:
507                 logger.info("Removing slice creation for site %s" % loginbase)
508                 if not debug:
509                         if not isSiteExempt(loginbase):
510                             api.UpdateSite(auth.auth, loginbase, {'enabled': False})
511         except Exception, exc:
512                 logger.info("removeSiteSliceCreation:  %s" % exc)
513
514 '''
515 Removes ability to create slices. Returns previous max_slices
516 '''
517 def removeSliceCreation(nodename):
518         loginbase = siteId(nodename)
519         removeSiteSliceCreation(loginbase)
520
521
522 '''
523 QED
524 '''
525 #def enableSliceCreation(nodename, maxslices):
526 #       api = xmlrpclib.Server(auth.server, verbose=False)
527 #       anon = {'AuthMethod': "anonymous"}
528 #       siteid = api.AnonAdmQuerySite (anon, {"node_hostname": nodename})
529 #       if len(siteid) == 1:
530 #               logger.info("Enabling slice creation for site %s" % siteId(nodename))
531 #               try:
532 #                       if not debug:
533 #                               api.AdmUpdateSite(auth.auth, siteid[0], {"max_slices" : maxslices})
534 #               except Exception, exc:
535 #                       logger.info("API:  %s" % exc)
536 #       else:
537 #               logger.debug("Cant find site for %s.  Cannot enable creation." % nodename)
538
539 def main():
540         logger.setLevel(logging.DEBUG)
541         ch = logging.StreamHandler()
542         ch.setLevel(logging.DEBUG)
543         formatter = logging.Formatter('logger - %(message)s')
544         ch.setFormatter(formatter)
545         logger.addHandler(ch)
546         #print getpcu("kupl2.ittc.ku.edu")
547         #print getpcu("planetlab1.cse.msu.edu")
548         #print getpcu("alice.cs.princeton.edu")
549         #print nodesDbg()
550         #nodeBootState("alice.cs.princeton.edu", "boot")
551         #freezeSite("alice.cs.princeton.edu")
552         print removeSliceCreation("alice.cs.princeton.edu")
553         #enableSliceCreation("alice.cs.princeton.edu", 1024)
554         #print getSiteNodes("princeton")
555         #print siteId("alice.cs.princeton.edu")
556         #print nodePOD("alice.cs.princeton.edu")
557         #print slices("princeton")
558
559 if __name__=="__main__":
560         main()