Beispiel #1
0
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.")
Beispiel #4
0
    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()
Beispiel #5
0
    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)
Beispiel #7
0
    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()
Beispiel #8
0
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)
Beispiel #10
0
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)
Beispiel #11
0
    def test_singleton(self):
        firstManager = SDXControllerConnectionManager()
        secondManager = SDXControllerConnectionManager()

        self.failUnless(firstManager is secondManager)