source: fedd/fedd_client.py @ 5d3f239

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

change package name to avoid conflicts with fedd on install

  • Property mode set to 100755
File size: 20.8 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6
7from federation import fedid, service_error
8from federation.util import fedd_ssl_context, pack_id, unpack_id
9from federation.remote_service import service_caller
10
11from optparse import OptionParser, OptionValueError
12
13
14class IDFormatException(RuntimeError): pass
15
16class access_method:
17    """Encapsulates an access method generically."""
18    (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey')
19    default_type = type_ssh
20    def __init__(self, buf=None, type=None, file=None):
21        self.buf = buf
22
23        if type != None: self.type = type
24        else: self.type = access_method.default_type
25
26        if file != None:
27            self.readfile(file)
28   
29    def readfile(self, file, type=None):
30        f = open(file, "r")
31        self.buf = f.read();
32        f.close()
33        if type == None:
34            if self.type == None:
35                self.type = access_method.default_type
36        else:
37            self.type = type;
38   
39class node_desc:
40    def __init__(self, image, hardware, count=1):
41        if getattr(image, "__iter__", None) == None:
42            if image == None: self.image = [ ]
43            else: self.image = [ image ]
44        else:
45            self.image = image
46
47        if getattr(hardware, "__iter__", None) == None: 
48            if hardware == None: self.hardware = [ ]
49            else: self.hardware = [ hardware ]
50        else:
51            self.hardware = hardware
52        if count != None: self.count = int(count)
53        else: self.count = 1
54
55class fedd_client_opts(OptionParser):
56    """Encapsulate option processing in this class, rather than in main"""
57    def __init__(self):
58        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
59                version="0.1")
60
61        self.add_option("-c","--cert", action="store", dest="cert",
62                type="string", help="my certificate file")
63        self.add_option("-d", "--debug", action="count", dest="debug", 
64                default=0, help="Set debug.  Repeat for more information")
65        self.add_option("-s", "--serializeOnly", action="store_true", 
66                dest="serialize_only", default=False,
67                help="Print the SOAP request that would be sent and exit")
68        self.add_option("-T","--trusted", action="store", dest="trusted",
69                type="string", help="Trusted certificates (required)")
70        self.add_option("-u", "--url", action="store", dest="url",
71                type="string",default="https://localhost:23235", 
72                help="URL to connect to (default %default)")
73        self.add_option("-x","--transport", action="store", type="choice",
74                choices=("xmlrpc", "soap"), default="soap",
75                help="Transport for request (xmlrpc|soap) (Default: %default)")
76        self.add_option("--trace", action="store_const", dest="tracefile", 
77                const=sys.stderr, help="Print SOAP exchange to stderr")
78
79class fedd_create_opts(fedd_client_opts):
80    def __init__(self, access_keys, add_key_callback=None, 
81            add_cert_callback=None):
82        fedd_client_opts.__init__(self)
83        self.add_option("-e", "--experiment_cert", dest="out_certfile",
84                type="string", help="output certificate file")
85        self.add_option("-E", "--experiment_name", dest="exp_name",
86                type="string", help="output certificate file")
87        self.add_option("-F","--useFedid", action="store_true",
88                dest="use_fedid", default=False,
89                help="Use a fedid derived from my certificate as user identity")
90        self.add_option("-f", "--file", dest="file", 
91                help="experiment description file")
92        self.add_option("-p", "--project", action="store", dest="project", 
93                type="string",
94                help="Project to export from master")
95        if add_key_callback:
96            self.add_option("-k", "--sshKey", action="callback", type="string", 
97                    callback=add_key_callback, callback_args=(access_keys,),
98                    help="ssh key for access (can be supplied more than once")
99        if add_cert_callback:
100            self.add_option("-K", "--x509Key", action="callback",
101                    type="string", callback=add_cert_callback,
102                    callback_args=(access_keys,),
103                    help="X509 certificate for access " + \
104                        "(can be supplied more than once")
105        self.add_option("-m", "--master", dest="master",
106                help="Master testbed in the federation")
107        self.add_option("-U", "--username", action="store", dest="user",
108                type="string", help="Use this username instead of the uid")
109
110class fedd_split_opts(fedd_create_opts):
111    def __init__(self, access_keys, add_key_callback=None, 
112            add_cert_callback=None):
113        fedd_create_opts.__init__(self, access_keys, add_key_callback,
114                add_cert_callback)
115        self.add_option('-t','--fedkit', action='store_true', dest='fedkit',
116                default=False,
117                help="get output suitable for federation kit install")
118
119
120class fedd_access_opts(fedd_create_opts):
121    def __init__(self, access_keys, node_descs, add_key_callback=None, 
122            add_cert_callback=None, add_node_callback=None):
123        fedd_create_opts.__init__(self, access_keys, add_key_callback,
124                add_cert_callback)
125        self.add_option("-a","--anonymous", action="store_true",
126                dest="anonymous", default=False,
127                help="Do not include a user in the request")
128        self.add_option("-l","--label", action="store", dest="label",
129                type="string", help="Label for output")
130        if add_node_callback:
131            self.add_option("-n", "--node", action="callback", type="string", 
132                    callback=add_node_callback, callback_args=(node_descs,),
133                    help="Node description: image:hardware[:count]")
134        self.add_option("-t", "--testbed", action="store", dest="testbed",
135                type="string",
136                help="Testbed identifier (URI) to contact (required)")
137
138class fedd_exp_data_opts(fedd_client_opts):
139    def __init__(self):
140        fedd_client_opts.__init__(self)
141        self.add_option("-e", "--experiment_cert", dest="exp_certfile",
142                type="string", help="output certificate file")
143        self.add_option("-E", "--experiment_name", dest="exp_name",
144                type="string", help="output certificate file")
145
146def exit_with_fault(dict, out=sys.stderr):
147    """ Print an error message and exit.
148
149    The dictionary contains the FeddFaultBody elements."""
150    codestr = ""
151
152    if dict.has_key('errstr'):
153        codestr = "Error: %s" % dict['errstr']
154
155    if dict.has_key('code'):
156        if len(codestr) > 0 : 
157            codestr += " (%d)" % dict['code']
158        else:
159            codestr = "Error Code: %d" % dict['code']
160
161    print>>out, codestr
162    print>>out, "Description: %s" % dict['desc']
163    sys.exit(dict.get('code', 20))
164# Base class that will do a the SOAP/XMLRPC exchange for a request.
165class fedd_rpc:
166    class RPCException:
167        def __init__(self, fb):
168            self.desc = fb.get('desc', None)
169            self.code = fb.get('code', -1)
170            self.errstr = fb.get('errstr', None)
171
172    def __init__(self, pre): 
173        """
174        Specialize the class for the pre method
175        """
176        self.RequestBody="%sRequestBody" % pre
177        self.ResponseBody="%sResponseBody" % pre
178        self.method = pre
179
180        self.caller = service_caller(self.method)
181        self.RPCException = fedd_rpc.RPCException
182
183
184    def add_ssh_key(self, option, opt_str, value, parser, access_keys):
185        try:
186            access_keys.append(access_method(file=value,
187                type=access_method.type_ssh))
188        except IOError, (errno, strerror):
189            raise OptionValueError("Cannot generate sshPubkey from %s: "\
190                    "%s (%d)" % (value,strerror,errno))
191
192    def add_x509_cert(self, option, opt_str, value, parser, access_keys):
193        try:
194            access_keys.append(access_method(file=value,
195                type=access_method.type_x509))
196        except IOError, (errno, strerror):
197            raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
198                    (value,strerror,errno))
199    def add_node_desc(self, option, opt_str, value, parser, node_descs):
200        def none_if_zero(x):
201            if len(x) > 0: return x
202            else: return None
203
204        params = map(none_if_zero, value.split(":"));
205       
206        if len(params) < 4 and len(params) > 1:
207            node_descs.append(node_desc(*params))
208        else:
209            raise OptionValueError("Bad node description: %s" % value)
210
211    def get_user_info(self, access_keys):
212        pw = pwd.getpwuid(os.getuid());
213        try_cert=None
214        user = None
215
216        if pw != None:
217            user = pw[0]
218            try_cert = "%s/.ssl/emulab.pem" % pw[5];
219            if not os.access(try_cert, os.R_OK):
220                try_cert = None
221            if len(access_keys) == 0:
222                for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
223                        "%s/.ssh/identity.pub"]:
224                    try_key = k % pw[5];
225                    if os.access(try_key, os.R_OK):
226                        access_keys.append(access_method(file=try_key,
227                            type=access_method.type_ssh))
228                        break
229        return (user, try_cert)
230
231    def do_rpc(self, req_dict, url, transport, cert, trusted, tracefile=None,
232            serialize_only=False):
233        """
234        The work of sending and parsing the RPC as either XMLRPC or SOAP
235        """
236
237        context = None
238        while context == None:
239            try:
240                context = fedd_ssl_context(cert, trusted)
241            except Exception, e:
242                # Yes, doing this on message type is not ideal.  The string
243                # comes from OpenSSL, so check there is this stops working.
244                if str(e) == "bad decrypt": 
245                    print >>sys.stderr, "Bad Passphrase given."
246                else: raise
247
248        if transport == "soap":
249            if serialize_only:
250                print self.caller.serialize_soap(req_dict) 
251                sys.exit(0)
252            else:
253                try:
254                    resp = self.caller.call_soap_service(url, req_dict, 
255                            context=context, tracefile=tracefile)
256                except service_error, e:
257                    raise self.RPCException( {\
258                            'code': e.code, 
259                            'desc': e.desc, 
260                            'errstr': e.code_string()\
261                        })
262        elif transport == "xmlrpc":
263            if serialize_only:
264                ser = dumps((req_dict,))
265                print ser
266                sys.exit(0)
267            else:
268                try:
269                    resp = self.caller.call_xmlrpc_service(url, req_dict, 
270                            context=context, tracefile=tracefile)
271                except service_error, e:
272                    raise self.RPCException( {\
273                            'code': e.code, 
274                            'desc': e.desc, 
275                            'errstr': e.code_string()\
276                        })
277
278        else:
279            raise RuntimeError("Unknown RPC transport: %s" % transport)
280
281        if resp.has_key(self.ResponseBody):
282            return resp[self.ResponseBody]
283        else:
284            raise RuntimeError("No body in response??")
285
286# Querying experiment data follows the same control flow regardless of the
287# specific data retrieved.  This class encapsulates that control flow.
288class exp_data(fedd_rpc):
289    def __init__(self, op): 
290        """
291        Specialize the class for the type of data requested (op)
292        """
293
294        fedd_rpc.__init__(self, op)
295        if op =='Vtopo':
296            self.key="vtopo"
297            self.xml='experiment'
298        elif op == 'Vis':
299            self.key="vis"
300            self.xml='vis'
301        elif op == 'Info': pass
302        else:
303            raise TypeError("Bad op: %s" % op)
304
305    def print_xml(self, d, out=sys.stdout):
306        """
307        Print the retrieved data is a simple xml representation of the dict.
308        """
309        str = "<%s>\n" % self.xml
310        for t in ('node', 'lan'):
311            if d.has_key(t): 
312                for x in d[t]:
313                    str += "<%s>" % t
314                    for k in x.keys():
315                        str += "<%s>%s</%s>" % (k, x[k],k)
316                    str += "</%s>\n" % t
317        str+= "</%s>" % self.xml
318        print >>out, str
319
320    def __call__(self):
321        """
322        The control flow.  Compose the request and print the response.
323        """
324        # Process the options using the customized option parser defined above
325        parser = fedd_exp_data_opts()
326
327        (opts, args) = parser.parse_args()
328
329        if opts.trusted:
330            if ( not os.access(opts.trusted, os.R_OK) ) :
331                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
332
333        if opts.debug > 0: opts.tracefile=sys.stderr
334
335        if opts.cert != None: cert = opts.cert
336
337        if cert == None:
338            sys.exit("No certificate given (--cert) or found")
339
340        if os.access(cert, os.R_OK):
341            fid = fedid(file=cert)
342        else:
343            sys.exit("Cannot read certificate (%s)" % cert)
344
345        if opts.exp_name and opts.exp_certfile:
346            sys.exit("Only one of --experiment_cert and " +\
347                    "--experiment_name permitted");
348
349        if opts.exp_certfile:
350            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
351
352        if opts.exp_name:
353            exp_id = { 'localname' : opts.exp_name }
354
355        req = { 'experiment': exp_id }
356
357        try:
358            resp_dict = self.do_rpc(req,
359                    opts.url, opts.transport, cert, opts.trusted, 
360                    serialize_only=opts.serialize_only,
361                    tracefile=opts.tracefile)
362        except self.RPCException, e:
363            exit_with_fault(\
364                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
365        except RuntimeError, e:
366            print e
367            sys.exit("Error processing RPC: %s" % e)
368
369        if getattr(self, 'key', None):
370            try:
371                if resp_dict.has_key(self.key):
372                    self.print_xml(resp_dict[self.key])
373            except RuntimeError, e:
374                sys.exit("Bad response. %s" % e.message)
375        else:
376            print resp_dict
377
378class terminate(fedd_rpc):
379    def __init__(self): 
380        """
381        Termination request
382        """
383
384        fedd_rpc.__init__(self, "Terminate")
385
386    def __call__(self):
387        """
388        The control flow.  Compose the request and print the response.
389        """
390        # Process the options using the customized option parser defined above
391        parser = fedd_exp_data_opts()
392
393        (opts, args) = parser.parse_args()
394
395        if opts.trusted:
396            if ( not os.access(opts.trusted, os.R_OK) ) :
397                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
398
399        if opts.debug > 0: opts.tracefile=sys.stderr
400
401        if opts.cert != None: cert = opts.cert
402
403        if cert == None:
404            sys.exit("No certificate given (--cert) or found")
405
406        if os.access(cert, os.R_OK):
407            fid = fedid(file=cert)
408        else:
409            sys.exit("Cannot read certificate (%s)" % cert)
410
411        if opts.exp_name and opts.exp_certfile:
412            sys.exit("Only one of --experiment_cert and " +\
413                    "--experiment_name permitted");
414
415        if opts.exp_certfile:
416            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
417
418        if opts.exp_name:
419            exp_id = { 'localname' : opts.exp_name }
420
421        req = { 'experiment': exp_id }
422
423        try:
424            resp_dict = self.do_rpc(req,
425                    opts.url, opts.transport, cert, opts.trusted, 
426                    serialize_only=opts.serialize_only,
427                    tracefile=opts.tracefile)
428        except self.RPCException, e:
429            exit_with_fault(\
430                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
431        except RuntimeError, e:
432            print e
433            sys.exit("Error processing RPC: %s" % e)
434
435        eid = resp_dict.get('experimentID', None)
436        if eid:
437            for id in eid:
438                for k in id.keys():
439                    if k == 'fedid': print "%s: %s" % (k,fedid(bits=id[k]))
440                    else: print "%s: %s" % (k, id[k])
441
442class create(fedd_rpc):
443    def __init__(self): 
444        fedd_rpc.__init__(self, "Create")
445    def __call__(self):
446        access_keys = []
447        # Process the options using the customized option parser defined above
448        parser = fedd_create_opts(access_keys, self.add_ssh_key,
449                self.add_x509_cert)
450
451        (opts, args) = parser.parse_args()
452
453        if opts.trusted:
454            if ( not os.access(opts.trusted, os.R_OK) ) :
455                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
456
457        if not opts.project :
458            parser.error('--project is required')
459
460        if opts.debug > 0: opts.tracefile=sys.stderr
461
462        (user, cert) = self.get_user_info(access_keys)
463
464        if opts.user: user = opts.user
465
466        if opts.cert != None: cert = opts.cert
467
468        if cert == None:
469            sys.exit("No certificate given (--cert) or found")
470
471        if os.access(cert, os.R_OK):
472            fid = fedid(file=cert)
473            if opts.use_fedid == True:
474                user = fid
475        else:
476            sys.exit("Cannot read certificate (%s)" % cert)
477
478        if opts.file:
479            exp_desc = ""
480            try:
481                f = open(opts.file, 'r')
482                for line in f:
483                    exp_desc += line
484                f.close()
485            except IOError:
486                sys.exit("Cannot read description file (%s)" %opts.file)
487        else:
488            sys.exit("Must specify an experiment description (--file)")
489
490        if not opts.master:
491            sys.exit("Must specify a master testbed (--master)")
492
493        out_certfile = opts.out_certfile
494
495        msg = {
496                'experimentdescription': { 'ns2description': exp_desc },
497                'master': opts.master,
498                'exportProject': { 'localname': opts.project },
499                'user' : [ {\
500                        'userID': pack_id(user), \
501                        'access': [ { a.type: a.buf } for a in access_keys]\
502                        } ]
503                }
504
505        if opts.exp_name:
506            msg['experimentID'] = { 'localname': opts.exp_name }
507
508        if opts.debug > 1: print >>sys.stderr, msg
509
510        try:
511            resp_dict = self.do_rpc(msg, 
512                    opts.url, opts.transport, cert, opts.trusted, 
513                    serialize_only=opts.serialize_only,
514                    tracefile=opts.tracefile)
515        except self.RPCException, e:
516            exit_with_fault(\
517                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
518        except RuntimeError, e:
519            sys.exit("Error processing RPC: %s" % e)
520
521        if opts.debug > 1: print >>sys.stderr, resp_dict
522
523        ea = resp_dict.get('experimentAccess', None)
524        if out_certfile and ea and ea.has_key('X509'):
525            try:
526                f = open(out_certfile, "w")
527                print >>f, ea['X509']
528                f.close()
529            except IOError:
530                sys.exit('Could not write to %s' %  out_certfile)
531        eid = resp_dict.get('experimentID', None)
532        if eid:
533            for id in eid:
534                for k in id.keys():
535                    print "%s: %s" % (k, id[k])
536
537class split(fedd_rpc):
538    def __init__(self): 
539        fedd_rpc.__init__(self, "Ns2Split")
540    def __call__(self):
541        access_keys = []
542        # Process the options using the customized option parser defined above
543        parser = fedd_split_opts(access_keys, self.add_ssh_key,
544                self.add_x509_cert)
545
546        (opts, args) = parser.parse_args()
547
548        if opts.trusted:
549            if ( not os.access(opts.trusted, os.R_OK) ) :
550                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
551
552        if opts.debug > 0: opts.tracefile=sys.stderr
553
554        if opts.cert != None: cert = opts.cert
555        else: cert = None
556
557        if cert == None:
558            sys.exit("No certificate given (--cert) or found")
559
560        if os.access(cert, os.R_OK):
561            fid = fedid(file=cert)
562            if opts.use_fedid == True:
563                user = fid
564        else:
565            sys.exit("Cannot read certificate (%s)" % cert)
566
567        if opts.file:
568            exp_desc = ""
569            try:
570                f = open(opts.file, 'r')
571                for line in f:
572                    exp_desc += line
573                f.close()
574            except IOError:
575                sys.exit("Cannot read description file (%s)" %opts.file)
576        else:
577            sys.exit("Must specify an experiment description (--file)")
578
579        if not opts.master:
580            sys.exit("Must specify a master testbed (--master)")
581
582        out_certfile = opts.out_certfile
583
584        msg = {
585                'description': { 'ns2description': exp_desc },
586                'master': opts.master,
587                'include_fedkit': opts.fedkit,
588                }
589
590        if opts.debug > 1: print >>sys.stderr, msg
591
592        try:
593            resp_dict = self.do_rpc(msg, 
594                    opts.url, opts.transport, cert, opts.trusted, 
595                    serialize_only=opts.serialize_only,
596                    tracefile=opts.tracefile)
597        except self.RPCException, e:
598            exit_with_fault(\
599                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
600        except RuntimeError, e:
601            sys.exit("Error processing RPC: %s" % e)
602
603        if opts.debug > 1: print >>sys.stderr, resp_dict
604
605        out = resp_dict.get('output', None)
606
607        for line in out.splitlines():
608            print "%s" % line
609
610class access(fedd_rpc):
611    def __init__(self):
612        fedd_rpc.__init__(self, "RequestAccess")
613
614    def print_response_as_testbed(self, resp, label, out=sys.stdout):
615        """Print the response as input to the splitter script"""
616
617        e = resp['emulab']
618        p = e['project']
619        fields = { 
620                "Boss": e['boss'],
621                "OpsNode": e['ops'],
622                "Domain": e['domain'],
623                "FileServer": e['fileServer'],
624                "EventServer": e['eventServer'],
625                "Project": unpack_id(p['name'])
626                }
627        if (label != None): print >> out, "[%s]" % label
628
629        for l, v in fields.iteritems():
630            print >>out, "%s: %s" % (l, v)
631
632        for u in p['user']:
633            print >>out, "User: %s" % unpack_id(u['userID'])
634
635        for a in e['fedAttr']:
636            print >>out, "%s: %s" % (a['attribute'], a['value'])
637
638    def __call__(self):
639        access_keys = []
640        node_descs = []
641        proj = None
642
643        # Process the options using the customized option parser defined above
644        parser = fedd_access_opts(access_keys, node_descs, self.add_ssh_key,
645                self.add_x509_cert, self.add_node_desc)
646
647        (opts, args) = parser.parse_args()
648
649        if opts.testbed == None:
650            parser.error("--testbed is required")
651
652        if opts.trusted:
653            if ( not os.access(opts.trusted, os.R_OK) ) :
654                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
655
656        if opts.debug > 0: opts.tracefile=sys.stderr
657
658        (user, cert) = self.get_user_info(access_keys)
659
660        if opts.user: user = opts.user
661
662        if opts.cert != None: cert = opts.cert
663
664        if cert == None:
665            sys.exit("No certificate given (--cert) or found")
666
667        if os.access(cert, os.R_OK):
668            fid = fedid(file=cert)
669            if opts.use_fedid == True:
670                user = fid
671        else:
672            sys.exit("Cannot read certificate (%s)" % cert)
673
674        msg = {
675                'allocID': pack_id('test alloc'),
676                'destinationTestbed': pack_id(opts.testbed),
677                'serviceAccess' : [ { a.type: a.buf } for a in access_keys ],
678                'createAccess' : [ { a.type: a.buf } for a in access_keys ],
679                }
680
681        if len(node_descs) > 0:
682            msg['resources'] = { 
683                    'node': [ 
684                        { 
685                            'image':  n.image ,
686                            'hardware':  n.hardware,
687                            'count': n.count,
688                        } for n in node_descs],
689                    }
690
691        if opts.project != None:
692            if not opts.anonymous and user != None:
693                msg['project'] = {
694                        'name': pack_id(opts.project),
695                        'user': [ { 'userID': pack_id(user) } ],
696                        }
697            else:
698                msg['project'] = { 'name': pack_id(opts.project) }
699        else:
700            if not opts.anonymous and user != None:
701                msg['user'] = [ { 'userID': pack_id(user) } ]
702            else:
703                msg['user'] = [];
704
705        if opts.debug > 1: print >>sys.stderr, msg
706
707        try:
708            resp_dict = self.do_rpc(msg, 
709                    opts.url, opts.transport, cert, opts.trusted, 
710                    serialize_only=opts.serialize_only,
711                    tracefile=opts.tracefile)
712        except self.RPCException, e:
713            exit_with_fault(\
714                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
715        except RuntimeError, e:
716            sys.exit("Error processing RPC: %s" % e.message)
717
718        if opts.debug > 1: print >>sys.stderr, resp_dict
719        self.print_response_as_testbed(resp_dict, opts.label)
720
721cmds = {\
722        'create': create(),\
723        'split': split(),\
724        'access': access(),\
725        'vtopo': exp_data('Vtopo'),\
726        'vis': exp_data('Vis'),\
727        'info': exp_data('Info'),\
728        'terminate': terminate(),\
729    }
730
731operation = cmds.get(sys.argv[1], None)
732if operation:
733    del sys.argv[1]
734    operation()
735else:
736    sys.exit("Bad command: %s.  Valid ones are: %s" % \
737            (sys.argv[1], ", ".join(cmds.keys())))
738
Note: See TracBrowser for help on using the repository browser.