source: fedd/fedd_client.py @ bb3769a

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since bb3769a was bb3769a, checked in by Ted Faber <faber@…>, 16 years ago

parsing SOAP faults (finally)

  • Property mode set to 100755
File size: 10.2 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6
7from fedd_services import *
8
9from M2Crypto import SSL, X509
10from M2Crypto.m2xmlrpclib import SSL_Transport
11import M2Crypto.httpslib
12
13from xmlrpclib import ServerProxy, Error, dumps, loads
14from ZSI import SoapWriter
15from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any
16from ZSI.wstools.Namespaces import SOAP
17from ZSI.fault import FaultType, Detail
18
19import xmlrpclib
20
21from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \
22        pack_id, unpack_id
23
24from optparse import OptionParser, OptionValueError
25
26import parse_detail
27
28# Turn off the matching of hostname to certificate ID
29SSL.Connection.clientPostConnectionCheck = None
30
31class IDFormatException(RuntimeError): pass
32
33class access_method:
34    """Encapsulates an access method generically."""
35    (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey')
36    default_type = type_ssh
37    def __init__(self, buf=None, type=None, file=None):
38        self.buf = buf
39
40        if type != None: self.type = type
41        else: self.type = access_method.default_type
42
43        if file != None:
44            self.readfile(file)
45   
46    def readfile(self, file, type=None):
47        f = open(file, "r")
48        self.buf = f.read();
49        f.close()
50        if type == None:
51            if self.type == None:
52                self.type = access_method.default_type
53        else:
54            self.type = type;
55   
56class node_desc:
57    def __init__(self, image, hardware, count=1):
58        if getattr(image, "__iter__", None) == None:
59            if image == None: self.image = [ ]
60            else: self.image = [ image ]
61        else:
62            self.image = image
63
64        if getattr(hardware, "__iter__", None) == None: 
65            if hardware == None: self.hardware = [ ]
66            else: self.hardware = [ hardware ]
67        else:
68            self.hardware = hardware
69        if count != None: self.count = int(count)
70        else: self.count = 1
71
72class fedd_client_opts(OptionParser):
73    """Encapsulate option processing in this class, rather than in main"""
74    def __init__(self):
75        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
76                version="0.1")
77
78        self.set_defaults(url="https://localhost:23235", anonymous=False,
79                serialize_only=False, transport="soap", use_fedid=False,
80                debug=0)
81
82        self.add_option("-a","--anonymous", action="store_true",
83                dest="anonymous", help="Do not include a user in the request")
84        self.add_option("-c","--cert", action="store", dest="cert",
85                type="string", help="my certificate file")
86        self.add_option("-d", "--debug", action="count", dest="debug", 
87                help="Set debug.  Repeat for more information")
88        self.add_option("-f","--useFedid", action="store_true",
89                dest="use_fedid",
90                help="Use a fedid derived from my certificate as user identity")
91        self.add_option("-k", "--sshKey", action="callback", type="string", 
92                callback=add_ssh_key, callback_args=(access_keys,),
93                help="ssh key for access (can be supplied more than once")
94        self.add_option("-K", "--x509Key", action="callback", type="string", 
95                callback=add_x509_cert, callback_args=(access_keys,),
96                help="X509 certificate for access " + \
97                        "(can be supplied more than once")
98        self.add_option("-l","--label", action="store", dest="label",
99                type="string", help="Label for output")
100        self.add_option("-n", "--node", action="callback", type="string", 
101                callback=add_node_desc, callback_args=(node_descs,),
102                help="Node description: image:hardware[:count]")
103        self.add_option("-p", "--project", action="store", dest="project", 
104                type="string",
105                help="Use a project request with this project name")
106        self.add_option("-s", "--serializeOnly", action="store_true", 
107                dest="serialize_only",
108                help="Print the SOAP request that would be sent and exit")
109        self.add_option("-t", "--testbed", action="store", dest="testbed",
110                type="string",
111                help="Testbed identifier (URI) to contact (required)")
112        self.add_option("-T","--trusted", action="store", dest="trusted",
113                type="string", help="Trusted certificates (required)")
114        self.add_option("-u", "--url", action="store", dest="url",
115                type="string",
116                help="URL to connect to (default %default)")
117        self.add_option("-U", "--username", action="store", dest="user",
118                type="string", help="Use this username instead of the uid")
119        self.add_option("-x","--transport", action="store", type="choice",
120                choices=("xmlrpc", "soap"),
121                help="Transport for request (xmlrpc|soap) (Default: %default)")
122        self.add_option("--trace", action="store_const", dest="tracefile", 
123                const=sys.stderr, help="Print SOAP exchange to stderr")
124
125def print_response_as_testbed(resp, label, out=sys.stdout):
126    """Print the response as input to the splitter script"""
127
128    e = resp['emulab']
129    p = e['project']
130    fields = { 
131            "Boss": e['boss'],
132            "OpsNode": e['ops'],
133            "Domain": e['domain'],
134            "FileServer": e['fileServer'],
135            "EventServer": e['eventServer'],
136            "Project": unpack_id(p['name'])
137            }
138    if (label != None): print >> out, "[%s]" % label
139
140    for l, v in fields.iteritems():
141        print >>out, "%s: %s" % (l, v)
142
143    for u in p['user']:
144        print >>out, "User: %s" % unpack_id(u['userID'])
145
146    for a in e['fedAttr']:
147        print >>out, "%s: %s" % (a['attribute'], a['value'])
148
149def add_ssh_key(option, opt_str, value, parser, access_keys):
150    try:
151        access_keys.append(access_method(file=value,
152            type=access_method.type_ssh))
153    except IOError, (errno, strerror):
154        raise OptionValueError("Cannot generate sshPubkey from %s: %s (%d)" %
155                (value,strerror,errno))
156
157def add_x509_cert(option, opt_str, value, parser, access_keys):
158    try:
159        access_keys.append(access_method(file=value,
160            type=access_method.type_x509))
161    except IOError, (errno, strerror):
162        raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
163                (value,strerror,errno))
164
165def add_node_desc(option, opt_str, value, parser, node_descs):
166    def none_if_zero(x):
167        if len(x) > 0: return x
168        else: return None
169
170    params = map(none_if_zero, value.split(":"));
171   
172    if len(params) < 4 and len(params) > 1:
173        node_descs.append(node_desc(*params))
174    else:
175        raise OptionValueError("Bad node description: %s" % value)
176
177def get_user_info(access_keys):
178    pw = pwd.getpwuid(os.getuid());
179    try_cert=None
180    user = None
181
182    if pw != None:
183        user = pw[0]
184        try_cert = "%s/.ssl/emulab.pem" % pw[5];
185        if not os.access(try_cert, os.R_OK):
186            try_cert = None
187        if len(access_keys) == 0:
188            for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
189                    "%s/.ssh/identity.pub"]:
190                try_key = k % pw[5];
191                if os.access(try_key, os.R_OK):
192                    access_keys.append(access_method(file=try_key,
193                        type=access_method.type_ssh))
194                    break
195    return (user, try_cert)
196
197access_keys = []
198node_descs = []
199proj = None
200
201# Process the options using the customized option parser defined above
202parser = fedd_client_opts()
203
204(opts, args) = parser.parse_args()
205
206if opts.testbed == None:
207    parser.error("--testbed is required")
208
209if opts.trusted != None:
210    if ( not os.access(opts.trusted, os.R_OK) ) :
211        sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
212else:
213    parser.error("--trusted is required")
214
215if opts.debug > 0: opts.tracefile=sys.stderr
216
217(user, cert) = get_user_info(access_keys)
218
219if opts.user: user = opts.user
220
221if opts.cert != None: cert = opts.cert
222
223if cert == None:
224    sys.exit("No certificate given (--cert) or found")
225
226if os.access(cert, os.R_OK):
227    fid = fedid(file=cert)
228    if opts.use_fedid == True:
229        user = fid
230else:
231    sys.exit("Cannot read certificate (%s)" % cert)
232
233context = None
234while context == None:
235    try:
236        context = fedd_ssl_context(cert, opts.trusted)
237    except SSL.SSLError, e:
238        # Yes, doing this on message type is not ideal.  The string comes from
239        # OpenSSL, so check there is this stops working.
240        if str(e) == "bad decrypt": 
241            print >>sys.stderr, "Bad Passphrase given."
242        else: raise
243
244msg = {
245        'allocID': pack_id('test alloc'),
246        'destinationTestbed': pack_id(opts.testbed),
247        'access' : [ { a.type: a.buf } for a in access_keys ],
248        }
249
250if len(node_descs) > 0:
251    msg['resources'] = { 
252            'node': [ 
253                { 
254                    'image':  n.image ,
255                    'hardware':  n.hardware,
256                    'count': n.count,
257                } for n in node_descs],
258            }
259
260if opts.project != None:
261    if not opts.anonymous and user != None:
262        msg['project'] = {
263                'name': pack_id(opts.project),
264                'user': [ { 'userID': pack_id(user) } ],
265                }
266    else:
267        msg['project'] = { 'name': pack_id(opts.project) }
268else:
269    if not opts.anonymous and user != None:
270        msg['user'] = [ { 'userID': pack_id(user) } ]
271    else:
272        msg['user'] = [];
273
274if opts.debug > 1: print >>sys.stderr, msg
275
276print FaultType.typecode;
277
278if opts.transport == "soap":
279    loc = feddServiceLocator();
280    port = loc.getfeddPortType(opts.url,
281            transport=M2Crypto.httpslib.HTTPSConnection, 
282            transdict={ 'ssl_context' : context },
283            tracefile=opts.tracefile)
284
285    req = RequestAccessRequestMessage()
286
287    req.set_element_RequestAccessRequestBody(
288            pack_soap(req, "RequestAccessRequestBody", msg))
289
290    if opts.serialize_only:
291        sw = SoapWriter()
292        sw.serialize(req)
293        print str(sw)
294        sys.exit(0)
295
296    try:
297        resp = port.RequestAccess(req)
298    except ZSI.ParseException, e:
299        sys.exit("Malformed response (XMLPRC?): %s" % e)
300    except ZSI.FaultException, e:
301        print dir(e.fault.detail[0])
302        print e.fault.detail[0].RequestAccessFaultBody.get_element_code()
303        d = unpack_soap(e.fault.detail[0].get_element_RequestAccessFaultBody())
304        f = unpack_soap(e.fault.detail[0])
305        print "unpack ", d
306        print "unpack ", f
307        sys.exit("Fault: %s %s" % (str(e.fault.code), str(e.fault.string)))
308
309
310    if (resp != None):
311        resp_body = resp.get_element_RequestAccessResponseBody()
312        if ( resp_body != None): 
313            try:
314                resp_dict = unpack_soap(resp_body)
315                if opts.debug > 1: print >>sys.stderr, resp_dict
316                print_response_as_testbed(resp_dict, opts.label)
317            except RuntimeError, e:
318                sys.exit("Bad response. %s" % e.messgae)
319        else: sys.exit("No body in resonpse!?")
320    else: sys.exit("No response?!?")
321elif opts.transport == "xmlrpc":
322    if opts.serialize_only:
323        ser = dumps((msg,))
324        sys.exit(0)
325
326    transport = SSL_Transport(context)
327    port = ServerProxy(opts.url, transport=transport)
328
329    try:
330        resp = port.RequestAccess({ 'RequestAccessRequestBody': msg})
331        resp, method = loads(resp)
332    except Error, e:
333        sys.exit("Fault: %s" % e)
334
335
336    if (resp != None):
337        try:
338            print resp
339            resp_dict = resp[0]['RequestAccessResponseBody']
340            if opts.debug > 1: print >>sys.stderr, resp_dict
341            print_response_as_testbed(resp_dict, opts.label)
342        except RuntimeError, e:
343            sys.exit("Bad response. %s" % e.messgae)
344    else: sys.exit("No response?!?")
Note: See TracBrowser for help on using the repository browser.