source: fedd/federation/client_lib.py @ 62f3dd9

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

allow command line progams to expand tildes. Added a class derived from OptionParser? to make that easily available.

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