source: fedd/fedd_client.py @ 4a6f04b

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 4a6f04b 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
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 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
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
221parser = fedd_client_opts()
222
223(opts, args) = parser.parse_args()
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.
259        if str(e) == "bad decrypt": 
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
293if opts.debug > 1: print >>sys.stderr, msg
294
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()
303
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)
315    except ZSI.ParseException, e:
316        sys.exit("Malformed response (XMLPRC?): %s" % e)
317    except ZSI.FaultException, e:
318        resp = e.fault.detail[0]
319
320    if (resp != None):
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!?")
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:
348        resp = { 'RequestAccessFaultBody': \
349                { 'errstr' : e.faultCode, 'desc' : e.faultString } }
350
351
352    if (resp != None):
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
364    else: sys.exit("No response?!?")
Note: See TracBrowser for help on using the repository browser.