svn kwds
[nodemanager.git] / api.py
1 #
2 """Sliver manager API.
3
4 This module exposes an XMLRPC interface that allows PlanetLab users to
5 create/destroy slivers with delegated instantiation, start and stop
6 slivers, make resource loans, and examine resource allocations.  The
7 XMLRPC is provided on a localhost-only TCP port as well as via a Unix
8 domain socket that is accessible by ssh-ing into a delegate account
9 with the forward_api_calls shell.
10 """
11
12 import SimpleXMLRPCServer
13 import SocketServer
14 import errno
15 import os
16 import pwd
17 import socket
18 import struct
19 import threading
20 import xmlrpclib
21 import sys
22
23 import accounts
24 import database
25 import sliver_vs
26 import ticket
27 import tools
28 from api_calls import *
29 import logger
30
31 try:
32     sys.path.append("/etc/planetlab")
33     from plc_config import *
34 except:
35     logger.log("api:  Warning: Configuration file /etc/planetlab/plc_config.py not found", 2)
36     PLC_SLICE_PREFIX="pl"
37     logger.log("api:  Warning: admin slice prefix set to %s" %(PLC_SLICE_PREFIX), 2)
38
39 API_SERVER_PORT = 812
40 UNIX_ADDR = '/tmp/nodemanager.api'
41
42 class APIRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
43     # overriding _dispatch to achieve this effect is officially deprecated,
44     # but I can't figure out how to get access to .request without
45     # duplicating SimpleXMLRPCServer code here, which is more likely to
46     # change than the deprecated behavior is to be broken
47
48     @database.synchronized
49     def _dispatch(self, method_name_unicode, args):
50         method_name = str(method_name_unicode)
51         try: method = api_method_dict[method_name]
52         except KeyError:
53             api_method_list = api_method_dict.keys()
54             api_method_list.sort()
55             raise xmlrpclib.Fault(100, 'Invalid API method %s.  Valid choices are %s' % \
56                 (method_name, ', '.join(api_method_list)))
57         expected_nargs = nargs_dict[method_name]
58         if len(args) != expected_nargs:
59             raise xmlrpclib.Fault(101, 'Invalid argument count: got %d, expecting %d.' % \
60                 (len(args), expected_nargs))
61         else:
62             # Figure out who's calling.
63             # XXX - these ought to be imported directly from some .h file
64             SO_PEERCRED = 17
65             sizeof_struct_ucred = 12
66             ucred = self.request.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, sizeof_struct_ucred)
67             xid = struct.unpack('3i', ucred)[1]
68             caller_name = pwd.getpwuid(xid)[0]
69             # Special case : the sfa component manager
70             if caller_name == PLC_SLICE_PREFIX+"_sfacm":
71                 try: result = method(*args)
72                 except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
73             # Anyone can call these functions
74             elif method_name in ('Help', 'Ticket', 'GetXIDs', 'GetSSHKeys'):
75                 try: result = method(*args)
76                 except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
77             else: # Execute anonymous call.
78                 # Authenticate the caller if not in the above fncts.
79                 if method_name == "GetRecord":
80                     target_name = caller_name
81                 else:
82                     target_name = args[0]
83
84                 # Gather target slice's object.
85                 target_rec = database.db.get(target_name)
86
87                 # only work on slivers or self. Sanity check.
88                 if not (target_rec and target_rec['type'].startswith('sliver.')):
89                     raise xmlrpclib.Fault(102, \
90                         'Invalid argument: the first argument must be a sliver name.')
91
92                 # only manipulate slivers who delegate you authority
93                 if caller_name in (target_name, target_rec['delegations']):
94                     try: result = method(target_rec, *args[1:])
95                     except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
96                 else:
97                     raise xmlrpclib.Fault(108, '%s: Permission denied.' % caller_name)
98             if result == None: result = 1
99             return result
100
101 class APIServer_INET(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): allow_reuse_address = True
102
103 class APIServer_UNIX(APIServer_INET): address_family = socket.AF_UNIX
104
105 def start():
106     """Start two XMLRPC interfaces: one bound to localhost, the other bound to a Unix domain socket."""
107     logger.log('api.start')
108     serv1 = APIServer_INET(('127.0.0.1', API_SERVER_PORT), requestHandler=APIRequestHandler, logRequests=0)
109     tools.as_daemon_thread(serv1.serve_forever)
110     try: os.unlink(UNIX_ADDR)
111     except OSError, e:
112         if e.errno != errno.ENOENT: raise
113     serv2 = APIServer_UNIX(UNIX_ADDR, requestHandler=APIRequestHandler, logRequests=0)
114     tools.as_daemon_thread(serv2.serve_forever)
115     os.chmod(UNIX_ADDR, 0666)