source: fedd/federation/client_lib.py @ 1553d27

version-3.02
Last change on this file since 1553d27 was 2fd8f8c, checked in by Ted Faber <faber@…>, 15 years ago

ftopo improvements

  • Property mode set to 100755
File size: 7.9 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                'embedding': 'embedding',
207            }
208        self.formatter = {
209                'vis':  self.print_vis_or_vtopo('vis', self.out),
210                'vtopo':  self.print_vis_or_vtopo('vtopo', self.out),
211                'federant':  self.print_xml,
212                'experimentdescription':  self.print_xml,
213                'id': self.print_id,
214                'status': self.print_string,
215                'log': self.print_string,
216                'embedding':  self.print_xml,
217            }
218
219    def print_string(self, d):
220        """
221        Print the string to the class output.
222        """
223        print >>self.out, d
224
225    def print_id(self, d):
226        """
227        d is an array of ID dicts.  Print each one to the class output.
228        """
229        for id in d or []:
230            for k, i in id.items():
231                print >>self.out, "%s: %s" % (k, i)
232
233    def print_xml(self, d):
234        """
235        Very simple ugly xml formatter of the kinds of dicts that come back
236        from services.
237        """
238        if isinstance(d, dict):
239            for k, v in d.items():
240                print >>self.out, "<%s>" % k
241                self.print_xml(v)
242                print >>self.out, "</%s>" % k
243        elif isinstance(d, list):
244            for x in d:
245                self.print_xml(x)
246        else:
247            print >>self.out, d
248
249
250    class print_vis_or_vtopo:
251        """
252        Print the retrieved data is a simple xml representation of the dict.
253        """
254        def __init__(self, top, out=sys.stdout):
255            self.xml = top
256            self.out = out
257
258        def __call__(self, d, out=sys.stdout):
259            str = "<%s>\n" % self.xml
260            for t in ('node', 'lan'):
261                if d.has_key(t): 
262                    for x in d[t]:
263                        str += "<%s>" % t
264                        for k in x.keys():
265                            str += "<%s>%s</%s>" % (k, x[k],k)
266                        str += "</%s>\n" % t
267            str+= "</%s>" % self.xml
268            print >>self.out, str
269
270    def __call__(self, d, r):
271        """
272        Print the data of type r (one of the keys of key) to the class output.
273        """
274        k = self.key.get(r, None)
275        if k:
276            if k in d: self.formatter[r](d[k])
277            else: raise RuntimeError("Bad response: no %s" %k)
278        else:
279            raise RuntimeError("Don't understand datatype %s" %r)
280
Note: See TracBrowser for help on using the repository browser.