source: fedd/fedd_client.py @ 6ff0b91

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

Reimplementation of fedd as a start in merging the codebases

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