clearer names for actions, and infer actions better
[monitor.git] / commands / myops.py
1 #!/usr/bin/python
2
3 import os
4 import sys
5 import string
6 import time
7
8 import getopt
9 import sys
10 import os
11 import xml, xmlrpclib
12 from getpass import getpass
13 from operator import attrgetter, itemgetter
14
15 def get_plc_api(target, url, username, password, expires, debug_mode):
16     # Either read session from disk or create it and save it for later
17     metasession = "%s/%s_%s" % (os.environ['HOME'], ".metasession", target)
18     if os.path.exists(metasession):
19         (localurl,session) =  open(metasession, 'r').read().strip().split()
20         plc = xmlrpclib.Server(localurl, verbose=False, allow_none=True)
21     else:
22         plc = xmlrpclib.Server(url, verbose=False, allow_none=True)
23         if password == None: password = getpass()
24         auth = {'Username' : username, 
25                 'AuthMethod' : 'password',
26                 'AuthString' : password}
27         session = plc.GetSession(auth, expires*(60*60*24))
28         with open(metasession, 'w') as f: f.write("%s %s\n" % (url,session)) # 'with' auto-closes
29
30     auth = {'AuthMethod' : 'session', 'session' : session}
31
32     class PLC:
33         def __init__(self, plc, auth):
34             self.plc = plc
35             self.auth = auth
36
37         def __getattr__(self, name):
38             method = getattr(self.plc, name)
39             if method is None:
40                 raise AssertionError("Method does not exist: %s" % method)
41             if not debug_mode or ('Get' in name or 'AuthCheck' in name):
42                 return lambda *params : method(self.auth, *params)
43             else:
44                 def call(name,*params):
45                     print "DEBUG not running: %s(%s)" % (name, params)
46                 return lambda *params : call(name,*params)
47
48     plc_api = PLC(plc, auth)
49     try:
50         # make sure the session is working
51         plc_api.AuthCheck()
52     except:
53         # everything worked except the auth check. try again asking for passwd.
54         plc_api = get_plc_api(target, url, username, None, expires, debug_mode)
55
56     return plc_api
57
58 def usage(parser):
59     print """
60 myops.py <TARGET> <ACTION> [<object>] [args]
61     MYOPS CLI uses sessions to avoid storing passwords.
62     You choose the session expiration via --expires <days>.
63
64 TARGET:
65     When your session is saved it is identified by your given 'target'
66     name.  This is a unique string you chose to identify the --apiurl. 
67     For example, one might use:
68         plc
69         vicci
70         test
71         vini
72
73 ACTION:
74     Connect to TARGET and perform ACTION. The current actions are:
75         enabled   --  Manage site, node, and slice 'enabled' states.
76                       Object may be sitename, hostname, or slicename.
77         
78         exempt    --  Manage site, node, and slice exemptions from 
79                       myops policy actions.  Object may be sitename, 
80                       hostname, or slicename.
81
82         removeall --  Remove all exemptions at site & from nodes, slices 
83
84         exemptall --  Add exemptions at site & to nodes, slices 
85
86         disableall--  Disable everything at a site: 
87                         disable site, 
88                         disable slices,
89
90         enableall --  Release everything at a site: 
91                         re-enable site, 
92                         re-enable slices,
93
94 EXAMPLES:
95     # setup session and save target name 'plc' for future calls
96     myops.py plc --apiurl https://boot.planet-lab.org/PLCAPI/ \\
97                 --username soltesz@cs.princeton.edu
98
99     # list current exemptions at plc
100     myops.py plc exempt
101
102     # to list only one site (nothing will show if no exemption is present)
103     myops.py plc exempt princeton
104
105     # add an exemption until a specific date
106     myops.py plc exempt princeton --expires 20120131
107
108     # remove this exemption
109     myops.py plc exempt princeton -r
110
111     # exempt just a slice, not the whole site.
112     myops.py plc exempt princeton_comon --expires 20120131
113
114     # re-enable a site & slices then, exempt site & slices for 7 days
115     myops.py plc enableall princeton 
116     myops.py plc exemptall princeton --expires 7
117
118 """ 
119     parser.print_help()
120
121 def unparse_expire_str(value):
122     if value == None:
123         expires = 60*60*24*30   # 30 days default
124     else:
125         expires = time.mktime(time.strptime(value, "%Y%m%d")) - time.time()
126     return int(expires)
127
128 def parse_expire_str(value):
129     import optparse
130     if value == None:
131         return None
132     elif len(value) <= 3:
133         # days from now
134         value = time.strftime("%Y%m%d", time.localtime(time.time()+int(value)*60*60*24))
135     elif len(value) != 8 and value[:3] != "201": # 201 == this decade.
136         # flip out
137         raise optparse.OptionValueError
138     return value
139
140 class PlcObj(object):
141     def __init__(self, name):
142         if type(name) == type(""):
143             self.name = name
144         elif type(name) == type({}):
145             if 'login_base' in name:
146                 self.name = name['login_base']
147             elif 'hostname' in name:
148                 self.name = name['hostname']
149             elif 'name' in name:
150                 self.name = name['name']
151
152         self.kind = None
153         if '_' in self.name: 
154             kind = 'Slice'
155         elif '.' in self.name: 
156             kind='Node'
157         else: 
158             kind='Site'
159         self.kind = kind
160
161     def list(self,target,action,*vals):
162         if action == "enabled":
163             print ("\t%s %s %s" % (sys.argv[0],target,action)) + (" %-20s --disable" % self.name)
164         elif action == "exempt":
165             print ("\t%s %s %s" % (sys.argv[0],target,action)) + (" %-20s --expires %s" % ((self.name,)+ vals))
166
167     def enable(self,api,state):
168         if self.kind == 'Slice':
169             # change value of existing slice tag, if it exists.
170             tl = api.GetSliceTags({'name' : self.name, 'tagname' : 'enabled', 'value' : '0' if state else '1'})
171             if len(tl) == 0:
172                 api.AddSliceTag(self.name, 'enabled', '1' if state else '0')
173             else:
174                 for t in tl: 
175                     api.UpdateSliceTag(t['slice_tag_id'], {'value' : '1' if state else '0'})
176         elif self.kind == 'Node':
177             if state == True: 
178                 api.UpdateNode(self.name, {'boot_state' : 'boot'})
179             else: 
180                 api.UpdateNode(self.name, {'boot_state' : 'disabled'})
181         elif self.kind == 'Site':
182             api.UpdateSite(self.name, {'enabled' : state})
183             
184     def exempt(self,api,expires):
185         if expires != None:
186             if self.kind == 'Slice': 
187                 try: api.AddSliceTag(self.name, 'exempt_slice_until', expires)
188                 except: api.UpdateSliceTag(self.name, expires)
189             elif self.kind == 'Node': 
190                 try: api.AddNodeTag(self.name, 'exempt_node_until', expires)
191                 except: api.UpdateNodeTag(self.name, expires)
192             elif self.kind == 'Site': 
193                 try: api.AddSiteTag(api.GetSites(self.name, ['site_id'])[0]['site_id'], 'exempt_site_until', expires)
194                 except: api.UpdateSiteTag(api.GetSiteTags({'login_base' : self.name, 'tagname' : 'exempt_site_until'})[0]['site_tag_id'], expires)
195         else:
196             # remove
197             if self.kind == 'Slice': 
198                 tag_id_l = api.GetSliceTags({'name' : self.name, 'tagname' : 'exempt_slice_until'}, ['slice_tag_id'])
199                 if len(tag_id_l) > 0:   
200                     tag_id = tag_id_l[0]['slice_tag_id']
201                     api.DeleteSliceTag(tag_id)
202             elif self.kind == 'Node': 
203                 tag_id_l = api.GetNodeTags({'hostname' : self.name, 'tagname' : 'exempt_node_until'}, ['node_tag_id'])
204                 if len(tag_id_l) > 0: 
205                     tag_id = tag_id_l[0]['node_tag_id']
206                     api.DeleteNodeTag(tag_id)
207             elif self.kind == 'Site': 
208                 tag_id_l = api.GetSiteTags({'login_base' : self.name, 'tagname' : 'exempt_site_until'}, ['site_tag_id'])
209                 if len(tag_id_l) > 0: 
210                     tag_id = tag_id_l[0]['site_tag_id']
211                     api.DeleteSiteTag(tag_id)
212
213
214 def main():
215     from optparse import OptionParser
216     copy = False
217     parser = OptionParser()
218
219     parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False, help="")
220     parser.add_option("-v", "--verbose", dest="verbose", default=False, help="")
221     parser.add_option("-u", "--apiurl", dest="url", default="https://www.planet-lab.org/PLCAPI/", help="Set PLC URL for action")
222     parser.add_option("-U", "--username", dest="username", default=None, help="Login as username")
223     parser.add_option("-P", "--password", dest="password", default=None, help="Use provided password; otherwise prompt for password")
224     parser.add_option("-e", "--expires", dest="expires", default=None, help="Set expiration date YYYYMMDD (or <days>); default is None (i.e. removed)")
225     parser.add_option("", "--disable", dest="disable", default=False, action="store_true", help="Disable object.")
226     parser.add_option("-r", "--remove", dest="remove", action="store_true", default=False, help="Remove object from exemption" )
227     parser.add_option("-l", "--list", dest="list", action="store_true", default=False, help="List objects with command used to generate them")
228     parser.add_option("-S", "--site", dest="login_base", default=None, help="Act on this site")
229     parser.add_option("-H", "--host", dest="hostname", default=None, help="Act on this node")
230     parser.add_option("-s", "--slice", dest="slicename", default=None, help="Act on this site")
231
232     (opt, args) = parser.parse_args()
233     opt.expires = parse_expire_str(opt.expires)
234
235     if len(args) == 0:
236         usage(parser)
237         sys.exit(1)
238
239     target = args[0]; 
240     api = get_plc_api(target, opt.url, opt.username, opt.password, unparse_expire_str(opt.expires), opt.debug)
241
242     action_list = ['enabled', 'exempt', 'removeall', 'exemptall', 'enableall', 'disableall']
243
244     for i,action in enumerate(args[1:]):
245         if action in action_list:
246             if len(args) > i+2 and args[i+2] not in action_list:
247                 objname = args[i+2]
248             else:
249                 objname = None
250
251         if action == "enabled":
252
253             if not opt.list and not opt.hostname and not opt.slicename and not opt.login_base:
254                 opt.list = True
255             if opt.list:
256                 print "Listing only *disabled* objects"
257                 sites = api.GetSites({'peer_id' : None, 'enabled': False})
258                 nodes = api.GetNodes({'peer_id' : None, 'boot_state' : 'disabled'})
259                 slices= api.GetSliceTags({'tagname' : 'enabled'})
260
261                 for (header,objlist) in [("Sites:",sites), ("Nodes:", nodes), ("Slices:", slices)]:
262                     if len(objlist) > 0: print header
263                     for t in objlist:
264                         o = PlcObj(t)
265                         o.list(target, action)
266
267         if action == "exempt":
268             if not opt.list and not opt.remove and opt.expires == None:
269                 opt.list = True
270
271             if opt.list:
272                 if objname == None:
273                     # NOTE: this works around a bug as of 2011/12/23 that
274                     #    deleted sites do not also delete all associated site tags.
275                     site_lb = [ l['login_base'] for l in api.GetSites({'peer_id' : None}, ['login_base']) ]
276                     sites = api.GetSiteTags({'tagname' : 'exempt_site_until'})
277                     sites = filter(lambda x: x['login_base'] in site_lb, sites)
278
279                     nodes = api.GetNodeTags({'tagname' : 'exempt_node_until'})
280                     slices = api.GetSliceTags({'tagname' : 'exempt_slice_until'})
281                 else:
282                     try: sites = api.GetSiteTags({'login_base': objname, 'tagname' : 'exempt_site_until'})
283                     except: sites = []
284                     try: nodes = api.GetNodeTags({'hostname' : objname, 'tagname' : 'exempt_node_until'})
285                     except: nodes = []
286                     try: slices = api.GetSliceTags({'name' : objname, 'tagname' : 'exempt_slice_until'})
287                     except: slices = []
288
289                 for (header,objlist) in [("Sites:",sites), ("Nodes:", nodes), ("Slices:", slices)]:
290                     if len(objlist) > 0: print header
291                     for t in objlist:
292                         o = PlcObj(t)
293                         o.list(target, action, t['value'])
294
295             if opt.remove or opt.expires:
296                 obj = PlcObj(objname)
297                 # if opt.expires == None, the exemption will be removed.
298                 obj.exempt(api,opt.expires)
299
300         if action == "disableall":
301             if objname == None: raise Exception("Provide a site name to disable")
302             #  disable site, disable slices,
303             try:
304                 slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
305             except:
306                 slices = []
307             obj = PlcObj(objname)
308             obj.enable(api,False)
309             for sl in slices:
310                 obj = PlcObj(sl['name'])
311                 obj.enable(api,False)
312                 
313         if action == "enableall":
314             #  enable site, enable slices,
315             if objname == None: raise Exception("Provide a site name to enableall")
316             try:
317                 slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
318             except:
319                 slices = []
320             obj = PlcObj(objname)
321             obj.enable(api,True)
322             for sl in slices:
323                 obj = PlcObj(sl['name'])
324                 obj.enable(api,True)
325
326         if action == "removeall":
327             #  remove enable site, enable slices,
328             if objname == None: raise Exception("Provide a site name to remove")
329             try:
330                 slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
331             except:
332                 slices = []
333             obj = PlcObj(objname)
334             obj.exempt(api,None)
335             for sl in slices:
336                 obj = PlcObj(sl['name'])
337                 obj.exempt(api,None)
338
339         if action == "exemptall":
340             if objname == None: raise Exception("Provide a site name to exempt")
341             try:
342                 slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
343             except:
344                 slices = []
345             obj = PlcObj(objname)
346             obj.exempt(api,opt.expires)
347             for sl in slices:
348                 obj = PlcObj(sl['name'])
349                 obj.exempt(api,opt.expires)
350
351 if __name__ == '__main__':
352     main()