reguire gnupg1 on f>=31; sense the system to use gpg1 when installed
[nodemanager.git] / api_calls.py
index 5b99a08..e39032d 100644 (file)
@@ -1,6 +1,4 @@
-# $Id$
-# $URL$
-
+#
 """Sliver manager API.
 
 This module exposes an XMLRPC interface that allows PlanetLab users to
@@ -11,15 +9,16 @@ domain socket that is accessible by ssh-ing into a delegate account
 with the forward_api_calls shell.
 """
 
-import SimpleXMLRPCServer
-import SocketServer
+import xmlrpc.server
+import socketserver
 import errno
 import os
 import pwd
 import socket
 import struct
 import threading
-import xmlrpclib
+import xmlrpc.client
+import slivermanager
 
 try:
     from PLC.Parameter import Parameter, Mixed
@@ -28,21 +27,19 @@ except:
     def Mixed(a = None, b = None, c = None): pass
 
 
-import accounts
+import account
 import logger
 
-# TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to 
-# import this file in order to extract the documentation from each 
-# exported function.  A better approach will involve more extensive code
-# splitting, I think.
+# TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to
+# import this file in order to extract the documentation from each
+# exported function.
+# A better approach will involve more extensive code splitting, I think.
 try: import database
 except: import logger as database
-try: import sliver_vs
-except: import logger as sliver_vs
 import ticket as ticket_module
 import tools
 
-deliver_ticket = None  # set in sm.py:start()
+deliver_ticket = None  # set in slivermanager.start()
 
 api_method_dict = {}
 nargs_dict = {}
@@ -68,8 +65,8 @@ def export_to_docbook(**kwargs):
     def export(method):
         def args():
             # Inspect method. Remove self from the argument list.
-            max_args = method.func_code.co_varnames[0:method.func_code.co_argcount]
-            defaults = method.func_defaults
+            max_args = method.__code__.co_varnames[0:method.__code__.co_argcount]
+            defaults = method.__defaults__
             if defaults is None:
                 defaults = ()
             min_args = max_args[0:len(max_args) - len(defaults)]
@@ -94,26 +91,28 @@ def export_to_docbook(**kwargs):
 # accepts,
 # returns
 
