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