source: fedd/fedd_create.py @ 987aaa1

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

get topo and vis data, persistent state

  • Property mode set to 100755
File size: 15.5 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# Querying experiment data follows the same control flow regardless of the
148# specific data retrieved.  This class encapsulates that control flow.
149class exp_data:
150    def __init__(self, op): 
151        """
152        Specialize the class for the type of data requested (op)
153        """
154        if op =='vtopo':
155            self.RequestMessage = VtopoRequestMessage;
156            self.ResponseMessage = VtopoResponseMessage;
157            self.RequestBody="VtopoRequestBody"
158            self.ResponseBody="VtopoResponseBody"
159            self.method = "Vtopo"
160            self.key="vtopo"
161            self.xml='experiment'
162        elif op == 'vis':
163            self.RequestMessage = VisRequestMessage;
164            self.ResponseMessage = VisResponseMessage;
165            self.RequestBody="VisRequestBody"
166            self.ResponseBody="VisResponseBody"
167            self.method = "Vis"
168            self.key="vis"
169            self.xml='vis'
170        else:
171            raise TypeError("Bad op: %s" % op)
172
173    def print_xml(self, d, out=sys.stdout):
174        """
175        Print the retrieved data is a simple xml representation of the dict.
176        """
177        str = "<%s>\n" % self.xml
178        for t in ('node', 'lan'):
179            if d.has_key(t): 
180                for x in d[t]:
181                    str += "<%s>" % t
182                    for k in x.keys():
183                        str += "<%s>%s</%s>" % (k, x[k],k)
184                    str += "</%s>\n" % t
185        str+= "</%s>" % self.xml
186        print >>out, str
187
188    def __call__(self):
189        """
190        The control flow.  Compose the request and print the response.
191        """
192        # Process the options using the customized option parser defined above
193        parser = fedd_exp_data_opts()
194
195        (opts, args) = parser.parse_args()
196
197        if opts.trusted != None:
198            if ( not os.access(opts.trusted, os.R_OK) ) :
199                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
200        else:
201            parser.error("--trusted is required")
202
203        if opts.debug > 0: opts.tracefile=sys.stderr
204
205        if opts.cert != None: cert = opts.cert
206
207        if cert == None:
208            sys.exit("No certificate given (--cert) or found")
209
210        if os.access(cert, os.R_OK):
211            fid = fedid(file=cert)
212        else:
213            sys.exit("Cannot read certificate (%s)" % cert)
214
215        if opts.exp_certfile:
216            exp_fedid = fedid(file=opts.exp_certfile)
217        else:
218            sys.exit("Experiment certfile required")
219
220        context = None
221        while context == None:
222            try:
223                context = fedd_ssl_context(cert, opts.trusted)
224            except SSL.SSLError, e:
225                # Yes, doing this on message type is not ideal.  The string
226                # comes from OpenSSL, so check there is this stops working.
227                if str(e) == "bad decrypt": 
228                    print >>sys.stderr, "Bad Passphrase given."
229                else: raise
230
231        msg = { 'experiment': { 'fedid': exp_fedid } }
232
233        if opts.debug > 1: print >>sys.stderr, msg
234
235        if opts.transport == "soap":
236            loc = feddServiceLocator();
237            port = loc.getfeddPortType(opts.url,
238                    transport=M2Crypto.httpslib.HTTPSConnection, 
239                    transdict={ 'ssl_context' : context },
240                    tracefile=opts.tracefile)
241
242            req = self.RequestMessage()
243
244            set_req = getattr(req, "set_element_%s" % self.RequestBody, None)
245
246            set_req(pack_soap(req, self.RequestBody, msg))
247
248            if opts.serialize_only:
249                sw = SoapWriter()
250                sw.serialize(req)
251                print str(sw)
252                sys.exit(0)
253
254            try:
255                method_call = getattr(port, self.method, None)
256                resp = method_call(req)
257            except ZSI.ParseException, e:
258                sys.exit("Malformed response (XMLPRC?): %s" % e)
259            except ZSI.FaultException, e:
260                resp = e.fault.detail[0]
261
262            if resp:
263                resp_call = getattr(resp, "get_element_%s" %self.ResponseBody,
264                        None)
265                if resp_call: 
266                    resp_body = resp_call()
267                    if ( resp_body != None): 
268                        try:
269                            resp_dict = unpack_soap(resp_body)
270                            if opts.debug > 1: print >>sys.stderr, resp_dict
271                            if resp_dict.has_key(self.key):
272                                self.print_xml(resp_dict[self.key])
273                        except RuntimeError, e:
274                            sys.exit("Bad response. %s" % e.message)
275                elif 'get_element_FeddFaultBody' in dir(resp): 
276                    resp_body = resp.get_element_FeddFaultBody()
277                    if resp_body != None: 
278                        exit_with_fault(unpack_soap(resp_body))
279                else: sys.exit("No body in response!?")
280            else: sys.exit("No response?!?")
281        elif opts.transport == "xmlrpc":
282            if opts.serialize_only:
283                ser = dumps((msg,))
284                print ser
285                sys.exit(0)
286
287            transport = SSL_Transport(context)
288            port = ServerProxy(opts.url, transport=transport)
289
290            try:
291                method_call = getattr(port, self.method, None)
292                resp = method_call(
293                        encapsulate_binaries({ self.RequestBody: msg},\
294                            ('fedid',)))
295            except Error, e:
296                resp = { 'FeddFaultBody': \
297                        { 'errstr' : e.faultCode, 'desc' : e.faultString } }
298            if resp:
299                if resp.has_key(self.ResponseBody): 
300                    try:
301                        resp_dict = resp[self.ResponseBody]
302                        if opts.debug > 1: print >>sys.stderr, resp_dict
303                        if resp_dict.has_key(self.key):
304                            self.print_xml(resp_dict[self.key])
305                    except RuntimeError, e:
306                        sys.exit("Bad response. %s" % e.messgae)
307                elif resp.has_key('FeddFaultBody'):
308                    exit_with_fault(resp['FeddFaultBody'])
309                else: sys.exit("No body in response!?")
310            else: sys.exit("No response?!?")
311
312class create:
313    def __init__(self): pass
314
315    def add_ssh_key(self, option, opt_str, value, parser, access_keys):
316        try:
317            access_keys.append(access_method(file=value,
318                type=access_method.type_ssh))
319        except IOError, (errno, strerror):
320            raise OptionValueError("Cannot generate sshPubkey from %s: "\
321                    "%s (%d)" % (value,strerror,errno))
322
323    def add_x509_cert(self, option, opt_str, value, parser, access_keys):
324        try:
325            access_keys.append(access_method(file=value,
326                type=access_method.type_x509))
327        except IOError, (errno, strerror):
328            raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
329                    (value,strerror,errno))
330
331    def get_user_info(self, access_keys):
332        pw = pwd.getpwuid(os.getuid());
333        try_cert=None
334        user = None
335
336        if pw != None:
337            user = pw[0]
338            try_cert = "%s/.ssl/emulab.pem" % pw[5];
339            if not os.access(try_cert, os.R_OK):
340                try_cert = None
341            if len(access_keys) == 0:
342                for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
343                        "%s/.ssh/identity.pub"]:
344                    try_key = k % pw[5];
345                    if os.access(try_key, os.R_OK):
346                        access_keys.append(access_method(file=try_key,
347                            type=access_method.type_ssh))
348                        break
349        return (user, try_cert)
350
351    def __call__(self):
352        access_keys = []
353        # Process the options using the customized option parser defined above
354        parser = fedd_create_opts(access_keys, self.add_ssh_key,
355                self.add_x509_cert)
356
357        (opts, args) = parser.parse_args()
358
359        if opts.trusted != None:
360            if ( not os.access(opts.trusted, os.R_OK) ) :
361                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
362        else:
363            parser.error("--trusted is required")
364
365        if opts.debug > 0: opts.tracefile=sys.stderr
366
367        (user, cert) = self.get_user_info(access_keys)
368
369        if opts.user: user = opts.user
370
371        if opts.cert != None: cert = opts.cert
372
373        if cert == None:
374            sys.exit("No certificate given (--cert) or found")
375
376        if os.access(cert, os.R_OK):
377            fid = fedid(file=cert)
378            if opts.use_fedid == True:
379                user = fid
380        else:
381            sys.exit("Cannot read certificate (%s)" % cert)
382
383        if opts.file:
384            exp_desc = ""
385            try:
386                f = open(opts.file, 'r')
387                for line in f:
388                    exp_desc += line
389                f.close()
390            except IOError:
391                sys.exit("Cannot read description file (%s)" %opts.file)
392        else:
393            sys.exit("Must specify an experiment description (--file)")
394
395        if not opts.master:
396            sys.exit("Must specify a master testbed (--master)")
397
398        out_certfile = opts.out_certfile
399
400        context = None
401        while context == None:
402            try:
403                context = fedd_ssl_context(cert, opts.trusted)
404            except SSL.SSLError, e:
405                # Yes, doing this on message type is not ideal.  The string comes
406                # from OpenSSL, so check there is this stops working.
407                if str(e) == "bad decrypt": 
408                    print >>sys.stderr, "Bad Passphrase given."
409                else: raise
410
411        msg = {
412                'experimentdescription': exp_desc,
413                'master': opts.master,
414                'user' : [ {\
415                        'userID': pack_id(user), \
416                        'access': [ { a.type: a.buf } for a in access_keys]\
417                        } ]
418                }
419
420        if opts.debug > 1: print >>sys.stderr, msg
421
422        if opts.transport == "soap":
423            loc = feddServiceLocator();
424            port = loc.getfeddPortType(opts.url,
425                    transport=M2Crypto.httpslib.HTTPSConnection, 
426                    transdict={ 'ssl_context' : context },
427                    tracefile=opts.tracefile)
428
429            req = CreateRequestMessage()
430
431            req.set_element_CreateRequestBody(
432                    pack_soap(req, "CreateRequestBody", msg))
433
434            if opts.serialize_only:
435                sw = SoapWriter()
436                sw.serialize(req)
437                print str(sw)
438                sys.exit(0)
439
440            try:
441                resp = port.Create(req)
442            except ZSI.ParseException, e:
443                sys.exit("Malformed response (XMLPRC?): %s" % e)
444            except ZSI.FaultException, e:
445                resp = e.fault.detail[0]
446
447            if resp:
448                if 'get_element_CreateResponseBody' in dir(resp): 
449                    resp_body = resp.get_element_CreateResponseBody()
450                    if ( resp_body != None): 
451                        try:
452                            resp_dict = unpack_soap(resp_body)
453                            if opts.debug > 1: print >>sys.stderr, resp_dict
454                            ea = resp_dict.get('experimentAccess', None)
455                            if out_certfile and ea and ea.has_key('X509'):
456                                try:
457                                    f = open(out_certfile, "w")
458                                    print >>f, ea['X509']
459                                    f.close()
460                                except IOError:
461                                    sys.exit('Could not write to %s' % \
462                                            out_certfile)
463                        except RuntimeError, e:
464                            sys.exit("Bad response. %s" % e.message)
465                elif 'get_element_FeddFaultBody' in dir(resp): 
466                    resp_body = resp.get_element_FeddFaultBody()
467                    if resp_body != None: 
468                        exit_with_fault(unpack_soap(resp_body))
469                else: sys.exit("No body in response!?")
470            else: sys.exit("No response?!?")
471        elif opts.transport == "xmlrpc":
472            if opts.serialize_only:
473                ser = dumps((msg,))
474                print ser
475                sys.exit(0)
476
477            transport = SSL_Transport(context)
478            port = ServerProxy(opts.url, transport=transport)
479
480            try:
481                resp = port.Create({ 'CreateRequestBody': msg})
482            except Error, e:
483                resp = { 'FeddFaultBody': \
484                        { 'errstr' : e.faultCode, 'desc' : e.faultString } }
485            if resp:
486                if resp.has_key('CreateResponseBody'): 
487                    try:
488                        resp_dict = resp['CreateResponseBody']
489                        decapsulate_binaries(resp_dict, ('fedid',))
490                        if opts.debug > 1: print >>sys.stderr, resp_dict
491                        ea = resp_dict.get('experimentAccess', None)
492                        if out_certfile and ea and ea.has_key('X509'):
493                            try:
494                                f = open(out_certfile, "w")
495                                print >>f, ea['X509']
496                                f.close()
497                            except IOError:
498                                sys.exit('Could not write to %s' % out_certfile)
499                    except RuntimeError, e:
500                        sys.exit("Bad response. %s" % e.messgae)
501                elif resp.has_key('FeddFaultBody'):
502                    exit_with_fault(resp['FeddFaultBody'])
503                else: sys.exit("No body in response!?")
504
505            else: sys.exit("No response?!?")
506
507valid_cmds = ['create']
508
509f = None
510if sys.argv[1] == 'create':
511    del sys.argv[1]
512    f = create()
513elif sys.argv[1] == 'vtopo':
514    del sys.argv[1]
515    f = exp_data('vtopo')
516elif sys.argv[1] == 'vis':
517    del sys.argv[1]
518    f = exp_data('vis')
519else:
520    sys.exit("Bad command: %s.  Valid ones are: %s" % \
521            (sys.argv[1], ", ".join(valid_cmds)))
522
523if f: f()
524else: sys.exit("Null function?!?")
Note: See TracBrowser for help on using the repository browser.