moving towards reservable nodes
[nodemanager.git] / api_calls.py
index 6d3377b..a175391 100644 (file)
@@ -1,3 +1,6 @@
+# $Id$
+# $URL$
+
 """Sliver manager API.
 
 This module exposes an XMLRPC interface that allows PlanetLab users to
@@ -29,9 +32,9 @@ import accounts
 import logger
 
 # TODO: These try/excepts are a hack to allow doc/DocBookLocal.py to 
-# import this file in order to extrac the documentation from each 
-# exported function.  A better approach will involve more extensive code
-# splitting, I think.
+# 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
@@ -39,7 +42,7 @@ 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 = {}
@@ -97,7 +100,10 @@ def export_to_docbook(**kwargs):
 @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=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()')], 
@@ -109,14 +115,13 @@ def Ticket(ticket):
     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,
-    """
+    This ticket is the value retured by PLC's GetSliceTicket() API call."""
     try:
         data = ticket_module.verify(ticket)
         name = data['slivers'][0]['name']
         if data != None:
             deliver_ticket(data)
-        logger.log('Ticket delivered for %s' % name)
+        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))
@@ -126,14 +131,13 @@ def Ticket(ticket):
                    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]
         name = data['slivers'][0]['name']
         if data != None:
             deliver_ticket(data)
-        logger.log('Admin Ticket delivered for %s' % name)
+        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))
@@ -167,8 +171,11 @@ def GetSSHKeys():
 def Create(sliver_name):
     """Create a non-PLC-instantiated sliver"""
     rec = sliver_name
-    if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_created(rec)
-    else: raise Exception, "Only PLC can create non delegated slivers."
+    if rec['instantiation'] == 'delegated': 
+        accounts.get(rec['name']).ensure_created(rec)
+        logger.log("api_calls: Create %s"%rec['name'])
+    else: 
+        raise Exception, "Only PLC can create non delegated slivers."
 
 
 @export_to_docbook(roles=['nm-controller', 'self'], 
@@ -178,8 +185,11 @@ def Create(sliver_name):
 def Destroy(sliver_name):
     """Destroy a non-PLC-instantiated sliver"""
     rec = sliver_name 
-    if rec['instantiation'] == 'delegated': accounts.get(rec['name']).ensure_destroyed()
-    else: raise Exception, "Only PLC can destroy non delegated slivers."
+    if rec['instantiation'] == 'delegated': 
+        accounts.get(rec['name']).ensure_destroyed()
+        logger.log("api_calls: Destroy %s"%rec['name'])
+    else: 
+        raise Exception, "Only PLC can destroy non delegated slivers."
 
 
 @export_to_docbook(roles=['nm-controller', 'self'], 
@@ -190,6 +200,7 @@ def Start(sliver_name):
     """Configure and start sliver."""
     rec = sliver_name
     accounts.get(rec['name']).start(rec)
+    logger.log("api_calls: Start %s"%rec['name'])
 
 
 @export_to_docbook(roles=['nm-controller', 'self'], 
@@ -200,6 +211,7 @@ def Stop(sliver_name):
     """Kill all processes belonging to the specified sliver"""
     rec = sliver_name
     accounts.get(rec['name']).stop()
+    logger.log("api_calls: Stop %s"%rec['name'])
 
 
 @export_to_docbook(roles=['nm-controller', 'self'], 
@@ -212,7 +224,7 @@ def ReCreate(sliver_name):
     accounts.get(rec['name']).stop()
     accounts.get(rec['name']).ensure_created(rec)
     accounts.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.')], 
@@ -246,17 +258,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)
+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'))
+                   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.
@@ -265,9 +280,17 @@ 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 xmlrpclib.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_api(0)
+def GetRecord(sliver_name):
+    """Return sliver record"""
+    rec = sliver_name
+    return rec