source: fedd/fedd_create.py @ 62f3dd9

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

allow command line progams to expand tildes. Added a class derived from OptionParser? to make that easily available.

  • Property mode set to 100755
File size: 9.5 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
159cert, fid, url = wrangle_standard_options(opts)
160
161if opts.file:
162
163    top = None
164    # Try the file as a topdl description
165    try:
166        top = topdl.topology_from_xml(filename=opts.file, top='experiment')
167    except EnvironmentError:
168        # Can't read the file, fail now
169        sys.exit("Cannot read description file (%s)" %opts.file)
170    except ExpatError:
171        # The file is not topdl, fall and assume it's ns2
172        pass
173
174    if top is None:
175        try:
176            lines = [ line for line in open(opts.file, 'r')]
177            exp_desc = "".join(lines)
178            # Parse all the strings that we can pull out of the file using the
179            # service_re.
180            svcs.extend([parse_service(service_re.match(l).group(1)) \
181                    for l in lines if service_re.match(l)])
182        except EnvironmentError:
183            sys.exit("Cannot read description file (%s)" %opts.file)
184else:
185    sys.exit("Must specify an experiment description (--file)")
186
187out_certfile = opts.out_certfile
188
189# If there is no abac directory in which to store a delegated credential, don't
190# delegate.
191if not opts.abac_dir and opts.delegate:
192    opts.delegate = False
193
194# Load ABAC certs
195if opts.abac_dir:
196    try:
197        acerts = get_abac_certs(opts.abac_dir)
198    except EnvironmentError, e:
199        sys.exit('%s: %s' % (e.filename, e.strerror))
200else:
201    acerts = None
202
203# Fill in services
204if opts.master and opts.project:
205    svcs.append(project_export_service(opts.master, opts.project))
206svcs.extend([ parse_service(s) for s in opts.service])
207
208# Create a testbed map if one is specified
209tbmap = { }
210for m in opts.map:
211    i = m.find(":")
212    if i != -1: tbmap[m[0:i]] = m[i+1:]
213    else: sys.exit("Bad mapping argument: %s" %m )
214
215
216if not svcs:
217    print >>sys.stderr, "Warning:Neither master/project nor services requested"
218
219# Construct the New experiment request
220msg = { }
221
222
223# Generate a certificate if requested and put it into the message
224if opts.gen_cert:
225    expid, expcert = generate_fedid(opts.exp_name or 'dummy')
226    msg['experimentAccess'] = { 'X509': expcert }
227else:
228    expid = expcert = None
229
230if opts.exp_name:
231    msg['experimentID'] = { 'localname': opts.exp_name }
232
233if acerts:
234    msg['credential'] = acerts
235
236if opts.debug > 1: print >>sys.stderr, msg
237
238# The New call
239try:
240    resp_dict = do_rpc(msg, 
241            url, opts.transport, cert, opts.trusted, 
242            serialize_only=opts.serialize_only,
243            tracefile=opts.tracefile,
244            caller=service_caller('New'), responseBody="NewResponseBody")
245except RPCException, e:
246    exit_with_fault(e)
247except RuntimeError, e:
248    sys.exit("Error processing RPC: %s" % e)
249
250if opts.debug > 1: print >>sys.stderr, resp_dict
251
252# Save the experiment ID certificate if we need it
253try:
254    save_certfile(opts.out_certfile, resp_dict.get('experimentAccess', None))
255except EnvironmentError, e:
256    sys.exit('Could not write to %s: %s' %  (opts.out_certfile, e))
257
258e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None))
259if not e_fedid and not e_local and opts.serialize_only:
260    e_local = "serialize"
261
262# If delegation is requested and we have a target, make the delegation, and add
263# the credential to acerts.
264if e_fedid and opts.delegate:
265    try:
266        creds = delegate(e_fedid, cert, opts.abac_dir, name=opts.exp_name)
267        if creds:
268            acerts.extend(creds)
269    except EnvironmentError, e:
270        sys.exit("Cannot delegate rights %s: %s" % (e.filename, e.strerror));
271
272# Construct the Create message
273if top: 
274    msg = { 'experimentdescription' : { 'topdldescription': top.to_dict() }, }
275else: 
276    msg = { 'experimentdescription': { 'ns2description': exp_desc }, }
277
278if svcs:
279    msg['service'] = svcs
280
281if e_fedid:
282    msg['experimentID'] = { 'fedid': e_fedid }
283elif e_local:
284    msg['experimentID'] = { 'localname': e_local }
285else:
286    sys.exit("New did not return an experiment ID??")
287
288if acerts:
289    msg['credential'] = acerts
290
291if tbmap:
292    msg['testbedmap'] = [ { 'testbed': t, 'uri': u } for t, u in tbmap.items() ]
293
294if opts.debug > 1: print >>sys.stderr, msg
295
296# make the call
297try:
298    resp_dict = do_rpc(msg, 
299            url, opts.transport, cert, opts.trusted, 
300            serialize_only=opts.serialize_only,
301            tracefile=opts.tracefile,
302            caller=service_caller('Create'), responseBody="CreateResponseBody")
303except RPCException, e:
304    exit_with_fault(e)
305except RuntimeError, e:
306    sys.exit("Error processing RPC: %s" % e)
307
308if opts.debug > 1: print >>sys.stderr, resp_dict
309
310# output
311e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None))
312st = resp_dict.get('experimentStatus', None)
313
314if e_local: print "localname: %s" % e_local
315if e_fedid: print "fedid: %s" % e_fedid
316if st: print "status: %s" % st
317
Note: See TracBrowser for help on using the repository browser.