svn:keywords
[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 logger
28 import sliver_vs
29 import ticket
30 import tools
31 from api_calls import *
32 try:
33     sys.path.append("/etc/planetlab")
34     from plc_config import *
35 except:
36     logger.log("api:  Warning: Configuration file /etc/planetlab/plc_config.py not found", 2)
37     PLC_SLICE_PREFIX="pl"
38     logger.log("api:  Warning: admin slice prefix set to %s" %(PLC_SLICE_PREFIX), 2)
39
40 API_SERVER_PORT = 812
41 UNIX_ADDR = '/tmp/sliver_mgr.api'
42
43 class APIRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
44     # overriding _dispatch to achieve this effect is officially deprecated,
45     # but I can't figure out how to get access to .request without
46     # duplicating SimpleXMLRPCServer code here, which is more likely to
47     # change than the deprecated behavior is to be broken
48
49     @database.synchronized
50     def _dispatch(self, method_name_unicode, args):
51         method_name = str(method_name_unicode)
52         try: method = api_method_dict[method_name]
53         except KeyError:
54             api_method_list = api_method_dict.keys()
55             api_method_list.sort()
56             raise xmlrpclib.Fault(100, 'Invalid API method %s.  Valid choices are %s' % \
57                 (method_name, ', '.join(api_method_list)))
58         expected_nargs = nargs_dict[method_name]
59         if len(args) != expected_nargs: 
60             raise xmlrpclib.Fault(101, 'Invalid argument count: got %d, expecting %d.' % \
61                 (len(args), expected_nargs))
62         else:
63             # Figure out who's calling.
64             # XXX - these ought to be imported directly from some .h file
65             SO_PEERCRED = 17
66             sizeof_struct_ucred = 12
67             ucred = self.request.getsockopt(socket.SOL_SOCKET, SO_PEERCRED, sizeof_struct_ucred)
68             xid = struct.unpack('3i', ucred)[1]
69             caller_name = pwd.getpwuid(xid)[0]
70             # Special case the genicw
71             if caller_name == PLC_SLICE_PREFIX+"_sfacm":
72                 try: result = method(*args)
73                 except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
74             # Anyone can call these functions
75             elif method_name not in ('Help', 'Ticket', 'GetXIDs', 'GetSSHKeys'):
76                 # Authenticate the caller if not in the above fncts.
77                 if method_name == "GetRecord":
78                     target_name = caller_name
79                 else:
80                     target_name = args[0]
81
82                 # Gather target slice's object.
83                 target_rec = database.db.get(target_name)
84
85                 # only work on slivers or self.  Sannity check.
86                 if not (target_rec and target_rec['type'].startswith('sliver.')): 
87                     raise xmlrpclib.Fault(102, \
88                         'Invalid argument: the first argument must be a sliver name.')
89
90                 # only manipulate slivers who delegate you authority 
91                 if caller_name in (target_name, target_rec['delegations']):
92                     try: result = method(target_rec, *args[1:])
93                     except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
94                 else:
95                     raise xmlrpclib.Fault(108, '%s: Permission denied.' % caller_name)
96             else: # Execute anonymous call. 
97                 try: result = method(*args)
98                 except Exception, err: raise xmlrpclib.Fault(104, 'Error in call: %s' %err)
99             if result == None: result = 1
100             return result
101
102 class APIServer_INET(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): allow_reuse_address = True
103
104 class APIServer_UNIX(APIServer_INET): address_family = socket.AF_UNIX
105
106 def start():
107     """Start two XMLRPC interfaces: one bound to localhost, the other bound to a Unix domain socket."""
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)