source: fedd/federation/client_lib.py @ c5869ef

axis_examplecompt_changesinfo-opsversion-3.01version-3.02
Last change on this file since c5869ef was d743d60, checked in by Ted Faber <faber@…>, 15 years ago

Totally refactor fedd_client.py into component scripts. The previous layout
have become a twisty hell of misdirected OOP and learning python run amok.
This version is actually pretty readable and will be much easier to build on.

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