source: fedd/fedd_client.py @ 2d58549

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

fix parser error reporting

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