source: fedd/federation/deter_internal_access.py @ b931822

compt_changes
Last change on this file since b931822 was 6bedbdba, checked in by Ted Faber <faber@…>, 13 years ago

Split topdl and fedid out to different packages. Add differential
installs

  • Property mode set to 100644
File size: 8.6 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 deter import fedid, generate_fedid
16from authorizer import authorizer, abac_authorizer
17from service_error import service_error
18from remote_service import xmlrpc_handler, soap_handler, service_caller
19
20import httplib
21import tempfile
22from urlparse import urlparse
23
24from deter import topdl
25import list_log
26
27from access import access_base
28
29# Make log messages disappear if noone configures a fedd logger
30class nullHandler(logging.Handler):
31    def emit(self, record): pass
32
33fl = logging.getLogger("fedd.access")
34fl.addHandler(nullHandler())
35
36class access(access_base):
37    @staticmethod
38    def parse_vlans(v, log=None):
39        """
40        Parse a vlan parameter into a set of vlan ids.  Comma separated
41        sequences of vlan ranges are acceptable.
42        """
43        # the vlans can be a single integer, a comma separated list or a
44        # comma separated lists of dashed ranges.  E.g 100 or 100,300 or
45        # 100,300-305,400
46        vset = set()
47        if v:
48            for v in [ x.strip() for x in v.split(",") ]:
49                try:
50                    if v.count("-") == 1:
51                        f, t = v.split("-", 1)
52                        for i in range(int(f), int(t)+1):
53                            vset.add(i)
54                    else:
55                        vset.add(int(v))
56                except ValueError:
57                    if log:
58                        log.warn("Invalid expression in vlan list: %s" % v)
59        return vset
60
61    def __init__(self, config=None, auth=None):
62        """
63        Initializer.  Pulls parameters out of the ConfigParser's access section.
64        """
65
66        access_base.__init__(self, config, auth)
67        self.domain = config.get("access", "domain")
68        vlan_str = config.get("access", "vlans")
69        self.vlans = self.parse_vlans(vlan_str)
70
71        self.attrs = { }
72        self.access = { }
73        # State is a dict of dicts indexed by segment fedid that includes the
74        # owners of the segment as fedids (who can manipulate it, key: owners),
75        # the repo dir/user for the allocation (key: user),  Current allocation
76        # log (key: log), and GRI of the reservation once made (key: gri)
77        self.log = logging.getLogger("fedd.access")
78        set_log_level(config, "access", self.log)
79
80
81        # authorization information
82        self.auth_type = config.get('access', 'auth_type') \
83                or 'abac'
84        self.auth_dir = config.get('access', 'auth_dir')
85        accessdb = config.get("access", "accessdb")
86        # initialize the authorization system
87        if self.auth_type == 'abac':
88            self.auth = abac_authorizer(load=self.auth_dir)
89            self.access = [ ]
90            if accessdb:
91                self.read_access(accessdb, default=[('access', None)])
92        else:
93            raise service_error(service_error.internal, 
94                    "Unknown auth_type: %s" % self.auth_type)
95
96        self.call_GetValue= service_caller('GetValue')
97        self.call_SetValue= service_caller('SetValue')
98
99        self.soap_services = {\
100            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
101            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
102            'StartSegment': soap_handler("StartSegment", self.StartSegment),
103            'TerminateSegment': soap_handler("TerminateSegment", 
104                self.TerminateSegment),
105            }
106        self.xmlrpc_services =  {\
107            'RequestAccess': xmlrpc_handler('RequestAccess',
108                self.RequestAccess),
109            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
110                self.ReleaseAccess),
111            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
112            'TerminateSegment': xmlrpc_handler('TerminateSegment',
113                self.TerminateSegment),
114            }
115
116    # RequestAccess and ReleaseAccess come from the base
117
118    def extract_parameters(self, top):
119        """
120        DETER's internal networking currently supports a fixed capacity link
121        between two endpoints.  Those endpoints may specify a VPN (or list or
122        range) to use.  This extracts the and vpn preferences from the segments
123        (as attributes) and the capacity of the connection as given by the
124        substrate.  The two endpoints VLAN choices are intersected to get set
125        of VLANs that are acceptable (no VLAN requiremnets means any is
126        acceptable).
127        """
128        segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements)
129
130        if len(segments) != 2 or len(top.substrates) != 1:
131            raise service_error(service_error.req,
132                    "Requests to DRAGON must have exactlty two segments " +\
133                            "and one substrate")
134
135        vlans = set()
136        for s in segments:
137            v = s.get_attribute('vlans')
138            vlans &= self.parse_vlans(v)
139
140        if len(vlans) == 0:
141            vlans = None
142
143        sub = top.substrates[0]
144        if sub.capacity:
145            cap = int(sub.capacity.rate / 1000.0)
146            if cap < 1: cap = 1
147        else:
148            cap = 100
149
150        return cap, vlans
151
152    def export_store_info(self, cf, vlan, connInfo):
153        """
154        For the export requests in the connection info, install the peer names
155        at the experiment controller via SetValue calls.
156        """
157
158        for c in connInfo:
159            for p in [ p for p in c.get('parameter', []) \
160                    if p.get('type', '') == 'output']:
161
162                if p.get('name', '') != 'vlan_id':
163                    self.log.error("Unknown export parameter: %s" % \
164                            p.get('name'))
165                    continue
166
167                k = p.get('key', None)
168                surl = p.get('store', None)
169                if surl and k:
170                    value = "%s" % vlan
171                    req = { 'name': k, 'value': value }
172                    self.call_SetValue(surl, req, cf)
173                else:
174                    self.log.error("Bad export request: %s" % p)
175
176    def StartSegment(self, req, fid):
177        err = None  # Any service_error generated after tmpdir is created
178        rv = None   # Return value from segment creation
179
180        try:
181            req = req['StartSegmentRequestBody']
182            topref = req['segmentdescription']['topdldescription']
183        except KeyError:
184            raise service_error(server_error.req, "Badly formed request")
185
186        auth_attr = req['allocID']['fedid']
187        aid = "%s" % auth_attr
188        attrs = req.get('fedAttr', [])
189        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
190                with_proof=True)
191        if not access_ok:
192            raise service_error(service_error.access, "Access denied")
193        else:
194            # See if this is a replay of an earlier succeeded StartSegment -
195            # sometimes SSL kills 'em.  If so, replay the response rather than
196            # redoing the allocation.
197            self.state_lock.acquire()
198            retval = self.state[aid].get('started', None)
199            self.state_lock.release()
200            if retval:
201                self.log.warning("Duplicate StartSegment for %s: " % aid + \
202                        "replaying response")
203                return retval
204
205        certfile = "%s/%s.pem" % (self.certdir, aid)
206
207        if topref: topo = topdl.Topology(**topref)
208        else:
209            raise service_error(service_error.req, 
210                    "Request missing segmentdescription'")
211
212        connInfo = req.get('connection', [])
213
214        cap, vlans = self.extract_parameters(topo)
215
216        # No vlans passes in, consider any vlan acceptable
217        if not vlans: 
218            vlans = self.vlans
219
220        avail = self.vlans | vlans
221
222        if len(avail) != 0:
223            vlan_no = avail.pop()
224            self.vlans.discard(vlan_no)
225        else:
226            raise service_error(service_error.federant, "No vlan available")
227
228        self.export_store_info(certfile, vlan_no, connInfo)
229
230
231        # This annotation isn't strictly necessary, but may help in debugging
232        rtopo = topo.clone()
233        for s in rtopo.substrates:
234            s.set_attribute('vlan', vlan_no)
235
236        # Grab the log (this is some anal locking, but better safe than
237        # sorry)
238        self.state_lock.acquire()
239        self.state[aid]['vlan'] = vlan_no
240        logv = "Allocated vlan: %d" % vlan_no
241        # It's possible that the StartSegment call gets retried (!).
242        # if the 'started' key is in the allocation, we'll return it rather
243        # than redo the setup.
244        self.state[aid]['started'] = { 
245                'allocID': req['allocID'],
246                'allocationLog': logv,
247                'segmentdescription': { 'topdldescription': rtopo.to_dict() },
248                'proof': proof.to_dict(),
249                }
250        retval = copy.deepcopy(self.state[aid]['started'])
251        self.write_state()
252        self.state_lock.release()
253
254        return retval
255
256    def TerminateSegment(self, req, fid):
257        try:
258            req = req['TerminateSegmentRequestBody']
259        except KeyError:
260            raise service_error(server_error.req, "Badly formed request")
261
262        auth_attr = req['allocID']['fedid']
263        aid = "%s" % auth_attr
264
265        self.log.debug("Terminate request for %s" %aid)
266        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
267                with_proof=True)
268        if not access_ok:
269            raise service_error(service_error.access, "Access denied")
270
271        self.state_lock.acquire()
272        if self.state.has_key(aid):
273            vlan_no = self.state[aid].get('vlan', None)
274        else:
275            vlan_no = None
276        self.state_lock.release()
277        self.log.debug("Stop segment for vlan: %s" % vlan_no)
278
279        if not vlan_no:
280            raise service_error(service_error.internal, 
281                    "Can't find assigfned vlan for for %s" % aid)
282   
283        self.vlans.add(vlan_no)
284        self.state_lock.acquire()
285        self.state[aid]['vlan'] = None
286        self.state_lock.release()
287        return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Note: See TracBrowser for help on using the repository browser.