class RemoteControllerHarness(object): ''' Harness for local controller testing. ''' __metaclass__ = Singleton def __init__(self): # Create useful examples self.examples = [] self.examples.append(OFR(OpenFlowMatch([ETH_DST("00:00:00:00:00:01")]), instruction_APPLY_ACTIONS([action_SET_FIELD(ETH_DST("00:00:00:00:00:02"))]))) self.examples.append(OFR(OpenFlowMatch([ETH_TYPE(0x0800), IPV4_SRC("1.2.3.4")]), instruction_APPLY_ACTIONS([action_SET_FIELD(IPV4_DST("2.3.4.5"))]))) self.examples.append(OFR(OpenFlowMatch([ETH_DST("00:00:00:00:00:03"), ETH_TYPE(0x0800), IPV4_SRC("3.4.5.6")]), instruction_APPLY_ACTIONS([action_SET_FIELD(ETH_SRC("00:00:00:00:00:04"))]))) self.examples.append(OFR(OpenFlowMatch([ETH_DST("00:00:00:00:00:05"), ETH_TYPE(0x0800), IPV4_SRC("4.5.6.7")]), instruction_APPLY_ACTIONS([action_SET_FIELD(ETH_SRC("00:00:00:00:00:04")), action_SET_FIELD(IPV4_DST("5.6.7.8"))]))) self.examples.append(OFR(OpenFlowMatch([IP_PROTO(6), ETH_TYPE(0x0800)]), # instruction_WRITE_ACTIONS([action_OUTPUT(1)]), instruction_APPLY_ACTIONS([action_OUTPUT(1)]), priority=123, cookie=1234, table=1, switch_id=1)) # Set up connection self.ip = IPADDR self.port = PORT self.client = None self.cm = SDXControllerConnectionManager() self.cm_thread = threading.Thread(target=self._cm_thread) self.cm_thread.daemon = True self.cm_thread.start() def _cm_thread(self): self.cm.new_connection_callback(self.connection_cb) self.cm.open_listening_port(self.ip, self.port) def connection_cb(self, cxn): self.client = cxn def is_connected(self): # figure out if connected return (self.client != None) def send_new_command(self, cmd): self.client.send_cmd(SDX_NEW_RULE, cmd) def send_rm_command(self, cmd): self.client.send_cmd(SDX_RM_RULE, cmd)
def __init__(self, runloop=False, options=None): # Setup logger self._setup_logger() self.name = options.name self.capabilities = "NOTHING YET" #FIXME: This needs to be updated self.logger.info("LocalController %s starting", self.name) # Import configuration information - this includes pulling information # from the stored DB (if there is anything), and from the options passed # in during startup (including the manifest file) self._setup(options) # Signal Handling signal.signal(signal.SIGINT, receive_signal) atexit.register(self.receive_exit) # Rules DB self.rm = LCRuleManager() # Initial Rules self._initial_rules_dict = {} # Setup switch self.switch_connection = RyuControllerInterface( self.name, self.manifest, self.lcip, self.ryu_cxn_port, self.openflow_port, self.switch_message_cb) self.logger.info("RyuControllerInterface setup finish.") # Setup connection manager self.cxn_q = Queue() self.sdx_cm = SDXControllerConnectionManager() self.sdx_connection = None self.start_cxn_thread = None self.logger.info("SDXControllerConnectionManager setup finish.") self.logger.debug("SDXControllerConnectionManager - %s" % (self.sdx_cm)) # Start connections: self.start_switch_connection() self.start_sdx_controller_connection() # Blocking call self.logger.info("SDX Controller Connection established.") # Start main loop if runloop: self.start_main_loop() self.logger.info("Main Loop started.")
def __init__(self, runloop=False, options=None): # Setup logger self._setup_logger() self.name = options.name self.logger.info("LocalController %s starting", self.name) # Parse out the options # Import configuration information self.manifest = options.manifest self.sdxip = options.host self.sdxport = options.sdxport self.lcip = LOCALHOST self.ryu_cxn_port = DEFAULT_RYU_CXN_PORT self.openflow_port = DEFAULT_OPENFLOW_PORT if self.manifest != None: self._import_configuration() # Setup switch self.switch_connection = RyuControllerInterface(self.name, self.manifest, self.lcip, self.ryu_cxn_port, self.openflow_port, self.switch_message_cb) self.logger.info("RyuControllerInterface setup finish.") # Setup connection manager self.sdx_cm = SDXControllerConnectionManager() self.sdx_connection = None self.logger.info("SDXControllerConnectionManager setup finish.") self.logger.debug("SDXControllerConnectionManager - %s" % (self.sdx_cm)) # Start connections: self.start_switch_connection() self.start_sdx_controller_connection() # Blocking call self.logger.info("SDX Controller Connection established.") # Start main loop if runloop: self.start_main_loop() self.logger.info("Main Loop started.")
def __init__(self): # Create useful examples self.examples = [] self.examples.append(OFR(OpenFlowMatch([ETH_DST("00:00:00:00:00:01")]), instruction_APPLY_ACTIONS([action_SET_FIELD(ETH_DST("00:00:00:00:00:02"))]))) self.examples.append(OFR(OpenFlowMatch([ETH_TYPE(0x0800), IPV4_SRC("1.2.3.4")]), instruction_APPLY_ACTIONS([action_SET_FIELD(IPV4_DST("2.3.4.5"))]))) self.examples.append(OFR(OpenFlowMatch([ETH_DST("00:00:00:00:00:03"), ETH_TYPE(0x0800), IPV4_SRC("3.4.5.6")]), instruction_APPLY_ACTIONS([action_SET_FIELD(ETH_SRC("00:00:00:00:00:04"))]))) self.examples.append(OFR(OpenFlowMatch([ETH_DST("00:00:00:00:00:05"), ETH_TYPE(0x0800), IPV4_SRC("4.5.6.7")]), instruction_APPLY_ACTIONS([action_SET_FIELD(ETH_SRC("00:00:00:00:00:04")), action_SET_FIELD(IPV4_DST("5.6.7.8"))]))) self.examples.append(OFR(OpenFlowMatch([IP_PROTO(6), ETH_TYPE(0x0800)]), # instruction_WRITE_ACTIONS([action_OUTPUT(1)]), instruction_APPLY_ACTIONS([action_OUTPUT(1)]), priority=123, cookie=1234, table=1, switch_id=1)) # Set up connection self.ip = IPADDR self.port = PORT self.client = None self.cm = SDXControllerConnectionManager() self.cm_thread = threading.Thread(target=self._cm_thread) self.cm_thread.daemon = True self.cm_thread.start()
def test_open_send_port(self): self.manager = SDXControllerConnectionManager() self.ListeningThread = threading.Thread(target=self.listening_thread) self.ListeningThread.daemon = True self.ListeningThread.start() self.SendThread = threading.Thread(target=self.sending_thread) self.SendThread.daemon = True self.SendThread.start() sleep(0.1) # Rather than messing about with locks self.failUnlessEqual(self.clientstate, "MAIN_PHASE") self.failUnlessEqual(self.serverstate, "MAIN_PHASE")
class LocalController(SingletonMixin): ''' The Local Controller is responsible for passing messages from the SDX Controller to the switch. It needs two connections to both the SDX controller and switch(es). Singleton. ''' def __init__(self, runloop=False, options=None): # Setup logger self._setup_logger() self.name = options.name self.capabilities = "NOTHING YET" #FIXME: This needs to be updated self.logger.info("LocalController %s starting", self.name) # Import configuration information - this includes pulling information # from the stored DB (if there is anything), and from the options passed # in during startup (including the manifest file) self._setup(options) # Signal Handling signal.signal(signal.SIGINT, receive_signal) atexit.register(self.receive_exit) # Rules DB self.rm = LCRuleManager() # Initial Rules self._initial_rules_dict = {} # Setup switch self.switch_connection = RyuControllerInterface( self.name, self.manifest, self.lcip, self.ryu_cxn_port, self.openflow_port, self.switch_message_cb) self.logger.info("RyuControllerInterface setup finish.") # Setup connection manager self.cxn_q = Queue() self.sdx_cm = SDXControllerConnectionManager() self.sdx_connection = None self.start_cxn_thread = None self.logger.info("SDXControllerConnectionManager setup finish.") self.logger.debug("SDXControllerConnectionManager - %s" % (self.sdx_cm)) # Start connections: self.start_switch_connection() self.start_sdx_controller_connection() # Blocking call self.logger.info("SDX Controller Connection established.") # Start main loop if runloop: self.start_main_loop() self.logger.info("Main Loop started.") def start_main_loop(self): self.main_loop_thread = threading.Thread(target=self._main_loop) self.main_loop_thread.daemon = True self.main_loop_thread.start() self.logger.debug("Main Loop - %s" % (self.main_loop_thread)) def _main_loop(self): ''' This is the main loop for the Local Controller. User should call start_main_loop() to start it. ''' rlist = [] wlist = [] xlist = rlist timeout = 1.0 self.logger.debug("Inside Main Loop, SDX connection: %s" % (self.sdx_connection)) while (True): # Based, in part, on https://pymotw.com/2/select/ and # https://stackoverflow.com/questions/17386487/ try: q_ele = self.sdx_cm.get_cxn_queue_element() while q_ele != None: (action, cxn) = q_ele if action == NEW_CXN: self.logger.warning("Adding connection %s" % cxn) if cxn in rlist: # Already there. Weird, but OK pass rlist.append(cxn) wlist = [] xlist = rlist elif action == DEL_CXN: self.logger.warning("Removing connection %s" % cxn) if cxn in rlist: # Need to clean up the sdx_connection, as it has # already failed and been (mostly) cleaned up. self.sdx_connection = None rlist.remove(cxn) wlist = [] xlist = rlist # Next queue element q_ele = self.sdx_cm.get_cxn_queue_element() except Empty as e: # This is raised if the cxn_q is empty of events. # Normal behaviour pass if self.sdx_connection == None: print "SDX_CXN = None, start_cxn_thread = %s" % str( self.start_cxn_thread) # Restart SDX Connection if it's failed. if (self.sdx_connection == None and self.start_cxn_thread == None): self.logger.info("Restarting SDX Connection") self.start_sdx_controller_connection() #Restart! if len(rlist) == 0: sleep(timeout / 2) continue try: readable, writable, exceptional = cxnselect( rlist, wlist, xlist, timeout) except Exception as e: self.logger.error("Error in select - %s" % (e)) # Loop through readable for entry in readable: # Get Message try: msg = entry.recv_protocol() except SDXMessageConnectionFailure as e: # Connection needs to be disconnected. self.cxn_q.put((DEL_CXN, entry)) entry.close() continue except: raise # Can return None if there was some internal message. if msg == None: #self.logger.debug("Received None from recv_protocol %s" % # (entry)) continue self.logger.debug("Received a %s message from %s" % (type(msg), entry)) #FIXME: Check if the SDXMessage is valid at this stage # If HeartbeatRequest, send a HeartbeatResponse. This doesn't # require tracking anything, unlike when sending out a # HeartbeatRequest ourselves. if type(msg) == SDXMessageHeartbeatRequest: hbr = SDXMessageHeartbeatResponse() entry.send_protocol(hbr) # If HeartbeatResponse, call HeartbeatResponseHandler elif type(msg) == SDXMessageHeartbeatResponse: entry.heartbeat_response_handler(msg) # If InstallRule elif type(msg) == SDXMessageInstallRule: self.install_rule_sdxmsg(msg) # If RemoveRule elif type(msg) == SDXMessageRemoveRule: self.remove_rule_sdxmsg(msg) # If InstallRuleComplete - ERROR! LC shouldn't receive this. # If InstallRuleFailure - ERROR! LC shouldn't receive this. # If RemoveRuleComplete - ERROR! LC shouldn't receive this. # If RemoveRuleFailure - ERROR! LC shouldn't receive this. # If UnknownSource - ERROR! LC shouldn't receive this. # If SwitchChangeCallback - ERROR! LC shouldn't receive this. elif (type(msg) == SDXMessageInstallRuleComplete and type(msg) == SDXMessageInstallRuleFailure and type(msg) == SDXMessageRemoveRuleComplete and type(msg) == SDXMessageRemoveRuleFailure and type(msg) == SDXMessageUnknownSource and type(msg) == SDXMessageSwitchChangeCallback): raise TypeError("msg type %s - not valid: %s" % (type(msg), msg)) # All other types are something that shouldn't be seen, likely # because they're from the a Message that's not currently valid else: raise TypeError("msg type %s - not valid: %s" % (type(msg), msg)) # Loop through writable for entry in writable: # Anything to do here? pass # Loop through exceptional for entry in exceptional: # FIXME: Handle connection failures # Clean up current connection self.sdx_connection.close() self.sdx_connection = None self.cxn_q.put((DEL_CXN, cxn)) # Restart new connection self.start_sdx_controller_connection() def _setup_logger(self): ''' Internal function for setting up the logger formats. ''' # reused from https://github.com/sdonovan1985/netassay-ryu/blob/master/base/mcm.py formatter = logging.Formatter( '%(asctime)s %(name)-12s: %(levelname)-8s %(message)s') console = logging.StreamHandler() console.setLevel(logging.WARNING) console.setFormatter(formatter) logfile = logging.FileHandler('localcontroller.log') logfile.setLevel(logging.DEBUG) logfile.setFormatter(formatter) self.logger = logging.getLogger('localcontroller') self.logger.setLevel(logging.DEBUG) self.logger.addHandler(console) self.logger.addHandler(logfile) def _initialize_db(self, db_filename): # Details on the setup: # https://dataset.readthedocs.io/en/latest/api.html # https://github.com/g2p/bedup/issues/38#issuecomment-43703630 self.logger.critical("Connection to DB: %s" % db_filename) self.db = dataset.connect( 'sqlite:///' + db_filename, engine_kwargs={'connect_args': { 'check_same_thread': False }}) #Try loading the tables, if they don't exist, create them. # <lcname>-config - Columns are 'key' and 'value' config_table_name = self.name + "-config" try: self.logger.info("Trying to load %s from DB" % config_table_name) self.config_table = self.db.load_table(config_table_name) except: # If load_table() fails, that's fine! It means that the table # doesn't yet exist. So, create it. self.logger.info("Failed to load %s from DB, creating new table" % config_table_name) self.config_table = self.db[config_table_name] def _add_switch_config_to_db(self, switch_name, switch_config): # Pushes a switch info dictionary from manifest. # key: "<switch_name>_switchinfo" # value: <switch_config> key = switch_name + "_switchinfo" value = pickle.dumps(switch_config) if self._get_switch_config_in_db(switch_name) == None: self.logger.info("Adding new switch configuration for switch %s" % switch_name) self.config_table.insert({'key': key, 'value': value}) else: # Already exists, must update. self.logger.info("Updating switch configuration for switch %s" % switch_name) self.config_table.update({'key': key, 'value': value}, ['key']) def _add_ryu_config_to_db(self, ryu_config): # Pushes Ryu network configuration info into the DB. # key: "ryu_config" # value: ryu_config key = 'ryu_config' value = pickle.dumps(ryu_config) if self._get_ryu_config_in_db() == None: self.logger.info("Adding new Ryu configuration %s" % ryu_config) self.config_table.insert({'key': key, 'value': value}) else: # Already exists, must update. self.logger.info("Updating Ryu configuration %s" % ryu_config) self.config_table.update({'key': key, 'value': value}, ['key']) def _add_LC_config_to_db(self, lc_config): # Pushes LC network configuration info into the DB. # key: "lc_config" # value: lc_config key = 'lc_config' value = pickle.dumps(lc_config) if self._get_LC_config_in_db() == None: self.logger.info("Adding new LC configuration %s" % lc_config) self.config_table.insert({'key': key, 'value': value}) else: # Already exists, must update. self.logger.info("Updating LC configuration %s" % lc_config) self.config_table.update({'key': key, 'value': value}, ['key']) def _add_SDX_config_to_db(self, sdx_config): # Pushes SDX network configuration info into the DB. # key: "sdx_config" # value: sdx_config key = 'sdx_config' value = pickle.dumps(sdx_config) if self._get_SDX_config_in_db() == None: self.logger.info("Adding new SDX configuration %s" % sdx_config) self.config_table.insert({'key': key, 'value': value}) else: # Already exists, must update. self.logger.info("Updating SDX configuration %s" % sdx_config) self.config_table.update({'key': key, 'value': value}, ['key']) def _add_manifest_filename_to_db(self, manifest_filename): # Pushes LC network configuration info into the DB. # key: "manifest_filename" # value: manifest_filename key = 'manifest_filename' value = pickle.dumps(manifest_filename) if self._get_manifest_filename_in_db() == None: self.logger.info("Adding new manifest filename %s" % manifest_filename) self.config_table.insert({'key': key, 'value': value}) else: # Already exists, must update. self.logger.info("Updating manifest filename %s" % manifest_filename) self.config_table.update({'key': key, 'value': value}, ['key']) def _get_switch_config_in_db(self, switch_name): # Returns a switch configuration dictionary if one exists or None if one # does not. key = switch_name + "_switchinfo" d = self.config_table.find_one(key=key) if d == None: return None val = d['value'] return pickle.loads(str(val)) def _get_ryu_config_in_db(self): # Returns the ryu configuration dictionary if it exists or None if it # does not. key = 'ryu_config' d = self.config_table.find_one(key=key) if d == None: return None val = d['value'] return pickle.loads(str(val)) def _get_LC_config_in_db(self): # Returns the LC configuration dictionary if it exists or None if it # does not. key = 'lc_config' d = self.config_table.find_one(key=key) if d == None: return None val = d['value'] return pickle.loads(str(val)) def _get_SDX_config_in_db(self): # Returns the SDX configuration dictionary if it exists or None if it # does not. key = 'sdx_config' d = self.config_table.find_one(key=key) if d == None: return None val = d['value'] return pickle.loads(str(val)) def _get_manifest_filename_in_db(self): # Returns the manifest filename if it exists or None if it does not. key = 'manifest_filename' d = self.config_table.find_one(key=key) if d == None: return None val = d['value'] return pickle.loads(str(val)) def _setup(self, options): self.manifest = options.manifest dbname = options.database # Get DB connection and tables setup. self._initialize_db(dbname) # If manifest is None, try to get the name from the DB. This is needed # for the LC's RyuTranslateInterface if self.manifest == None: self.manifest = self._get_manifest_filename_in_db() elif (self.manifest != self._get_manifest_filename_in_db() and None != self._get_manifest_filename_in_db()): # Make sure it matches! #FIXME: Should we force evertying to be imported if different. raise Exception( "Stored and passed in manifest filenames don't match up %s:%s" % (str(self.manifest), str(self._get_manifest_filename_in_db()))) # Get Manifest, if it exists try: self.logger.info("Opening manifest file %s" % self.manifest) with open(self.manifest) as data_file: data = json.load(data_file) lcdata = data['localcontrollers'][self.name] except Exception as e: self.logger.warning("Exception when opening manifest file: %s" % str(e)) lcdata = None self._add_manifest_filename_to_db(self.manifest) # Check if things are stored in the db. If they are, use them. # If not, load from the manifest and store to DB. # Switch Config #FIXME: This isn't used here. It's used in RyuTranslateInterface. #Not storing it for now, despite having the helper functions written. # Ryu Config config = self._get_ryu_config_in_db() if config != None: self.ryu_cxn_port = config['ryucxninternalport'] self.openflow_port = config['openflowport'] else: self.ryu_cxn_port = lcdata['internalconfig']['ryucxninternalport'] self.openflow_port = lcdata['internalconfig']['openflowport'] self._add_ryu_config_to_db({ 'ryucxninternalport': self.ryu_cxn_port, 'openflowport': self.openflow_port }) # LC Config config = self._get_LC_config_in_db() if config != None: self.lcip = config['lcip'] else: self.lcip = lcdata['lcip'] self._add_LC_config_to_db({'lcip': self.lcip}) # SDX Config config = self._get_SDX_config_in_db() if config != None: self.sdxip = config['sdxip'] self.sdxport = config['sdxport'] else: # Well, not quite the manifest... self.sdxip = options.host self.sdxport = options.sdxport self._add_SDX_config_to_db({ 'sdxip': self.sdxip, 'sdxport': self.sdxport }) def start_sdx_controller_connection(self): # Kick off thread to start connection. if self.start_cxn_thread != None: raise Exception("start_cxn_thread %s already exists." % self.start_cxn_thread) self.start_cxn_thread = threading.Thread( target=self._start_sdx_controller_connection_thread) self.start_cxn_thread.daemon = True self.start_cxn_thread.start() self.logger.debug("start_cdx_controller_connection-thread - %s" % self.start_cxn_thread) def _start_sdx_controller_connection_thread(self): # Start connection self.logger.debug("About to open SDX Controller Connection to %s:%s" % (self.sdxip, self.sdxport)) self.sdx_connection = self.sdx_cm.open_outbound_connection( self.sdxip, self.sdxport) self.logger.debug("SDX Controller Connection - %s" % (self.sdx_connection)) # Transition to Main Phase try: self.sdx_connection.transition_to_main_phase_LC( self.name, self.capabilities, self._initial_rule_install, self._initial_rules_complete) except (SDXControllerConnectionTypeError, SDXControllerConnectionValueError) as e: # This can happen. In this case, we need to close the connection, # null out the connection, and end the thread. self.logger.error( "SDX Connection transition to main phase failed with possible benign error. %s : %s" % (self.sdx_connection, e)) self.sdx_connection.close() self.sdx_connection = None self.start_cxn_thread = None return except Exception as e: # We need to clean up the connection as in the errors above, # then raise this error, as this *is* a problem that we need more # info on. self.logger.error( "SDX Connection transition to main phase failed with unhandled exception %s : %s" % (self.sdx_connection, e)) self.sdx_connection.close() self.sdx_connection = None self.start_cxn_thread = None raise # Upon successful return of connection, add NEW_CXN to cxn_q self.cxn_q.put((NEW_CXN, self.sdx_connection)) # Finish up thread self.start_cxn_thread = None return def start_switch_connection(self): pass def sdx_message_callback(self): pass # Is this necessary? def install_rule_sdxmsg(self, msg): switch_id = msg.get_data()['switch_id'] rule = msg.get_data()['rule'] cookie = rule.get_cookie() self.rm.add_rule(cookie, switch_id, rule, RULE_STATUS_INSTALLING) self.switch_connection.send_command(switch_id, rule) #FIXME: This should be moved to somewhere where we have positively #confirmed a rule has been installed. Right now, there is no such #location as the LC/RyuTranslateInteface protocol is primitive. self.rm.set_status(cookie, switch_id, RULE_STATUS_ACTIVE) def remove_rule_sdxmsg(self, msg): ''' Removes rules based on cookie sent from the SDX Controller. If we do not have a rule under that cookie, odds are it's for the previous instance of this LC. Log and drop it. ''' switch_id = msg.get_data()['switch_id'] cookie = msg.get_data()['cookie'] rules = self.rm.get_rules(cookie, switch_id) if rules == []: self.logger.error( "remove_rule_sdxmsg: trying to remove a rule that doesn't exist %s" % cookie) return self.rm.set_status(cookie, switch_id, RULE_STATUS_DELETING) self.switch_connection.remove_rule(switch_id, cookie) #FIXME: This should be moved to somewhere where we have positively #confirmed a rule has been removed. Right now, there is no such #location as the LC/RyuTranslateInteface protocol is primitive. self.rm.set_status(cookie, switch_id, RULE_STATUS_REMOVED) self.rm.rm_rule(cookie, switch_id) def _initial_rule_install(self, rule): ''' This builds up a list of rules to be installed. _initial_rules_complete() actually kicks off the install. ''' c = rule.get_data()['rule'].get_cookie() sw = rule.get_data()['rule'].get_switch_id() self.rm.add_initial_rule(rule, c, sw) def _initial_rules_complete(self): ''' Calls the LCRuleManager to get delete and add rule lists, then deletes outdated rules and adds new rule. ''' delete_list = [] add_list = [] (delete_list, add_list) = self.rm.initial_rules_complete() self.rm.clear_initial_rules() for (entry, cookie, switch_id) in add_list: # Cookie isn't needed self.install_rule_sdxmsg(entry) for (entry, cookie, switch_id) in delete_list: # Create the RemoveRule to send to self.remove_rule_sdxmsg() msg = SDXMessageRemoveRule(cookie, switch_id) self.remove_rule_sdxmsg(msg) def switch_message_cb(self, cmd, opaque): ''' Called by SwitchConnection to pass information back from the Switch type is the type of message defined in switch_messages.py. opaque is the message that's being sent back, which is source dependent. ''' if cmd == SM_UNKNOWN_SOURCE: # Create an SDXMessageUnknownSource and send it. msg = SDXMessageUnknownSource(opaque['src'], opaque['port'], opaque['switch']) self.sdx_connection.send_protocol(msg) elif cmd == SM_L2MULTIPOINT_UNKNOWN_SOURCE: # Create an SDXMessageSwitchChangeCallback and send it. msg = SDXMessageSwitchChangeCallback(opaque) self.sdx_connection.send_protocol(msg) #FIXME: Else? def get_ryu_process(self): return self.switch_connection.get_ryu_process() def receive_exit(self): print "EXIT RECEIVED" print "\n\n%d\n\n" % self.get_ryu_process().pid os.killpg(self.get_ryu_process().pid, signal.SIGKILL)
def __init__(self, runloop=True, options=None): ''' The bulk of the work happens here. This initializes nearly everything and starts up the main processing loop for the entire SDX Controller. ''' self._setup_logger() mani = options.manifest db = options.database run_topo = options.topo # Start DB connection. Used by other modules. details on the setup: # https://dataset.readthedocs.io/en/latest/api.html # https://github.com/g2p/bedup/issues/38#issuecomment-43703630 self.logger.critical("Connection to DB: %s" % db) self.db = dataset.connect( 'sqlite:///' + db, engine_kwargs={'connect_args': { 'check_same_thread': False }}) # self.run_topo decides whether or not to send rules. self.run_topo = run_topo # Modules with configuration files self.tm = TopologyManager.instance(mani) # Initialize all the modules - Ordering is relatively important here self.aci = AuthenticationInspector.instance() self.azi = AuthorizationInspector.instance() self.be = BreakdownEngine.instance() self.rr = RuleRegistry.instance() self.vi = ValidityInspector.instance() self.um = UserManager.instance(self.db, mani) if mani != None: self.lcm = LocalControllerManager.instance(mani) else: self.lcm = LocalControllerManager.instance() topo = self.tm.get_topology() # Set up the connection-related nonsense - Have a connection event queue self.ip = options.host self.port = options.lcport self.connections = {} self.sdx_cm = SDXControllerConnectionManager() self.cm_thread = threading.Thread(target=self._cm_thread) self.cm_thread.daemon = True self.cm_thread.start() # Register known UserPolicies self.rr.add_ruletype(L2TunnelPolicy) self.rr.add_ruletype(L2MultipointPolicy) self.rr.add_ruletype(EndpointConnectionPolicy) self.rr.add_ruletype(EdgePortPolicy) self.rr.add_ruletype(LearnedDestinationPolicy) self.rr.add_ruletype(FloodTreePolicy) self.rr.add_ruletype(SDXEgressPolicy) self.rr.add_ruletype(SDXIngressPolicy) # Start these modules last! if self.run_topo: self.rm = RuleManager.instance(self.db, self.sdx_cm.send_breakdown_rule_add, self.sdx_cm.send_breakdown_rule_rm) else: self.rm = RuleManager.instance(self.db, send_no_rules, send_no_rules) self.rapi = RestAPI.instance(options.host, options.port, options.shib) # Install any rules switches will need. self._prep_switches() # Go to main loop if runloop: self._main_loop()
class SDXController(SingletonMixin): ''' This is the main coordinating module of the SDX controller. It mostly provides startup and coordination, rather than performan many actions by itself. Singleton. ''' def __init__(self, runloop=True, options=None): ''' The bulk of the work happens here. This initializes nearly everything and starts up the main processing loop for the entire SDX Controller. ''' self._setup_logger() mani = options.manifest db = options.database run_topo = options.topo # Start DB connection. Used by other modules. details on the setup: # https://dataset.readthedocs.io/en/latest/api.html # https://github.com/g2p/bedup/issues/38#issuecomment-43703630 self.logger.critical("Connection to DB: %s" % db) self.db = dataset.connect( 'sqlite:///' + db, engine_kwargs={'connect_args': { 'check_same_thread': False }}) # self.run_topo decides whether or not to send rules. self.run_topo = run_topo # Modules with configuration files self.tm = TopologyManager.instance(mani) # Initialize all the modules - Ordering is relatively important here self.aci = AuthenticationInspector.instance() self.azi = AuthorizationInspector.instance() self.be = BreakdownEngine.instance() self.rr = RuleRegistry.instance() self.vi = ValidityInspector.instance() self.um = UserManager.instance(self.db, mani) if mani != None: self.lcm = LocalControllerManager.instance(mani) else: self.lcm = LocalControllerManager.instance() topo = self.tm.get_topology() # Set up the connection-related nonsense - Have a connection event queue self.ip = options.host self.port = options.lcport self.connections = {} self.sdx_cm = SDXControllerConnectionManager() self.cm_thread = threading.Thread(target=self._cm_thread) self.cm_thread.daemon = True self.cm_thread.start() # Register known UserPolicies self.rr.add_ruletype(L2TunnelPolicy) self.rr.add_ruletype(L2MultipointPolicy) self.rr.add_ruletype(EndpointConnectionPolicy) self.rr.add_ruletype(EdgePortPolicy) self.rr.add_ruletype(LearnedDestinationPolicy) self.rr.add_ruletype(FloodTreePolicy) self.rr.add_ruletype(SDXEgressPolicy) self.rr.add_ruletype(SDXIngressPolicy) # Start these modules last! if self.run_topo: self.rm = RuleManager.instance(self.db, self.sdx_cm.send_breakdown_rule_add, self.sdx_cm.send_breakdown_rule_rm) else: self.rm = RuleManager.instance(self.db, send_no_rules, send_no_rules) self.rapi = RestAPI.instance(options.host, options.port, options.shib) # Install any rules switches will need. self._prep_switches() # Go to main loop if runloop: self._main_loop() def _setup_logger(self): ''' Internal function for setting up the logger formats. ''' # reused from https://github.com/sdonovan1985/netassay-ryu/blob/master/base/mcm.py formatter = logging.Formatter( '%(asctime)s %(name)-12s: %(levelname)-8s %(message)s') console = logging.StreamHandler() console.setLevel(logging.WARNING) console.setFormatter(formatter) logfile = logging.FileHandler('sdxcontroller.log') logfile.setLevel(logging.DEBUG) logfile.setFormatter(formatter) self.logger = logging.getLogger('sdxcontroller') self.logger.setLevel(logging.DEBUG) self.logger.addHandler(console) self.logger.addHandler(logfile) def _cm_thread(self): self.sdx_cm.new_connection_callback(self._handle_new_connection) self.sdx_cm.open_listening_port(self.ip, self.port) self.logger.critical("Listening on %s:%d" % (self.ip, self.port)) print("Listening on %s:%d" % (self.ip, self.port)) pass def _get_existing_rules_by_name(self, name): # Goes to the RuleManager to get existing rules for a particular LC return self.rm.get_breakdown_rules_by_LC(name) def _clean_up_oustanding_cxn(self, name): # Check to see if there's an existing connection. If there is, close it. if name in self.connections.keys(): old_cxn = self.connections[name] self.logger.warning("Closing old connection for %s : %s" % (name, old_cxn)) self._handle_connection_loss(old_cxn) def _handle_new_connection(self, cxn): # Receive name from LocalController, verify that it's in the topology self.logger.critical("Handling new connection %s" % cxn) # Get connection to main phase try: cxn.transition_to_main_phase_SDX(self._clean_up_oustanding_cxn, self._get_existing_rules_by_name) except (SDXControllerConnectionTypeError, SDXControllerConnectionValueError) as e: # These error can happen, and their not the end of the world. In # this case, we need to close the connection. self.logger.error( "Connection transition to main phase failed. %s: %s" % (cxn, e)) cxn.close() return except Exception as e: # We need to close the connection, and raise the exception as this # is unhandled. self.logger.error( "Connection transition to main phase failed. %s: %s" % (cxn, e)) cxn.close() raise # Associate connection with name name = cxn.get_name() self.logger.info("LocalController attempting to log in with name %s." % name) self.connections[name] = cxn self.sdx_cm.associate_cxn_with_name(name, cxn) # Update to Rule Manager #FIXME: This is to update the Rule Manager. It seems that whenever a callback is set, it gets a static image of that function/object. That seems incorrect. This should be a workaround. if self.run_topo: self.rm.set_send_add_rule(self.sdx_cm.send_breakdown_rule_add) self.rm.set_send_rm_rule(self.sdx_cm.send_breakdown_rule_rm) # Create EdgePortPolicy # Install an EdgePortPolicy per switch connected to this LC. This # installs basic rules for edge ports that are automatically determined # during breakdown of the rule topo = self.tm.get_topology() self.logger.warning("New connection - name %s details %s" % (name, cxn)) for switch in topo.node[name]['switches']: json_rule = {"EdgePort": {"switch": switch}} epp = EdgePortPolicy(AUTOGENERATED_USERNAME, json_rule) self.rm.add_rule(epp) def _handle_connection_loss(self, cxn): #FIXME: Send this to the LocalControllerManager # Remove connection association # Update the Rule Manager # Remove rules that are installed at startup # - Edgeport rules # Get LC name name = cxn.get_name() # Delete connections associations self.sdx_cm.dissociate_name_with_cxn(name) # Get all EdgePort policies all_edgeport_policies = self.rm.get_rules({'ruletype': 'EdgePort'}) # for each switch owned by a particular LC, delete that particular rule topo = self.tm.get_topology() for switch in topo.node[name]['switches']: json_rule = {"EdgePort": {"switch": switch}} for p in all_edgeport_policies: (rule_hash, json_ver, ruletype, user, state) = p if json_rule == json_ver: self.rm.remove_rule(rule_hash, user) pass def start_main_loop(self): self.main_loop_thread = threading.Thread(target=self._main_loop) self.main_loop_thread.daemon = True self.main_loop_thread.start() self.logger.debug("Main Loop - %s" % (self.main_loop_thread)) def _main_loop(self): # Set up the select structures rlist = self.connections.values() wlist = [] xlist = rlist timeout = 2.0 # Main loop - Have a ~500ms timer on the select call to handle cxn # events while True: # Handle event queue messages try: q_ele = self.sdx_cm.get_cxn_queue_element() while q_ele != None: (action, cxn) = q_ele if action == NEW_CXN: self.logger.warning("Adding connection %s" % cxn) if cxn in rlist: # Already there. Weird, but OK pass rlist.append(cxn) wlist = [] xlist = rlist elif action == DEL_CXN: self.logger.warning("Removing connection %s" % cxn) if cxn in rlist: self._handle_connection_loss(cxn) rlist.remove(cxn) wlist = [] xlist = rlist # Next queue element q_ele = self.sdx_cm.get_cxn_queue_element() except: raise # This is raised if the cxn_q is empty of events. # Normal behaviour pass # Dispatch messages as appropriate try: readable, writable, exceptional = cxnselect( rlist, wlist, xlist, timeout) except Exception as e: self.logger.warning("select returned error: %s" % e) # This can happen if, say, there's a disconnection that hasn't # cleaned up or occured *during* the timeout period. This is due # to select failing. sleep(timeout / 2) continue # Loop through readable for entry in readable: # Get Message try: msg = entry.recv_protocol() except SDXMessageConnectionFailure as e: # Connection needs to be disconnected. entry.close() continue except: raise # Can return None if there was some internal message. if msg == None: self.logger.debug("Received None from recv_protocol %s" % (entry)) continue self.logger.debug("Received a %s message from %s" % (type(msg), entry)) # If message is UnknownSource or L2MultipointUnknownSource, # Send the appropriate handler. if isinstance(msg, SDXMessageUnknownSource): self._switch_message_unknown_source(msg) elif isinstance(msg, SDXMessageL2MultipointUnknownSource): self._switch_change_callback_handler(msg) # Else: Log an error else: self.logger.error("Message %s is not valid" % msg) # Loop through writable for entry in writable: # Anything to do here? pass # Loop through exceptional for entry in exceptional: # FIXME: Handle connection failures pass def _switch_message_unknown_source(self, msg): ''' This handles SDXMessageUnknownSource messages. 'data' is a dictionary of the following pattern - see shared/SDXControllerConnectionManagerConnection.py: {'switch':name, 'port':number, 'src':address} ''' data = msg.get_data() json_rule = { "learneddest": { "dstswitch": data['switch'], "dstport": data['port'], "dstaddress": data['src'] } } ldp = LearnedDestinationPolicy(AUTOGENERATED_USERNAME, json_rule) self.rm.add_rule(ldp) def _switch_change_callback_handler(self, msg): ''' This handles any message for switch_change_callbacks. 'data' is a dictionary of the following pattern: {'cookie':cookie, 'data':opaque data for handler} The cookie is for looking up the policy that needs to handle this callback. data is opaque data that is passed to the UserPolicy.switch_change_callback() function. ''' data = msg.get_data() cookie = data['cookie'] opaque = data['data'] self.rm.change_callback_dispatch(cookie, opaque) def _prep_switches(self): ''' This sends any rules that are necessary to the switches, such as installing broadcast flooding rules. FIXME: Will need to be updated to accomodate dynamic topologies. Also need to deal with deleting this when there's a topology change. ''' json_rule = {FloodTreePolicy.get_policy_name(): None} ftp = FloodTreePolicy(AUTOGENERATED_USERNAME, json_rule) self.rm.add_rule(ftp)
class LocalController(SingletonMixin): ''' The Local Controller is responsible for passing messages from the SDX Controller to the switch. It needs two connections to both the SDX controller and switch(es). Singleton. ''' def __init__(self, runloop=False, options=None): # Setup logger self._setup_logger() self.name = options.name self.logger.info("LocalController %s starting", self.name) # Parse out the options # Import configuration information self.manifest = options.manifest self.sdxip = options.host self.sdxport = options.sdxport self.lcip = LOCALHOST self.ryu_cxn_port = DEFAULT_RYU_CXN_PORT self.openflow_port = DEFAULT_OPENFLOW_PORT if self.manifest != None: self._import_configuration() # Setup switch self.switch_connection = RyuControllerInterface(self.name, self.manifest, self.lcip, self.ryu_cxn_port, self.openflow_port, self.switch_message_cb) self.logger.info("RyuControllerInterface setup finish.") # Setup connection manager self.sdx_cm = SDXControllerConnectionManager() self.sdx_connection = None self.logger.info("SDXControllerConnectionManager setup finish.") self.logger.debug("SDXControllerConnectionManager - %s" % (self.sdx_cm)) # Start connections: self.start_switch_connection() self.start_sdx_controller_connection() # Blocking call self.logger.info("SDX Controller Connection established.") # Start main loop if runloop: self.start_main_loop() self.logger.info("Main Loop started.") def start_main_loop(self): self.main_loop_thread = threading.Thread(target=self._main_loop) self.main_loop_thread.daemon = True self.main_loop_thread.start() self.logger.debug("Main Loop - %s" % (self.main_loop_thread)) def _main_loop(self): ''' This is the main loop for the Local Controller. User should call start_main_loop() to start it. ''' rlist = [self.sdx_connection] wlist = [] xlist = rlist self.logger.debug("Inside Main Loop, SDX connection: %s" % (self.sdx_connection)) while(True): # Based, in part, on https://pymotw.com/2/select/ try: readable, writable, exceptional = cxnselect(rlist, wlist, xlist) except Exception as e: self.logger.error("Error in select - %s" % (e)) # Loop through readable for entry in readable: if entry == self.sdx_connection: self.logger.debug("Receiving Command on sdx_connection") cmd, data = self.sdx_connection.recv_cmd() (switch_id, cmddata) = data self.logger.debug("Received : %s:%s" % (cmd, data)) if cmd == SDX_NEW_RULE: self.switch_connection.send_command(switch_id, cmddata) elif cmd == SDX_RM_RULE: self.switch_connection.remove_rule(switch_id, cmddata) self.logger.debug("Sent : %s:%s" % (cmd, data)) #elif? # Loop through writable for entry in writable: # Anything to do here? pass # Loop through exceptional for entry in exceptional: # FIXME: Handle connection failures pass time.sleep(0.1) def _setup_logger(self): ''' Internal function for setting up the logger formats. ''' # reused from https://github.com/sdonovan1985/netassay-ryu/blob/master/base/mcm.py formatter = logging.Formatter('%(asctime)s %(name)-12s: %(levelname)-8s %(message)s') console = logging.StreamHandler() console.setLevel(logging.WARNING) console.setFormatter(formatter) logfile = logging.FileHandler('localcontroller.log') logfile.setLevel(logging.DEBUG) logfile.setFormatter(formatter) self.logger = logging.getLogger('localcontroller') self.logger.setLevel(logging.DEBUG) self.logger.addHandler(console) self.logger.addHandler(logfile) def _import_configuration(self): with open(self.manifest) as data_file: data = json.load(data_file) # Look at information under the self.name entry lcdata = data['localcontrollers'][self.name] self.lcip = lcdata['lcip'] self.ryu_cxn_port = lcdata['internalconfig']['ryucxninternalport'] self.openflow_port = lcdata['internalconfig']['openflowport'] def start_sdx_controller_connection(self): self.logger.debug("About to open SDX Controller Connection to %s:%s" % (self.sdxip, self.sdxport)) self.sdx_connection = self.sdx_cm.open_outbound_connection(self.sdxip, self.sdxport) self.logger.debug("SDX Controller Connection - %s" % (self.sdx_connection)) # Send name self.sdx_connection.send_cmd(SDX_IDENTIFY, self.name) # FIXME: Credentials exchange def start_switch_connection(self): pass def sdx_message_callback(self): pass # Is this necessary? def switch_message_cb(self, cmd, msg): ''' Called by SwitchConnection to pass information back from the Switch. type is the type of message defined in shared/switch_messages.py. msg is the message that's being sent back, which is type dependant. ''' if (cmd == SM_UNKNOWN_SOURCE or cmd == SM_L2MULTIPOINT_UNKNOWN_SOURCE): self.sdx_connection.send_cmd(cmd, msg)
class SDXController(SingletonMixin): ''' This is the main coordinating module of the SDX controller. It mostly provides startup and coordination, rather than performan many actions by itself. Singleton. ''' def __init__(self, runloop=True, options=None): ''' The bulk of the work happens here. This initializes nearly everything and starts up the main processing loop for the entire SDX Controller. ''' self._setup_logger() mani = options.manifest db = options.database run_topo = options.topo # Start DB connection. Used by other modules. details on the setup: # https://dataset.readthedocs.io/en/latest/api.html # https://github.com/g2p/bedup/issues/38#issuecomment-43703630 self.db = dataset.connect( 'sqlite:///' + db, engine_kwargs={'connect_args': { 'check_same_thread': False }}) # self.run_topo decides whether or not to send rules. self.run_topo = run_topo # Modules with configuration files self.tm = TopologyManager.instance(mani) # Initialize all the modules - Ordering is relatively important here self.aci = AuthenticationInspector.instance() self.azi = AuthorizationInspector.instance() self.be = BreakdownEngine.instance() self.rr = RuleRegistry.instance() self.vi = ValidityInspector.instance() self.um = UserManager.instance(self.db, mani) if mani != None: self.lcm = LocalControllerManager.instance(mani) else: self.lcm = LocalControllerManager.instance() topo = self.tm.get_topology() # Set up the connection-related nonsense - Have a connection event queue self.ip = options.host self.port = options.lcport self.cxn_q = Queue() self.connections = {} self.sdx_cm = SDXControllerConnectionManager() self.cm_thread = threading.Thread(target=self._cm_thread) self.cm_thread.daemon = True self.cm_thread.start() # Register known UserPolicies self.rr.add_ruletype(L2TunnelPolicy) self.rr.add_ruletype(L2MultipointPolicy) self.rr.add_ruletype(EndpointConnectionPolicy) self.rr.add_ruletype(EdgePortPolicy) self.rr.add_ruletype(LearnedDestinationPolicy) self.rr.add_ruletype(FloodTreePolicy) self.rr.add_ruletype(SDXEgressPolicy) self.rr.add_ruletype(SDXIngressPolicy) # Start these modules last! if self.run_topo: self.rm = RuleManager.instance(self.db, self.sdx_cm.send_breakdown_rule_add, self.sdx_cm.send_breakdown_rule_rm) else: self.rm = RuleManager.instance(self.db, send_no_rules, send_no_rules) self.rapi = RestAPI.instance(options.host, options.port, options.shib) # Install any rules switches will need. self._prep_switches() # Go to main loop if runloop: self._main_loop() def _setup_logger(self): ''' Internal function for setting up the logger formats. ''' # reused from https://github.com/sdonovan1985/netassay-ryu/blob/master/base/mcm.py formatter = logging.Formatter( '%(asctime)s %(name)-12s: %(levelname)-8s %(message)s') console = logging.StreamHandler() console.setLevel(logging.WARNING) console.setFormatter(formatter) logfile = logging.FileHandler('sdxcontroller.log') logfile.setLevel(logging.DEBUG) logfile.setFormatter(formatter) self.logger = logging.getLogger('sdxcontroller') self.logger.setLevel(logging.DEBUG) self.logger.addHandler(console) self.logger.addHandler(logfile) def _cm_thread(self): self.sdx_cm.new_connection_callback(self._handle_new_connection) self.sdx_cm.open_listening_port(self.ip, self.port) self.logger.critical("Listening on %s:%d" % (self.ip, self.port)) print("Listening on %s:%d" % (self.ip, self.port)) pass def _handle_new_connection(self, cxn): # Receive name from LocalController, verify that it's in the topology self.logger.critical("Handling new connection %s" % cxn) print("Handling new connection %s" % cxn) cmd, name = cxn.recv_cmd() if cmd != SDX_IDENTIFY: #FIXME: raise some sort of error here. self.logger.error( "LocalController not identifying correctly: %s, %s" % (cmd, name)) return lcs = self.tm.get_lcs() self.logger.info("LocalController attempting to log in with name %s." % name) if name not in lcs: self.logger.error( "LocalController with name %s attempting to get in. Only known nodes: %s" % (name, lcs)) return # FIXME: Credentials exchange # Add connection to connections list self.connections[name] = cxn self.sdx_cm.associate_cxn_with_name(name, cxn) self.cxn_q.put((NEW_CXN, cxn)) #FIXME: Send this to the LocalControllerManager #FIXME: This is to update the Rule Manager. It seems that whenever a callback is set, it gets a static image of that function/object. That seems incorrect. This should be a workaround. if self.run_topo: self.rm.set_send_add_rule(self.sdx_cm.send_breakdown_rule_add) self.rm.set_send_rm_rule(self.sdx_cm.send_breakdown_rule_rm) # Install an EdgePortPolicy per switch connected to this LC. This # installs basic rules for edge ports that are automatically determined # during breakdown of the rule topo = self.tm.get_topology() for switch in topo.node[name]['switches']: json_rule = {"edgeport": {"switch": switch}} epp = EdgePortPolicy(AUTOGENERATED_USERNAME, json_rule) self.rm.add_rule(epp) def _handle_connection_loss(self, cxn): #FIXME: Send this to the LocalControllerManager pass def start_main_loop(self): self.main_loop_thread = threading.Thread(target=self._main_loop) self.main_loop_thread.daemon = True self.main_loop_thread.start() self.logger.debug("Main Loop - %s" % (self.main_loop_thread)) def _main_loop(self): # Set up the select structures rlist = self.connections.values() wlist = [] xlist = rlist timeout = 2.0 # Main loop - Have a ~500ms timer on the select call to handle cxn events while True: # Handle event queue messages try: while not self.cxn_q.empty(): (action, cxn) = self.cxn_q.get(False) if action == NEW_CXN: if cxn in rlist: # Already there. Weird, but OK pass rlist.append(cxn) wlist = [] xlist = rlist elif action == DEL_CXN: if cxn in rlist: rlist.remove(cxn) wlist = [] xlist = rlist except Queue.Empty as e: # This is raised if the cxn_q is empty of events. # Normal behaviour pass # Dispatch messages as appropriate try: readable, writable, exceptional = cxnselect( rlist, wlist, xlist, timeout) except Exception as e: raise # Loop through readable for entry in readable: cmd, data = entry.recv_cmd() self.logger.debug("Received from %s : %s:%s" % (entry, cmd, data)) # Send command to handling function if cmd == SM_UNKNOWN_SOURCE: self._switch_message_unknown_source(data) # This should be expanded for furture commands. Currently only # handles SM_L2MULTIPOINT_UNKNOWN_SOURCE. elif cmd == SM_L2MULTIPOINT_UNKNOWN_SOURCE: self._switch_change_callback_handler(data) # Loop through writable for entry in writable: # Anything to do here? pass # Loop through exceptional for entry in exceptional: # FIXME: Handle connection failures pass def _switch_message_unknown_source(self, data): ''' This handles an SM_UNKNOWN_SOURCE message. 'data' is a dictionary of the following pattern - see shared/switch_messages.py: {'switch':name, 'port':number, 'src':address} ''' json_rule = { "learneddest": { "dstswitch": data['switch'], "dstport": data['port'], "dstaddress": data['src'] } } ldp = LearnedDestinationPolicy(AUTOGENERATED_USERNAME, json_rule) self.rm.add_rule(ldp) def _switch_change_callback_handler(self, data): ''' This handles any message for switch_change_callbacks. 'data' is a dictionary of the following pattern - see shared/switch_messages.py: {'cookie':cookie, 'data':opaque data for handler} The cookie is for looking up the policy that needs to handle this callback. data is opaque data that is passed to the UserPolicy.switch_change_callback() function. ''' cookie = data['cookie'] opaque = data['data'] self.rm.change_callback_dispatch(cookie, opaque) def _prep_switches(self): ''' This sends any rules that are necessary to the switches, such as installing broadcast flooding rules. ''' json_rule = {FloodTreePolicy.get_policy_name(): None} ftp = FloodTreePolicy(AUTOGENERATED_USERNAME, json_rule) self.rm.add_rule(ftp)
def test_singleton(self): firstManager = SDXControllerConnectionManager() secondManager = SDXControllerConnectionManager() self.failUnless(firstManager is secondManager)