source: fedd/fedd_create.py @ 45ebc4d

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

consolidate some code

  • Property mode set to 100755
File size: 14.0 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6
7from fedd_services import *
8
9from M2Crypto import SSL, X509
10from M2Crypto.m2xmlrpclib import SSL_Transport
11import M2Crypto.httpslib
12
13from xmlrpclib import ServerProxy, Error, dumps, loads
14from ZSI import SoapWriter
15from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any
16from ZSI.wstools.Namespaces import SOAP
17from ZSI.fault import FaultType, Detail
18
19import xmlrpclib
20
21from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \
22        pack_id, unpack_id, encapsulate_binaries, decapsulate_binaries
23
24from optparse import OptionParser, OptionValueError
25
26import parse_detail
27
28# Turn off the matching of hostname to certificate ID
29SSL.Connection.clientPostConnectionCheck = None
30
31class IDFormatException(RuntimeError): pass
32
33class access_method:
34    """Encapsulates an access method generically."""
35    (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey')
36    default_type = type_ssh
37    def __init__(self, buf=None, type=None, file=None):
38        self.buf = buf
39
40        if type != None: self.type = type
41        else: self.type = access_method.default_type
42
43        if file != None:
44            self.readfile(file)
45   
46    def readfile(self, file, type=None):
47        f = open(file, "r")
48        self.buf = f.read();
49        f.close()
50        if type == None:
51            if self.type == None:
52                self.type = access_method.default_type
53        else:
54            self.type = type;
55   
56class node_desc:
57    def __init__(self, image, hardware, count=1):
58        if getattr(image, "__iter__", None) == None:
59            if image == None: self.image = [ ]
60            else: self.image = [ image ]
61        else:
62            self.image = image
63
64        if getattr(hardware, "__iter__", None) == None: 
65            if hardware == None: self.hardware = [ ]
66            else: self.hardware = [ hardware ]
67        else:
68            self.hardware = hardware
69        if count != None: self.count = int(count)
70        else: self.count = 1
71
72class fedd_client_opts(OptionParser):
73    """Encapsulate option processing in this class, rather than in main"""
74    def __init__(self):
75        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
76                version="0.1")
77
78        self.add_option("-c","--cert", action="store", dest="cert",
79                type="string", help="my certificate file")
80        self.add_option("-d", "--debug", action="count", dest="debug", 
81                default=0, help="Set debug.  Repeat for more information")
82        self.add_option("-s", "--serializeOnly", action="store_true", 
83                dest="serialize_only", default=False,
84                help="Print the SOAP request that would be sent and exit")
85        self.add_option("-T","--trusted", action="store", dest="trusted",
86                type="string", help="Trusted certificates (required)")
87        self.add_option("-u", "--url", action="store", dest="url",
88                type="string",default="https://localhost:23235", 
89                help="URL to connect to (default %default)")
90        self.add_option("-x","--transport", action="store", type="choice",
91                choices=("xmlrpc", "soap"), default="soap",
92                help="Transport for request (xmlrpc|soap) (Default: %default)")
93        self.add_option("--trace", action="store_const", dest="tracefile", 
94                const=sys.stderr, help="Print SOAP exchange to stderr")
95
96class fedd_create_opts(fedd_client_opts):
97    def __init__(self, access_keys, add_key_callback=None, 
98            add_cert_callback=None):
99        fedd_client_opts.__init__(self)
100        self.add_option("-e", "--experiment_cert", dest="out_certfile",
101                type="string", help="output certificate file")
102        self.add_option("-F","--useFedid", action="store_true",
103                dest="use_fedid", default=False,
104                help="Use a fedid derived from my certificate as user identity")
105        self.add_option("-f", "--file", dest="file", 
106                help="experiment description file")
107        if add_key_callback:
108            self.add_option("-k", "--sshKey", action="callback", type="string", 
109                    callback=add_key_callback, callback_args=(access_keys,),
110                    help="ssh key for access (can be supplied more than once")
111        if add_cert_callback:
112            self.add_option("-K", "--x509Key", action="callback",
113                    type="string", callback=add_cert_callback,
114                    callback_args=(access_keys,),
115                    help="X509 certificate for access " + \
116                        "(can be supplied more than once")
117        self.add_option("-m", "--master", dest="master",
118                help="Master testbed in the federation")
119        self.add_option("-U", "--username", action="store", dest="user",
120                type="string", help="Use this username instead of the uid")
121
122def exit_with_fault(dict, out=sys.stderr):
123    """ Print an error message and exit.
124
125    The dictionary contains the FeddFaultBody elements."""
126    codestr = ""
127
128    if dict.has_key('errstr'):
129        codestr = "Error: %s" % dict['errstr']
130
131    if dict.has_key('code'):
132        if len(codestr) > 0 : 
133            codestr += " (%d)" % dict['code']
134        else:
135            codestr = "Error Code: %d" % dict['code']
136
137    print>>out, codestr
138    print>>out, "Description: %s" % dict['desc']
139    sys.exit(dict.get('code', 20))
140
141class fedd_exp_data_opts(fedd_client_opts):
142    def __init__(self):
143        fedd_client_opts.__init__(self)
144        self.add_option("-e", "--experiment_cert", dest="exp_certfile",
145                type="string", help="output certificate file")
146
147# Base class that will do a the SOAP/XMLRPC exchange for a request.
148class fedd_rpc:
149    class RPCException:
150        def __init__(self, fb):
151            self.desc = fb.get('desc', None)
152            self.code = fb.get('code', None)
153            self.errstr = fb.get('errstr', None)
154
155    def __init__(self, pre): 
156        """
157        Specialize the class for the prc method
158        """
159        self.RequestMessage = globals()["%sRequestMessage" % pre]
160        self.ResponseMessage = globals()["%sResponseMessage" % pre]
161        self.RequestBody="%sRequestBody" % pre
162        self.ResponseBody="%sResponseBody" % pre
163        self.method = pre
164        self.RPCException = fedd_rpc.RPCException
165
166    def do_rpc(self, req_dict, url, transport, cert, trusted, tracefile=None,
167            serialize_only=False):
168        """
169        The work of sending and parsing the RPC as either XMLRPC or SOAP
170        """
171
172        context = None
173        while context == None:
174            try:
175                context = fedd_ssl_context(cert, trusted)
176            except SSL.SSLError, e:
177                # Yes, doing this on message type is not ideal.  The string
178                # comes from OpenSSL, so check there is this stops working.
179                if str(e) == "bad decrypt": 
180                    print >>sys.stderr, "Bad Passphrase given."
181                else: raise
182
183        if transport == "soap":
184            loc = feddServiceLocator();
185            port = loc.getfeddPortType(url,
186                    transport=M2Crypto.httpslib.HTTPSConnection, 
187                    transdict={ 'ssl_context' : context },
188                    tracefile=tracefile)
189
190            req = self.RequestMessage()
191
192            set_req = getattr(req, "set_element_%s" % self.RequestBody, None)
193            set_req(pack_soap(req, self.RequestBody, req_dict))
194
195            if serialize_only:
196                sw = SoapWriter()
197                sw.serialize(req)
198                print str(sw)
199                sys.exit(0)
200
201            try:
202                method_call = getattr(port, self.method, None)
203                resp = method_call(req)
204            except ZSI.ParseException, e:
205                raise RuntimeError("Malformed response (XMLPRC?): %s" % e)
206            except ZSI.FaultException, e:
207                resp = e.fault.detail[0]
208
209            if resp:
210                resp_call = getattr(resp, "get_element_%s" %self.ResponseBody,
211                        None)
212                if resp_call: 
213                    resp_body = resp_call()
214                    if ( resp_body != None): 
215                        try:
216                            return unpack_soap(resp_body)
217                        except RuntimeError, e:
218                            raise RuntimeError("Bad response. %s" % e.message)
219                elif 'get_element_FeddFaultBody' in dir(resp): 
220                    resp_body = resp.get_element_FeddFaultBody()
221                    if resp_body != None: 
222                        try:
223                            fb = unpack_soap(resp_body)
224                        except RuntimeError, e:
225                            raise RuntimeError("Bad response. %s" % e.message)
226                        raise self.RPCException(fb)
227                else: 
228                    raise RuntimeError("No body in response!?")
229            else: 
230                raise RuntimeError("No response?!?")
231        elif transport == "xmlrpc":
232            if serialize_only:
233                ser = dumps((req_dict,))
234                print ser
235                sys.exit(0)
236
237            xtransport = SSL_Transport(context)
238            port = ServerProxy(url, transport=xtransport)
239
240            try:
241                method_call = getattr(port, self.method, None)
242                resp = method_call(
243                        encapsulate_binaries({ self.RequestBody: msg},\
244                            ('fedid',)))
245            except Error, e:
246                resp = { 'FeddFaultBody': \
247                        { 'errstr' : e.faultCode, 'desc' : e.faultString } }
248            if resp:
249                if resp.has_key(self.ResponseBody): 
250                    return resp[self.ResponseBody]
251                elif resp.has_key('FeddFaultBody'):
252                    raise self.RPCException(resp['FeddFaultBody'])
253                else: 
254                    raise RuntimeError("No body in response!?")
255            else: 
256                raise RuntimeError("No response?!?")
257        else:
258            raise RuntimeError("Unknown RPC transport: %s" % transport)
259
260# Querying experiment data follows the same control flow regardless of the
261# specific data retrieved.  This class encapsulates that control flow.
262class exp_data(fedd_rpc):
263    def __init__(self, op): 
264        """
265        Specialize the class for the type of data requested (op)
266        """
267
268        fedd_rpc.__init__(self, op)
269        if op =='Vtopo':
270            self.key="vtopo"
271            self.xml='experiment'
272        elif op == 'Vis':
273            self.key="vis"
274            self.xml='vis'
275        else:
276            raise TypeError("Bad op: %s" % op)
277
278    def print_xml(self, d, out=sys.stdout):
279        """
280        Print the retrieved data is a simple xml representation of the dict.
281        """
282        str = "<%s>\n" % self.xml
283        for t in ('node', 'lan'):
284            if d.has_key(t): 
285                for x in d[t]:
286                    str += "<%s>" % t
287                    for k in x.keys():
288                        str += "<%s>%s</%s>" % (k, x[k],k)
289                    str += "</%s>\n" % t
290        str+= "</%s>" % self.xml
291        print >>out, str
292
293    def __call__(self):
294        """
295        The control flow.  Compose the request and print the response.
296        """
297        # Process the options using the customized option parser defined above
298        parser = fedd_exp_data_opts()
299
300        (opts, args) = parser.parse_args()
301
302        if opts.trusted != None:
303            if ( not os.access(opts.trusted, os.R_OK) ) :
304                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
305        else:
306            parser.error("--trusted is required")
307
308        if opts.debug > 0: opts.tracefile=sys.stderr
309
310        if opts.cert != None: cert = opts.cert
311
312        if cert == None:
313            sys.exit("No certificate given (--cert) or found")
314
315        if os.access(cert, os.R_OK):
316            fid = fedid(file=cert)
317        else:
318            sys.exit("Cannot read certificate (%s)" % cert)
319
320        if opts.exp_certfile:
321            exp_fedid = fedid(file=opts.exp_certfile)
322        else:
323            sys.exit("Experiment certfile required")
324
325        try:
326            resp_dict = self.do_rpc({ 'experiment': { 'fedid': exp_fedid } }, 
327                    opts.url, opts.transport, cert, opts.trusted, 
328                    serialize_only=opts.serialize_only,
329                    tracefile=opts.tracefile)
330        except self.RPCException, e:
331            exit_with_fault(\
332                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
333        except RuntimeError, e:
334            sys.exit("Error processing RPC: %s" % e.message)
335
336        try:
337            if resp_dict.has_key(self.key):
338                self.print_xml(resp_dict[self.key])
339        except RuntimeError, e:
340            sys.exit("Bad response. %s" % e.message)
341
342class create(fedd_rpc):
343    def __init__(self): 
344        fedd_rpc.__init__(self, "Create")
345
346    def add_ssh_key(self, option, opt_str, value, parser, access_keys):
347        try:
348            access_keys.append(access_method(file=value,
349                type=access_method.type_ssh))
350        except IOError, (errno, strerror):
351            raise OptionValueError("Cannot generate sshPubkey from %s: "\
352                    "%s (%d)" % (value,strerror,errno))
353
354    def add_x509_cert(self, option, opt_str, value, parser, access_keys):
355        try:
356            access_keys.append(access_method(file=value,
357                type=access_method.type_x509))
358        except IOError, (errno, strerror):
359            raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
360                    (value,strerror,errno))
361
362    def get_user_info(self, access_keys):
363        pw = pwd.getpwuid(os.getuid());
364        try_cert=None
365        user = None
366
367        if pw != None:
368            user = pw[0]
369            try_cert = "%s/.ssl/emulab.pem" % pw[5];
370            if not os.access(try_cert, os.R_OK):
371                try_cert = None
372            if len(access_keys) == 0:
373                for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
374                        "%s/.ssh/identity.pub"]:
375                    try_key = k % pw[5];
376                    if os.access(try_key, os.R_OK):
377                        access_keys.append(access_method(file=try_key,
378                            type=access_method.type_ssh))
379                        break
380        return (user, try_cert)
381
382    def __call__(self):
383        access_keys = []
384        # Process the options using the customized option parser defined above
385        parser = fedd_create_opts(access_keys, self.add_ssh_key,
386                self.add_x509_cert)
387
388        (opts, args) = parser.parse_args()
389
390        if opts.trusted != None:
391            if ( not os.access(opts.trusted, os.R_OK) ) :
392                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
393        else:
394            parser.error("--trusted is required")
395
396        if opts.debug > 0: opts.tracefile=sys.stderr
397
398        (user, cert) = self.get_user_info(access_keys)
399
400        if opts.user: user = opts.user
401
402        if opts.cert != None: cert = opts.cert
403
404        if cert == None:
405            sys.exit("No certificate given (--cert) or found")
406
407        if os.access(cert, os.R_OK):
408            fid = fedid(file=cert)
409            if opts.use_fedid == True:
410                user = fid
411        else:
412            sys.exit("Cannot read certificate (%s)" % cert)
413
414        if opts.file:
415            exp_desc = ""
416            try:
417                f = open(opts.file, 'r')
418                for line in f:
419                    exp_desc += line
420                f.close()
421            except IOError:
422                sys.exit("Cannot read description file (%s)" %opts.file)
423        else:
424            sys.exit("Must specify an experiment description (--file)")
425
426        if not opts.master:
427            sys.exit("Must specify a master testbed (--master)")
428
429        out_certfile = opts.out_certfile
430
431        msg = {
432                'experimentdescription': exp_desc,
433                'master': opts.master,
434                'user' : [ {\
435                        'userID': pack_id(user), \
436                        'access': [ { a.type: a.buf } for a in access_keys]\
437                        } ]
438                }
439
440        if opts.debug > 1: print >>sys.stderr, msg
441
442        try:
443            resp_dict = self.do_rpc(msg, 
444                    opts.url, opts.transport, cert, opts.trusted, 
445                    serialize_only=opts.serialize_only,
446                    tracefile=opts.tracefile)
447        except self.RPCException, e:
448            exit_with_fault(\
449                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
450        except RuntimeError, e:
451            sys.exit("Error processing RPC: %s" % e.message)
452
453        if opts.debug > 1: print >>sys.stderr, resp_dict
454
455        ea = resp_dict.get('experimentAccess', None)
456        if out_certfile and ea and ea.has_key('X509'):
457            try:
458                f = open(out_certfile, "w")
459                print >>f, ea['X509']
460                f.close()
461            except IOError:
462                sys.exit('Could not write to %s' %  out_certfile)
463
464valid_cmds = ['create']
465
466f = None
467if sys.argv[1] == 'create':
468    del sys.argv[1]
469    f = create()
470elif sys.argv[1] == 'vtopo':
471    del sys.argv[1]
472    f = exp_data('Vtopo')
473elif sys.argv[1] == 'vis':
474    del sys.argv[1]
475    f = exp_data('Vis')
476else:
477    sys.exit("Bad command: %s.  Valid ones are: %s" % \
478            (sys.argv[1], ", ".join(valid_cmds)))
479
480if f: f()
481else: sys.exit("Null function?!?")
Note: See TracBrowser for help on using the repository browser.