source: fedd/fedd_allocate_project.py @ 9c166cf

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 9c166cf was 0ea11af, checked in by Ted Faber <faber@…>, 16 years ago

clean up and add some docs

  • Property mode set to 100644
File size: 7.6 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4
5from BaseHTTPServer import BaseHTTPRequestHandler
6from ZSI import *
7from M2Crypto import SSL
8from M2Crypto.m2xmlrpclib import SSL_Transport
9from M2Crypto.SSL.SSLServer import SSLServer
10import M2Crypto.httpslib
11import xmlrpclib
12
13import re
14import random
15import string
16import subprocess
17import tempfile
18
19from fedd_services import *
20from fedd_internal_services import *
21from fedd_util import *
22import parse_detail
23from service_error import *
24import logging
25
26
27# Configure loggers to dump to /dev/null which avoids errors if calling classes
28# don't configure them.
29class nullHandler(logging.Handler):
30    def emit(self, record): pass
31
32fl = logging.getLogger("fedd.allocate.local")
33fl.addHandler(nullHandler())
34fl = logging.getLogger("fedd.allocate.remote")
35fl.addHandler(nullHandler())
36
37class fedd_allocate_project_local:
38    """
39    Allocate projects on this machine in response to an access request.
40    """
41    def __init__(self, dp=False, url=None, certs=None):
42        """
43        Initializer.  Parses a configuration if one is given.
44        """
45
46        self.dynamic_projects = dp
47        self.wap = '/usr/testbed/sbin/wap'
48        self.newproj = '/usr/testbed/sbin/newproj'
49        self.mkproj = '/usr/testbed/sbin/mkproj'
50        self.grantnodetype = '/usr/testbed/sbin/grantnodetype'
51        self.log = logging.getLogger("fedd.allocate.local")
52
53        # Internal services are SOAP only
54        self.soap_services = {\
55                "AllocateProject": make_soap_handler(\
56                AllocateProjectRequestMessage.typecode,\
57                self.dynamic_project, AllocateProjectResponseMessage,\
58                "AllocateProjectResponseBody")\
59                }
60        self.xmlrpc_services = { }
61
62    def random_string(self, s, n=3):
63        """Append n random ASCII characters to s and return the string"""
64        rv = s
65        for i in range(0,n):
66            rv += random.choice(string.ascii_letters)
67        return rv
68
69    def write_attr_xml(self, file, root, lines):
70        """
71        Write an emulab config file for a dynamic project.
72
73        Format is <root><attribute name=lines[0]>lines[1]</attribute></root>
74        """
75        # Convert a pair to an attribute line
76        out_attr = lambda a,v : \
77                '<attribute name="%s"><value>%s</value></attribute>' % (a, v)
78
79        f = os.fdopen(file, "w")
80        f.write("<%s>\n" % root)
81        f.write("\n".join([out_attr(*l) for l in lines]))
82        f.write("</%s>\n" % root)
83        f.close()
84
85
86    def dynamic_project(self, req, fedid=None):
87        """
88        Create a dynamic project with ssh access
89
90        Req includes the project and resources as a dictionary
91        """
92        # tempfiles for the parameter files
93        uf, userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
94                dir="/tmp")
95        pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
96                dir="/tmp")
97
98        if req.has_key('AllocateProjectRequestBody') and \
99                req['AllocateProjectRequestBody'].has_key('project'):
100            proj = req['AllocateProjectRequestBody']['project']
101        else:
102            raise service_error(service_error.req, 
103                    "Badly formed allocation request")
104        # Take the first user and ssh key
105        name = proj.get('name', None) or self.random_string("proj",4)
106        user = proj.get('user', None)
107        if user != None:
108            user = user[0]      # User is a list, take the first entry
109            if not user.has_key("userID"):
110                uname = self.random_string("user", 3)
111            else:
112                uid = proj['userID']
113                # XXX: fedid
114                uname = uid.get('localname', None) or \
115                        uid.get('kerberosUsername', None) or \
116                        uid.get('uri', None)
117                if uname == None:
118                    raise fedd_proj.service_error(fedd_proj.service_error.req, 
119                            "No ID for user");
120
121            access = user.get('access', None)
122            if access != None:
123                ssh = access[0].get('sshPubkey', None)
124                if ssh == None:
125                    raise fedd_proj.service_error(fedd_proj.service_error.req, 
126                            "No ssh key for user");
127        else:
128            raise fedd_proj.service_error(fedd_proj.service_error.req, 
129                    "No access information for project");
130
131        # uname, name and ssh are set
132        user_fields = [
133                ("name", "Federation User %s" % uname),
134                ("email", "%s-fed@isi.deterlab.net" % uname),
135                ("password", self.random_string("", 8)),
136                ("login", uname),
137                ("address", "4676 Admiralty"),
138                ("city", "Marina del Rey"),
139                ("state", "CA"),
140                ("zip", "90292"),
141                ("country", "USA"),
142                ("phone", "310-448-9190"),
143                ("title", "None"),
144                ("affiliation", "USC/ISI"),
145                ("pubkey", ssh)
146        ]
147
148        proj_fields = [
149                ("name", name),
150                ("short description", "dynamic federated project"),
151                ("URL", "http://www.isi.edu/~faber"),
152                ("funders", "USC/USU"),
153                ("long description", "Federation access control"),
154                ("public", "1"),
155                ("num_pcs", "100"),
156                ("linkedtous", "1"),
157                ("newuser_xml", userfile)
158        ]
159       
160
161        # Write out the files
162        self.write_attr_xml(uf, "user", user_fields)
163        self.write_attr_xml(pf, "project", proj_fields)
164
165        # Generate the commands (only grantnodetype's are dynamic)
166        cmds = [
167                (self.wap, self.newproj, projfile),
168                (self.wap, self.mkproj, name)
169                ]
170
171        # Add commands to grant access to any resources in the request.
172        for nt in [ h for r in req.get('resources', []) \
173                if r.has_key('node') and r['node'].has_key('hardware')\
174                    for h in r['node']['hardware'] ] :
175            cmds.append((self.wap, self.grantnodetype, '-p', name, nt))
176
177        # Create the projects
178        rc = 0
179        for cmd in cmds:
180            self.log.debug("[dynamic_project]: %s" % ' '.join(cmd))
181            if self.dynamic_projects:
182                try:
183                    rc = subprocess.call(cmd)
184                except OSerror, e:
185                    raise fedd_proj.service_error(\
186                            fedd_proj.service_error.internal,
187                            "Dynamic project subprocess creation error "+ \
188                                    "[%s] (%s)" %  (cmd[1], e.strerror))
189
190            if rc != 0: 
191                raise fedd_proj.service_error(\
192                        fedd_proj.service_error.internal,
193                        "Dynamic project subprocess error " +\
194                                "[%s] (%d)" % (cmd[1], rc))
195        # Clean up tempfiles
196        os.unlink(userfile)
197        os.unlink(projfile)
198        rv = {\
199            'project': {\
200                'name': { 'localname': name }, 
201                'user' : [ {\
202                    'userID': { 'localname' : uname },
203                    'access': [ { 'sshPubkey' : ssh } ],
204                } ]\
205            }\
206        }
207        return rv
208
209
210class fedd_allocate_project_remote:
211    """
212    Allocate projects on a remote machine using the internal SOAP interface
213    """
214    def __init__(self, dp=False, url=None, certs=None):
215        """
216        Initializer.  Parses a configuration if one is given.
217        """
218
219        self.dynamic_projects = dp
220        self.url = url
221
222        if certs != None and isinstance(certs, type(tuple())):
223            self.cert_file, self.trusted_certs, self.cert_pwd = certs
224        else:
225            self.cert_file, self.trusted_certs, self.cert_pwd = \
226                    (None, None, None)
227        self.soap_services = { }
228        self.xmlrpc_services = { }
229       
230    def dynamic_project(self, req, fedid=None):
231        """
232        Send req on to a remote project instantiator.
233
234        Req is just the projectAllocType object.  This function re-wraps it.
235        It also rethrows any faults.
236        """
237        # No retry loop here.  Proxy servers must correctly authenticate
238        # themselves without help
239        try:
240            ctx = fedd_ssl_context(self.cert_file, self.trusted_certs,
241                    password=self.cert_pwd)
242        except SSL.SSLError:
243            raise service_error(service_error.server_config, 
244                    "Server certificates misconfigured")
245
246        loc = feddInternalServiceLocator();
247        port = loc.getfeddInternalPortType(self.url,
248                transport=M2Crypto.httpslib.HTTPSConnection, 
249                transdict={ 'ssl_context' : ctx })
250
251        if req.has_key('AllocateProjectRequestBody'):
252            req = req['AllocateProjectRequestBody']
253        else:
254            raise service_error(service_error.req, "Bad formated request");
255
256        # Reconstruct the full request message
257        msg = AllocateProjectRequestMessage()
258        msg.set_element_AllocateProjectRequestBody(
259                pack_soap(msg, "AllocateProjectRequestBody", req))
260        try:
261            resp = port.AllocateProject(msg)
262        except ZSI.ParseException, e:
263            raise service_error(service_error.proxy,
264                    "Bad format message (XMLRPC??): %s" % str(e))
265        r = unpack_soap(resp)
266
267        if r.has_key('AllocateProjectResponseBody'):
268            return r['AllocateProjectResponseBody']
269        else:
270            raise service_error(service_error.proxy, "Bad proxy response")
271
Note: See TracBrowser for help on using the repository browser.