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