source: fedd/federation/client_lib.py @ 814b5e5

axis_examplecompt_changesinfo-ops
Last change on this file since 814b5e5 was 7206e5a, checked in by Ted Faber <faber@…>, 14 years ago

checkpoint: new works pretty well

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