source: fedd/fedd_client.py @ 65f3f29

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

Added multi info functions and some helpers to fedd_client

  • Property mode set to 100755
File size: 40.5 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6import tempfile
7import subprocess
8import re
9import xml.parsers.expat
10import time
11
12from federation import fedid, service_error
13from federation.util import fedd_ssl_context, pack_id, unpack_id
14from federation.remote_service import service_caller
15
16from optparse import OptionParser, OptionValueError
17
18
19class IDFormatException(RuntimeError): pass
20
21class access_method:
22    """Encapsulates an access method generically."""
23    (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey')
24    default_type = type_ssh
25    def __init__(self, buf=None, type=None, file=None):
26        self.buf = buf
27
28        if type != None: self.type = type
29        else: self.type = access_method.default_type
30
31        if file != None:
32            self.readfile(file)
33   
34    def readfile(self, file, type=None):
35        f = open(file, "r")
36        self.buf = f.read();
37        f.close()
38        if type == None:
39            if self.type == None:
40                self.type = access_method.default_type
41        else:
42            self.type = type;
43   
44class node_desc:
45    def __init__(self, image, hardware, count=1):
46        if getattr(image, "__iter__", None) == None:
47            if image == None: self.image = [ ]
48            else: self.image = [ image ]
49        else:
50            self.image = image
51
52        if getattr(hardware, "__iter__", None) == None: 
53            if hardware == None: self.hardware = [ ]
54            else: self.hardware = [ hardware ]
55        else:
56            self.hardware = hardware
57        if count != None: self.count = int(count)
58        else: self.count = 1
59
60class fedd_client_opts(OptionParser):
61    """Encapsulate option processing in this class, rather than in main"""
62    def __init__(self):
63        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
64                version="0.1")
65
66        self.add_option("-c","--cert", action="store", dest="cert",
67                type="string", help="my certificate file")
68        self.add_option("-d", "--debug", action="count", dest="debug", 
69                default=0, help="Set debug.  Repeat for more information")
70        self.add_option("-s", "--serializeOnly", action="store_true", 
71                dest="serialize_only", default=False,
72                help="Print the SOAP request that would be sent and exit")
73        self.add_option("-T","--trusted", action="store", dest="trusted",
74                type="string", help="Trusted certificates (required)")
75        self.add_option("-u", "--url", action="store", dest="url",
76                type="string",default="https://localhost:23235", 
77                help="URL to connect to (default %default)")
78        self.add_option("-x","--transport", action="store", type="choice",
79                choices=("xmlrpc", "soap"), default="soap",
80                help="Transport for request (xmlrpc|soap) (Default: %default)")
81        self.add_option("--trace", action="store_const", dest="tracefile", 
82                const=sys.stderr, help="Print SOAP exchange to stderr")
83
84class fedd_create_opts(fedd_client_opts):
85    def __init__(self, access_keys, add_key_callback=None, 
86            add_cert_callback=None):
87        fedd_client_opts.__init__(self)
88        self.add_option("-e", "--experiment_cert", dest="out_certfile",
89                type="string", help="output certificate file")
90        self.add_option("-E", "--experiment_name", dest="exp_name",
91                type="string", help="Suggested experiment name")
92        self.add_option("-F","--useFedid", action="store_true",
93                dest="use_fedid", default=False,
94                help="Use a fedid derived from my certificate as user identity")
95        self.add_option("-f", "--file", dest="file", 
96                help="experiment description file")
97        self.add_option("-p", "--project", action="store", dest="project", 
98                type="string",
99                help="Project to export from master")
100        if add_key_callback:
101            self.add_option("-k", "--ssh_key", action="callback",
102                    type="string", callback=add_key_callback,
103                    callback_args=(access_keys,),
104                    help="ssh key for access (can be supplied more than once")
105        if add_cert_callback:
106            self.add_option("-K", "--x509Key", action="callback",
107                    type="string", callback=add_cert_callback,
108                    callback_args=(access_keys,),
109                    help="X509 certificate for access " + \
110                        "(can be supplied more than once")
111        self.add_option("-m", "--master", dest="master",
112                help="Master testbed in the federation")
113        self.add_option("-U", "--username", action="store", dest="user",
114                type="string", help="Use this username instead of the uid")
115
116class fedd_split_opts(fedd_create_opts):
117    def __init__(self, access_keys, add_key_callback=None, 
118            add_cert_callback=None):
119        fedd_create_opts.__init__(self, access_keys, add_key_callback,
120                add_cert_callback)
121        self.add_option('-t','--fedkit', action='store_true', dest='fedkit',
122                default=False,
123                help="get output suitable for federation kit install")
124        self.add_option('-G','--gatewaykit', action='store_true',
125                dest='gatewaykit', default=False,
126                help="get output suitable for federation gateway kit install")
127
128
129class fedd_access_opts(fedd_create_opts):
130    def __init__(self, access_keys, node_descs, add_key_callback=None, 
131            add_cert_callback=None, add_node_callback=None):
132        fedd_create_opts.__init__(self, access_keys, add_key_callback,
133                add_cert_callback)
134        self.add_option("-a","--anonymous", action="store_true",
135                dest="anonymous", default=False,
136                help="Do not include a user in the request")
137        self.add_option("-l","--label", action="store", dest="label",
138                type="string", help="Label for output")
139        if add_node_callback:
140            self.add_option("-n", "--node", action="callback", type="string", 
141                    callback=add_node_callback, callback_args=(node_descs,),
142                    help="Node description: image:hardware[:count]")
143        self.add_option("-t", "--testbed", action="store", dest="testbed",
144                type="string",
145                help="Testbed identifier (URI) to contact (required)")
146
147class fedd_exp_data_opts(fedd_client_opts):
148    def __init__(self):
149        fedd_client_opts.__init__(self)
150        self.add_option("-e", "--experiment_cert", dest="exp_certfile",
151                type="string", help="experiment certificate file")
152        self.add_option("-E", "--experiment_name", dest="exp_name",
153                type="string", help="human readable experiment name")
154        self.add_option("--data", dest="data", default=[],
155                action="append", type="choice",
156                choices=("id", "federant", "vtopo", "vis", "log", "status"),
157                help="data to extract")
158
159class fedd_multi_exp_data_opts(fedd_client_opts):
160    def __init__(self):
161        fedd_client_opts.__init__(self)
162        self.add_option("--data", dest="data", default=[],
163                action="append", type="choice",
164                choices=("id", "federant", "vtopo", "vis", "log", "status"),
165                help="data to extract")
166
167class fedd_spew_opts(fedd_client_opts):
168    def __init__(self):
169        fedd_client_opts.__init__(self)
170        self.add_option("-e", "--experiment_cert", dest="exp_certfile",
171                type="string", help="experiment name certificate file")
172        self.add_option("-E", "--experiment_name", dest="exp_name",
173                type="string", help="human readable experiment name")
174        self.add_option("--logfile", dest="logfile", default=None,
175                help="File to write log to")
176        self.add_option('--update_time', dest='update', type='int', default=10,
177                help='how often to update the printed log')
178
179class fedd_image_opts(fedd_exp_data_opts):
180    def __init__(self):
181        fedd_exp_data_opts.__init__(self)
182        self.add_option("-o", "--output", dest="outfile", type="string",
183                help="output image file")
184        self.add_option("-F", "--format", dest="format", type="choice", 
185                choices=("jpg", "png", "dot", "svg"),
186                help="Output file format override")
187        self.add_option("-P", "--program", dest="neato", default=None,
188                type="string",
189                help="Program compatible with dot (from graphviz) used to " + \
190                        "render image")
191        self.add_option('-L', "--labels", dest='labels', action='store_true',
192                default=True, help='Label nodes and edges')
193        self.add_option('-A', "--no_labels", dest='labels',
194                default=True, action='store_false',
195                help='Label nodes and edges')
196        self.add_option('-j','--pixels', dest="pixels", default=None,
197                type="int",
198                help="Size of output in pixels (diagrams are square")
199
200class fedd_ns_image_opts(fedd_split_opts):
201    def __init__(self, access_keys, add_key_callback=None, 
202            add_cert_callback=None):
203        fedd_split_opts.__init__(self, access_keys, add_key_callback, 
204                add_cert_callback)
205        self.add_option("-o", "--output", dest="outfile", type="string",
206                help="output image file")
207        self.add_option("-Q", "--format", dest="format", type="choice", 
208                choices=("jpg", "png", "dot", "svg"),
209                help="Output file format override")
210        self.add_option("-P", "--program", dest="neato", default=None,
211                type="string",
212                help="Program compatible with dot (from graphviz) used to " + \
213                        "render image")
214        self.add_option('-L', "--labels", dest='labels', action='store_true',
215                default=True, help='Label nodes and edges')
216        self.add_option('-A', "--no_labels", dest='labels',
217                default=True, action='store_false',
218                help='Label nodes and edges')
219        self.add_option('-j','--pixels', dest="pixels", default=None,
220                type="int",
221                help="Size of output in pixels (diagrams are square")
222
223def exit_with_fault(dict, out=sys.stderr):
224    """ Print an error message and exit.
225
226    The dictionary contains the FeddFaultBody elements."""
227    codestr = ""
228
229    if dict.has_key('errstr'):
230        codestr = "Error: %s" % dict['errstr']
231
232    if dict.has_key('code'):
233        if len(codestr) > 0 : 
234            codestr += " (%d)" % dict['code']
235        else:
236            codestr = "Error Code: %d" % dict['code']
237
238    print>>out, codestr
239    print>>out, "Description: %s" % dict['desc']
240    sys.exit(dict.get('code', 20))
241# Base class that will do a the SOAP/XMLRPC exchange for a request.
242class fedd_rpc:
243    class RPCException:
244        def __init__(self, fb):
245            self.desc = fb.get('desc', None)
246            self.code = fb.get('code', -1)
247            self.errstr = fb.get('errstr', None)
248
249    def __init__(self, pre): 
250        """
251        Specialize the class for the pre method
252        """
253        self.RequestBody="%sRequestBody" % pre
254        self.ResponseBody="%sResponseBody" % pre
255        self.method = pre
256
257        self.caller = service_caller(self.method)
258        self.RPCException = fedd_rpc.RPCException
259
260
261    def add_ssh_key(self, option, opt_str, value, parser, access_keys):
262        try:
263            access_keys.append(access_method(file=value,
264                type=access_method.type_ssh))
265        except IOError, (errno, strerror):
266            raise OptionValueError("Cannot generate sshPubkey from %s: "\
267                    "%s (%d)" % (value,strerror,errno))
268
269    def add_x509_cert(self, option, opt_str, value, parser, access_keys):
270        try:
271            access_keys.append(access_method(file=value,
272                type=access_method.type_x509))
273        except IOError, (errno, strerror):
274            raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
275                    (value,strerror,errno))
276    def add_node_desc(self, option, opt_str, value, parser, node_descs):
277        def none_if_zero(x):
278            if len(x) > 0: return x
279            else: return None
280
281        params = map(none_if_zero, value.split(":"));
282       
283        if len(params) < 4 and len(params) > 1:
284            node_descs.append(node_desc(*params))
285        else:
286            raise OptionValueError("Bad node description: %s" % value)
287
288    def get_user_info(self, access_keys):
289        pw = pwd.getpwuid(os.getuid());
290        try_cert=None
291        user = None
292
293        if pw != None:
294            user = pw[0]
295            try_cert = "%s/.ssl/emulab.pem" % pw[5];
296            if not os.access(try_cert, os.R_OK):
297                try_cert = None
298            if len(access_keys) == 0:
299                for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
300                        "%s/.ssh/identity.pub"]:
301                    try_key = k % pw[5];
302                    if os.access(try_key, os.R_OK):
303                        access_keys.append(access_method(file=try_key,
304                            type=access_method.type_ssh))
305                        break
306        return (user, try_cert)
307
308    def do_rpc(self, req_dict, url, transport, cert, trusted, tracefile=None,
309            serialize_only=False):
310        """
311        The work of sending and parsing the RPC as either XMLRPC or SOAP
312        """
313
314        context = None
315        while context == None:
316            try:
317                context = fedd_ssl_context(cert, trusted)
318            except Exception, e:
319                # Yes, doing this on message type is not ideal.  The string
320                # comes from OpenSSL, so check there is this stops working.
321                if str(e) == "bad decrypt": 
322                    print >>sys.stderr, "Bad Passphrase given."
323                else: raise
324
325        if transport == "soap":
326            if serialize_only:
327                print self.caller.serialize_soap(req_dict) 
328                sys.exit(0)
329            else:
330                try:
331                    resp = self.caller.call_soap_service(url, req_dict, 
332                            context=context, tracefile=tracefile)
333                except service_error, e:
334                    raise self.RPCException( {\
335                            'code': e.code, 
336                            'desc': e.desc, 
337                            'errstr': e.code_string()\
338                        })
339        elif transport == "xmlrpc":
340            if serialize_only:
341                ser = dumps((req_dict,))
342                print ser
343                sys.exit(0)
344            else:
345                try:
346                    resp = self.caller.call_xmlrpc_service(url, req_dict, 
347                            context=context, tracefile=tracefile)
348                except service_error, e:
349                    raise self.RPCException( {\
350                            'code': e.code, 
351                            'desc': e.desc, 
352                            'errstr': e.code_string()\
353                        })
354
355        else:
356            raise RuntimeError("Unknown RPC transport: %s" % transport)
357
358        if resp.has_key(self.ResponseBody):
359            return resp[self.ResponseBody]
360        else:
361            raise RuntimeError("No body in response??")
362
363class exp_data_base(fedd_rpc):
364    def __init__(self, op='Info'):
365        """
366        Init the various conversions
367        """
368
369        fedd_rpc.__init__(self, op)
370        # List of things one could ask for and what formatting routine is
371        # called.
372        self.params = {
373                'vis': ('vis', self.print_xml('vis')),
374                'vtopo': ('vtopo', self.print_xml('vtopo')),
375                'federant': ('federant', self.print_xml),
376                'id': ('experimentID', self.print_id),
377                'status': ('experimentStatus', self.print_string),
378                'log': ('allocationLog', self.print_string),
379                'access': ('experimentAccess', self.print_string),
380            }
381
382    # Utility functions
383    def print_string(self, d, out=sys.stdout):
384        print >>out, d
385
386    def print_id(self, d, out=sys.stdout):
387        if d:
388            for id in d:
389                for k in id.keys():
390                    print >>out, "%s: %s" % (k, id[k])
391
392    class print_xml:
393        """
394        Print the retrieved data is a simple xml representation of the dict.
395        """
396        def __init__(self, top):
397            self.xml = top
398
399        def __call__(self, d, out=sys.stdout):
400            str = "<%s>\n" % self.xml
401            for t in ('node', 'lan'):
402                if d.has_key(t): 
403                    for x in d[t]:
404                        str += "<%s>" % t
405                        for k in x.keys():
406                            str += "<%s>%s</%s>" % (k, x[k],k)
407                        str += "</%s>\n" % t
408            str+= "</%s>" % self.xml
409            print >>out, str
410
411       
412
413# Querying experiment data follows the same control flow regardless of the
414# specific data retrieved.  This class encapsulates that control flow.
415class exp_data(exp_data_base):
416    def __init__(self): 
417        exp_data_base.__init__(self, 'Info')
418
419    def __call__(self):
420        """
421        The control flow.  Compose the request and print the response.
422        """
423        # Process the options using the customized option parser defined above
424        parser = fedd_exp_data_opts()
425
426        (opts, args) = parser.parse_args()
427
428        if opts.trusted:
429            if ( not os.access(opts.trusted, os.R_OK) ) :
430                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
431
432        if opts.debug > 0: opts.tracefile=sys.stderr
433
434        (user, cert) = self.get_user_info([])
435
436        if opts.cert != None: cert = opts.cert
437
438        if cert == None:
439            sys.exit("No certificate given (--cert) or found")
440
441        if os.access(cert, os.R_OK):
442            fid = fedid(file=cert)
443        else:
444            sys.exit("Cannot read certificate (%s)" % cert)
445
446        if opts.exp_name and opts.exp_certfile:
447            sys.exit("Only one of --experiment_cert and " +\
448                    "--experiment_name permitted");
449
450        if opts.exp_certfile:
451            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
452
453        if opts.exp_name:
454            exp_id = { 'localname' : opts.exp_name }
455
456        req = { 'experiment': exp_id }
457
458        try:
459            resp_dict = self.do_rpc(req,
460                    opts.url, opts.transport, cert, opts.trusted, 
461                    serialize_only=opts.serialize_only,
462                    tracefile=opts.tracefile)
463        except self.RPCException, e:
464            exit_with_fault(\
465                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
466        except RuntimeError, e:
467            print e
468            sys.exit("Error processing RPC: %s" % e)
469
470        for d in opts.data:
471            key, output = self.params[d]
472            try:
473                if resp_dict.has_key(key):
474                    output(resp_dict[key])
475            except RuntimeError, e:
476                sys.exit("Bad response. %s" % e.message)
477
478class vtopo(exp_data):
479    """
480    vtopo is just an info --data=vtopo request, so this adds that parameter to
481    the arguments and executes exp_info when called.
482    """
483    def __init__(self):
484        exp_data.__init__(self)
485    def __call__(self):
486        sys.argv.append('--data=vtopo')
487        exp_data.__call__(self)
488
489
490class vis(exp_data):
491    """
492    vis is just an info --data=vis request, so this adds that parameter to
493    the arguments and executes exp_info when called.
494    """
495    def __init__(self):
496        exp_data.__init__(self)
497    def __call__(self):
498        sys.argv.append('--data=vis')
499        exp_data.__call__(self)
500
501class status(exp_data):
502    """
503    status is just an info --data=status request, so this adds that parameter
504    to the arguments and executes exp_info when called.
505    """
506    def __init__(self):
507        exp_data.__init__(self)
508    def __call__(self):
509        sys.argv.append('--data=status')
510        exp_data.__call__(self)
511
512class multi_exp_data(exp_data_base):
513    def __init__(self): 
514        exp_data_base.__init__(self, 'MultiInfo')
515
516
517    def __call__(self):
518        """
519        The control flow.  Compose the request and print the response.
520        """
521        # Process the options using the customized option parser defined above
522        parser = fedd_multi_exp_data_opts()
523
524        (opts, args) = parser.parse_args()
525
526        if opts.trusted:
527            if ( not os.access(opts.trusted, os.R_OK) ) :
528                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
529
530        if opts.debug > 0: opts.tracefile=sys.stderr
531
532        (user, cert) = self.get_user_info([])
533
534        if opts.cert != None: cert = opts.cert
535
536        if cert == None:
537            sys.exit("No certificate given (--cert) or found")
538
539        if os.access(cert, os.R_OK):
540            fid = fedid(file=cert)
541        else:
542            sys.exit("Cannot read certificate (%s)" % cert)
543
544        req = { }
545
546        try:
547            resp_dict = self.do_rpc(req,
548                    opts.url, opts.transport, cert, opts.trusted, 
549                    serialize_only=opts.serialize_only,
550                    tracefile=opts.tracefile)
551        except self.RPCException, e:
552            exit_with_fault(\
553                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
554        except RuntimeError, e:
555            print e
556            sys.exit("Error processing RPC: %s" % e)
557
558        exps = resp_dict.get('info', [])
559        if exps:
560            print '---'
561            for exp in exps:
562                for d in opts.data:
563                    key, output = self.params[d]
564                    try:
565                        if exp.has_key(key):
566                            output(exp[key])
567                    except RuntimeError, e:
568                        sys.exit("Bad response. %s" % e.message)
569                print '---'
570
571
572class multi_status(exp_data_base):
573    def __init__(self): 
574        exp_data_base.__init__(self, 'MultiInfo')
575
576
577    def __call__(self):
578        """
579        The control flow.  Compose the request and print the response.
580        """
581        # Process the options using the customized option parser defined above
582        parser = fedd_client_opts()
583
584        (opts, args) = parser.parse_args()
585
586        if opts.trusted:
587            if ( not os.access(opts.trusted, os.R_OK) ) :
588                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
589
590        if opts.debug > 0: opts.tracefile=sys.stderr
591
592        (user, cert) = self.get_user_info([])
593
594        if opts.cert != None: cert = opts.cert
595
596        if cert == None:
597            sys.exit("No certificate given (--cert) or found")
598
599        if os.access(cert, os.R_OK):
600            fid = fedid(file=cert)
601        else:
602            sys.exit("Cannot read certificate (%s)" % cert)
603
604        req = { }
605
606        try:
607            resp_dict = self.do_rpc(req,
608                    opts.url, opts.transport, cert, opts.trusted, 
609                    serialize_only=opts.serialize_only,
610                    tracefile=opts.tracefile)
611        except self.RPCException, e:
612            exit_with_fault(\
613                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
614        except RuntimeError, e:
615            print e
616            sys.exit("Error processing RPC: %s" % e)
617
618        for exp in resp_dict.get('info', []):
619            out = []
620            for eid in exp.get('experimentID', []):
621                if eid.has_key('localname'):
622                    out.append(eid['localname'])
623                    break
624            else:
625                out.append("")
626            for eid in exp.get('experimentID', []):
627                if eid.has_key('fedid'):
628                    out.append("%s" % eid['fedid'])
629                    break
630            else:
631                out.append("")
632
633            out.append(exp.get('experimentStatus', ""))
634
635            for f in exp.get('federant', []):
636                if f.get('master', False):
637                    em = f.get('emulab', None)
638                    if em:
639                        project = em.get('project', None)
640                        if project:
641                            tb = project.get('testbed', None)
642                            if tb and tb.has_key('localname'):
643                                out.append(tb['localname'])
644                            else:
645                                out.append("")
646                            pn = project.get('name', None)
647                            if pn and pn.has_key('localname'):
648                                out.append(pn['localname'])
649                            else:
650                                out.append("")
651                        else:
652                            out.extend(("", ""))
653                    else:
654                        out.extend(("", ""))
655                    break
656            else:
657                out.extend(("",""))
658
659            print ":".join(out)
660
661class image(fedd_rpc):
662    def __init__(self, op='Vtopo'): 
663        """
664        Null constructor
665        """
666
667        fedd_rpc.__init__(self, 'Vtopo')
668
669    def gen_image(self, d, file, fmt, neato, labels, pix=None):
670
671        # Open up a temporary file for dot to turn into a visualization
672        try:
673            df, dotname = tempfile.mkstemp(prefix='fedd_client', suffix=".dot")
674            dotfile = os.fdopen(df, 'w')
675        except IOError:
676            raise service_error(service_error.internal,
677                    "Failed to open file in genviz")
678
679        if not neato:
680            for f in ['/usr/bin/neato', '/usr/local/bin/neato', 
681                    '/usr/bin/dot', '/usr/local/bin/dot']:
682                if os.access(f, os.X_OK):
683                    neato = f
684                    break
685            else:
686                sys.exit("Cannot find graph rendering program")
687
688        cmd = [neato, '-Gsplines=true']
689        if fmt != 'dot': cmd.append('-T%s' % fmt)
690        if file:
691            cmd.append('-o')
692            cmd.append(file)
693        cmd.append(dotname)
694
695        nodes = d.get('node',[])
696
697        if len(nodes) < 10: size = 5
698        elif len(nodes) < 50: size = 10
699        else: size = 18
700
701        if pix:
702            dpi = pix / size
703        else:
704            dpi = None
705
706
707        print >>dotfile, "graph G {"
708        if dpi:
709            print >>dotfile, '\tgraph [size="%i,%i", dpi="%i", ratio=fill];' \
710                    % (size, size, dpi)
711        else:
712            print >>dotfile, '\tgraph [size="%i,%i", ratio=fill];' \
713                    % (size, size)
714
715        if labels:
716            print >>dotfile, '\tnode [fontname=arial,fontsize=9,label="\N"];'
717            print >>dotfile, '\tedge [fontname=arial,fontsize=9];\n'
718        else:
719            print >>dotfile, '\tnode [label=""];'
720
721        for n in d.get('node', []):
722            print >>dotfile, '\t"%s" [shape=box,style=filled,\\' % n['vname']
723            print >>dotfile, '\t\tcolor=black,fillcolor="#60f8c0",regular=1]'
724
725
726        lans = { }
727        links = { }
728
729        # Collect lan members (we have to draw extra stuff for these)
730        for n in d.get('lan', []):
731            v = n['vname']
732            m = n['member']
733            i = n['ip']
734            if m.find(':') != -1:
735                m = m[0:m.find(':')]
736            if lans.has_key(v):
737                lans[v].append((m, i))
738            elif links.has_key(v):
739                links[v].append((m, i))
740                if len(links[v]) > 2:
741                    lans[v] = links[v]
742                    del links[v]
743            else:
744                links[v] = [(m, i)]
745
746        # Encode the lans and the links
747        for l in lans.keys():
748            print >>dotfile, '\t"%s" [shape=ellipse, style=filled,\\' % l
749            print >>dotfile,'\t\tcolor=black,fillcolor="#80c0f8",regular=1]'
750            for n in lans[l]:
751                if labels:
752                    print >>dotfile, '\t"%s" -- "%s" [headlabel="%s"]' % \
753                            (l, n[0], n[1])
754                else:
755                    print >>dotfile, '\t"%s" -- "%s"' % (l, n[0])
756
757        for k, l in links.items():
758            if len(l) == 2:
759                if labels:
760                    print >>dotfile, \
761                            ('\t"%s" -- "%s" [label="%s",taillabel="%s",' + \
762                            'headlabel="%s"]') % \
763                            (l[0][0], l[1][0], k, l[0][1], l[1][1])
764                else:
765                    print >>dotfile, '\t"%s" -- "%s" ' % (l[0][0], l[1][0])
766
767        print >>dotfile, "}"
768        dotfile.close()
769
770        # Redirect the drawing program stderr
771        dev_null = open("/dev/null", "w")
772        rv = subprocess.call(cmd, stderr=dev_null)
773        os.remove(dotname)
774        dev_null.close()
775        if rv != 0:
776            sys.exit("Error creating graph")
777
778
779
780    def __call__(self):
781        """
782        The control flow.  Compose the request and print the response.
783        """
784        # Process the options using the customized option parser defined above
785        parser = fedd_image_opts()
786
787        (opts, args) = parser.parse_args()
788
789        if opts.trusted:
790            if ( not os.access(opts.trusted, os.R_OK) ) :
791                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
792
793        if opts.debug > 0: opts.tracefile=sys.stderr
794
795        (user, cert) = self.get_user_info([])
796
797        if opts.cert != None: cert = opts.cert
798
799        if cert == None:
800            sys.exit("No certificate given (--cert) or found")
801
802        if os.access(cert, os.R_OK):
803            fid = fedid(file=cert)
804        else:
805            sys.exit("Cannot read certificate (%s)" % cert)
806
807        if opts.exp_name and opts.exp_certfile:
808            sys.exit("Only one of --experiment_cert and " +\
809                    "--experiment_name permitted");
810
811        if opts.exp_certfile:
812            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
813
814        if opts.exp_name:
815            exp_id = { 'localname' : opts.exp_name }
816
817        if opts.format and opts.outfile:
818            fmt = opts.format
819            file = opts.outfile
820        elif not opts.format and opts.outfile:
821            fmt = opts.outfile[-3:]
822            if fmt not in ("png", "jpg", "dot", "svg"):
823                sys.exit("Unexpected file type and no format specified")
824            file = opts.outfile
825        elif opts.format and not opts.outfile:
826            fmt = opts.format
827            file = None
828        else:
829            fmt="dot"
830            file = None
831
832
833        req = { 'experiment': exp_id }
834
835        try:
836            resp_dict = self.do_rpc(req,
837                    opts.url, opts.transport, cert, opts.trusted, 
838                    serialize_only=opts.serialize_only,
839                    tracefile=opts.tracefile)
840        except self.RPCException, e:
841            exit_with_fault(\
842                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
843        except RuntimeError, e:
844            print e
845            sys.exit("Error processing RPC: %s" % e)
846
847
848        if resp_dict.has_key('vtopo'):
849            self.gen_image(resp_dict['vtopo'], file, fmt, opts.neato,
850                    opts.labels, opts.pixels)
851        else:
852            sys.exit("Bad response. %s" % e.message)
853
854class ns_image(image):
855    def __init__(self, op='Ns2Split'): 
856        """
857        Null constructor
858        """
859
860        image.__init__(self, 'Ns2Split')
861
862    def generate_topo_dict(self, splitout):
863        class topo_parse:
864            """
865            Parse the topology XML and create the dats structure.  This class
866            is copied from federation.experiment_control.
867            """
868            def __init__(self):
869                # Typing of the subelements for data conversion
870                self.str_subelements = ('vname', 'vnode', 'ips', 'ip', 'member')
871                self.int_subelements = ( 'bandwidth',)
872                self.float_subelements = ( 'delay',)
873                # The final data structure
874                self.nodes = [ ]
875                self.lans =  [ ]
876                self.topo = { \
877                        'node': self.nodes,\
878                        'lan' : self.lans,\
879                    }
880                self.element = { }  # Current element being created
881                self.chars = ""     # Last text seen
882
883            def end_element(self, name):
884                # After each sub element the contents is added to the current
885                # element or to the appropriate list.
886                if name == 'node':
887                    self.nodes.append(self.element)
888                    self.element = { }
889                elif name == 'lan':
890                    self.lans.append(self.element)
891                    self.element = { }
892                elif name in self.str_subelements:
893                    self.element[name] = self.chars
894                    self.chars = ""
895                elif name in self.int_subelements:
896                    self.element[name] = int(self.chars)
897                    self.chars = ""
898                elif name in self.float_subelements:
899                    self.element[name] = float(self.chars)
900                    self.chars = ""
901
902            def found_chars(self, data):
903                self.chars += data.rstrip()
904
905
906        tp = topo_parse();
907        parser = xml.parsers.expat.ParserCreate()
908        parser.EndElementHandler = tp.end_element
909        parser.CharacterDataHandler = tp.found_chars
910
911        m = re.search('^#\s+Begin\s+Vtopo\s*$(.*)^#\s+End\s+Vtopo', splitout, 
912                re.MULTILINE | re.DOTALL)
913        if m:
914            str = m.group(1)
915        else:
916            sys.exit("Badly formatted split")
917
918        parser.Parse(str)
919
920        return tp.topo
921
922    def __call__(self):
923        """
924        The control flow.  Compose the request and print the response.
925        """
926        access_keys = []
927        # Process the options using the customized option parser defined above
928        parser = fedd_ns_image_opts(access_keys, self.add_ssh_key,
929                self.add_x509_cert)
930
931        (opts, args) = parser.parse_args()
932
933        if opts.trusted:
934            if ( not os.access(opts.trusted, os.R_OK) ) :
935                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
936
937        if opts.debug > 0: opts.tracefile=sys.stderr
938
939        (user, cert) = self.get_user_info([])
940
941        if opts.cert != None: cert = opts.cert
942
943        if cert == None:
944            sys.exit("No certificate given (--cert) or found")
945
946        if os.access(cert, os.R_OK):
947            fid = fedid(file=cert)
948        else:
949            sys.exit("Cannot read certificate (%s)" % cert)
950
951        if opts.file:
952            exp_desc = ""
953            try:
954                f = open(opts.file, 'r')
955                for line in f:
956                    exp_desc += line
957                f.close()
958            except IOError:
959                sys.exit("Cannot read description file (%s)" %opts.file)
960        else:
961            sys.exit("Must specify an experiment description (--file)")
962
963        if not opts.master:
964            opts.master="dummy"
965
966
967        req = {
968                'description': { 'ns2description': exp_desc },
969                'master': opts.master,
970                'include_fedkit': opts.fedkit,
971                'include_gatewaykit': opts.gatewaykit,
972                }
973
974
975        if opts.format and opts.outfile:
976            fmt = opts.format
977            file = opts.outfile
978        elif not opts.format and opts.outfile:
979            fmt = opts.outfile[-3:]
980            if fmt not in ("png", "jpg", "dot", "svg"):
981                sys.exit("Unexpected file type and no format specified")
982            file = opts.outfile
983        elif opts.format and not opts.outfile:
984            fmt = opts.format
985            file = None
986        else:
987            fmt="dot"
988            file = None
989
990
991        try:
992            resp_dict = self.do_rpc(req,
993                    opts.url, opts.transport, cert, opts.trusted, 
994                    serialize_only=opts.serialize_only,
995                    tracefile=opts.tracefile)
996        except self.RPCException, e:
997            exit_with_fault(\
998                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
999        except RuntimeError, e:
1000            print e
1001            sys.exit("Error processing RPC: %s" % e)
1002
1003
1004        if resp_dict.has_key('output'):
1005            if len(resp_dict['output']) < 1:
1006                sys.exit("Bad response: could not split")
1007            topo = self.generate_topo_dict(resp_dict['output'])
1008            self.gen_image(topo, file, fmt, opts.neato, opts.labels,
1009                    opts.pixels)
1010        else:
1011            sys.exit("Bad response. %s" % e.message)
1012
1013class terminate(fedd_rpc):
1014    def __init__(self): 
1015        """
1016        Termination request
1017        """
1018
1019        fedd_rpc.__init__(self, "Terminate")
1020
1021    def __call__(self):
1022        """
1023        The control flow.  Compose the request and print the response.
1024        """
1025        # Process the options using the customized option parser defined above
1026        parser = fedd_exp_data_opts()
1027
1028        (opts, args) = parser.parse_args()
1029
1030        (user, cert) = self.get_user_info([])
1031        if opts.trusted:
1032            if ( not os.access(opts.trusted, os.R_OK) ) :
1033                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
1034
1035        if opts.debug > 0: opts.tracefile=sys.stderr
1036
1037        if opts.cert != None: cert = opts.cert
1038
1039        if cert == None:
1040            sys.exit("No certificate given (--cert) or found")
1041
1042        if os.access(cert, os.R_OK):
1043            fid = fedid(file=cert)
1044        else:
1045            sys.exit("Cannot read certificate (%s)" % cert)
1046
1047        if opts.exp_name and opts.exp_certfile:
1048            sys.exit("Only one of --experiment_cert and " +\
1049                    "--experiment_name permitted");
1050
1051        exp_id = None
1052
1053        if opts.exp_certfile:
1054            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
1055
1056        if opts.exp_name:
1057            exp_id = { 'localname' : opts.exp_name }
1058
1059        if not exp_id:
1060            sys.exit("Must give one of --experiment_cert and " +\
1061                    "--experiment_name");
1062
1063        req = { 'experiment': exp_id }
1064
1065        try:
1066            resp_dict = self.do_rpc(req,
1067                    opts.url, opts.transport, cert, opts.trusted, 
1068                    serialize_only=opts.serialize_only,
1069                    tracefile=opts.tracefile)
1070        except self.RPCException, e:
1071            exit_with_fault(\
1072                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
1073        except RuntimeError, e:
1074            print e
1075            sys.exit("Error processing RPC: %s" % e)
1076
1077        eid = resp_dict.get('experimentID', None)
1078        if eid:
1079            for id in eid:
1080                for k in id.keys():
1081                    if k == 'fedid': print "%s: %s" % (k,fedid(bits=id[k]))
1082                    else: print "%s: %s" % (k, id[k])
1083
1084class create(fedd_rpc):
1085    def __init__(self): 
1086        fedd_rpc.__init__(self, "Create")
1087    def __call__(self):
1088        access_keys = []
1089        # Process the options using the customized option parser defined above
1090        parser = fedd_create_opts(access_keys, self.add_ssh_key,
1091                self.add_x509_cert)
1092
1093        (opts, args) = parser.parse_args()
1094
1095        if opts.trusted:
1096            if ( not os.access(opts.trusted, os.R_OK) ) :
1097                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
1098
1099        if not opts.project :
1100            parser.error('--project is required')
1101
1102        if opts.debug > 0: opts.tracefile=sys.stderr
1103
1104        (user, cert) = self.get_user_info(access_keys)
1105
1106        if opts.user: user = opts.user
1107
1108        if opts.cert != None: cert = opts.cert
1109
1110        if cert == None:
1111            sys.exit("No certificate given (--cert) or found")
1112
1113        if os.access(cert, os.R_OK):
1114            fid = fedid(file=cert)
1115            if opts.use_fedid == True:
1116                user = fid
1117        else:
1118            sys.exit("Cannot read certificate (%s)" % cert)
1119
1120        if opts.file:
1121            exp_desc = ""
1122            try:
1123                f = open(opts.file, 'r')
1124                for line in f:
1125                    exp_desc += line
1126                f.close()
1127            except IOError:
1128                sys.exit("Cannot read description file (%s)" %opts.file)
1129        else:
1130            sys.exit("Must specify an experiment description (--file)")
1131
1132        if not opts.master:
1133            sys.exit("Must specify a master testbed (--master)")
1134
1135        out_certfile = opts.out_certfile
1136
1137        msg = {
1138                'experimentdescription': { 'ns2description': exp_desc },
1139                'master': opts.master,
1140                'exportProject': { 'localname': opts.project },
1141                'user' : [ {\
1142                        'userID': pack_id(user), \
1143                        'access': [ { a.type: a.buf } for a in access_keys]\
1144                        } ]
1145                }
1146
1147        if opts.exp_name:
1148            msg['experimentID'] = { 'localname': opts.exp_name }
1149
1150        if opts.debug > 1: print >>sys.stderr, msg
1151
1152        try:
1153            resp_dict = self.do_rpc(msg, 
1154                    opts.url, opts.transport, cert, opts.trusted, 
1155                    serialize_only=opts.serialize_only,
1156                    tracefile=opts.tracefile)
1157        except self.RPCException, e:
1158            exit_with_fault(\
1159                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
1160        except RuntimeError, e:
1161            sys.exit("Error processing RPC: %s" % e)
1162
1163        if opts.debug > 1: print >>sys.stderr, resp_dict
1164
1165        ea = resp_dict.get('experimentAccess', None)
1166        if out_certfile and ea and ea.has_key('X509'):
1167            try:
1168                f = open(out_certfile, "w")
1169                print >>f, ea['X509']
1170                f.close()
1171            except IOError:
1172                sys.exit('Could not write to %s' %  out_certfile)
1173        eid = resp_dict.get('experimentID', None)
1174        if eid:
1175            for id in eid:
1176                for k in id.keys():
1177                    print "%s: %s" % (k, id[k])
1178        st = resp_dict.get('experimentStatus', None)
1179        if st:
1180            print "status: %s" % st
1181
1182class split(fedd_rpc):
1183    def __init__(self): 
1184        fedd_rpc.__init__(self, "Ns2Split")
1185    def __call__(self):
1186        access_keys = []
1187        # Process the options using the customized option parser defined above
1188        parser = fedd_split_opts(access_keys, self.add_ssh_key,
1189                self.add_x509_cert)
1190
1191        (opts, args) = parser.parse_args()
1192
1193        if opts.trusted:
1194            if ( not os.access(opts.trusted, os.R_OK) ) :
1195                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
1196
1197        if opts.debug > 0: opts.tracefile=sys.stderr
1198
1199        (user, cert) = self.get_user_info(access_keys)
1200
1201        if opts.cert != None: cert = opts.cert
1202
1203        if cert == None:
1204            sys.exit("No certificate given (--cert) or found")
1205
1206        if os.access(cert, os.R_OK):
1207            fid = fedid(file=cert)
1208            if opts.use_fedid == True:
1209                user = fid
1210        else:
1211            sys.exit("Cannot read certificate (%s)" % cert)
1212
1213        if opts.file:
1214            exp_desc = ""
1215            try:
1216                f = open(opts.file, 'r')
1217                for line in f:
1218                    exp_desc += line
1219                f.close()
1220            except IOError:
1221                sys.exit("Cannot read description file (%s)" %opts.file)
1222        else:
1223            sys.exit("Must specify an experiment description (--file)")
1224
1225        if not opts.master:
1226            sys.exit("Must specify a master testbed (--master)")
1227
1228        out_certfile = opts.out_certfile
1229
1230        msg = {
1231                'description': { 'ns2description': exp_desc },
1232                'master': opts.master,
1233                'include_fedkit': opts.fedkit,
1234                'include_gatewaykit': opts.gatewaykit,
1235                }
1236
1237        if opts.debug > 1: print >>sys.stderr, msg
1238
1239        try:
1240            resp_dict = self.do_rpc(msg, 
1241                    opts.url, opts.transport, cert, opts.trusted, 
1242                    serialize_only=opts.serialize_only,
1243                    tracefile=opts.tracefile)
1244        except self.RPCException, e:
1245            exit_with_fault(\
1246                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
1247        except RuntimeError, e:
1248            sys.exit("Error processing RPC: %s" % e)
1249
1250        if opts.debug > 1: print >>sys.stderr, resp_dict
1251
1252        out = resp_dict.get('output', None)
1253
1254        for line in out.splitlines():
1255            print "%s" % line
1256
1257class access(fedd_rpc):
1258    def __init__(self):
1259        fedd_rpc.__init__(self, "RequestAccess")
1260
1261    def print_response_as_testbed(self, resp, label, out=sys.stdout):
1262        """Print the response as input to the splitter script"""
1263
1264        e = resp['emulab']
1265        p = e['project']
1266        fields = { 
1267                "Boss": e['boss'],
1268                "OpsNode": e['ops'],
1269                "Domain": e['domain'],
1270                "FileServer": e['fileServer'],
1271                "EventServer": e['eventServer'],
1272                "Project": unpack_id(p['name'])
1273                }
1274        if (label != None): print >> out, "[%s]" % label
1275
1276        for l, v in fields.iteritems():
1277            print >>out, "%s: %s" % (l, v)
1278
1279        for u in p['user']:
1280            print >>out, "User: %s" % unpack_id(u['userID'])
1281
1282        for a in e['fedAttr']:
1283            print >>out, "%s: %s" % (a['attribute'], a['value'])
1284
1285    def __call__(self):
1286        access_keys = []
1287        node_descs = []
1288        proj = None
1289
1290        # Process the options using the customized option parser defined above
1291        parser = fedd_access_opts(access_keys, node_descs, self.add_ssh_key,
1292                self.add_x509_cert, self.add_node_desc)
1293
1294        (opts, args) = parser.parse_args()
1295
1296        if opts.testbed == None:
1297            parser.error("--testbed is required")
1298
1299        if opts.trusted:
1300            if ( not os.access(opts.trusted, os.R_OK) ) :
1301                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
1302
1303        if opts.debug > 0: opts.tracefile=sys.stderr
1304
1305        (user, cert) = self.get_user_info(access_keys)
1306
1307        if opts.user: user = opts.user
1308
1309        if opts.cert != None: cert = opts.cert
1310
1311        if cert == None:
1312            sys.exit("No certificate given (--cert) or found")
1313
1314        if os.access(cert, os.R_OK):
1315            fid = fedid(file=cert)
1316            if opts.use_fedid == True:
1317                user = fid
1318        else:
1319            sys.exit("Cannot read certificate (%s)" % cert)
1320
1321        msg = {
1322                'allocID': pack_id('test alloc'),
1323                'destinationTestbed': pack_id(opts.testbed),
1324                'serviceAccess' : [ { a.type: a.buf } for a in access_keys ],
1325                'createAccess' : [ { a.type: a.buf } for a in access_keys ],
1326                }
1327
1328        if len(node_descs) > 0:
1329            msg['resources'] = { 
1330                    'node': [ 
1331                        { 
1332                            'image':  n.image ,
1333                            'hardware':  n.hardware,
1334                            'count': n.count,
1335                        } for n in node_descs],
1336                    }
1337
1338        if opts.project != None:
1339            if not opts.anonymous and user != None:
1340                msg['project'] = {
1341                        'name': pack_id(opts.project),
1342                        'user': [ { 'userID': pack_id(user) } ],
1343                        }
1344            else:
1345                msg['project'] = { 'name': pack_id(opts.project) }
1346        else:
1347            if not opts.anonymous and user != None:
1348                msg['user'] = [ { 'userID': pack_id(user) } ]
1349            else:
1350                msg['user'] = [];
1351
1352        if opts.debug > 1: print >>sys.stderr, msg
1353
1354        try:
1355            resp_dict = self.do_rpc(msg, 
1356                    opts.url, opts.transport, cert, opts.trusted, 
1357                    serialize_only=opts.serialize_only,
1358                    tracefile=opts.tracefile)
1359        except self.RPCException, e:
1360            exit_with_fault(\
1361                    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
1362        except RuntimeError, e:
1363            sys.exit("Error processing RPC: %s" % e.message)
1364
1365        if opts.debug > 1: print >>sys.stderr, resp_dict
1366        self.print_response_as_testbed(resp_dict, opts.label)
1367
1368# Keep requesting experiment status and printing updates to the log until the
1369# experiment is done being created.
1370class spew_log(fedd_rpc):
1371    def __init__(self): 
1372        """
1373        Init the superclass
1374        """
1375
1376        fedd_rpc.__init__(self, 'Info')
1377
1378    def __call__(self):
1379        """
1380        The control flow.  Compose the request and print the response.
1381        """
1382        # Process the options using the customized option parser defined above
1383        parser = fedd_spew_opts()
1384
1385        (opts, args) = parser.parse_args()
1386
1387        if opts.trusted:
1388            if ( not os.access(opts.trusted, os.R_OK) ) :
1389                sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
1390
1391        if opts.debug > 0: opts.tracefile=sys.stderr
1392
1393        (user, cert) = self.get_user_info([])
1394
1395        if opts.cert != None: cert = opts.cert
1396
1397        if cert == None:
1398            sys.exit("No certificate given (--cert) or found")
1399
1400        if os.access(cert, os.R_OK):
1401            fid = fedid(file=cert)
1402        else:
1403            sys.exit("Cannot read certificate (%s)" % cert)
1404
1405        if opts.exp_name and opts.exp_certfile:
1406            sys.exit("Only one of --experiment_cert and " +\
1407                    "--experiment_name permitted");
1408
1409        if opts.exp_certfile:
1410            exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
1411
1412        if opts.exp_name:
1413            exp_id = { 'localname' : opts.exp_name }
1414
1415        if opts.logfile:
1416            try:
1417                out = open(opts.logfile, "w")
1418            except IOError,e:
1419                sys.exit("Cannot open logfile: %s" %e)
1420        else:
1421            out = sys.stdout
1422
1423        req = { 'experiment': exp_id }
1424
1425        status = "starting"
1426        log = ""
1427        log_offset = 0
1428        update = opts.update
1429        while status == 'starting':
1430            try:
1431                resp_dict = self.do_rpc(req,
1432                        opts.url, opts.transport, cert, opts.trusted, 
1433                        serialize_only=opts.serialize_only,
1434                        tracefile=opts.tracefile)
1435            except self.RPCException, e:
1436                exit_with_fault(\
1437                        {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
1438            except RuntimeError, e:
1439                sys.exit("Error processing RPC: %s" % e)
1440
1441            status = resp_dict.get('experimentStatus', None)
1442            log = resp_dict.get('allocationLog', None)
1443            if not status:
1444                sys.exit("No status in Info response??")
1445            if not log:
1446                sys.exit("No log in Info response??")
1447
1448            if len(log) > log_offset:
1449                print >>out, log[log_offset:],
1450                out.flush()
1451                log_offset = len(log)
1452            if status == 'starting': 
1453                time.sleep(update)
1454
1455        print >>out
1456        print >>out, status
1457        out.close()
1458
1459
1460cmds = {\
1461        'create': create(),\
1462        'split': split(),\
1463        'access': access(),\
1464        'vtopo': vtopo(),\
1465        'vis': vis(),\
1466        'info': exp_data(),\
1467        'multiinfo': multi_exp_data(),\
1468        'multistatus': multi_status(),\
1469        'image': image(),\
1470        'ns_image': ns_image(),\
1471        'status': status(),\
1472        'terminate': terminate(),\
1473        'spewlog': spew_log(),
1474    }
1475
1476operation = cmds.get(sys.argv[1], None)
1477if operation:
1478    del sys.argv[1]
1479    operation()
1480else:
1481    sys.exit("Bad command: %s.  Valid ones are: %s" % \
1482            (sys.argv[1], ", ".join(cmds.keys())))
1483
Note: See TracBrowser for help on using the repository browser.