diff --git a/configuration/helloworld-multi-vagrant/vagrant-setup.sh b/configuration/helloworld-multi-vagrant/vagrant-setup.sh index 1c2e25ec..e7bde753 100644 --- a/configuration/helloworld-multi-vagrant/vagrant-setup.sh +++ b/configuration/helloworld-multi-vagrant/vagrant-setup.sh @@ -9,7 +9,7 @@ sudo usermod -aG docker $USER # git work -git clone -b mcevik-l2multipoint-ratelimiting https://github.com/atlanticwave-sdx/atlanticwave-proto.git +git clone -b master https://github.com/atlanticwave-sdx/atlanticwave-proto.git # Docker work: build SDX Controller and Local Controller containers cd ~/atlanticwave-proto/ diff --git a/configuration/renci_testbed/renci_ben_backup_port.manifest b/configuration/renci_testbed/renci_ben_backup_port.manifest new file mode 100644 index 00000000..f34e5f5c --- /dev/null +++ b/configuration/renci_testbed/renci_ben_backup_port.manifest @@ -0,0 +1,400 @@ +{ + "endpoints": { + "sdx": { + "type": "sdxcontroller", + "friendlyname": "SDX Controller", + "location": "35.939796, -79.018203", + "vlan": 1411}, + "rencictlr": { + "type": "localcontroller", + "friendlyname": "RENCI Controller", + "location": "35.939796, -79.018203", + "vlan": 1411}, + "rencidtn": { + "type": "dtn", + "friendlyname": "RENCI BF40G Node", + "location": "35.939796, -79.018203", + "vlan": 1421}, + "rencibm8": { + "type": "dtn", + "friendlyname": "RENCI BM8 Node", + "location": "35.939796, -79.018203", + "vlan": 1425}, + "dukectlr": { + "type": "localcontroller", + "friendlyname": "DUKE Controller", + "location": "36.003614, -78.938755", + "vlan": 1411}, + "dukedtn": { + "type": "dtn", + "friendlyname": "DUKE BF40G Node", + "location": "36.003614, -78.938755", + "vlan": 1422}, + "uncctlr": { + "type": "localcontroller", + "friendlyname": "UNC Controller", + "location": "35.903395, -79.048370", + "vlan": 1411}, + "uncdtn": { + "type": "dtn", + "friendlyname": "UNC BF40G Node", + "location": "35.903395, -79.048370", + "vlan": 1423}, + "ncsuctlr": { + "type": "localcontroller", + "friendlyname": "NCSU Controller", + "location": "35.773395, -78.677730", + "vlan": 1411}, + "ncsudtn": { + "type": "dtn", + "friendlyname": "NCSU BF40G Node", + "location": "35.773395, -78.677730", + "vlan": 1424} + }, + "localcontrollers": { + "rencictlr":{ + "shortname": "rencictlr", + "credentials": "rencipwd", + "location": "35.939796, -79.018203", + "lcip": "10.14.11.1", + "internalconfig": { + "ryucxninternalport": 55781, + "openflowport": 6681, + "backuplcswitch": "dukes1" + }, + "switchinfo": [ + { + "name":"rencis1", + "friendlyname":"RENCI Corsa DP2100", + "ip": "10.0.201.1", + "dpid":"201", + "brand":"Open vSwitch", + "model": "2.3.0", + "portinfo": [ + { + "portnumber": 1, + "speed": 80000000000, + "destination": "uncs1" + }, + { + "portnumber": 2, + "speed": 80000000000, + "destination": "dukes1" + }, + { + "portnumber": 23, + "speed": 80000000000, + "destination": "rencis2" + }, + { + "portnumber": 11, + "speed": 80000000000, + "destination": "rencictlr" + }, + { + "portnumber": 12, + "speed": 80000000000, + "destination": "rencidtn" + }, + { + "portnumber": 30, + "speed": 80000000000, + "destination": "sdx" + } + ], + "internalconfig": { + "corsaurl": "https://corsa-2.renci.ben/", + "corsatoken": "ddc60638fa7a7aa3fb5ca10de8f4e5e8bf82cd289187f933cfc7d7a01e7f7f3839ecac1145bc9908abfd03aa493e4acda448522b304a6ce779f82ce9f1528356", + "corsabridge": "br21", + "corsabwin": 19, + "corsabwout": 20, + "corsaratelimitbridge": "br20", + "corsaratelimitports": [21,22], + "managementvlan":1411, + "managementvlanports":[1,2,11,23,30] + } + }, + { + "name":"rencis2", + "friendlyname":"RENCI INNO Corsa DP2100", + "ip": "10.0.201.1", + "dpid":"205", + "brand":"Open vSwitch", + "model": "2.3.0", + "portinfo": [ + { + "portnumber": 23, + "speed": 80000000000, + "destination": "rencis1" + }, + { + "portnumber": 8, + "speed": 80000000000, + "destination": "rencibm8" + } + ], + "internalconfig": { + "corsaurl": "https://corsa-1.renci.ben/", + "corsatoken": "245c25eac968cb22624438f0e1bcd2d766be666843676e413b6739d86f6c3523bf7aab1e5c3231aa2d7acd6757371dea3d8cbc470a1e60825f93a85dabd01ddb", + "corsabridge": "br25", + "corsabwin": 25, + "corsabwout": 26, + "corsaratelimitbridge": "br20", + "corsaratelimitports": [27,28], + "managementvlan":1411, + "managementvlanports":[23] + } + } + ], + "operatorinfo": { + "organization": "RENCI", + "administrator": "Mert Cevik", + "contact": "mcevik@renci.org" + } + }, + "dukectlr":{ + "shortname": "dukectlr", + "credentials": "dukepwd", + "location":"36.003614, -78.938755", + "lcip": "10.14.11.2", + "internalconfig": { + "ryucxninternalport": 55782, + "openflowport": 6682, + "backuplcswitch": "rencis1" + }, + "switchinfo": [ + { + "name":"dukes1", + "friendlyname":"DUKE Corsa DP2100", + "ip": "10.0.202.1", + "dpid": "202", + "brand": "Open vSwitch", + "model": "2.3.0", + "portinfo": [ + { + "portnumber": 1, + "speed": 80000000000, + "destination": "rencis1" + }, + { + "portnumber": 2, + "speed": 80000000000, + "destination": "ncsus1" + }, + { + "portnumber": 11, + "speed": 80000000000, + "destination": "dukectlr" + }, + { + "portnumber": 12, + "speed": 80000000000, + "destination": "dukedtn" + } + ], + "internalconfig": { + "corsaurl": "https://corsa-1.duke.ben/", + "corsatoken": "c73ad6e29773582187c06a1558f8ecc71ea273b3a5ae9e4f03f153d73f6436a619f3c8471205c28e25f9ae62741d5435e54c8a6f33f2b333154430448dd18215", + "corsabridge": "br22", + "corsabwin":19, + "corsabwout":20, + "corsaratelimitbridge":"br20", + "corsaratelimitports":[21,22], + "managementvlan":1411, + "managementvlanports":[1,2,11] + } + } + ], + "operatorinfo": { + "organization": "RENCI", + "administrator": "Mert Cevik", + "contact": "mcevik@renci.org" + } + }, + "uncctlr":{ + "shortname": "uncctlr", + "credentials": "uncpwd", + "location":"35.903395, -79.048370", + "lcip": "10.14.11.3", + "internalconfig": { + "ryucxninternalport": 55783, + "openflowport": 6683, + "backuplcswitch": "ncsus1" + }, + "switchinfo": [ + { + "name":"uncs1", + "friendlyname":"UNC Corsa DP2100", + "ip": "10.0.203.1", + "dpid":"203", + "brand":"Open vSwitch", + "model": "2.3.0", + "portinfo": [ + { + "portnumber": 1, + "speed": 80000000000, + "destination": "rencis1" + }, + { + "portnumber": 11, + "speed": 80000000000, + "destination": "uncctlr" + }, + { + "portnumber": 12, + "speed": 80000000000, + "destination": "uncdtn" + } + ], + "internalconfig": { + "corsaurl": "https://corsa-1.unc.ben/", + "corsatoken": "fa18b3d4d84507dba6568678a45fcecdec03247d7b5c18e45c5f288066a52d970cf8ee0bcf7759c698a7b56b92824963c8c03acf77f0a3aa91ad4f64c3aa7b15", + "corsabridge": "br23", + "corsabwin":19, + "corsabwout":20, + "corsaratelimitbridge":"br20", + "corsaratelimitports":[21,22], + "managementvlan":1411, + "managementvlanports":[1,11], + "managementvlanbackupports":[1, 2, 11], + "sdxmanagementvlanbackupports":[1, 2, 11] + } + } + ], + "operatorinfo": { + "organization": "RENCI", + "administrator": "Mert Cevik", + "contact": "mcevik@renci.org" + } + }, + "ncsuctlr":{ + "shortname": "ncsuctlr", + "credentials": "ncsupwd", + "location": "35.773395, -78.677730", + "lcip": "10.14.11.4", + "internalconfig": { + "ryucxninternalport": 55784, + "openflowport": 6684, + "backuplc": "uncs1" + }, + "switchinfo": [ + { + "name":"ncsus1", + "friendlyname":"NCSU Corsa DP2100", + "ip": "10.0.204.1", + "dpid":"204", + "brand":"Open vSwitch", + "model": "2.3.0", + "portinfo": [ + { + "portnumber": 1, + "speed": 80000000000, + "destination": "dukes1" + }, + { + "portnumber": 11, + "speed": 80000000000, + "destination": "ncsuctlr" + }, + { + "portnumber": 12, + "speed": 80000000000, + "destination": "ncsudtn" + } + ], + "internalconfig": { + "corsaurl": "https://corsa-1.ncsu.ben/", + "corsatoken": "9b95dda0314beb7acf620084dff53e5df7eaf80f9ee453cfb3550f33aecd356561fcf22568ac8365f2892725f129147ceb5718cb711c3a93b136030348dd9eeb", + "corsabridge": "br24", + "corsabwin":19, + "corsabwout":20, + "corsaratelimitbridge":"br20", + "corsaratelimitports":[21,22], + "managementvlan":1411, + "managementvlanports":[1,11], + "managementvlanbackupports":[2, 11] + } + } + ], + "operatorinfo": { + "organization": "RENCI", + "administrator": "Mert Cevik", + "contact": "mcevik@renci.org" + } + } + }, + "participants": { + "sdonovan": { + "credentials": "1234", + "organization": "Georgia Tech/RNOC", + "contact": "sdonovan@gatech.edu", + "type": "administrator", + "permitted_actions": [ + "tbd" + ], + "restrictions": [ + "tbd" + ] + }, + "jchung": { + "credentials": "4321", + "organization": "Georgia Tech/RNOC", + "contact": "jchung@gatech.edu", + "type": "user", + "permitted_actions": [ + "tbd" + ], + "restrictions": [ + "tbd" + ] + }, + "cwang": { + "credentials": "1234", + "organization": "RENCI", + "contact": "cwang@renci.org", + "type": "administrator", + "permitted_actions": [ + "tbd" + ], + "restrictions": [ + "tbd" + ] + }, + "yxin": { + "credentials": "1234", + "organization": "RENCI", + "contact": "yxin@renci.org", + "type": "administrator", + "permitted_actions": [ + "tbd" + ], + "restrictions": [ + "tbd" + ] + }, + "stealey": { + "credentials": "1234", + "organization": "RENCI", + "contact": "stealey@renci.org", + "type": "administrator", + "permitted_actions": [ + "tbd" + ], + "restrictions": [ + "tbd" + ] + }, + "mcevik": { + "credentials": "1234", + "organization": "RENCI", + "contact": "mcevik@renci.org", + "type": "administrator", + "permitted_actions": [ + "tbd" + ], + "restrictions": [ + "tbd" + ] + } + } +} diff --git a/docker/lc_container/Dockerfile b/docker/lc_container/Dockerfile index 487eed8f..e906f2ad 100644 --- a/docker/lc_container/Dockerfile +++ b/docker/lc_container/Dockerfile @@ -3,7 +3,7 @@ FROM ubuntu:16.04 RUN apt update && apt install -y git python-virtualenv pypy python-pip net-tools vim # This really should point to a particular version -RUN git clone -b mcevik-l2multipoint-ratelimiting https://github.com/atlanticwave-sdx/atlanticwave-proto.git +RUN git clone -b master https://github.com/atlanticwave-sdx/atlanticwave-proto.git # Virtualenv setup RUN virtualenv -p /usr/bin/pypy /appenv diff --git a/docker/sdx_container/Dockerfile b/docker/sdx_container/Dockerfile index a4cc8991..411ca3fc 100644 --- a/docker/sdx_container/Dockerfile +++ b/docker/sdx_container/Dockerfile @@ -14,7 +14,7 @@ COPY run_sdx.sh . COPY ./*.manifest ./ # This really should point to a particular version -RUN git clone -b mcevik-l2multipoint-ratelimiting https://github.com/atlanticwave-sdx/atlanticwave-proto.git +RUN git clone -b master https://github.com/atlanticwave-sdx/atlanticwave-proto.git RUN cat atlanticwave-proto/requirements.txt RUN pip install -r atlanticwave-proto/requirements.txt diff --git a/localctlr/LCRuleManager.py b/localctlr/LCRuleManager.py index 6d73b297..bd01f20e 100644 --- a/localctlr/LCRuleManager.py +++ b/localctlr/LCRuleManager.py @@ -67,9 +67,12 @@ def add_rule(self, cookie, switch_id, lcrule, for dupe in dupes: (c,sid,lcr,stat) = dupe if lcr == lcrule: - raise LCRuleManagerValidationError( - "Duplicate add_rule for %s:%s:%s" % - (cookie, switch_id, str(lcrule))) + if isinstance(ManagementLCRecoverRule): + self.logger.debug("ManagementLCRecoverRule, ignored.") + else: + raise LCRuleManagerValidationError( + "Duplicate add_rule for %s:%s:%s" % + (cookie, switch_id, str(lcrule))) # Translate rule into a string so it can be stored self.rule_table.insert({'cookie':cookie, @@ -129,7 +132,18 @@ def _find_rules(self, filter={}): pickle.loads(str(x['rule'])), x['status']) for x in results] return retval - + + def list_all_rules(self, full_tuple=False): + rules = self.rule_table.find() + self.logger.debug("Retrieving all rules.") + if full_tuple: + retval = [(x['cookie'], + x['switch_id'], + pickle.loads(str(x['rule'])), + x['status']) for x in rules] + return retval + retval = [pickle.loads(str(x['rule'])) for x in rules] + return retval def get_rules(self, cookie, switch_id, full_tuple=False): ''' Returns a list of all rules matching cookie and switch_id. diff --git a/localctlr/LocalController.py b/localctlr/LocalController.py index 5449df78..0e09bfc6 100644 --- a/localctlr/LocalController.py +++ b/localctlr/LocalController.py @@ -23,6 +23,7 @@ from shared.SDXControllerConnectionManager import * from shared.SDXControllerConnectionManagerConnection import * from switch_messages import * +from shared.ManagementLCRecoverRule import * LOCALHOST = "127.0.0.1" DEFAULT_RYU_CXN_PORT = 55767 @@ -107,6 +108,8 @@ def _main_loop(self): wlist = [] xlist = rlist timeout = 1.0 + num_of_retry = 0 + all_rules = [] self.logger.debug("Inside Main Loop, SDX connection: %s" % (self.sdx_connection)) @@ -151,12 +154,17 @@ def _main_loop(self): if (self.sdx_connection == None and self.start_cxn_thread == None): self.logger.info("Restarting SDX Connection") + for entry in self.lcconfigdata['switchinfo']: + dpid = int(entry['dpid'], 0) + lc_recover = ManagementLCRecoverRule(0, dpid) + self.install_rule_sdxmsg(lc_recover) + self.logger.debug("ManagementLCRecoverRule sent. About to restart SDX connection.") self.start_sdx_controller_connection() #Restart! if len(rlist) == 0: sleep(timeout/2) continue - + try: readable, writable, exceptional = cxnselect(rlist, wlist, @@ -165,7 +173,6 @@ def _main_loop(self): except Exception as e: self.logger.error("LocalController: Error in select - %s" % (e)) - # Loop through readable for entry in readable: # Get Message @@ -250,9 +257,10 @@ def _main_loop(self): self.sdx_connection.close() self.sdx_connection = None self.cxn_q.put((DEL_CXN, cxn)) - + # Restart new connection - self.start_sdx_controller_connection() + self.start_sdx_controller_connection() + def _add_switch_internal_config_to_db(self, dpid, internal_config): @@ -371,6 +379,7 @@ def _get_switch_internal_config(self, dpid): Pulls information from the DB. ''' key = str(dpid) + d = self.config_table.find_one(key=key) if d == None: return None @@ -407,6 +416,33 @@ def _get_SDX_config_in_db(self): val = d['value'] return pickle.loads(str(val)) + def _get_switch_internal_config_count(self): + # Returns a count of internal configs. + d = self.config_table.find() + count = 0 + for entry in d: + if (entry['key'] == 'lcip' or + entry['key'] == 'manifest_filename' or + entry['key'] == 'ryucxnport'): + continue + count += 1 + return count + + def _add_switch_internal_config_to_db(self, dpid, internal_config): + # Pushes a switch internal_config into the db. + # key: "" + # value: + key = dpid + value = pickle.dumps(internal_config) + if self._get_switch_internal_config(dpid) == None: + self.logger.info("Adding new internal_config for DPID %s" % dpid) + self.config_table.insert({'key': key, 'value': value}) + else: + # Already exists, must update + self.logger.info("updating internal_config for DPID %s" % dpid) + self.config_table.update({'key': key, 'value': value}, + ['key']) + def _get_manifest_filename_in_db(self): # Returns the manifest filename if it exists or None if it does not. key = 'manifest_filename' @@ -416,6 +452,15 @@ def _get_manifest_filename_in_db(self): val = d['value'] return pickle.loads(str(val)) + def _get_config_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 @@ -435,13 +480,38 @@ def _setup(self, options): raise Exception("Stored and passed in manifest filenames don't match up %s:%s" % (str(self.manifest), str(self._get_manifest_filename_in_db()))) - + + self.conf_file = None + # If the conf_name is None, try to get the name from the DB. + if self.conf_file == None: + self.conf_file = self._get_config_filename_in_db() + elif (self.conf_file != self._get_config_filename_in_db() and + None != self._get_config_filename_in_db()): + # Make sure it matches! + # FIXME: Should we force everything to be imported if different. + raise Exception("Stored and passed in manifest filenames don't match up %s:%s" % + (str(self.conf_file), + str(self._get_config_filename_in_db()))) + + self.lcconfigdata = None + # Get config file, if it exists + try: + self.logger.info("Opening config file %s" % self.conf_file) + with open(self.conf_file) as data_file: + data = json.load(data_file) + lcdata = data['localcontrollers'][self.name] + except Exception as e: + self.logger.warning("exception when opening config file: %s" % + str(e)) + # 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] + # Save lcdata for future use + self.lcconfigdata = lcdata self.logger.info("Successfully opened manifest file %s" % self.manifest) except Exception as e: @@ -496,7 +566,6 @@ def _setup(self, options): ic['name'] = entry['name'] self._add_switch_internal_config_to_db(dpid, ic) - def start_sdx_controller_connection(self): # Kick off thread to start connection. if self.start_cxn_thread != None: @@ -559,9 +628,15 @@ def sdx_message_callback(self): # 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() + if type(msg) == ManagementLCRecoverRule: + switch_id = msg.get_switch_id() + rule = msg + cookie = msg.get_cookie() + self.logger.debug("Got ManagementLCRecoverRule, to be installed") + else: + switch_id = msg.get_data()['switch_id'] + rule = msg.get_data()['rule'] + cookie = rule.get_cookie() self.logger.debug("install_rule_sdxmsg: %d:%s:%s" % (cookie, switch_id, @@ -569,13 +644,34 @@ def install_rule_sdxmsg(self, msg): 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_all_rules_sdxmsg(self): + ''' Removes all data plane rules. ''' + rules = self.rm.list_all_rules() + + if rules == []: + self.logger.error("remove_rule_sdxmsg: trying to remove a rule that doesn't exist %s" % cookie) + return + for rule in rules: + self.logger.debug("Removing rule:") + self.logger.debug(rule) + self.logger.debug("Type of rule:") + self.logger.debug(type(rule)) + cookie = rule.get_cookie() + switch_id = rule.get_switch_id() + 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 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 @@ -600,7 +696,6 @@ def remove_rule_sdxmsg(self, msg): self.logger.debug("remove tunnels for L2Multipoint rate limiting: %d:%s:%s" % (cookie, switch_id, r)) self.remove_l2mp_ratelimiting_tunnel(switch_id, cookie, rules) - self.logger.debug("remove_rule_sdxmsg: %d:%s:%s" % (cookie, switch_id, rules)) diff --git a/localctlr/RyuTranslateInterface.py b/localctlr/RyuTranslateInterface.py index e898ca79..d3882c6d 100644 --- a/localctlr/RyuTranslateInterface.py +++ b/localctlr/RyuTranslateInterface.py @@ -37,6 +37,8 @@ from shared.L2MultipointLearnedDestinationLCRule import * from shared.FloodTreeLCRule import * from shared.ManagementVLANLCRule import * +from shared.ManagementLCRecoverRule import * +from shared.ManagementSDXRecoverRule import * LOCALHOST = "127.0.0.1" @@ -558,9 +560,10 @@ def _new_switch_bootstrapping(self, ev): # For last table # - Create a default drop rule (if necessary needed). Priority 0 for i in (managementvlanports): + self.logger.debug("Using default management ports.") matches = [IN_PORT(i)] actions = [Drop()] - priorty = PRIORITY_DEFAULT_PLUS_ONE + priority = PRIORITY_DEFAULT_PLUS_ONE table = LASTTABLE marule = MatchActionLCRule(switch_id, matches, actions) results += self._translate_MatchActionLCRule(datapath, @@ -571,7 +574,7 @@ def _new_switch_bootstrapping(self, ev): # Catch-all for those not in the same port matches = [] actions = [Drop()] - priorty = PRIORITY_DEFAULT + priority = PRIORITY_DEFAULT table = LASTTABLE marule = MatchActionLCRule(switch_id, matches, actions) results += self._translate_MatchActionLCRule(datapath, @@ -603,6 +606,156 @@ def _new_switch_bootstrapping(self, ev): for rule in results: self.add_flow(datapath, rule) + def _backup_port_recover(self, datapath, of_cookie, lc_recover_rule): + '''Remove existing default port and use backup port''' + self.logger.debug("got ManagementLCRecoverRule") + self.logger.debug("Checking if backup port is available") + switch_id = 0 # This is unimportant: + # it's never used in the translation + + of_cookie = self._get_new_OF_cookie(-1, -1) # FIXME: magic number + results = [] + + # In-band Communication + # Extract management VLAN and ports from the manifest + internal_config = self._get_switch_internal_config(datapath.id) + if internal_config == None: + raise ValueError("DPID %s does not have internal_config" % + datapath.id) + if 'managementvlan' in internal_config.keys(): + managementvlan = internal_config['managementvlan'] + if 'managementvlanports' in internal_config.keys(): + managementvlanports = internal_config['managementvlanports'] + + if 'managementvlanbackupports' in internal_config.keys(): + managementvlanbackupports = internal_config['managementvlanbackupports'] + else: + self.logger.debug("No backup port provided") + return + + # In-band Communication + # If the management VLAN needs to be setup, set it up. + if 'managementvlan' in internal_config.keys(): + managementvlan = internal_config['managementvlan'] + managementvlanports = internal_config['managementvlanports'] + untaggedmanagementvlanports = [] + if 'untaggedmanagementvlanports' in internal_config.keys(): + untaggedmanagementvlanports = internal_config['untaggedmanagementvlanports'] + + table = L2TUNNELTABLE + mvrule = ManagementVLANLCRule(switch_id, + managementvlan, + managementvlanports, + untaggedmanagementvlanports) + results += self._translate_ManagementVLANLCRule(datapath, + table, + of_cookie, + mvrule) + + self.logger.debug("REMOVING default flows") + # Install default rules + for rule in results: + self.remove_flow(datapath, rule) + + # Add backup management vlan ports + results = [] + if 'managementvlan' in internal_config.keys(): + managementvlan = internal_config['managementvlan'] + managementvlanbackupports = internal_config['managementvlanbackupports'] + untaggedmanagementvlanports = [] + if 'untaggedmanagementvlanports' in internal_config.keys(): + untaggedmanagementvlanports = internal_config['untaggedmanagementvlanports'] + + table = L2TUNNELTABLE + mvrule = ManagementVLANLCRule(switch_id, + managementvlan, + managementvlanbackupports, + untaggedmanagementvlanports) + results += self._translate_ManagementVLANLCRule(datapath, + table, + of_cookie, + mvrule) + + self.logger.debug("ADDING backup management VLAN flows") + # Install default rules + for rule in results: + self.add_flow(datapath, rule) + + def _backup_port_recover_from_sdx_msg(self, datapath, of_cookie, lc_recover_rule): + '''Remove existing default port and use backup port''' + self.logger.debug("got ManagementSDXRecoverRule from SDX message") + self.logger.debug("Checking if backup port is available") + switch_id = 0 # This is unimportant: + # it's never used in the translation + + of_cookie = self._get_new_OF_cookie(-1, -1) # FIXME: magic number + results = [] + + # In-band Communication + # Extract management VLAN and ports from the manifest + internal_config = self._get_switch_internal_config(datapath.id) + if internal_config == None: + raise ValueError("DPID %s does not have internal_config" % + datapath.id) + if 'managementvlan' in internal_config.keys(): + managementvlan = internal_config['managementvlan'] + if 'managementvlanports' in internal_config.keys(): + managementvlanports = internal_config['managementvlanports'] + + if 'sdxmanagementvlanbackupports' in internal_config.keys(): + sdxmanagementvlanbackupports = internal_config['sdxmanagementvlanbackupports'] + else: + self.logger.debug("No SDX management VLAN backup port provided") + return + + # In-band Communication + # If the management VLAN needs to be setup, set it up. + if 'managementvlan' in internal_config.keys(): + managementvlan = internal_config['managementvlan'] + managementvlanports = internal_config['managementvlanports'] + untaggedmanagementvlanports = [] + if 'untaggedmanagementvlanports' in internal_config.keys(): + untaggedmanagementvlanports = internal_config['untaggedmanagementvlanports'] + + table = L2TUNNELTABLE + mvrule = ManagementVLANLCRule(switch_id, + managementvlan, + managementvlanports, + untaggedmanagementvlanports) + results += self._translate_ManagementVLANLCRule(datapath, + table, + of_cookie, + mvrule) + + self.logger.debug("REMOVING default flows") + # Install default rules + for rule in results: + self.remove_flow(datapath, rule) + + # Add backup management vlan ports + results = [] + if 'managementvlan' in internal_config.keys(): + managementvlan = internal_config['managementvlan'] + sdxmanagementvlanbackupports = internal_config['sdxmanagementvlanbackupports'] + untaggedmanagementvlanports = [] + if 'untaggedmanagementvlanports' in internal_config.keys(): + untaggedmanagementvlanports = internal_config['untaggedmanagementvlanports'] + + table = L2TUNNELTABLE + mvrule = ManagementVLANLCRule(switch_id, + managementvlan, + sdxmanagementvlanbackupports, + untaggedmanagementvlanports) + results += self._translate_ManagementVLANLCRule(datapath, + table, + of_cookie, + mvrule) + + self.logger.debug("ADDING SDX msg backup flows") + # Install default rules + for rule in results: + self.add_flow(datapath, rule) + def _translate_MatchActionLCRule(self, datapath, table, of_cookie, marule, priority=100): ''' This translates MatchActionLCRules. There is only one rule generated @@ -1594,10 +1747,19 @@ def install_rule(self, datapath, sdx_rule): of_cookie, sdx_rule) + elif isinstance(sdx_rule, ManagementLCRecoverRule): + self._backup_port_recover(datapath, of_cookie, sdx_rule) + return + + elif isinstance(sdx_rule, ManagementSDXRecoverRule): + self._backup_port_recover_from_sdx_msg(datapath, of_cookie, sdx_rule) + return + if switch_rules == None or switch_table == None: - self.logger.error( - "switch_rules or switch_table is None for msg: %s\n switch_rules - %s\n switch_table - %s" % - sdx_rule, switch_rules, switch_table) + if not isinstance(sdx_rule, ManagementLCRecoverRule): + self.logger.error( + "switch_rules or switch_table is None for msg: %s\n switch_rules - %s\n switch_table - %s" % + sdx_rule, switch_rules, switch_table) # FIXME: This shouldn't happen... pass diff --git a/sdxctlr/SDXController.py b/sdxctlr/SDXController.py index 78113d0b..543f1d77 100644 --- a/sdxctlr/SDXController.py +++ b/sdxctlr/SDXController.py @@ -32,7 +32,7 @@ from shared.LearnedDestinationPolicy import * from shared.FloodTreePolicy import * from shared.SDXPolicy import SDXEgressPolicy, SDXIngressPolicy - +from shared.ManagementSDXRecoverPolicy import * class SDXControllerError(Exception): @@ -105,6 +105,7 @@ def __init__(self, runloop=True, options=None): self.rr.add_ruletype(FloodTreePolicy) self.rr.add_ruletype(SDXEgressPolicy) self.rr.add_ruletype(SDXIngressPolicy) + self.rr.add_ruletype(ManagementSDXRecoverPolicy) # Start these modules last! if self.run_topo: @@ -213,7 +214,7 @@ def _handle_connection_loss(self, cxn): # Get LC name name = cxn.get_name() - + self.logger.debug("Local Controller Lost connection: " + str(name)) # Delete connections associations self.sdx_cm.dissociate_name_with_cxn(name) # Get all EdgePort policies @@ -221,6 +222,10 @@ def _handle_connection_loss(self, cxn): # for each switch owned by a particular LC, delete that particular rule topo = self.tm.get_topology() + self.logger.debug("Getting backup LC.") + backuplc = topo.node[name]['internalconfig']['backuplc'] + self.logger.debug("Got backup LC: " + str(backuplc)) + for switch in topo.node[name]['switches']: json_rule = {"EdgePort":{"switch":switch}} for p in all_edgeport_policies: @@ -228,7 +233,9 @@ def _handle_connection_loss(self, cxn): if json_rule == json_ver: self.rm.remove_rule(rule_hash, user) - + json_rule = {"ManagementSDXRecover":{"switch":backuplc}} + msr = ManagementSDXRecoverPolicy(AUTOGENERATED_USERNAME, json_rule) + self.rm.add_rule(msr) pass def start_main_loop(self): diff --git a/shared/FloodTreeLCRule.py b/shared/FloodTreeLCRule.py index 7b8ee080..c2d2107c 100644 --- a/shared/FloodTreeLCRule.py +++ b/shared/FloodTreeLCRule.py @@ -43,3 +43,18 @@ def __eq__(self, other): def get_ports(self): return self.ports + + def get_switch_id(self): + return self.switch_id + + def get_cookie(self): + return self.cookie + + def set_ports(self, ports): + self.ports = ports + + def set_switch_id(self, switch_id): + self.switch_id = switch_id + + def set_cookie(self, cookie): + self.cookie = cookie diff --git a/shared/ManagementLCRecoverRule.py b/shared/ManagementLCRecoverRule.py new file mode 100644 index 00000000..eb2112e1 --- /dev/null +++ b/shared/ManagementLCRecoverRule.py @@ -0,0 +1,31 @@ +# Copyright 2018 - Sean Donovan +# AtlanticWave/SDX Project + +from LCRule import * + +class ManagementLCRecoverRule(LCRule): + ''' This structure is used to pass the Management VLAN recover rule to + RyuTranslateInterface. This rule is created just for trigger recovery. ''' + + def __init__(self, cookie, switch_id): + ''' Field descritpions: + switch_id - Which switch is involved + ''' + super(ManagementLCRecoverRule, self).__init__(self, switch_id) + self.cookie = cookie + self.switch_id = switch_id + + def __str__(self): + retstr = ("ManagementLCRecoverRule: switch %s" % + (self.switch_id)) + return retstr + + def __eq__(self, other): + return (type(self) == type(other) and + self.get_switch_id() == other.get_switch_id()) + + def get_switch_id(self): + return self.switch_id + + def get_cookie(self): + return self.cookie diff --git a/shared/ManagementSDXRecoverPolicy.py b/shared/ManagementSDXRecoverPolicy.py new file mode 100644 index 00000000..70bcd69e --- /dev/null +++ b/shared/ManagementSDXRecoverPolicy.py @@ -0,0 +1,100 @@ +# Copyright 2017 - Sean Donovan +# AtlanticWave/SDX Project + + +from UserPolicy import * +from datetime import datetime +from shared.constants import * +from EdgePortLCRule import * +from ManagementSDXRecoverRule import * +import networkx as nx + +class ManagementSDXRecoverPolicy(UserPolicy): + ''' This policy is used by SDX to try covering connection once LocalController + connection is lost (heart beat is missing). + It requires the following information: + - Switch + Example Json: + {"EdgePort":{ + "switch":"mia-switch"}} + ''' + + def __init__(self, username, json_rule): + self.switch = None + + super(ManagementSDXRecoverPolicy, self).__init__(username, + json_rule) + + # Anything specific here? + pass + + def __str__(self): + return "%s(%s)" % (self.get_policy_name(), self.switch) + + @classmethod + def check_syntax(cls, json_rule): + try: + # Make sure the times are the right format + # https://stackoverflow.com/questions/455580/json-datetime-between-python-and-javascript + # 'switch' is the LocalController NAME! + switch = json_rule[cls.get_policy_name()]['switch'] + + except Exception as e: + import os + exc_type, exc_obj, exc_tb = sys.exc_info() + filename = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + lineno = exc_tb.tb_lineno + print "%s: Exception %s at %s:%d" % (self.get_policy_name(), + str(e), filename,lineno) + raise + + def breakdown_rule(self, tm, ai): + self.breakdown = [] + topology = tm.get_topology() + authorization_func = ai.is_authorized + switch_id = topology.node[self.switch]['dpid'] + shortname = topology.node[self.switch]['locationshortname'] + bd = UserPolicyBreakdown(shortname, []) + msr = ManagementSDXRecoverRule(switch_id) + bd.add_to_list_of_rules(msr) + + self.breakdown.append(bd) + return self.breakdown + + + def check_validity(self, tm, ai): + #FIXME: This is going to be skipped for now, as we need to figure out what's authorized and what's not. + return True + + def _parse_json(self, json_rule): + jsonstring = self.ruletype + + if type(json_rule) is not dict: + raise UserPolicyTypeError("json_rule is not a dictionary:\n %s" % json_rule) + if jsonstring not in json_rule.keys(): + raise UserPolicyValueError("%s value not in entry:\n %s" % ('rules', json_rule)) + + self.switch = str(json_rule[jsonstring]['switch']) + + #FIXME: Really need some type verifications here. + + + def pre_add_callback(self, tm, ai): + ''' This is called before a rule is added to the database. For instance, + if certain resources need to be locked down or rules authorized, + this can do it. May not need to be implemented. ''' + pass + + def pre_remove_callback(self, tm, ai): + ''' This is called before a rule is removed from the database. For + instance, if certain resources need to be released, this can do it. + May not need to be implemented. ''' + + pass + + + + + + + diff --git a/shared/ManagementSDXRecoverRule.py b/shared/ManagementSDXRecoverRule.py new file mode 100644 index 00000000..f7d97ce3 --- /dev/null +++ b/shared/ManagementSDXRecoverRule.py @@ -0,0 +1,34 @@ +# Copyright 2018 - Sean Donovan +# AtlanticWave/SDX Project + +from LCRule import * + +from LCRule import * + +class ManagementSDXRecoverRule(LCRule): + ''' This rule by SDX to try covering connection once LocalController + connection is lost (heart beat is missing). + Created by ManagementSDXRecoverPolicy. ''' + + def __init__(self, switch_id): + ''' Field descritpions: + switch_id - Which switch is involved + ''' + self.switch_id = switch_id + super(ManagementSDXRecoverRule, self).__init__(switch_id) + + + def __str__(self): + retstr = ("ManagementSDXRecoverRule: switch %s" % + (self.switch_id)) + return retstr + + def __eq__(self, other): + return (type(self) == type(other) and + self.get_switch_id() == other.get_switch_id()) + + def get_switch_id(self): + return self.switch_id + + def get_cookie(self): + return self.cookie