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
RevLine 
[6ff0b91]1#!/usr/local/bin/python
2
3import sys
4import os
5import pwd
6
7from fedd_services import *
8
9from M2Crypto import SSL, X509
[329f61d]10from M2Crypto.m2xmlrpclib import SSL_Transport
[6ff0b91]11import M2Crypto.httpslib
[329f61d]12
13from xmlrpclib import ServerProxy, Error, dumps, loads
[8f91e66]14from ZSI import SoapWriter
[bb3769a]15from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any
16from ZSI.wstools.Namespaces import SOAP
17from ZSI.fault import FaultType, Detail
[6ff0b91]18
[2d58549]19import xmlrpclib
20
[2106ed1]21from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \
22        pack_id, unpack_id
[6ff0b91]23
24from optparse import OptionParser, OptionValueError
25
[bb3769a]26import parse_detail
27
[6ff0b91]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,
[329f61d]79                serialize_only=False, transport="soap", use_fedid=False,
80                debug=0)
[6ff0b91]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")
[8f91e66]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")
[6ff0b91]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")
[329f61d]119        self.add_option("-x","--transport", action="store", type="choice",
120                choices=("xmlrpc", "soap"),
121                help="Transport for request (xmlrpc|soap) (Default: %default)")
[6ff0b91]122        self.add_option("--trace", action="store_const", dest="tracefile", 
123                const=sys.stderr, help="Print SOAP exchange to stderr")
124
[2106ed1]125def print_response_as_testbed(resp, label, out=sys.stdout):
126    """Print the response as input to the splitter script"""
[6ff0b91]127
[2106ed1]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
[6ff0b91]139
[2106ed1]140    for l, v in fields.iteritems():
141        print >>out, "%s: %s" % (l, v)
[6ff0b91]142
[2106ed1]143    for u in p['user']:
144        print >>out, "User: %s" % unpack_id(u['userID'])
[6ff0b91]145
[2106ed1]146    for a in e['fedAttr']:
147        print >>out, "%s: %s" % (a['attribute'], a['value'])
[6ff0b91]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
[2d58549]202parser = fedd_client_opts()
203
204(opts, args) = parser.parse_args()
[6ff0b91]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.
[2106ed1]240        if str(e) == "bad decrypt": 
[6ff0b91]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
[2106ed1]274if opts.debug > 1: print >>sys.stderr, msg
[8f91e66]275
[bb3769a]276print FaultType.typecode;
277
[329f61d]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()
[6ff0b91]286
[329f61d]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)
[0a47d52]298    except ZSI.ParseException, e:
299        sys.exit("Malformed response (XMLPRC?): %s" % e)
[329f61d]300    except ZSI.FaultException, e:
[bb3769a]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)))
[329f61d]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)
[6ff0b91]334
[2106ed1]335
[329f61d]336    if (resp != None):
[6ff0b91]337        try:
[329f61d]338            print resp
339            resp_dict = resp[0]['RequestAccessResponseBody']
[2106ed1]340            if opts.debug > 1: print >>sys.stderr, resp_dict
341            print_response_as_testbed(resp_dict, opts.label)
[6ff0b91]342        except RuntimeError, e:
343            sys.exit("Bad response. %s" % e.messgae)
[329f61d]344    else: sys.exit("No response?!?")
Note: See TracBrowser for help on using the repository browser.