source: fedd/federation/client_lib.py @ d39809f

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

Add get_abac_certs a function to get the certificates from a directory

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