source: fedd/fedd_create.py @ a0c2866

axis_examplecompt_changesinfo-ops
Last change on this file since a0c2866 was a0c2866, checked in by Ted Faber <faber@…>, 13 years ago

Make sure there is an abac directory.

  • Property mode set to 100755
File size: 9.6 KB
Line 
1#!/usr/local/bin/python
2
3import sys, os
4import re
5import subprocess
6
7import ABAC
8
9from string import join
10
11from federation.fedid import fedid, generate_fedid
12from federation.remote_service import service_caller
13from federation.client_lib import client_opts, exit_with_fault, RPCException, \
14        wrangle_standard_options, do_rpc, get_experiment_names, save_certfile,\
15        get_abac_certs
16from federation.util import abac_split_cert, abac_context_to_creds
17from federation import topdl
18
19from xml.parsers.expat import ExpatError
20
21class fedd_create_opts(client_opts):
22    """
23    Extra options that create needs.  Help entries should explain them.
24    """
25    def __init__(self):
26        client_opts.__init__(self)
27        self.add_option("--experiment_cert", dest="out_certfile",
28                action="callback", callback=self.expand_file,
29                type="string", help="output certificate file")
30        self.add_option("--experiment_name", dest="exp_name",
31                type="string", help="Suggested experiment name")
32        self.add_option("--file", dest="file", action="callback",
33                callback=self.expand_file, type="str",
34                help="experiment description file")
35        self.add_option("--project", action="store", dest="project", 
36                type="string",
37                help="Project to export from master")
38        self.add_option("--master", dest="master",
39                help="Master testbed in the federation (pseudo project export)")
40        self.add_option("--service", dest="service", action="append",
41                type="string", default=[],
42                help="Service description name:exporters:importers:attrs")
43        self.add_option("--map", dest="map", action="append",
44                type="string", default=[],
45                help="Explicit map from testbed label to URI - " + \
46                        "deter:https://users.isi.deterlab/net:13232")
47        self.add_option('--gen_cert', action='store_true', dest='gen_cert',
48                default=False,
49                help='generate a cert to which to delegate rights')
50        self.add_option('--delegate', action='store_true', dest='generate',
51                help='Delegate rights to a generated cert (default)')
52        self.add_option('--no-delegate', action='store_true', dest='generate',
53                help='Do not delegate rights to a generated cert')
54
55        self.set_defaults(delegate=True)
56
57def parse_service(svc):
58    """
59    Pasre a service entry into a hash representing a service entry in a
60    message.  The format is:
61        svc_name:exporter(s):importer(s):attr=val,attr=val
62    The svc_name is teh service name, exporter is the exporting testbeds
63    (comma-separated) importer is the importing testbeds (if any) and the rest
64    are attr=val pairs that will become attributes of the service.  These
65    include parameters and other such stuff.
66    """
67
68    terms = svc.split(':')
69    svcd = { }
70    if len(terms) < 2 or len(terms[0]) == 0 or len(terms[1]) == 0:
71        sys.exit("Bad service description '%s': Not enough terms" % svc)
72   
73    svcd['name'] = terms[0]
74    svcd['export'] = terms[1].split(",")
75    if len(terms) > 2 and len(terms[2]) > 0:
76        svcd['import'] = terms[2].split(",")
77    if len(terms) > 3 and len(terms[3]) > 0:
78        svcd['fedAttr'] = [ ]
79        for t in terms[3].split(";"):
80            i = t.find("=")
81            if i != -1 :
82                svcd['fedAttr'].append(
83                        {'attribute': t[0:i], 'value': t[i+1:]})
84            else:
85                sys.exit("Bad service attribute '%s': no equals sign" % t)
86    return svcd
87
88def project_export_service(master, project):
89    """
90    Output the dict representation of a project_export service for this project
91    and master.
92    """
93    return {
94            'name': 'project_export', 
95            'export': [master], 
96            'importall': True,
97            'fedAttr': [ 
98                { 'attribute': 'project', 'value': project },
99                ],
100            }
101
102def delegate(fedid, cert, dir, name=None, debug=False, 
103        creddy='/usr/local/bin/creddy'):
104    '''
105    Make the creddy call to create an attribute delegating rights to the new
106    experiment.  The cert parameter points to a conventional cert & key combo,
107    which we split out into tempfiles, which we delete on return.  The return
108    value if the filename in which the certificate was stored.
109    '''
110    certfile = keyfile = None
111    expid = "%s" % fedid
112
113    # Trim the "fedid:"
114    if expid.startswith("fedid:"): expid = expid[6:]
115
116    try:
117        keyfile, certfile = abac_split_cert(cert)
118
119        rv = 0
120        if name: 
121            fn ='%s/%s_attr.der' % (dir, name) 
122            id_fn = '%s/%s_id.pem' % (dir, name)
123        else: 
124            fn = '%s/%s_attr.der' % (dir, expid)
125            id_fn = '%s/%s_id.pem' % (dir, expid)
126
127        cmd = [creddy, '--attribute', '--issuer=%s' % certfile, 
128                '--key=%s' % keyfile,
129                '--role=acting_for', '--subject-id=%s' % expid, 
130                '--out=%s' % fn ]
131        if not debug:
132            if subprocess.call(cmd) != 0:
133                return []
134        else:
135            print join(cmd)
136            return []
137
138        context = ABAC.Context()
139        if context.load_id_file(certfile) != ABAC.ABAC_CERT_SUCCESS or \
140                context.load_attribute_file(fn) != ABAC.ABAC_CERT_SUCCESS:
141            return []
142        ids, attrs = abac_context_to_creds(context)
143
144        return ids + attrs
145
146
147    finally:
148        if keyfile: os.unlink(keyfile)
149        if certfile: os.unlink(certfile)
150
151
152# Main line
153service_re = re.compile('^\\s*#\\s*SERVICE:\\s*([\\S]+)')
154parser = fedd_create_opts()
155(opts, args) = parser.parse_args()
156
157svcs = []
158# Option processing
159try:
160    cert, fid, url = wrangle_standard_options(opts)
161except RuntimeError, e:
162    sys.exit("%s" %e)
163
164if opts.file:
165
166    top = None
167    # Try the file as a topdl description
168    try:
169        top = topdl.topology_from_xml(filename=opts.file, top='experiment')
170    except EnvironmentError:
171        # Can't read the file, fail now
172        sys.exit("Cannot read description file (%s)" %opts.file)
173    except ExpatError:
174        # The file is not topdl, fall and assume it's ns2
175        pass
176
177    if top is None:
178        try:
179            lines = [ line for line in open(opts.file, 'r')]
180            exp_desc = "".join(lines)
181            # Parse all the strings that we can pull out of the file using the
182            # service_re.
183            svcs.extend([parse_service(service_re.match(l).group(1)) \
184                    for l in lines if service_re.match(l)])
185        except EnvironmentError:
186            sys.exit("Cannot read description file (%s)" %opts.file)
187else:
188    sys.exit("Must specify an experiment description (--file)")
189
190out_certfile = opts.out_certfile
191
192# If there is no abac directory in which to store a delegated credential, don't
193# delegate.
194if not opts.abac_dir and opts.delegate:
195    opts.delegate = False
196
197# Load ABAC certs
198if opts.abac_dir:
199    try:
200        acerts = get_abac_certs(opts.abac_dir)
201    except EnvironmentError, e:
202        sys.exit('%s: %s' % (e.filename, e.strerror))
203else:
204    acerts = None
205
206# Fill in services
207if opts.master and opts.project:
208    svcs.append(project_export_service(opts.master, opts.project))
209svcs.extend([ parse_service(s) for s in opts.service])
210
211# Create a testbed map if one is specified
212tbmap = { }
213for m in opts.map:
214    i = m.find(":")
215    if i != -1: tbmap[m[0:i]] = m[i+1:]
216    else: sys.exit("Bad mapping argument: %s" %m )
217
218
219if not svcs:
220    print >>sys.stderr, "Warning:Neither master/project nor services requested"
221
222# Construct the New experiment request
223msg = { }
224
225
226# Generate a certificate if requested and put it into the message
227if opts.gen_cert:
228    expid, expcert = generate_fedid(opts.exp_name or 'dummy')
229    msg['experimentAccess'] = { 'X509': expcert }
230else:
231    expid = expcert = None
232
233if opts.exp_name:
234    msg['experimentID'] = { 'localname': opts.exp_name }
235
236if acerts:
237    msg['credential'] = acerts
238
239if opts.debug > 1: print >>sys.stderr, msg
240
241# The New call
242try:
243    resp_dict = do_rpc(msg, 
244            url, opts.transport, cert, opts.trusted, 
245            serialize_only=opts.serialize_only,
246            tracefile=opts.tracefile,
247            caller=service_caller('New'), responseBody="NewResponseBody")
248except RPCException, e:
249    exit_with_fault(e)
250except RuntimeError, e:
251    sys.exit("Error processing RPC: %s" % e)
252
253if opts.debug > 1: print >>sys.stderr, resp_dict
254
255# Save the experiment ID certificate if we need it
256try:
257    save_certfile(opts.out_certfile, resp_dict.get('experimentAccess', None))
258except EnvironmentError, e:
259    sys.exit('Could not write to %s: %s' %  (opts.out_certfile, e))
260
261e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None))
262if not e_fedid and not e_local and opts.serialize_only:
263    e_local = "serialize"
264
265# If delegation is requested and we have a target, make the delegation, and add
266# the credential to acerts.
267if e_fedid and opts.delegate:
268    try:
269        creds = delegate(e_fedid, cert, opts.abac_dir, name=opts.exp_name)
270        if creds:
271            acerts.extend(creds)
272    except EnvironmentError, e:
273        sys.exit("Cannot delegate rights %s: %s" % (e.filename, e.strerror));
274
275# Construct the Create message
276if top: 
277    msg = { 'experimentdescription' : { 'topdldescription': top.to_dict() }, }
278else: 
279    msg = { 'experimentdescription': { 'ns2description': exp_desc }, }
280
281if svcs:
282    msg['service'] = svcs
283
284if e_fedid:
285    msg['experimentID'] = { 'fedid': e_fedid }
286elif e_local:
287    msg['experimentID'] = { 'localname': e_local }
288else:
289    sys.exit("New did not return an experiment ID??")
290
291if acerts:
292    msg['credential'] = acerts
293
294if tbmap:
295    msg['testbedmap'] = [ { 'testbed': t, 'uri': u } for t, u in tbmap.items() ]
296
297if opts.debug > 1: print >>sys.stderr, msg
298
299# make the call
300try:
301    resp_dict = do_rpc(msg, 
302            url, opts.transport, cert, opts.trusted, 
303            serialize_only=opts.serialize_only,
304            tracefile=opts.tracefile,
305            caller=service_caller('Create'), responseBody="CreateResponseBody")
306except RPCException, e:
307    exit_with_fault(e)
308except RuntimeError, e:
309    sys.exit("Error processing RPC: %s" % e)
310
311if opts.debug > 1: print >>sys.stderr, resp_dict
312
313# output
314e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None))
315st = resp_dict.get('experimentStatus', None)
316
317if e_local: print "localname: %s" % e_local
318if e_fedid: print "fedid: %s" % e_fedid
319if st: print "status: %s" % st
320
Note: See TracBrowser for help on using the repository browser.