-@export_to_docbook(roles=['self'], 
-                   accepts=[], 
+@export_to_docbook(roles=['self'],
+                   accepts=[],
                    returns=Parameter([], 'A list of supported functions'))
 @export_to_api(0)
 def Help():
     """Get a list of functions currently supported by the Node Manager API"""
-    return ''.join([method.__doc__ + '\n' for method in api_method_dict.itervalues()])
+    names=list(api_method_dict.keys())
+    names.sort()
+    return ''.join(['**** ' + api_method_dict[name].__name__ + '\n' + api_method_dict[name].__doc__ + '\n'
+                    for name in names])
 
-@export_to_docbook(roles=['self'], 
-                   accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')], 
+@export_to_docbook(roles=['self'],
+                   accepts=[Parameter(str, 'A ticket returned from GetSliceTicket()')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def Ticket(ticket):
     """The Node Manager periodically polls the PLC API for a list of all
-    slices that are allowed to exist on the given node. Before 
+    slices that are allowed to exist on the given node. Before
     actions are performed on a delegated slice (such as creation),
-    a controller slice must deliver a valid slice ticket to NM. 
-    
-    This ticket is the value retured by PLC's GetSliceTicket() API call,
-    """
+    a controller slice must deliver a valid slice ticket to NM.
+
+    This ticket is the value retured by PLC's GetSliceTicket() API call."""
     try:
         data = ticket_module.verify(ticket)
         name = data['slivers'][0]['name']
@@ -121,112 +120,111 @@ def Ticket(ticket):
             deliver_ticket(data)
         logger.log('api_calls: Ticket delivered for %s' % name)
         Create(database.db.get(name))
-    except Exception, err:
-        raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
+    except Exception as err:
+        raise xmlrpc.client.Fault(102, 'Ticket error: ' + str(err))
 
-@export_to_docbook(roles=['self'], 
-                   accepts=[Parameter(str, 'A ticket returned from GetSlivers()')], 
+@export_to_docbook(roles=['self'],
+                   accepts=[Parameter(str, 'A ticket returned from GetSlivers()')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def AdminTicket(ticket):
-    """Admin interface to create slivers based on ticket returned by GetSlivers().
-    """
+    """Admin interface to create slivers based on ticket returned by GetSlivers()."""
     try:
-        data, = xmlrpclib.loads(ticket)[0]
+        data, = xmlrpc.client.loads(ticket)[0]
         name = data['slivers'][0]['name']
         if data != None:
             deliver_ticket(data)
         logger.log('api_calls: Admin Ticket delivered for %s' % name)
         Create(database.db.get(name))
-    except Exception, err:
-        raise xmlrpclib.Fault(102, 'Ticket error: ' + str(err))
+    except Exception as err:
+        raise xmlrpc.client.Fault(102, 'Ticket error: ' + str(err))
 
 
 @export_to_docbook(roles=['self'],
-                   accepts=[], 
+                   accepts=[],
                    returns={'sliver_name' : Parameter(int, 'the associated xid')})
 @export_to_api(0)
 def GetXIDs():
     """Return an dictionary mapping Slice names to XIDs"""
-    return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == sliver_vs.Sliver_VS.SHELL])
+    return dict([(pwent[0], pwent[2]) for pwent in pwd.getpwall() if pwent[6] == slivermanager.sliver_password_shell])
 
 @export_to_docbook(roles=['self'],
-                   accepts=[], 
+                   accepts=[],
                    returns={ 'sliver_name' : Parameter(str, 'the associated SSHKey')})
 @export_to_api(0)
 def GetSSHKeys():
     """Return an dictionary mapping slice names to SSH keys"""
     keydict = {}
-    for rec in database.db.itervalues():
+    for rec in database.db.values():
         if 'keys' in rec:
             keydict[rec['name']] = rec['keys']
     return keydict
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def Create(sliver_name):
     """Create a non-PLC-instantiated sliver"""
     rec = sliver_name
-    if rec['instantiation'] == 'delegated': 
-        accounts.get(rec['name']).ensure_created(rec)
+    if rec['instantiation'] == 'delegated':
+        account.get(rec['name']).ensure_created(rec)
         logger.log("api_calls: Create %s"%rec['name'])
-    else: 
-        raise Exception, "Only PLC can create non delegated slivers."
+    else:
+        raise Exception("Only PLC can create non delegated slivers.")
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def Destroy(sliver_name):
     """Destroy a non-PLC-instantiated sliver"""
-    rec = sliver_name 
-    if rec['instantiation'] == 'delegated': 
-        accounts.get(rec['name']).ensure_destroyed()
+    rec = sliver_name
+    if rec['instantiation'] == 'delegated':
+        account.get(rec['name']).ensure_destroyed()
         logger.log("api_calls: Destroy %s"%rec['name'])
-    else: 
-        raise Exception, "Only PLC can destroy non delegated slivers."
+    else:
+        raise Exception("Only PLC can destroy non delegated slivers.")
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def Start(sliver_name):
     """Configure and start sliver."""
     rec = sliver_name
-    accounts.get(rec['name']).start(rec)
+    account.get(rec['name']).start(rec)
     logger.log("api_calls: Start %s"%rec['name'])
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def Stop(sliver_name):
     """Kill all processes belonging to the specified sliver"""
     rec = sliver_name
-    accounts.get(rec['name']).stop()
+    account.get(rec['name']).stop()
     logger.log("api_calls: Stop %s"%rec['name'])
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                    returns=Parameter(int, '1 if successful'))
 @export_to_api(1)
 def ReCreate(sliver_name):
     """Stop, Destroy, Create, Start sliver in order to reinstall it."""
     rec = sliver_name
-    accounts.get(rec['name']).stop()
-    accounts.get(rec['name']).ensure_created(rec)
-    accounts.get(rec['name']).start(rec)
+    account.get(rec['name']).stop()
+    account.get(rec['name']).ensure_created(rec)
+    account.get(rec['name']).start(rec)
     logger.log("api_calls: ReCreate %s"%rec['name'])
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                    returns=Parameter(dict, "A resource specification"))
 @export_to_api(1)
 def GetEffectiveRSpec(sliver_name):
@@ -235,8 +233,8 @@ def GetEffectiveRSpec(sliver_name):
     return rec.get('_rspec', {}).copy()
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                     returns={"resource name" : Parameter(int, "amount")})
 @export_to_api(1)
 def GetRSpec(sliver_name):
@@ -245,8 +243,8 @@ def GetRSpec(sliver_name):
     return rec.get('rspec', {}).copy()
 
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                    accepts=[Parameter(str, 'A sliver/slice name.')], 
+@export_to_docbook(roles=['nm-controller', 'self'],
+                    accepts=[Parameter(str, 'A sliver/slice name.')],
                     returns=[Mixed(Parameter(str, 'recipient slice name'),
                              Parameter(str, 'resource name'),
                              Parameter(int, 'resource amount'))])
@@ -257,17 +255,20 @@ def GetLoans(sliver_name):
     rec = sliver_name
     return rec.get('_loans', [])[:]
 
-def validate_loans(obj):
-    """Check that <obj> is a valid loan specification."""
-    def validate_loan(obj): return (type(obj)==list or type(obj)==tuple) and len(obj)==3 and type(obj[0])==str and type(obj[1])==str and obj[1] in database.LOANABLE_RESOURCES and type(obj[2])==int and obj[2]>=0
-    return type(obj)==list and False not in map(validate_loan, obj)
-
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                accepts=[ Parameter(str, 'A sliver/slice name.'),
-                          [Mixed(Parameter(str, 'recipient slice name'),
-                           Parameter(str, 'resource name'),
-                           Parameter(int, 'resource amount'))] ],
-                returns=Parameter(int, '1 if successful'))
+def validate_loans(loans):
+    """Check that <obj> is a list of valid loan specifications."""
+    def validate_loan(loan):
+        return (type(loan)==list or type(loan)==tuple) and len(loan)==3 \
+            and type(loan[0])==str and type(loan[1])==str and loan[1] in database.LOANABLE_RESOURCES and type(loan[2])==int and loan[2]>=0
+    return type(loans)==list and False not in [validate_loan(load) for loan in loans]
+
+
+@export_to_docbook(roles=['nm-controller', 'self'],
+                   accepts=[ Parameter(str, 'A sliver/slice name.'),
+                             [Mixed(Parameter(str, 'recipient slice name'),
+                                    Parameter(str, 'resource name'),
+                                    Parameter(int, 'resource amount'))], ],
+                   returns=Parameter(int, '1 if successful'))
 @export_to_api(2)
 def SetLoans(sliver_name, loans):
     """Overwrite the list of loans made by the specified sliver.
@@ -276,15 +277,15 @@ def SetLoans(sliver_name, loans):
     RSpec is handed out, but it will silently discard those loans that would
     put it over capacity.  This behavior may be replaced with error semantics
     in the future.  As well, there is currently no asynchronous notification
-    of loss of resources.
-    """
+    of loss of resources."""
     rec = sliver_name
-    if not validate_loans(loans): raise xmlrpclib.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
+    if not validate_loans(loans):
+        raise xmlrpc.client.Fault(102, 'Invalid argument: the second argument must be a well-formed loan specification')
     rec['_loans'] = loans
     database.db.sync()
 
-@export_to_docbook(roles=['nm-controller', 'self'], 
-                         returns=Parameter(dict, 'Record dictionary'))
+@export_to_docbook(roles=['nm-controller', 'self'],
+                   returns=Parameter(dict, 'Record dictionary'))
 @export_to_api(0)
 def GetRecord(sliver_name):
     """Return sliver record"""