source: fedd/fedd_client.py @ 329f61d

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

basic XMLRPC

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