source: fedd/federation/client_lib.py @ 3bf0b3c

axis_examplecompt_changesinfo-ops
Last change on this file since 3bf0b3c was 5d854e1, checked in by Ted Faber <faber@…>, 14 years ago

allow FEDD_URL in the environment to set the contact URL. Man am I sick of typing --url.

  • Property mode set to 100755
File size: 8.0 KB
RevLine 
[d743d60]1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6
7
8from fedid import fedid
9from util import fedd_ssl_context
10from remote_service import service_caller
11from service_error import service_error
12
13from optparse import OptionParser, OptionValueError
14
15
16class client_opts(OptionParser):
17    """
18    Standatd set of options that all clients talking to fedd can probably use.
19    Client code usually specializes this.
20    """
21    def __init__(self):
22        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
23                version="0.1")
24
25        self.add_option("--cert", action="store", dest="cert",
26                type="string", help="my certificate file")
27        self.add_option( "--debug", action="count", dest="debug", 
28                default=0, help="Set debug.  Repeat for more information")
29        self.add_option("--serialize_only", action="store_true", 
30                dest="serialize_only", default=False,
31                help="Print the SOAP request that would be sent and exit")
32        self.add_option("--trusted", action="store", dest="trusted",
33                type="string", help="Trusted certificates (required)")
34        self.add_option("--url", action="store", dest="url",
[5d854e1]35                type="string",default=None, help="URL to connect to")
[d743d60]36        self.add_option("--transport", action="store", type="choice",
37                choices=("xmlrpc", "soap"), default="soap",
38                help="Transport for request (xmlrpc|soap) (Default: %default)")
39        self.add_option("--trace", action="store_const", dest="tracefile", 
40                const=sys.stderr, help="Print SOAP exchange to stderr")
41
42def exit_with_fault(exc, out=sys.stderr):
43    """
44    Print an error message and exit.  exc is the RPCException that caused the
45    failure.
46    """
47    codestr = ""
48
49    if exc.errstr: codestr = "Error: %s" % exc.errstr
50    else: codestr = ""
51
52    if exc.code:
53        if isinstance(exc.code, int): code = exc.code
54        else: code = -1
55
56        if len(codestr) > 0 : codestr += " (%d)" % code
57        else: codestr = "Error Code: %d" % code
58    else:
59        code = -1
60
61    print>>out, codestr
62    print>>out, "Description: %s" % exc.desc
63    sys.exit(code)
64
65class RPCException(RuntimeError):
66    """
67    An error during the RPC exchange.  It unifies errors from both SOAP and
68    XMLPRC calls.
69    """
70    def __init__(self, desc=None, code=None, errstr=None):
71        RuntimeError.__init__(self)
72        self.desc = desc
73        if isinstance(code, int): self.code = code
74        else: self.code = -1
75        self.errstr = errstr
76
77
78def get_user_cert():
79    cert = os.path.expanduser("~/.ssl/emulab.pem")
80    if not os.access(cert, os.R_OK):
81        cert = None
82    return cert
83
84def wrangle_standard_options(opts):
85    """
86    Look for the certificate to use for the call (check for the standard emulab
87    file and any passed in.  Make sure a present cert file can be read and
88    generate a fedid from it.  Make sure that any trusted cert file can be read
89    and if the debug level is set, set the tracefile attribute as well.  If any
90    of these tests fail, raise a RuntimeError.  Otherwise return the
[5d854e1]91    certificate file, the fedid, and the fedd url.
[d743d60]92    """
[5d854e1]93    default_url="https://localhost:23235"
94
[d743d60]95    if opts.debug > 0: opts.tracefile=sys.stderr
96
97    if opts.trusted:
98        if ( not os.access(opts.trusted, os.R_OK) ) :
99            raise RuntimeError("Cannot read trusted certificates (%s)" \
100                    % opts.trusted)
101
102    cert = get_user_cert()
103    if opts.cert: cert = opts.cert
104
105    if cert is None:
106        raise RuntimeError("No certificate given (--cert) or found")
107
108    if os.access(cert, os.R_OK):
109        fid = fedid(file=cert)
110    else:
111        raise RuntimeError("Cannot read certificate (%s)" % cert)
112
[5d854e1]113    if opts.url: url = opts.url
114    elif 'FEDD_URL' in os.environ: url = os.environ['FEDD_URL']
115    else: url = default_url
116
117
118    return (cert, fid, url)
[d743d60]119
120def save_certfile(out_certfile, ea):
121    """
122    if the experiment authority section in ea has a certificate and the
123    out_certfile parameter has a place to put it, save the cert to the file.
124    EnvronnemtError s can come from the file operations.
125    """
126    if out_certfile and ea and 'X509' in ea:
127        f = open(out_certfile, "w")
128        print >>f, ea['X509']
129        f.close()
130
131
132def do_rpc(req_dict, url, transport, cert, trusted, tracefile=None,
133        serialize_only=False, caller=None, responseBody=None):
134    """
135    The work of sending and parsing the RPC as either XMLRPC or SOAP
136    """
137
138    if caller is None: 
139        raise RuntimeError("Must provide caller to do_rpc")
140
141    context = None
142    while context == None:
143        try:
144            context = fedd_ssl_context(cert, trusted)
145        except Exception, e:
146            # Yes, doing this on message type is not ideal.  The string
147            # comes from OpenSSL, so check there is this stops working.
148            if str(e) == "bad decrypt": 
149                print >>sys.stderr, "Bad Passphrase given."
150            else: raise
151
152    if transport == "soap":
153        if serialize_only:
154            print caller.serialize_soap(req_dict) 
155            return { }
156        else:
157            try:
158                resp = caller.call_soap_service(url, req_dict, 
159                        context=context, tracefile=tracefile)
160            except service_error, e:
161                raise RPCException(desc=e.desc, code=e.code, 
162                        errstr=e.code_string())
163    elif transport == "xmlrpc":
164        if serialize_only:
165            ser = dumps((req_dict,))
166            print ser
167            return { }
168        else:
169            try:
170                resp = caller.call_xmlrpc_service(url, req_dict, 
171                        context=context, tracefile=tracefile)
172            except service_error, e:
173                raise RPCException(desc=e.desc, code=e.code, 
174                        errstr=e.code_string())
175    else:
176        raise RuntimeError("Unknown RPC transport: %s" % transport)
177
178    if responseBody:
179        if responseBody in resp: return resp[responseBody]
180        else: raise RuntimeError("No body in response??")
181    else:
182        return resp
183
184def get_experiment_names(eid):
185    """
186    eid is the experimentID entry in one of a dict representing a fedd message.
187    Pull out the fedid and localname from that hash and return them as fedid,
188    localid)
189    """
190    fedid = None
191    local = None
192    for id in eid or []:
193        for k in id.keys():
194            if k =='fedid':
195                fedid = id[k]
196            elif k =='localname':
197                local = id[k]
198
199    return (fedid, local)
200
201class info_format:
202    def __init__(self, out=sys.stdout):
203        self.out = out
204        self.key = {
205                'vis': 'vis', 
206                'vtopo': 'vtopo',
207                'federant': 'federant',
208                'experimentdescription': 'experimentdescription',
209                'id': 'experimentID',
210                'status': 'experimentStatus',
211                'log': 'allocationLog',
[2fd8f8c]212                'embedding': 'embedding',
[d743d60]213            }
214        self.formatter = {
215                'vis':  self.print_vis_or_vtopo('vis', self.out),
216                'vtopo':  self.print_vis_or_vtopo('vtopo', self.out),
217                'federant':  self.print_xml,
218                'experimentdescription':  self.print_xml,
219                'id': self.print_id,
220                'status': self.print_string,
221                'log': self.print_string,
[2fd8f8c]222                'embedding':  self.print_xml,
[d743d60]223            }
224
225    def print_string(self, d):
226        """
227        Print the string to the class output.
228        """
229        print >>self.out, d
230
231    def print_id(self, d):
232        """
233        d is an array of ID dicts.  Print each one to the class output.
234        """
235        for id in d or []:
236            for k, i in id.items():
237                print >>self.out, "%s: %s" % (k, i)
238
239    def print_xml(self, d):
240        """
241        Very simple ugly xml formatter of the kinds of dicts that come back
242        from services.
243        """
244        if isinstance(d, dict):
245            for k, v in d.items():
246                print >>self.out, "<%s>" % k
247                self.print_xml(v)
248                print >>self.out, "</%s>" % k
249        elif isinstance(d, list):
250            for x in d:
251                self.print_xml(x)
252        else:
253            print >>self.out, d
254
255
256    class print_vis_or_vtopo:
257        """
258        Print the retrieved data is a simple xml representation of the dict.
259        """
260        def __init__(self, top, out=sys.stdout):
261            self.xml = top
262            self.out = out
263
264        def __call__(self, d, out=sys.stdout):
265            str = "<%s>\n" % self.xml
266            for t in ('node', 'lan'):
267                if d.has_key(t): 
268                    for x in d[t]:
269                        str += "<%s>" % t
270                        for k in x.keys():
271                            str += "<%s>%s</%s>" % (k, x[k],k)
272                        str += "</%s>\n" % t
273            str+= "</%s>" % self.xml
274            print >>self.out, str
275
276    def __call__(self, d, r):
277        """
278        Print the data of type r (one of the keys of key) to the class output.
279        """
280        k = self.key.get(r, None)
281        if k:
282            if k in d: self.formatter[r](d[k])
283            else: raise RuntimeError("Bad response: no %s" %k)
284        else:
285            raise RuntimeError("Don't understand datatype %s" %r)
286
Note: See TracBrowser for help on using the repository browser.