adding myops.py a CLI to add/remove exemptions to sites/nodes/slices
Stephen Soltesz [Fri, 23 Dec 2011 23:14:00 +0000 (23:14 +0000)]
commands/myops.py [new file with mode: 0755]

diff --git a/commands/myops.py b/commands/myops.py
new file mode 100755 (executable)
index 0000000..9f7b88b
--- /dev/null
@@ -0,0 +1,339 @@
+#!/usr/bin/python
+
+import os
+import sys
+import string
+import time
+
+import getopt
+import sys
+import os
+import xml, xmlrpclib
+from getpass import getpass
+from operator import attrgetter, itemgetter
+
+def get_plc_api(target, url, username, password, expires, debug_mode):
+    # Either read session from disk or create it and save it for later
+    metasession = "%s/%s_%s" % (os.environ['HOME'], ".metasession", target)
+    if os.path.exists(metasession):
+        (localurl,session) =  open(metasession, 'r').read().strip().split()
+        plc = xmlrpclib.Server(localurl, verbose=False, allow_none=True)
+    else:
+        plc = xmlrpclib.Server(url, verbose=False, allow_none=True)
+        if password == None: password = getpass()
+        auth = {'Username' : username, 
+                'AuthMethod' : 'password',
+                'AuthString' : password}
+        session = plc.GetSession(auth, expires*(60*60*24))
+        with open(metasession, 'w') as f: f.write("%s %s\n" % (url,session)) # 'with' auto-closes
+
+    auth = {'AuthMethod' : 'session', 'session' : session}
+
+    class PLC:
+        def __init__(self, plc, auth):
+            self.plc = plc
+            self.auth = auth
+
+        def __getattr__(self, name):
+            method = getattr(self.plc, name)
+            if method is None:
+                raise AssertionError("Method does not exist: %s" % method)
+            if not debug_mode or ('Get' in name or 'AuthCheck' in name):
+                return lambda *params : method(self.auth, *params)
+            else:
+                def call(name,*params):
+                    print "DEBUG not running: %s(%s)" % (name, params)
+                return lambda *params : call(name,*params)
+
+    plc_api = PLC(plc, auth)
+    try:
+        # make sure the session is working
+        plc_api.AuthCheck()
+    except:
+        # everything worked except the auth check. try again asking for passwd.
+        plc_api = get_plc_api(target, url, username, None, expires, debug_mode)
+
+    return plc_api
+
+def usage(parser):
+    print """
+myops.py <TARGET> <ACTION> [<object>] [args]
+    MYOPS CLI uses sessions to avoid storing passwords.
+    You choose the session expiration via --expires days.
+
+TARGET:
+    When your session is saved it is identified by your given 'target'
+    name.  This is a unique string you chose to identify the --url. 
+    For example, one might use:
+        plc
+        vicci
+        test
+        vini
+
+ACTION:
+    Connect to TARGET and perform ACTION. The current actions are:
+        enabled   --  Manage site, node, and slice 'enabled' states.
+        
+        exempt    --  Manage site, node, and slice exemptions from 
+                     myops policy actions.
+
+        removeall --  Remove all exemptions at site from site, nodes, slices 
+        addall    --  Add exemptions at site to site, nodes, slices 
+
+        freeze    --  Clamp down everything at a site: 
+                        disable site, 
+                        disable slices,
+
+        release   --  Release everything at a site: 
+                        re-enable site, 
+                        re-enable slices,
+EXAMPLES:
+    # setup session and save target name 'plc' for future calls
+    myops.py plc --apiurl https://boot.planet-lab.org/PLCAPI/ \\
+                --username soltesz@cs.princeton.edu
+
+    # list current exemptions at plc
+    myops.py plc exempt
+
+    # to list only one site (nothing will show if no exemption is present)
+    myops.py plc exempt princeton
+
+    # add a new exemption to site 'princeton' for a day
+    myops.py plc exempt princeton -a
+
+    # add a new exemption to a specific date
+    myops.py plc exempt princeton -a --expires 20120131
+
+""" 
+    parser.print_help()
+
+def unparse_expire_str(value):
+    expires = time.mktime(time.strptime(value, "%Y%m%d")) - time.time()
+    return int(expires)
+
+def parse_expire_str(value):
+    import optparse
+    if value == "0":
+        value = "20990101"
+    elif len(value) <= 3:
+        # days from now
+        value = time.strftime("%Y%m%d", time.localtime(time.time()+int(value)*60*60*24))
+    elif len(value) != 8 and value[:3] != "201": # 201 == this decade.
+        # flip out
+        raise optparse.OptionValueError
+    return value
+
+class PlcObj(object):
+    def __init__(self, name):
+        if type(name) == type(""):
+            self.name = name
+        elif type(name) == type({}):
+            if 'login_base' in name:
+                self.name = name['login_base']
+            elif 'hostname' in name:
+                self.name = name['hostname']
+            elif 'name' in name:
+                self.name = name['name']
+
+        self.kind = None
+        if '_' in self.name: 
+            kind = 'Slice'
+        elif '.' in self.name: 
+            kind='Node'
+        else: 
+            kind='Site'
+        self.kind = kind
+
+    def list(self,target,action,*vals):
+        if action == "enabled":
+            print ("\t%s %s %s" % (sys.argv[0],target,action)) + (" %-20s --disable" % self.name)
+        elif action == "exempt":
+            print ("\t%s %s %s" % (sys.argv[0],target,action)) + (" %-20s -a --expires %s" % ((self.name,)+ vals))
+
+    def enable(self,api,state):
+        if self.kind == 'Slice':
+            # change value of existing slice tag, if it exists.
+            tl = api.GetSliceTags({'name' : self.name, 'tagname' : 'enabled', 'value' : '0' if state else '1'})
+            if len(tl) == 0:
+                api.AddSliceTag(self.name, 'enabled', '1' if state else '0')
+            else:
+                for t in tl: 
+                    api.UpdateSliceTag(t['slice_tag_id'], {'value' : '1' if state else '0'})
+        elif self.kind == 'Node':
+            if state == True: 
+                api.UpdateNode(self.name, {'boot_state' : 'boot'})
+            else: 
+                api.UpdateNode(self.name, {'boot_state' : 'disabled'})
+        elif self.kind == 'Site':
+            api.UpdateSite(self.name, {'enabled' : state})
+            
+    def exempt(self,api,expires):
+        if expires != None:
+            if self.kind == 'Slice': 
+                try: api.AddSliceTag(self.name, 'exempt_slice_until', expires)
+                except: api.UpdateSliceTag(self.name, expires)
+            elif self.kind == 'Node': 
+                try: api.AddNodeTag(self.name, 'exempt_node_until', expires)
+                except: api.UpdateNodeTag(self.name, expires)
+            elif self.kind == 'Site': 
+                try: api.AddSiteTag(api.GetSites(self.name, ['site_id'])[0]['site_id'], 'exempt_site_until', expires)
+                except: api.UpdateSiteTag(api.GetSiteTags({'login_base' : self.name, 'tagname' : 'exempt_site_until'})[0]['site_tag_id'], expires)
+        else:
+            # remove
+            if self.kind == 'Slice': 
+                tag_id_l = api.GetSliceTags({'name' : self.name, 'tagname' : 'exempt_slice_until'}, ['slice_tag_id'])
+                if len(tag_id_l) > 0:   
+                    tag_id = tag_id_l[0]['slice_tag_id']
+                    api.DeleteSliceTag(tag_id)
+            elif self.kind == 'Node': 
+                tag_id_l = api.GetNodeTags({'hostname' : self.name, 'tagname' : 'exempt_node_until'}, ['node_tag_id'])
+                if len(tag_id_l) > 0: 
+                    tag_id = tag_id_l[0]['node_tag_id']
+                    api.DeleteNodeTag(tag_id)
+            elif self.kind == 'Site': 
+                tag_id_l = api.GetSiteTags({'login_base' : self.name, 'tagname' : 'exempt_site_until'}, ['site_tag_id'])
+                if len(tag_id_l) > 0: 
+                    tag_id = tag_id_l[0]['site_tag_id']
+                    api.DeleteSiteTag(tag_id)
+
+
+def main():
+    from optparse import OptionParser
+    copy = False
+    parser = OptionParser()
+
+    parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False, help="")
+    parser.add_option("-v", "--verbose", dest="verbose", default=False, help="")
+    parser.add_option("-u", "--apiurl", dest="url", default="https://www.planet-lab.org/PLCAPI/", help="Set PLC URL for action")
+    parser.add_option("-U", "--username", dest="username", default=None, help="Login as username")
+    parser.add_option("-P", "--password", dest="password", default=None, help="Use provided password; otherwise prompt for password")
+    parser.add_option("-e", "--expires", dest="expires", default="1", help="Set expiration date YYYYMMDD; default is 1 day from now.")
+    parser.add_option("", "--disable", dest="disable", default=False, action="store_true", help="Disable object.")
+    parser.add_option("-r", "--remove", dest="remove", action="store_true", default=False, help="Remove object from exemption" )
+    parser.add_option("-l", "--list", dest="list", action="store_true", default=False, help="List objects with command used to generate them")
+    parser.add_option("-a", "--add", dest="add", action="store_true", default=False, help="Add exempt object")
+    parser.add_option("-S", "--site", dest="login_base", default=None, help="Act on this site")
+    parser.add_option("-H", "--host", dest="hostname", default=None, help="Act on this node")
+    parser.add_option("-s", "--slice", dest="slicename", default=None, help="Act on this site")
+
+    (opt, args) = parser.parse_args()
+    opt.expires = parse_expire_str(opt.expires)
+
+    if len(args) == 0:
+        usage(parser)
+        sys.exit(1)
+
+    target = args[0]; 
+    api = get_plc_api(target, opt.url, opt.username, opt.password, unparse_expire_str(opt.expires), opt.debug)
+
+    action_list = ['enabled', 'exempt', 'removeall', 'addall', 'release', 'freeze']
+
+    for i,action in enumerate(args[1:]):
+        if action in action_list:
+            if len(args) > i+2 and args[i+2] not in action_list:
+                objname = args[i+2]
+            else:
+                objname = None
+
+        if action == "enabled":
+
+            if not opt.list and not opt.hostname and not opt.slicename and not opt.login_base:
+                opt.list = True
+            if opt.list:
+                print "Listing only *disabled* objects"
+                sites = api.GetSites({'peer_id' : None, 'enabled': False})
+                nodes = api.GetNodes({'peer_id' : None, 'boot_state' : 'disabled'})
+                slices= api.GetSliceTags({'tagname' : 'enabled'})
+
+                for (header,objlist) in [("Sites:",sites), ("Nodes:", nodes), ("Slices:", slices)]:
+                    if len(objlist) > 0: print header
+                    for t in objlist:
+                        o = PlcObj(t)
+                        o.list(target, action)
+
+        if action == "exempt":
+            if not opt.list and not opt.remove and not opt.add:
+                opt.list = True
+
+            if opt.list:
+                if objname == None:
+                    sites = api.GetSiteTags({'tagname' : 'exempt_site_until'})
+                    nodes = api.GetNodeTags({'tagname' : 'exempt_node_until'})
+                    slices = api.GetSliceTags({'tagname' : 'exempt_slice_until'})
+                else:
+                    try: sites = api.GetSiteTags({'login_base': objname, 'tagname' : 'exempt_site_until'})
+                    except: sites = []
+                    try: nodes = api.GetNodeTags({'hostname' : objname, 'tagname' : 'exempt_node_until'})
+                    except: nodes = []
+                    try: slices = api.GetSliceTags({'name' : objname, 'tagname' : 'exempt_slice_until'})
+                    except: slices = []
+
+                for (header,objlist) in [("Sites:",sites), ("Nodes:", nodes), ("Slices:", slices)]:
+                    if len(objlist) > 0: print header
+                    for t in objlist:
+                        o = PlcObj(t)
+                        o.list(target, action, t['value'])
+
+            if opt.remove:
+                if objname == None: raise Exception("provide an object name to remove exemption")
+                obj = PlcObj(objname)
+                obj.exempt(api,None)
+
+            if opt.add:
+                if objname == None: raise Exception("provide an object name to add exemption")
+                obj = PlcObj(objname)
+                obj.exempt(api,opt.expires)
+
+        if action == "freeze":
+            if objname == None: raise Exception("Provide a site name to freeze")
+            #  disable site, disable slices,
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.enable(api,False)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.enable(api,False)
+                
+        if action == "release":
+            #  enable site, enable slices,
+            if objname == None: raise Exception("Provide a site name to release")
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.enable(api,True)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.enable(api,True)
+
+        if action == "removeall":
+            #  remove enable site, enable slices,
+            if objname == None: raise Exception("Provide a site name to release")
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.exempt(api,None)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.exempt(api,None)
+
+        if action == "addall":
+            if objname == None: raise Exception("Provide a site name to release")
+            try:
+                slices = api.GetSlices(api.GetSites(objname, ['slice_ids'])[0]['slice_ids'])
+            except:
+                slices = []
+            obj = PlcObj(objname)
+            obj.exempt(api,opt.expires)
+            for sl in slices:
+                obj = PlcObj(sl['name'])
+                obj.exempt(api,opt.expires)
+
+if __name__ == '__main__':
+    main()