source: fedd/fedd_create.py @ b4e5366

axis_examplecompt_changesinfo-ops
Last change on this file since b4e5366 was 990b746, checked in by Ted Faber <faber@…>, 14 years ago

Properly deal with empty New messages. I swear I fixed this before.

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