source: fedd/federation/deter_internal_access.py @ 027b87b

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

This little class added a useless complexity. While I'm in here I removed it.

  • Property mode set to 100644
File size: 11.3 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
7import pickle
8import logging
9import time
10
11from threading import Thread, Lock
12from subprocess import Popen, call, PIPE, STDOUT
13
14from util import *
15from allocate_project import allocate_project_local, allocate_project_remote
16from fedid import fedid, generate_fedid
17from authorizer import authorizer
18from service_error import service_error
19from remote_service import xmlrpc_handler, soap_handler, service_caller
20
21import httplib
22import tempfile
23from urlparse import urlparse
24
25import topdl
26import list_log
27import proxy_emulab_segment
28import local_emulab_segment
29
30from access import access_base
31
32# Make log messages disappear if noone configures a fedd logger
33class nullHandler(logging.Handler):
34    def emit(self, record): pass
35
36fl = logging.getLogger("fedd.access")
37fl.addHandler(nullHandler())
38
39class access(access_base):
40    @staticmethod
41    def parse_vlans(v, log=None):
42        """
43        Parse a vlan parameter into a set of vlan ids.  Comma separated
44        sequences of vlan ranges are acceptable.
45        """
46        # the vlans can be a single integer, a comma separated list or a
47        # comma separated lists of dashed ranges.  E.g 100 or 100,300 or
48        # 100,300-305,400
49        vset = set()
50        if v:
51            for v in [ x.strip() for x in v.split(",") ]:
52                try:
53                    if v.count("-") == 1:
54                        f, t = v.split("-", 1)
55                        for i in range(int(f), int(t)+1):
56                            vset.add(i)
57                    else:
58                        vset.add(int(v))
59                except ValueError:
60                    if log:
61                        log.warn("Invalid expression in vlan list: %s" % v)
62        return vset
63
64    def __init__(self, config=None, auth=None):
65        """
66        Initializer.  Pulls parameters out of the ConfigParser's access section.
67        """
68
69        access_base.__init__(self, config, auth)
70        self.domain = config.get("access", "domain")
71        vlan_str = config.get("access", "vlans")
72        self.vlans = self.parse_vlans(vlan_str)
73
74        self.attrs = { }
75        self.access = { }
76        # State is a dict of dicts indexed by segment fedid that includes the
77        # owners of the segment as fedids (who can manipulate it, key: owners),
78        # the repo dir/user for the allocation (key: user),  Current allocation
79        # log (key: log), and GRI of the reservation once made (key: gri)
80        self.log = logging.getLogger("fedd.access")
81        set_log_level(config, "access", self.log)
82
83        if config.has_option("access", "accessdb"):
84            self.read_access(config.get("access", "accessdb"))
85
86        # Add the ownership attributes to the authorizer.  Note that the
87        # indices of the allocation dict are strings, but the attributes are
88        # fedids, so there is a conversion.
89        self.state_lock.acquire()
90        for k in self.state.keys():
91            for o in self.state[k].get('owners', []):
92                self.auth.set_attribute(o, fedid(hexstr=k))
93            self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
94            # If the allocation has a vlan assigned, remove it from the
95            # available vlans
96            v = self.state[k].get('vlan', None)
97            if v:
98                self.vlans.discard(v)
99        self.state_lock.release()
100
101        self.lookup_access = self.lookup_access_base
102
103        self.call_GetValue= service_caller('GetValue')
104        self.call_SetValue= service_caller('SetValue')
105
106        self.soap_services = {\
107            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
108            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
109            'StartSegment': soap_handler("StartSegment", self.StartSegment),
110            'TerminateSegment': soap_handler("TerminateSegment", 
111                self.TerminateSegment),
112            }
113        self.xmlrpc_services =  {\
114            'RequestAccess': xmlrpc_handler('RequestAccess',
115                self.RequestAccess),
116            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
117                self.ReleaseAccess),
118            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
119            'TerminateSegment': xmlrpc_handler('TerminateSegment',
120                self.TerminateSegment),
121            }
122
123    def RequestAccess(self, req, fid):
124        """
125        Handle the access request.  Proxy if not for us.
126
127        Parse out the fields and make the allocations or rejections if for us,
128        otherwise, assuming we're willing to proxy, proxy the request out.
129        """
130
131        # The dance to get into the request body
132        if req.has_key('RequestAccessRequestBody'):
133            req = req['RequestAccessRequestBody']
134        else:
135            raise service_error(service_error.req, "No request!?")
136
137        found, match = self.lookup_access(req, fid)
138        # keep track of what's been added
139        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
140        aid = unicode(allocID)
141
142        self.state_lock.acquire()
143        self.state[aid] = { }
144        self.state[aid]['user'] = found
145        self.state[aid]['owners'] = [ fid ]
146        self.state[aid]['vlan'] = None
147        self.write_state()
148        self.state_lock.release()
149        self.auth.set_attribute(fid, allocID)
150        self.auth.set_attribute(allocID, allocID)
151
152        try:
153            f = open("%s/%s.pem" % (self.certdir, aid), "w")
154            print >>f, alloc_cert
155            f.close()
156        except EnvironmentError, e:
157            raise service_error(service_error.internal, 
158                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
159        return { 'allocID': { 'fedid': allocID } }
160
161    def ReleaseAccess(self, req, fid):
162        # The dance to get into the request body
163        if req.has_key('ReleaseAccessRequestBody'):
164            req = req['ReleaseAccessRequestBody']
165        else:
166            raise service_error(service_error.req, "No request!?")
167
168        # Local request
169        try:
170            if req['allocID'].has_key('localname'):
171                auth_attr = aid = req['allocID']['localname']
172            elif req['allocID'].has_key('fedid'):
173                aid = unicode(req['allocID']['fedid'])
174                auth_attr = req['allocID']['fedid']
175            else:
176                raise service_error(service_error.req,
177                        "Only localnames and fedids are understood")
178        except KeyError:
179            raise service_error(service_error.req, "Badly formed request")
180
181        self.log.debug("[access] deallocation requested for %s", aid)
182        if not self.auth.check_attribute(fid, auth_attr):
183            self.log.debug("[access] deallocation denied for %s", aid)
184            raise service_error(service_error.access, "Access Denied")
185
186        self.state_lock.acquire()
187        if self.state.has_key(aid):
188            self.log.debug("Found allocation for %s" %aid)
189            del self.state[aid]
190            self.write_state()
191            self.state_lock.release()
192            # And remove the access cert
193            cf = "%s/%s.pem" % (self.certdir, aid)
194            self.log.debug("Removing %s" % cf)
195            os.remove(cf)
196            return { 'allocID': req['allocID'] } 
197        else:
198            self.state_lock.release()
199            raise service_error(service_error.req, "No such allocation")
200
201    def extract_parameters(self, top):
202        """
203        DETER's internal networking currently supports a fixed capacity link
204        between two endpoints.  Those endpoints may specify a VPN (or list or
205        range) to use.  This extracts the and vpn preferences from the segments
206        (as attributes) and the capacity of the connection as given by the
207        substrate.  The two endpoints VLAN choices are intersected to get set
208        of VLANs that are acceptable (no VLAN requiremnets means any is
209        acceptable).
210        """
211        segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements)
212
213        if len(segments) != 2 or len(top.substrates) != 1:
214            raise service_error(service_error.req,
215                    "Requests to DRAGON must have exactlty two segments " +\
216                            "and one substrate")
217
218        vlans = set()
219        for s in segments:
220            v = s.get_attribute('vlans')
221            vlans &= self.parse_vlans(v)
222
223        if len(vlans) == 0:
224            vlans = None
225
226        sub = top.substrates[0]
227        if sub.capacity:
228            cap = int(sub.capacity.rate / 1000.0)
229            if cap < 1: cap = 1
230        else:
231            cap = 100
232
233        return cap, vlans
234
235    def export_store_info(self, cf, vlan, connInfo):
236        """
237        For the export requests in the connection info, install the peer names
238        at the experiment controller via SetValue calls.
239        """
240
241        for c in connInfo:
242            for p in [ p for p in c.get('parameter', []) \
243                    if p.get('type', '') == 'output']:
244
245                if p.get('name', '') != 'vlan_id':
246                    self.log.error("Unknown export parameter: %s" % \
247                            p.get('name'))
248                    continue
249
250                k = p.get('key', None)
251                surl = p.get('store', None)
252                if surl and k:
253                    value = "%s" % vlan
254                    req = { 'name': k, 'value': value }
255                    self.call_SetValue(surl, req, cf)
256                else:
257                    self.log.error("Bad export request: %s" % p)
258
259    def StartSegment(self, req, fid):
260        err = None  # Any service_error generated after tmpdir is created
261        rv = None   # Return value from segment creation
262
263        try:
264            req = req['StartSegmentRequestBody']
265            topref = req['segmentdescription']['topdldescription']
266        except KeyError:
267            raise service_error(server_error.req, "Badly formed request")
268
269        auth_attr = req['allocID']['fedid']
270        aid = "%s" % auth_attr
271        attrs = req.get('fedAttr', [])
272        if not self.auth.check_attribute(fid, auth_attr):
273            raise service_error(service_error.access, "Access denied")
274        else:
275            # See if this is a replay of an earlier succeeded StartSegment -
276            # sometimes SSL kills 'em.  If so, replay the response rather than
277            # redoing the allocation.
278            self.state_lock.acquire()
279            retval = self.state[aid].get('started', None)
280            self.state_lock.release()
281            if retval:
282                self.log.warning("Duplicate StartSegment for %s: " % aid + \
283                        "replaying response")
284                return retval
285
286        certfile = "%s/%s.pem" % (self.certdir, aid)
287
288        if topref: topo = topdl.Topology(**topref)
289        else:
290            raise service_error(service_error.req, 
291                    "Request missing segmentdescription'")
292
293        connInfo = req.get('connection', [])
294
295        cap, vlans = self.extract_parameters(topo)
296
297        # No vlans passes in, consider any vlan acceptable
298        if not vlans: 
299            vlans = self.vlans
300
301        avail = self.vlans | vlans
302
303        if len(avail) != 0:
304            vlan_no = avail.pop()
305            self.vlans.discard(vlan_no)
306        else:
307            raise service_error(service_error.federant, "No vlan available")
308
309        self.export_store_info(certfile, vlan_no, connInfo)
310
311
312        # This annotation isn't strictly necessary, but may help in debugging
313        rtopo = topo.clone()
314        for s in rtopo.substrates:
315            s.set_attribute('vlan', vlan_no)
316
317        # Grab the log (this is some anal locking, but better safe than
318        # sorry)
319        self.state_lock.acquire()
320        self.state[aid]['vlan'] = vlan_no
321        logv = "Allocated vlan: %d" % vlan_no
322        # It's possible that the StartSegment call gets retried (!).
323        # if the 'started' key is in the allocation, we'll return it rather
324        # than redo the setup.
325        self.state[aid]['started'] = { 
326                'allocID': req['allocID'],
327                'allocationLog': logv,
328                'segmentdescription': { 'topdldescription': rtopo.to_dict() }
329                }
330        retval = copy.deepcopy(self.state[aid]['started'])
331        self.write_state()
332        self.state_lock.release()
333
334        return retval
335
336    def TerminateSegment(self, req, fid):
337        try:
338            req = req['TerminateSegmentRequestBody']
339        except KeyError:
340            raise service_error(server_error.req, "Badly formed request")
341
342        auth_attr = req['allocID']['fedid']
343        aid = "%s" % auth_attr
344
345        self.log.debug("Terminate request for %s" %aid)
346        if not self.auth.check_attribute(fid, auth_attr):
347            raise service_error(service_error.access, "Access denied")
348
349        self.state_lock.acquire()
350        if self.state.has_key(aid):
351            vlan_no = self.state[aid].get('vlan', None)
352        else:
353            vlan_no = None
354        self.state_lock.release()
355        self.log.debug("Stop segment for vlan: %s" % vlan_no)
356
357        if not vlan_no:
358            raise service_error(service_error.internal, 
359                    "Can't find assigfned vlan for for %s" % aid)
360   
361        self.vlans.add(vlan_no)
362        self.state_lock.acquire()
363        self.state[aid]['vlan'] = None
364        self.state_lock.release()
365        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.