#!/usr/local/bin/python import os,sys import re import string import copy import pickle import logging import time from threading import Thread, Lock from subprocess import Popen, call, PIPE, STDOUT from util import * from deter import fedid, generate_fedid from authorizer import authorizer, abac_authorizer from service_error import service_error from remote_service import xmlrpc_handler, soap_handler, service_caller import httplib import tempfile from urlparse import urlparse from deter import topdl import list_log from access import access_base # Make log messages disappear if noone configures a fedd logger class nullHandler(logging.Handler): def emit(self, record): pass fl = logging.getLogger("fedd.access") fl.addHandler(nullHandler()) class access(access_base): @staticmethod def parse_vlans(v, log=None): """ Parse a vlan parameter into a set of vlan ids. Comma separated sequences of vlan ranges are acceptable. """ # the vlans can be a single integer, a comma separated list or a # comma separated lists of dashed ranges. E.g 100 or 100,300 or # 100,300-305,400 vset = set() if v: for v in [ x.strip() for x in v.split(",") ]: try: if v.count("-") == 1: f, t = v.split("-", 1) for i in range(int(f), int(t)+1): vset.add(i) else: vset.add(int(v)) except ValueError: if log: log.warn("Invalid expression in vlan list: %s" % v) return vset def __init__(self, config=None, auth=None): """ Initializer. Pulls parameters out of the ConfigParser's access section. """ access_base.__init__(self, config, auth) self.domain = config.get("access", "domain") vlan_str = config.get("access", "vlans") self.vlans = self.parse_vlans(vlan_str) self.attrs = { } self.access = { } # State is a dict of dicts indexed by segment fedid that includes the # owners of the segment as fedids (who can manipulate it, key: owners), # the repo dir/user for the allocation (key: user), Current allocation # log (key: log), and GRI of the reservation once made (key: gri) self.log = logging.getLogger("fedd.access") set_log_level(config, "access", self.log) # authorization information self.auth_type = config.get('access', 'auth_type') \ or 'abac' self.auth_dir = config.get('access', 'auth_dir') accessdb = config.get("access", "accessdb") # initialize the authorization system if self.auth_type == 'abac': self.auth = abac_authorizer(load=self.auth_dir) self.access = [ ] if accessdb: self.read_access(accessdb, default=[('access', None)]) else: raise service_error(service_error.internal, "Unknown auth_type: %s" % self.auth_type) self.call_GetValue= service_caller('GetValue') self.call_SetValue= service_caller('SetValue') self.soap_services = {\ 'RequestAccess': soap_handler("RequestAccess", self.RequestAccess), 'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess), 'StartSegment': soap_handler("StartSegment", self.StartSegment), 'TerminateSegment': soap_handler("TerminateSegment", self.TerminateSegment), } self.xmlrpc_services = {\ 'RequestAccess': xmlrpc_handler('RequestAccess', self.RequestAccess), 'ReleaseAccess': xmlrpc_handler('ReleaseAccess', self.ReleaseAccess), 'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment), 'TerminateSegment': xmlrpc_handler('TerminateSegment', self.TerminateSegment), } # RequestAccess and ReleaseAccess come from the base def extract_parameters(self, top): """ DETER's internal networking currently supports a fixed capacity link between two endpoints. Those endpoints may specify a VPN (or list or range) to use. This extracts the and vpn preferences from the segments (as attributes) and the capacity of the connection as given by the substrate. The two endpoints VLAN choices are intersected to get set of VLANs that are acceptable (no VLAN requiremnets means any is acceptable). """ segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements) if len(segments) != 2 or len(top.substrates) != 1: raise service_error(service_error.req, "Requests to DRAGON must have exactlty two segments " +\ "and one substrate") vlans = set() for s in segments: v = s.get_attribute('vlans') vlans &= self.parse_vlans(v) if len(vlans) == 0: vlans = None sub = top.substrates[0] if sub.capacity: cap = int(sub.capacity.rate / 1000.0) if cap < 1: cap = 1 else: cap = 100 return cap, vlans def export_store_info(self, cf, vlan, connInfo): """ For the export requests in the connection info, install the peer names at the experiment controller via SetValue calls. """ for c in connInfo: for p in [ p for p in c.get('parameter', []) \ if p.get('type', '') == 'output']: if p.get('name', '') != 'vlan_id': self.log.error("Unknown export parameter: %s" % \ p.get('name')) continue k = p.get('key', None) surl = p.get('store', None) if surl and k: value = "%s" % vlan req = { 'name': k, 'value': value } self.call_SetValue(surl, req, cf) else: self.log.error("Bad export request: %s" % p) def StartSegment(self, req, fid): err = None # Any service_error generated after tmpdir is created rv = None # Return value from segment creation try: req = req['StartSegmentRequestBody'] topref = req['segmentdescription']['topdldescription'] except KeyError: raise service_error(server_error.req, "Badly formed request") auth_attr = req['allocID']['fedid'] aid = "%s" % auth_attr attrs = req.get('fedAttr', []) access_ok, proof = self.auth.check_attribute(fid, auth_attr, with_proof=True) if not access_ok: raise service_error(service_error.access, "Access denied") else: # See if this is a replay of an earlier succeeded StartSegment - # sometimes SSL kills 'em. If so, replay the response rather than # redoing the allocation. self.state_lock.acquire() retval = self.state[aid].get('started', None) self.state_lock.release() if retval: self.log.warning("Duplicate StartSegment for %s: " % aid + \ "replaying response") return retval certfile = "%s/%s.pem" % (self.certdir, aid) if topref: topo = topdl.Topology(**topref) else: raise service_error(service_error.req, "Request missing segmentdescription'") connInfo = req.get('connection', []) cap, vlans = self.extract_parameters(topo) # No vlans passes in, consider any vlan acceptable if not vlans: vlans = self.vlans avail = self.vlans | vlans if len(avail) != 0: vlan_no = avail.pop() self.vlans.discard(vlan_no) else: raise service_error(service_error.federant, "No vlan available") self.export_store_info(certfile, vlan_no, connInfo) # This annotation isn't strictly necessary, but may help in debugging rtopo = topo.clone() for s in rtopo.substrates: s.set_attribute('vlan', vlan_no) # Grab the log (this is some anal locking, but better safe than # sorry) self.state_lock.acquire() self.state[aid]['vlan'] = vlan_no logv = "Allocated vlan: %d" % vlan_no # It's possible that the StartSegment call gets retried (!). # if the 'started' key is in the allocation, we'll return it rather # than redo the setup. self.state[aid]['started'] = { 'allocID': req['allocID'], 'allocationLog': logv, 'segmentdescription': { 'topdldescription': rtopo.to_dict() }, 'proof': proof.to_dict(), } retval = copy.deepcopy(self.state[aid]['started']) self.write_state() self.state_lock.release() return retval def TerminateSegment(self, req, fid): try: req = req['TerminateSegmentRequestBody'] except KeyError: raise service_error(server_error.req, "Badly formed request") auth_attr = req['allocID']['fedid'] aid = "%s" % auth_attr self.log.debug("Terminate request for %s" %aid) access_ok, proof = self.auth.check_attribute(fid, auth_attr, with_proof=True) if not access_ok: raise service_error(service_error.access, "Access denied") self.state_lock.acquire() if self.state.has_key(aid): vlan_no = self.state[aid].get('vlan', None) else: vlan_no = None self.state_lock.release() self.log.debug("Stop segment for vlan: %s" % vlan_no) if not vlan_no: raise service_error(service_error.internal, "Can't find assigfned vlan for for %s" % aid) self.vlans.add(vlan_no) self.state_lock.acquire() self.state[aid]['vlan'] = None self.state_lock.release() return { 'allocID': req['allocID'], 'proof': proof.to_dict() }