source: fedd/fedd_client.py @ 0c0b13c

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

cross transport proxy operations work!

  • Property mode set to 100755
File size: 10.9 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
[0c0b13c]149def exit_with_fault(dict, out=sys.stderr):
150    """ Print an error message and exit.
151
152    The dictionary contains the RequestAccessFaultBody elements."""
153    codestr = ""
154
155    if dict.has_key('errstr'):
156        codestr = "Error: %s" % dict['errstr']
157
158    if dict.has_key('code'):
159        if len(codestr) > 0 : 
160            codestr += " (%d)" % dict['code']
161        else:
162            codestr = "Error Code: %d" % dict['code']
163
164    print>>out, codestr
165    print>>out, "Description: %s" % dict['desc']
166    sys.exit(dict.get('code', 20))
167
[6ff0b91]168def add_ssh_key(option, opt_str, value, parser, access_keys):
169    try:
170        access_keys.append(access_method(file=value,
171            type=access_method.type_ssh))
172    except IOError, (errno, strerror):
173        raise OptionValueError("Cannot generate sshPubkey from %s: %s (%d)" %
174                (value,strerror,errno))
175
176def add_x509_cert(option, opt_str, value, parser, access_keys):
177    try:
178        access_keys.append(access_method(file=value,
179            type=access_method.type_x509))
180    except IOError, (errno, strerror):
181        raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
182                (value,strerror,errno))
183
184def add_node_desc(option, opt_str, value, parser, node_descs):
185    def none_if_zero(x):
186        if len(x) > 0: return x
187        else: return None
188
189    params = map(none_if_zero, value.split(":"));
190   
191    if len(params) < 4 and len(params) > 1:
192        node_descs.append(node_desc(*params))
193    else:
194        raise OptionValueError("Bad node description: %s" % value)
195
196def get_user_info(access_keys):
197    pw = pwd.getpwuid(os.getuid());
198    try_cert=None
199    user = None
200
201    if pw != None:
202        user = pw[0]
203        try_cert = "%s/.ssl/emulab.pem" % pw[5];
204        if not os.access(try_cert, os.R_OK):
205            try_cert = None
206        if len(access_keys) == 0:
207            for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
208                    "%s/.ssh/identity.pub"]:
209                try_key = k % pw[5];
210                if os.access(try_key, os.R_OK):
211                    access_keys.append(access_method(file=try_key,
212                        type=access_method.type_ssh))
213                    break
214    return (user, try_cert)
215
216access_keys = []
217node_descs = []
218proj = None
219
220# Process the options using the customized option parser defined above
[2d58549]221parser = fedd_client_opts()
222
223(opts, args) = parser.parse_args()
[6ff0b91]224
225if opts.testbed == None:
226    parser.error("--testbed is required")
227
228if opts.trusted != None:
229    if ( not os.access(opts.trusted, os.R_OK) ) :
230        sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
231else:
232    parser.error("--trusted is required")
233
234if opts.debug > 0: opts.tracefile=sys.stderr
235
236(user, cert) = get_user_info(access_keys)
237
238if opts.user: user = opts.user
239
240if opts.cert != None: cert = opts.cert
241
242if cert == None:
243    sys.exit("No certificate given (--cert) or found")
244
245if os.access(cert, os.R_OK):
246    fid = fedid(file=cert)
247    if opts.use_fedid == True:
248        user = fid
249else:
250    sys.exit("Cannot read certificate (%s)" % cert)
251
252context = None
253while context == None:
254    try:
255        context = fedd_ssl_context(cert, opts.trusted)
256    except SSL.SSLError, e:
257        # Yes, doing this on message type is not ideal.  The string comes from
258        # OpenSSL, so check there is this stops working.
[2106ed1]259        if str(e) == "bad decrypt": 
[6ff0b91]260            print >>sys.stderr, "Bad Passphrase given."
261        else: raise
262
263msg = {
264        'allocID': pack_id('test alloc'),
265        'destinationTestbed': pack_id(opts.testbed),
266        'access' : [ { a.type: a.buf } for a in access_keys ],
267        }
268
269if len(node_descs) > 0:
270    msg['resources'] = { 
271            'node': [ 
272                { 
273                    'image':  n.image ,
274                    'hardware':  n.hardware,
275                    'count': n.count,
276                } for n in node_descs],
277            }
278
279if opts.project != None:
280    if not opts.anonymous and user != None:
281        msg['project'] = {
282                'name': pack_id(opts.project),
283                'user': [ { 'userID': pack_id(user) } ],
284                }
285    else:
286        msg['project'] = { 'name': pack_id(opts.project) }
287else:
288    if not opts.anonymous and user != None:
289        msg['user'] = [ { 'userID': pack_id(user) } ]
290    else:
291        msg['user'] = [];
292
[2106ed1]293if opts.debug > 1: print >>sys.stderr, msg
[8f91e66]294
[329f61d]295if opts.transport == "soap":
296    loc = feddServiceLocator();
297    port = loc.getfeddPortType(opts.url,
298            transport=M2Crypto.httpslib.HTTPSConnection, 
299            transdict={ 'ssl_context' : context },
300            tracefile=opts.tracefile)
301
302    req = RequestAccessRequestMessage()
[6ff0b91]303
[329f61d]304    req.set_element_RequestAccessRequestBody(
305            pack_soap(req, "RequestAccessRequestBody", msg))
306
307    if opts.serialize_only:
308        sw = SoapWriter()
309        sw.serialize(req)
310        print str(sw)
311        sys.exit(0)
312
313    try:
314        resp = port.RequestAccess(req)
[0a47d52]315    except ZSI.ParseException, e:
316        sys.exit("Malformed response (XMLPRC?): %s" % e)
[329f61d]317    except ZSI.FaultException, e:
[0c0b13c]318        resp = e.fault.detail[0]
[329f61d]319
320    if (resp != None):
[0c0b13c]321        if 'get_element_RequestAccessResponseBody' in dir(resp): 
322            resp_body = resp.get_element_RequestAccessResponseBody()
323            if ( resp_body != None): 
324                try:
325                    resp_dict = unpack_soap(resp_body)
326                    if opts.debug > 1: print >>sys.stderr, resp_dict
327                    print_response_as_testbed(resp_dict, opts.label)
328                except RuntimeError, e:
329                    sys.exit("Bad response. %s" % e.message)
330        elif 'get_element_RequestAccessFaultBody' in dir(resp): 
331            resp_body = resp.get_element_RequestAccessFaultBody()
332            if resp_body != None: 
333                exit_with_fault(unpack_soap(resp_body))
334        else: sys.exit("No body in response!?")
[329f61d]335    else: sys.exit("No response?!?")
336elif opts.transport == "xmlrpc":
337    if opts.serialize_only:
338        ser = dumps((msg,))
339        sys.exit(0)
340
341    transport = SSL_Transport(context)
342    port = ServerProxy(opts.url, transport=transport)
343
344    try:
345        resp = port.RequestAccess({ 'RequestAccessRequestBody': msg})
346        resp, method = loads(resp)
347    except Error, e:
[0c0b13c]348        resp = { 'RequestAccessFaultBody': \
349                { 'errstr' : e.faultCode, 'desc' : e.faultString } }
[6ff0b91]350
[2106ed1]351
[329f61d]352    if (resp != None):
[0c0b13c]353        if resp.has_key('RequestAccessReponseBody'): 
354            try:
355                resp_dict = resp[0]['RequestAccessResponseBody']
356                if opts.debug > 1: print >>sys.stderr, resp_dict
357                print_response_as_testbed(resp_dict, opts.label)
358            except RuntimeError, e:
359                sys.exit("Bad response. %s" % e.messgae)
360        elif resp.has_key('RequestAccessFaultBody'):
361            exit_with_fault(resp['RequestAccessFaultBody'])
362        else: sys.exit("No body in response!?")
363
[329f61d]364    else: sys.exit("No response?!?")
Note: See TracBrowser for help on using the repository browser.