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

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

Make sure there is an abac directory.

  • Property mode set to 100755
File size: 9.5 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
[62f3dd9]12from util import fedd_ssl_context, file_expanding_opts
[d743d60]13from remote_service import service_caller
14from service_error import service_error
15
16from optparse import OptionParser, OptionValueError
17
18
[62f3dd9]19class client_opts(file_expanding_opts):
[d743d60]20    """
21    Standatd set of options that all clients talking to fedd can probably use.
22    Client code usually specializes this.
23    """
[62f3dd9]24
[d743d60]25    def __init__(self):
[62f3dd9]26        file_expanding_opts.__init__(self,
27                usage="%prog [opts] (--help for details)",
[d743d60]28                version="0.1")
29
[62f3dd9]30        self.add_option("--cert", action="callback", dest="cert", 
31                callback=self.expand_file,
[d743d60]32                type="string", help="my certificate file")
[62f3dd9]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')
[d743d60]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")
[62f3dd9]44        self.add_option("--trusted", action="callback", dest="trusted",
45                callback=self.expand_file,
[d743d60]46                type="string", help="Trusted certificates (required)")
47        self.add_option("--url", action="store", dest="url",
[5d854e1]48                type="string",default=None, help="URL to connect to")
[d743d60]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
[7206e5a]90class CertificateMismatchError(RuntimeError): pass
91
[d743d60]92
93def get_user_cert():
[c573278]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
[d743d60]100    return cert
101
[d39809f]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 = [ ]
[62f3dd9]108    if dir and os.path.isdir(dir):
[d39809f]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')
[7206e5a]112            rv.append(f.read())
[d39809f]113            f.close()
114    return rv
115
[d743d60]116def wrangle_standard_options(opts):
117    """
118    Look for the certificate to use for the call (check for the standard emulab
[3ff5e2a]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.
[d743d60]124    """
[5d854e1]125    default_url="https://localhost:23235"
126
[d743d60]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
[5d854e1]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
[a0c2866]149    if opts.abac_dir:
150        if not os.access(opts.abac_dir, os.F_OK):
151            raise RuntimeError("No ABAC directory: %s" % opts.abac_dir)
152        elif not os.path.isdir(opts.abac_dir):
153            raise RuntimeError("ABAC directory not a directory: %s" \
154                    % opts.abac_dir)
155        elif not os.access(opts.abac_dir, os.W_OK):
156            raise RuntimeError("Cannot write to ABAC directory: %s" \
157                    % opts.abac_dir)
158
159
[5d854e1]160
161    return (cert, fid, url)
[d743d60]162
[7206e5a]163def save_certfile(out_certfile, ea, check_cert=None):
[d743d60]164    """
165    if the experiment authority section in ea has a certificate and the
166    out_certfile parameter has a place to put it, save the cert to the file.
[7206e5a]167    EnvronmentError s can come from the file operations.  If check_cert is
168    given, the certificate in ea is compared with it and if they are not equal,
169    a CertificateMismatchError is raised.
[d743d60]170    """
171    if out_certfile and ea and 'X509' in ea:
[7206e5a]172        out_cert = ea['X509']
173        if check_cert and check_cert != out_cert:
174            raise CertificateMismatchError()
[d743d60]175        f = open(out_certfile, "w")
[7206e5a]176        f.write(out_cert)
[d743d60]177        f.close()
178
179
180def do_rpc(req_dict, url, transport, cert, trusted, tracefile=None,
181        serialize_only=False, caller=None, responseBody=None):
182    """
183    The work of sending and parsing the RPC as either XMLRPC or SOAP
184    """
185
186    if caller is None: 
187        raise RuntimeError("Must provide caller to do_rpc")
188
189    context = None
190    while context == None:
191        try:
192            context = fedd_ssl_context(cert, trusted)
193        except Exception, e:
194            # Yes, doing this on message type is not ideal.  The string
195            # comes from OpenSSL, so check there is this stops working.
196            if str(e) == "bad decrypt": 
197                print >>sys.stderr, "Bad Passphrase given."
198            else: raise
199
200    if transport == "soap":
201        if serialize_only:
202            print caller.serialize_soap(req_dict) 
203            return { }
204        else:
205            try:
206                resp = caller.call_soap_service(url, req_dict, 
207                        context=context, tracefile=tracefile)
208            except service_error, e:
209                raise RPCException(desc=e.desc, code=e.code, 
210                        errstr=e.code_string())
211    elif transport == "xmlrpc":
212        if serialize_only:
213            ser = dumps((req_dict,))
214            print ser
215            return { }
216        else:
217            try:
218                resp = caller.call_xmlrpc_service(url, req_dict, 
219                        context=context, tracefile=tracefile)
220            except service_error, e:
221                raise RPCException(desc=e.desc, code=e.code, 
222                        errstr=e.code_string())
223    else:
224        raise RuntimeError("Unknown RPC transport: %s" % transport)
225
226    if responseBody:
227        if responseBody in resp: return resp[responseBody]
228        else: raise RuntimeError("No body in response??")
229    else:
230        return resp
231
232def get_experiment_names(eid):
233    """
234    eid is the experimentID entry in one of a dict representing a fedd message.
235    Pull out the fedid and localname from that hash and return them as fedid,
236    localid)
237    """
238    fedid = None
239    local = None
240    for id in eid or []:
241        for k in id.keys():
242            if k =='fedid':
243                fedid = id[k]
244            elif k =='localname':
245                local = id[k]
246
247    return (fedid, local)
248
249class info_format:
250    def __init__(self, out=sys.stdout):
251        self.out = out
252        self.key = {
253                'vis': 'vis', 
254                'vtopo': 'vtopo',
255                'federant': 'federant',
256                'experimentdescription': 'experimentdescription',
257                'id': 'experimentID',
258                'status': 'experimentStatus',
259                'log': 'allocationLog',
[2fd8f8c]260                'embedding': 'embedding',
[d743d60]261            }
262        self.formatter = {
263                'vis':  self.print_vis_or_vtopo('vis', self.out),
264                'vtopo':  self.print_vis_or_vtopo('vtopo', self.out),
265                'federant':  self.print_xml,
266                'experimentdescription':  self.print_xml,
267                'id': self.print_id,
268                'status': self.print_string,
269                'log': self.print_string,
[2fd8f8c]270                'embedding':  self.print_xml,
[d743d60]271            }
272
273    def print_string(self, d):
274        """
275        Print the string to the class output.
276        """
277        print >>self.out, d
278
279    def print_id(self, d):
280        """
281        d is an array of ID dicts.  Print each one to the class output.
282        """
283        for id in d or []:
284            for k, i in id.items():
285                print >>self.out, "%s: %s" % (k, i)
286
287    def print_xml(self, d):
288        """
289        Very simple ugly xml formatter of the kinds of dicts that come back
290        from services.
291        """
292        if isinstance(d, dict):
293            for k, v in d.items():
294                print >>self.out, "<%s>" % k
295                self.print_xml(v)
296                print >>self.out, "</%s>" % k
297        elif isinstance(d, list):
298            for x in d:
299                self.print_xml(x)
300        else:
301            print >>self.out, d
302
303
304    class print_vis_or_vtopo:
305        """
306        Print the retrieved data is a simple xml representation of the dict.
307        """
308        def __init__(self, top, out=sys.stdout):
309            self.xml = top
310            self.out = out
311
312        def __call__(self, d, out=sys.stdout):
313            str = "<%s>\n" % self.xml
314            for t in ('node', 'lan'):
315                if d.has_key(t): 
316                    for x in d[t]:
317                        str += "<%s>" % t
318                        for k in x.keys():
319                            str += "<%s>%s</%s>" % (k, x[k],k)
320                        str += "</%s>\n" % t
321            str+= "</%s>" % self.xml
322            print >>self.out, str
323
324    def __call__(self, d, r):
325        """
326        Print the data of type r (one of the keys of key) to the class output.
327        """
328        k = self.key.get(r, None)
329        if k:
330            if k in d: self.formatter[r](d[k])
331            else: raise RuntimeError("Bad response: no %s" %k)
332        else:
333            raise RuntimeError("Don't understand datatype %s" %r)
334
Note: See TracBrowser for help on using the repository browser.