source: fedd/federation/client_lib.py @ c573278

axis_examplecompt_changesinfo-ops
Last change on this file since c573278 was c573278, checked in by Ted Faber <faber@…>, 13 years ago

Checkpoint. Still lots to do

  • Property mode set to 100755
File size: 8.8 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4import pwd
5import os
6import os.path
7
8from string import join
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")
30        self.add_option("--abac", action="store", dest="abac_dir",
31                type="string", help="Directory with abac certs")
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",
40                type="string",default=None, help="URL to connect to")
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
82class CertificateMismatchError(RuntimeError): pass
83
84
85def get_user_cert():
86    for c in ("~/.ssl/fedid.pem", "~/.ssl/emulab.pem"):
87        cert = os.path.expanduser(c)
88        if os.access(cert, os.R_OK):
89            break
90        else:
91            cert = None
92    return cert
93
94def get_abac_certs(dir):
95    '''
96    Return a list of the contents of the files in dir.  These should be abac
97    certificates, but that isn't checked.
98    '''
99    rv = [ ]
100    if dir:
101        for fn in ["%s/%s" % (dir, p) for p in os.listdir(dir) \
102                if os.path.isfile("%s/%s" % (dir,p))]:
103            f = open(fn, 'r')
104            rv.append(f.read())
105            f.close()
106    return rv
107
108def wrangle_standard_options(opts):
109    """
110    Look for the certificate to use for the call (check for the standard emulab
111    file and any passed in.  Make sure a present cert file can be read.  Make
112    sure that any trusted cert file can be read and if the debug level is set,
113    set the tracefile attribute as well.  If any of these tests fail, raise a
114    RuntimeError.  Otherwise return the certificate file, the fedid, and the
115    fedd url.
116    """
117    default_url="https://localhost:23235"
118
119    if opts.debug > 0: opts.tracefile=sys.stderr
120
121    if opts.trusted:
122        if ( not os.access(opts.trusted, os.R_OK) ) :
123            raise RuntimeError("Cannot read trusted certificates (%s)" \
124                    % opts.trusted)
125
126    cert = get_user_cert()
127    if opts.cert: cert = opts.cert
128
129    if cert is None:
130        raise RuntimeError("No certificate given (--cert) or found")
131
132    if os.access(cert, os.R_OK):
133        fid = fedid(file=cert)
134    else:
135        raise RuntimeError("Cannot read certificate (%s)" % cert)
136
137    if opts.url: url = opts.url
138    elif 'FEDD_URL' in os.environ: url = os.environ['FEDD_URL']
139    else: url = default_url
140
141
142    return (cert, fid, url)
143
144def save_certfile(out_certfile, ea, check_cert=None):
145    """
146    if the experiment authority section in ea has a certificate and the
147    out_certfile parameter has a place to put it, save the cert to the file.
148    EnvronmentError s can come from the file operations.  If check_cert is
149    given, the certificate in ea is compared with it and if they are not equal,
150    a CertificateMismatchError is raised.
151    """
152    if out_certfile and ea and 'X509' in ea:
153        out_cert = ea['X509']
154        if check_cert and check_cert != out_cert:
155            raise CertificateMismatchError()
156        f = open(out_certfile, "w")
157        f.write(out_cert)
158        f.close()
159
160
161def do_rpc(req_dict, url, transport, cert, trusted, tracefile=None,
162        serialize_only=False, caller=None, responseBody=None):
163    """
164    The work of sending and parsing the RPC as either XMLRPC or SOAP
165    """
166
167    if caller is None: 
168        raise RuntimeError("Must provide caller to do_rpc")
169
170    context = None
171    while context == None:
172        try:
173            context = fedd_ssl_context(cert, trusted)
174        except Exception, e:
175            # Yes, doing this on message type is not ideal.  The string
176            # comes from OpenSSL, so check there is this stops working.
177            if str(e) == "bad decrypt": 
178                print >>sys.stderr, "Bad Passphrase given."
179            else: raise
180
181    if transport == "soap":
182        if serialize_only:
183            print caller.serialize_soap(req_dict) 
184            return { }
185        else:
186            try:
187                resp = caller.call_soap_service(url, req_dict, 
188                        context=context, tracefile=tracefile)
189            except service_error, e:
190                raise RPCException(desc=e.desc, code=e.code, 
191                        errstr=e.code_string())
192    elif transport == "xmlrpc":
193        if serialize_only:
194            ser = dumps((req_dict,))
195            print ser
196            return { }
197        else:
198            try:
199                resp = caller.call_xmlrpc_service(url, req_dict, 
200                        context=context, tracefile=tracefile)
201            except service_error, e:
202                raise RPCException(desc=e.desc, code=e.code, 
203                        errstr=e.code_string())
204    else:
205        raise RuntimeError("Unknown RPC transport: %s" % transport)
206
207    if responseBody:
208        if responseBody in resp: return resp[responseBody]
209        else: raise RuntimeError("No body in response??")
210    else:
211        return resp
212
213def get_experiment_names(eid):
214    """
215    eid is the experimentID entry in one of a dict representing a fedd message.
216    Pull out the fedid and localname from that hash and return them as fedid,
217    localid)
218    """
219    fedid = None
220    local = None
221    for id in eid or []:
222        for k in id.keys():
223            if k =='fedid':
224                fedid = id[k]
225            elif k =='localname':
226                local = id[k]
227
228    return (fedid, local)
229
230class info_format:
231    def __init__(self, out=sys.stdout):
232        self.out = out
233        self.key = {
234                'vis': 'vis', 
235                'vtopo': 'vtopo',
236                'federant': 'federant',
237                'experimentdescription': 'experimentdescription',
238                'id': 'experimentID',
239                'status': 'experimentStatus',
240                'log': 'allocationLog',
241                'embedding': 'embedding',
242            }
243        self.formatter = {
244                'vis':  self.print_vis_or_vtopo('vis', self.out),
245                'vtopo':  self.print_vis_or_vtopo('vtopo', self.out),
246                'federant':  self.print_xml,
247                'experimentdescription':  self.print_xml,
248                'id': self.print_id,
249                'status': self.print_string,
250                'log': self.print_string,
251                'embedding':  self.print_xml,
252            }
253
254    def print_string(self, d):
255        """
256        Print the string to the class output.
257        """
258        print >>self.out, d
259
260    def print_id(self, d):
261        """
262        d is an array of ID dicts.  Print each one to the class output.
263        """
264        for id in d or []:
265            for k, i in id.items():
266                print >>self.out, "%s: %s" % (k, i)
267
268    def print_xml(self, d):
269        """
270        Very simple ugly xml formatter of the kinds of dicts that come back
271        from services.
272        """
273        if isinstance(d, dict):
274            for k, v in d.items():
275                print >>self.out, "<%s>" % k
276                self.print_xml(v)
277                print >>self.out, "</%s>" % k
278        elif isinstance(d, list):
279            for x in d:
280                self.print_xml(x)
281        else:
282            print >>self.out, d
283
284
285    class print_vis_or_vtopo:
286        """
287        Print the retrieved data is a simple xml representation of the dict.
288        """
289        def __init__(self, top, out=sys.stdout):
290            self.xml = top
291            self.out = out
292
293        def __call__(self, d, out=sys.stdout):
294            str = "<%s>\n" % self.xml
295            for t in ('node', 'lan'):
296                if d.has_key(t): 
297                    for x in d[t]:
298                        str += "<%s>" % t
299                        for k in x.keys():
300                            str += "<%s>%s</%s>" % (k, x[k],k)
301                        str += "</%s>\n" % t
302            str+= "</%s>" % self.xml
303            print >>self.out, str
304
305    def __call__(self, d, r):
306        """
307        Print the data of type r (one of the keys of key) to the class output.
308        """
309        k = self.key.get(r, None)
310        if k:
311            if k in d: self.formatter[r](d[k])
312            else: raise RuntimeError("Bad response: no %s" %k)
313        else:
314            raise RuntimeError("Don't understand datatype %s" %r)
315
Note: See TracBrowser for help on using the repository browser.