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
Line 
1#!/usr/local/bin/python
2
3import sys, os
4import re
5import subprocess
6
7import ABAC
8
9from string import join, ascii_letters
10from random import choice
11
12from federation.proof import proof
13from federation.fedid import fedid, generate_fedid
14from federation.remote_service import service_caller
15from federation.client_lib import client_opts, exit_with_fault, RPCException, \
16        wrangle_standard_options, do_rpc, get_experiment_names, save_certfile,\
17        get_abac_certs, log_authentication
18from federation.util import abac_split_cert, abac_context_to_creds
19from federation import topdl
20
21from xml.parsers.expat import ExpatError
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",
30                action="callback", callback=self.expand_file,
31                type="string", help="output certificate file")
32        self.add_option("--experiment_name", dest="exp_name",
33                type="string", help="Suggested experiment name")
34        self.add_option("--file", dest="file", action="callback",
35                callback=self.expand_file, type="str",
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")
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")
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
57        self.set_defaults(delegate=True)
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'] = [ ]
81        for t in terms[3].split(";"):
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
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:
119        keyfile, certfile = abac_split_cert(cert)
120
121        rv = 0
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)
128
129        cmd = [creddy, '--attribute', '--issuer=%s' % certfile, 
130                '--key=%s' % keyfile,
131                '--role=acting_for', '--subject-id=%s' % expid, 
132                '--out=%s' % fn ]
133        if not debug:
134            if subprocess.call(cmd) != 0:
135                return []
136        else:
137            print join(cmd)
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
147
148
149    finally:
150        if keyfile: os.unlink(keyfile)
151        if certfile: os.unlink(certfile)
152
153
154# Main line
155service_re = re.compile('^\\s*#\\s*SERVICE:\\s*([\\S]+)')
156parser = fedd_create_opts()
157(opts, args) = parser.parse_args()
158
159svcs = []
160# Option processing
161try:
162    cert, fid, url = wrangle_standard_options(opts)
163except RuntimeError, e:
164    sys.exit("%s" %e)
165
166if opts.file:
167
168    top = None
169    # Try the file as a topdl description
170    try:
171        top = topdl.topology_from_xml(filename=opts.file, top='experiment')
172    except EnvironmentError:
173        # Can't read the file, fail now
174        sys.exit("Cannot read description file (%s)" %opts.file)
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)
189else:
190    sys.exit("Must specify an experiment description (--file)")
191
192out_certfile = opts.out_certfile
193
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))
205else:
206    acerts = None
207
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])
212
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
221if not svcs:
222    print >>sys.stderr, "Warning:Neither master/project nor services requested"
223
224# Construct the New experiment request
225msg = { }
226
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
235if opts.exp_name:
236    msg['experimentID'] = { 'localname': opts.exp_name }
237
238if acerts:
239    msg['credential'] = acerts
240
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
249if opts.debug > 1: print >>sys.stderr, msg
250
251# The New call
252try:
253    resp_dict = do_rpc(msg, 
254            url, opts.transport, cert, opts.trusted, 
255            serialize_only=opts.serialize_only,
256            tracefile=opts.tracefile,
257            caller=service_caller('New'), responseBody="NewResponseBody")
258except RPCException, e:
259    exit_with_fault(e, 'New (create)', opts)
260except RuntimeError, e:
261    sys.exit("Error processing RPC: %s" % e)
262
263if opts.debug > 1: print >>sys.stderr, resp_dict
264
265proof = proof.from_dict(resp_dict.get('proof', {}))
266if proof and opts.auth_log:
267    log_authentication(opts.auth_log, 'New (create)', 'succeeded', proof)
268# Save the experiment ID certificate if we need it
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
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:
281    try:
282        creds = delegate(e_fedid, cert, opts.abac_dir, name=opts.exp_name)
283        if creds:
284            acerts.extend(creds)
285    except EnvironmentError, e:
286        sys.exit("Cannot delegate rights %s: %s" % (e.filename, e.strerror));
287
288# Construct the Create message
289if top: 
290    msg = { 'experimentdescription' : { 'topdldescription': top.to_dict() }, }
291else: 
292    msg = { 'experimentdescription': { 'ns2description': exp_desc }, }
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
304if acerts:
305    msg['credential'] = acerts
306
307if tbmap:
308    msg['testbedmap'] = [ { 'testbed': t, 'uri': u } for t, u in tbmap.items() ]
309
310if opts.debug > 1: print >>sys.stderr, msg
311
312# make the call
313try:
314    resp_dict = do_rpc(msg, 
315            url, opts.transport, cert, opts.trusted, 
316            serialize_only=opts.serialize_only,
317            tracefile=opts.tracefile,
318            caller=service_caller('Create', max_retries=1), responseBody="CreateResponseBody")
319except RPCException, e:
320    exit_with_fault(e, 'Create', opts)
321except RuntimeError, e:
322    sys.exit("Error processing RPC: %s" % e)
323
324if opts.debug > 1: print >>sys.stderr, resp_dict
325
326# output
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
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.