diff --git a/config/config_mgmt.py b/config/config_mgmt.py index e4a5268e1f..af8b845c7a 100644 --- a/config/config_mgmt.py +++ b/config/config_mgmt.py @@ -651,8 +651,10 @@ def insertHandler(diff, inp, outp, config): elif isinstance(outp, list): # just take list from output - # print("Delete from List: {} {} {}".format(inp, outp, list)) config.extend(outp) + # in config DB, empty lists are represented by [""] + if "" in config: + config.remove("") return diff --git a/sonic-utilities-tests/config_mgmt_test.py b/sonic-utilities-tests/config_mgmt_test.py new file mode 100644 index 0000000000..3bbbd565b2 --- /dev/null +++ b/sonic-utilities-tests/config_mgmt_test.py @@ -0,0 +1,723 @@ +import imp +import os +# import file under test i.e. config_mgmt.py +imp.load_source('config_mgmt', \ + os.path.join(os.path.dirname(__file__), '..', 'config', 'config_mgmt.py')) +import config_mgmt + +from unittest import TestCase +from mock import MagicMock, call +from json import dump + +class TestConfigMgmt(TestCase): + ''' + Test Class for config_mgmt.py + ''' + + def setUp(self): + config_mgmt.CONFIG_DB_JSON_FILE = "startConfigDb.json" + config_mgmt.DEFAULT_CONFIG_DB_JSON_FILE = "portBreakOutConfigDb.json" + return + + def test_config_validation(self): + curConfig = dict(configDbJson) + self.writeJson(curConfig, config_mgmt.CONFIG_DB_JSON_FILE) + cm = config_mgmt.ConfigMgmt(source=config_mgmt.CONFIG_DB_JSON_FILE) + assert cm.validateConfigData() == True + return + + def test_table_without_yang(self): + curConfig = dict(configDbJson) + unknown = {"unknown_table": {"ukey": "uvalue"}} + self.updateConfig(curConfig, unknown) + self.writeJson(curConfig, config_mgmt.CONFIG_DB_JSON_FILE) + cm = config_mgmt.ConfigMgmt(source=config_mgmt.CONFIG_DB_JSON_FILE) + #assert "unknown_table" in cm.tablesWithoutYang() + return + + def test_search_keys(self): + curConfig = dict(configDbJson) + self.writeJson(curConfig, config_mgmt.CONFIG_DB_JSON_FILE) + cmdpb = config_mgmt.ConfigMgmtDPB(source=config_mgmt.CONFIG_DB_JSON_FILE) + out = cmdpb.configWithKeys(portBreakOutConfigDbJson, \ + ["Ethernet8","Ethernet9"]) + assert "VLAN" not in out.keys() + assert "INTERFACE" not in out.keys() + for k in out['ACL_TABLE'].keys(): + # only ports must be chosen + len(out['ACL_TABLE'][k]) == 1 + out = cmdpb.configWithKeys(portBreakOutConfigDbJson, \ + ["Ethernet10","Ethernet11"]) + assert "INTERFACE" in out.keys() + for k in out['ACL_TABLE'].keys(): + # only ports must be chosen + len(out['ACL_TABLE'][k]) == 1 + return + + def test_break_out(self): + # prepare default config + self.writeJson(portBreakOutConfigDbJson, \ + config_mgmt.DEFAULT_CONFIG_DB_JSON_FILE) + # prepare config dj json to start with + curConfig = dict(configDbJson) + #Ethernet8: start from 4x25G-->2x50G with -f -l + self.dpb_port8_4x25G_2x50G_f_l(curConfig) + #Ethernet8: move from 2x50G-->1x100G without force, list deps + self.dpb_port8_2x50G_1x100G(curConfig) + # Ethernet8: move from 2x50G-->1x100G with force, where deps exists + self.dpb_port8_2x50G_1x100G_f(curConfig) + # Ethernet8: move from 1x100G-->4x25G without force, no deps + self.dpb_port8_1x100G_4x25G(curConfig) + # Ethernet8: move from 4x25G-->1x100G with force, no deps + self.dpb_port8_4x25G_1x100G_f(curConfig) + # Ethernet8: move from 1x100G-->1x50G(2)+2x25G(2) with -f -l, + self.dpb_port8_1x100G_1x50G_2x25G_f_l(curConfig) + # Ethernet4: breakout from 4x25G to 2x50G with -f -l + self.dpb_port4_4x25G_2x50G_f_l(curConfig) + return + + def tearDown(self): + try: + os.remove(config_mgmt.CONFIG_DB_JSON_FILE) + os.remove(config_mgmt.DEFAULT_CONFIG_DB_JSON_FILE) + except Exception as e: + pass + return + + ########### HELPER FUNCS ##################################### + def writeJson(self, d, file): + with open(file, 'w') as f: + dump(d, f, indent=4) + return + + def config_mgmt_dpb(self, curConfig): + ''' + config_mgmt.ConfigMgmtDPB class instance with mocked functions. Not using + pytest fixture, because it is used in non test funcs. + + Parameter: + curConfig (dict): Config to start with. + + Return: + cmdpb (ConfigMgmtDPB): Class instance of ConfigMgmtDPB. + ''' + # create object + self.writeJson(curConfig, config_mgmt.CONFIG_DB_JSON_FILE) + cmdpb = config_mgmt.ConfigMgmtDPB(source=config_mgmt.CONFIG_DB_JSON_FILE) + # mock funcs + cmdpb.writeConfigDB = MagicMock(return_value=True) + cmdpb.verifyAsicDB = MagicMock(return_value=True) + cmdpb._shutdownIntf = MagicMock(return_value=True) + import mock_tables.dbconnector + return cmdpb + + def generate_args(self, portIdx, laneIdx, curMode, newMode): + ''' + Generate port to deleted, added and {lanes, speed} setting based on + current and new mode. + Example: + For generate_args(8, 73, '4x25G', '2x50G'): + output: + ( + ['Ethernet8', 'Ethernet9', 'Ethernet10', 'Ethernet11'], + ['Ethernet8', 'Ethernet10'], + {'Ethernet8': {'lanes': '73,74', 'speed': '50000'}, + 'Ethernet10': {'lanes': '75,76', 'speed': '50000'}}) + + Parameters: + portIdx (int): Port Index. + laneIdx (int): Lane Index. + curMode (str): current breakout mode of Port. + newMode (str): new breakout mode of Port. + + Return: + dPorts, aPorts, pJson (tuple)[list, dict] + ''' + # default params + pre = "Ethernet" + laneMap = {"4x25G": [1,1,1,1], "2x50G": [2,2], "1x100G":[4], \ + "1x50G(2)+2x25G(2)":[2,1,1], "2x25G(2)+1x50G(2)":[1,1,2]} + laneSpeed = 25000 + # generate dPorts + l = list(laneMap[curMode]); l.insert(0, 0); id = portIdx; dPorts = list() + for i in l[:-1]: + id = id + i + portName = portName = "{}{}".format(pre, id) + dPorts.append(portName) + # generate aPorts + l = list(laneMap[newMode]); l.insert(0, 0); id = portIdx; aPorts = list() + for i in l[:-1]: + id = id + i + portName = portName = "{}{}".format(pre, id) + aPorts.append(portName) + # generate pJson + l = laneMap[newMode]; pJson = {"PORT": {}}; li = laneIdx; pi = 0 + for i in l: + speed = laneSpeed*i + lanes = [str(li+j) for j in range(i)]; lanes = ','.join(lanes) + pJson['PORT'][aPorts[pi]] = {"speed": str(speed), "lanes": str(lanes)} + li = li+i; pi = pi + 1 + return dPorts, aPorts, pJson + + def updateConfig(self, conf, uconf): + ''' + update the config to emulate continous breakingout a single port. + + Parameters: + conf (dict): current config in config DB. + uconf (dict): config Diff to be pushed in config DB. + + Return: + void + conf will be updated with uconf, i.e. config diff. + ''' + try: + for it in uconf.keys(): + # if conf has the key + if conf.get(it): + # if marked for deletion + if uconf[it] == None: + del conf[it] + else: + if isinstance(conf[it], list) and isinstance(uconf[it], list): + conf[it] = list(uconf[it]) + if len(conf[it])==0: + conf[it] = [""] + elif isinstance(conf[it], dict) and isinstance(uconf[it], dict): + self.updateConfig(conf[it], uconf[it]) + else: + conf[it] = uconf[it] + del uconf[it] + # update new keys in conf + conf.update(uconf) + except Exception as e: + print("update Config failed") + print(e) + raise e + return + + def checkResult(self, cmdpb, delConfig, addConfig): + ''' + Usual result check in many test is: Make sure delConfig and addConfig is + pushed in order to configDb + + Parameters: + cmdpb (ConfigMgmtDPB): Class instance of ConfigMgmtDPB. + delConfig (dict): config Diff to be pushed in config DB while deletion + of ports. + addConfig (dict): config Diff to be pushed in config DB while addition + of ports. + + Return: + void + ''' + calls = [call(delConfig), call(addConfig)] + assert cmdpb.writeConfigDB.call_count == 2 + cmdpb.writeConfigDB.assert_has_calls(calls, any_order=False) + return + + def postUpdateConfig(self, curConfig, delConfig, addConfig): + ''' + After breakout, update the config to emulate continous breakingout a + single port. + + Parameters: + curConfig (dict): current Config in config DB. + delConfig (dict): config Diff to be pushed in config DB while deletion + of ports. + addConfig (dict): config Diff to be pushed in config DB while addition + of ports. + + Return: + void + curConfig will be updated with delConfig and addConfig. + ''' + # update the curConfig with change + self.updateConfig(curConfig, delConfig) + self.updateConfig(curConfig, addConfig) + return + + def dpb_port8_1x100G_1x50G_2x25G_f_l(self, curConfig): + ''' + Breakout Port 8 1x100G->1x50G_2x25G with -f -l + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + # create ARGS + dPorts, aPorts, pJson = self.generate_args(portIdx=8, laneIdx=73, \ + curMode='1x100G', newMode='1x50G(2)+2x25G(2)') + deps, ret = cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, + force=True, loadDefConfig=True) + # Expected Result delConfig and addConfig is pushed in order + delConfig = { + u'PORT': { + u'Ethernet8': None + } + } + addConfig = { + u'ACL_TABLE': { + u'NO-NSW-PACL-V4': { + u'ports': ['Ethernet0', 'Ethernet4', 'Ethernet8', 'Ethernet10'] + }, + u'NO-NSW-PACL-TEST': { + u'ports': ['Ethernet11'] + } + }, + u'INTERFACE': { + u'Ethernet11|2a04:1111:40:a709::1/126': { + u'scope': u'global', + u'family': u'IPv6' + }, + u'Ethernet11': {} + }, + u'VLAN_MEMBER': { + u'Vlan100|Ethernet8': { + u'tagging_mode': u'untagged' + }, + u'Vlan100|Ethernet11': { + u'tagging_mode': u'untagged' + } + }, + u'PORT': { + 'Ethernet8': { + 'speed': '50000', + 'lanes': '73,74' + }, + 'Ethernet10': { + 'speed': '25000', + 'lanes': '75' + }, + 'Ethernet11': { + 'speed': '25000', + 'lanes': '76' + } + } + } + self.checkResult(cmdpb, delConfig, addConfig) + self.postUpdateConfig(curConfig, delConfig, addConfig) + return + + def dpb_port8_4x25G_1x100G_f(self, curConfig): + ''' + Breakout Port 8 4x25G->1x100G with -f + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + # create ARGS + dPorts, aPorts, pJson = self.generate_args(portIdx=8, laneIdx=73, \ + curMode='4x25G', newMode='1x100G') + deps, ret = cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, + force=False, loadDefConfig=False) + # Expected Result delConfig and addConfig is pushed in order + delConfig = { + u'PORT': { + u'Ethernet8': None, + u'Ethernet9': None, + u'Ethernet10': None, + u'Ethernet11': None + } + } + addConfig = pJson + self.checkResult(cmdpb, delConfig, addConfig) + self.postUpdateConfig(curConfig, delConfig, addConfig) + return + + def dpb_port8_1x100G_4x25G(self, curConfig): + ''' + Breakout Port 8 1x100G->4x25G + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + dPorts, aPorts, pJson = self.generate_args(portIdx=8, laneIdx=73, \ + curMode='1x100G', newMode='4x25G') + deps, ret = cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, + force=False, loadDefConfig=False) + # Expected Result delConfig and addConfig is pushed in order + delConfig = { + u'PORT': { + u'Ethernet8': None + } + } + addConfig = pJson + self.checkResult(cmdpb, delConfig, addConfig) + self.postUpdateConfig(curConfig, delConfig, addConfig) + return + + def dpb_port8_2x50G_1x100G_f(self, curConfig): + ''' + Breakout Port 8 2x50G->1x100G with -f + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + # create ARGS + dPorts, aPorts, pJson = self.generate_args(portIdx=8, laneIdx=73, \ + curMode='2x50G', newMode='1x100G') + deps, ret = cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, + force=True, loadDefConfig=False) + # Expected Result delConfig and addConfig is pushed in order + delConfig = { + u'ACL_TABLE': { + u'NO-NSW-PACL-V4': { + u'ports': ['Ethernet0', 'Ethernet4'] + } + }, + u'VLAN_MEMBER': { + u'Vlan100|Ethernet8': None + }, + u'PORT': { + u'Ethernet8': None, + u'Ethernet10': None + } + } + addConfig = pJson + self.checkResult(cmdpb, delConfig, addConfig) + self.postUpdateConfig(curConfig, delConfig, addConfig) + + def dpb_port8_2x50G_1x100G(self, curConfig): + ''' + Breakout Port 8 2x50G->1x100G + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + # create ARGS + dPorts, aPorts, pJson = self.generate_args(portIdx=8, laneIdx=73, \ + curMode='2x50G', newMode='1x100G') + deps, ret = cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, + force=False, loadDefConfig=False) + # Expected Result + assert ret == False and len(deps) == 3 + assert cmdpb.writeConfigDB.call_count == 0 + return + + def dpb_port8_4x25G_2x50G_f_l(self, curConfig): + ''' + Breakout Port 8 4x25G->2x50G with -f -l + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + # create ARGS + dPorts, aPorts, pJson = self.generate_args(portIdx=8, laneIdx=73, \ + curMode='4x25G', newMode='2x50G') + cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, force=True, \ + loadDefConfig=True) + # Expected Result delConfig and addConfig is pushed in order + delConfig = { + u'ACL_TABLE': { + u'NO-NSW-PACL-V4': { + u'ports': ['Ethernet0', 'Ethernet4'] + }, + u'NO-NSW-PACL-TEST': { + u'ports': [] + } + }, + u'INTERFACE': None, + u'VLAN_MEMBER': { + u'Vlan100|Ethernet8': None, + u'Vlan100|Ethernet11': None + }, + u'PORT': { + u'Ethernet8': None, + u'Ethernet9': None, + u'Ethernet10': None, + u'Ethernet11': None + } + } + addConfig = { + u'ACL_TABLE': { + u'NO-NSW-PACL-V4': { + u'ports': ['Ethernet0', 'Ethernet4', 'Ethernet8', 'Ethernet10'] + } + }, + u'VLAN_MEMBER': { + u'Vlan100|Ethernet8': { + u'tagging_mode': u'untagged' + } + }, + u'PORT': { + 'Ethernet8': { + 'speed': '50000', + 'lanes': '73,74' + }, + 'Ethernet10': { + 'speed': '50000', + 'lanes': '75,76' + } + } + } + self.checkResult(cmdpb, delConfig, addConfig) + self.postUpdateConfig(curConfig, delConfig, addConfig) + return + + def dpb_port4_4x25G_2x50G_f_l(self, curConfig): + ''' + Breakout Port 4 4x25G->2x50G with -f -l + + Parameters: + curConfig (dict): current Config in config DB. + + Return: + void + assert for success and failure. + ''' + cmdpb = self.config_mgmt_dpb(curConfig) + # create ARGS + dPorts, aPorts, pJson = self.generate_args(portIdx=4, laneIdx=69, \ + curMode='4x25G', newMode='2x50G') + cmdpb.breakOutPort(delPorts=dPorts, addPorts=aPorts, portJson=pJson, force=True, \ + loadDefConfig=True) + # Expected Result delConfig and addConfig is pushed in order + delConfig = { + u'ACL_TABLE': { + u'NO-NSW-PACL-V4': { + u'ports': ['Ethernet0', 'Ethernet8', 'Ethernet10'] + } + }, + u'PORT': { + u'Ethernet4': None, + u'Ethernet5': None, + u'Ethernet6': None, + u'Ethernet7': None + } + } + addConfig = { + u'ACL_TABLE': { + u'NO-NSW-PACL-V4': { + u'ports': ['Ethernet0', 'Ethernet8', 'Ethernet10', 'Ethernet4'] + } + }, + u'PORT': { + 'Ethernet4': { + 'speed': '50000', + 'lanes': '69,70' + }, + 'Ethernet6': { + 'speed': '50000', + 'lanes': '71,72' + } + } + } + self.checkResult(cmdpb, delConfig, addConfig) + self.postUpdateConfig(curConfig, delConfig, addConfig) + return + +###########GLOBAL Configs##################################### +configDbJson = { + "ACL_TABLE": { + "NO-NSW-PACL-TEST": { + "policy_desc": "NO-NSW-PACL-TEST", + "type": "L3", + "stage": "INGRESS", + "ports": [ + "Ethernet9", + "Ethernet11", + ] + }, + "NO-NSW-PACL-V4": { + "policy_desc": "NO-NSW-PACL-V4", + "type": "L3", + "stage": "INGRESS", + "ports": [ + "Ethernet0", + "Ethernet4", + "Ethernet8", + "Ethernet10" + ] + } + }, + "VLAN": { + "Vlan100": { + "admin_status": "up", + "description": "server_vlan", + "dhcp_servers": [ + "10.186.72.116" + ] + }, + }, + "VLAN_MEMBER": { + "Vlan100|Ethernet0": { + "tagging_mode": "untagged" + }, + "Vlan100|Ethernet2": { + "tagging_mode": "untagged" + }, + "Vlan100|Ethernet8": { + "tagging_mode": "untagged" + }, + "Vlan100|Ethernet11": { + "tagging_mode": "untagged" + }, + }, + "INTERFACE": { + "Ethernet10": {}, + "Ethernet10|2a04:0000:40:a709::1/126": { + "scope": "global", + "family": "IPv6" + } + }, + "PORT": { + "Ethernet0": { + "alias": "Eth1/1", + "lanes": "65", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet1": { + "alias": "Eth1/2", + "lanes": "66", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet2": { + "alias": "Eth1/3", + "lanes": "67", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet3": { + "alias": "Eth1/4", + "lanes": "68", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet4": { + "alias": "Eth2/1", + "lanes": "69", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet5": { + "alias": "Eth2/2", + "lanes": "70", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet6": { + "alias": "Eth2/3", + "lanes": "71", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet7": { + "alias": "Eth2/4", + "lanes": "72", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet8": { + "alias": "Eth3/1", + "lanes": "73", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet9": { + "alias": "Eth3/2", + "lanes": "74", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet10": { + "alias": "Eth3/3", + "lanes": "75", + "description": "", + "speed": "25000", + "admin_status": "up" + }, + "Ethernet11": { + "alias": "Eth3/4", + "lanes": "76", + "description": "", + "speed": "25000", + "admin_status": "up" + } + } +} + +portBreakOutConfigDbJson = { + "ACL_TABLE": { + "NO-NSW-PACL-TEST": { + "ports": [ + "Ethernet9", + "Ethernet11", + ] + }, + "NO-NSW-PACL-V4": { + "policy_desc": "NO-NSW-PACL-V4", + "ports": [ + "Ethernet0", + "Ethernet4", + "Ethernet8", + "Ethernet10" + ] + } + }, + "VLAN": { + "Vlan100": { + "admin_status": "up", + "description": "server_vlan", + "dhcp_servers": [ + "10.186.72.116" + ] + } + }, + "VLAN_MEMBER": { + "Vlan100|Ethernet8": { + "tagging_mode": "untagged" + }, + "Vlan100|Ethernet11": { + "tagging_mode": "untagged" + } + }, + "INTERFACE": { + "Ethernet11": {}, + "Ethernet11|2a04:1111:40:a709::1/126": { + "scope": "global", + "family": "IPv6" + } + } +}