From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mga09.intel.com (mga09.intel.com [134.134.136.24]) by dpdk.org (Postfix) with ESMTP id E58D11B7C2 for ; Wed, 6 Jun 2018 07:38:36 +0200 (CEST) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga102.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 05 Jun 2018 22:38:36 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.49,482,1520924400"; d="scan'208";a="54248633" Received: from shecgisg006.sh.intel.com ([10.239.39.68]) by fmsmga002.fm.intel.com with ESMTP; 05 Jun 2018 22:38:34 -0700 Received: from shecgisg006.sh.intel.com (localhost [127.0.0.1]) by shecgisg006.sh.intel.com with ESMTP id w565cYbd026696; Wed, 6 Jun 2018 13:38:34 +0800 Received: (from yufengmx@localhost) by shecgisg006.sh.intel.com with œ id w565cYm1026692; Wed, 6 Jun 2018 13:38:34 +0800 From: yufengx.mo@intel.com To: dts@dpdk.org Cc: yufengmx Date: Wed, 6 Jun 2018 13:38:30 +0800 Message-Id: <1528263513-26500-4-git-send-email-yufengx.mo@intel.com> X-Mailer: git-send-email 1.7.0.7 In-Reply-To: <1528263513-26500-1-git-send-email-yufengx.mo@intel.com> References: <1528263513-26500-1-git-send-email-yufengx.mo@intel.com> Subject: [dts] [PATCH V1 3/6] pmd_bonded_8023ad: add switch module in dts/framework X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 06 Jun 2018 05:38:37 -0000 From: yufengmx support to query and configure switch equipment running parameters/statistics. Currently, support quanta switch model t3048-iz1, which is a 10G switch equipment. This module support to auto configure a 802.3ad running environment and query statistics data. Signed-off-by: yufengmx --- framework/switch.py | 1445 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1445 insertions(+) create mode 100644 framework/switch.py diff --git a/framework/switch.py b/framework/switch.py new file mode 100644 index 0000000..d006f76 --- /dev/null +++ b/framework/switch.py @@ -0,0 +1,1445 @@ +# BSD LICENSE +# +# Copyright(c) 2010-2018 Intel Corporation. All rights reserved. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import os +import sys +import re +import time +from abc import abstractmethod + +from config import SwitchConf +from logger import getLogger +from ssh_connection import SSHConnection +from exception import TimeoutException, ConfigParseException, SwitchException + +################################################ +# use for put switch console on background, control it using queue. +# it is thread safe +import traceback + +join_l = os.linesep.join + +class QuantaONS(object): + '''quanta switch + + Intel Open Network Software (ONS) + ''' + NAME = 'Quanta switch' + MAX_PORTS = None + VLAN_RANGE = None + PORT_CHANNEL_RANGE = None + def __init__(self, *args, **kwargs): + self.console = kwargs.get('console') + self.logger = kwargs.get('logger') + # use for static port attribute + self._cache = {} + ######################################################################## + #### utils method + ######################################################################## + def filter_one_attrs(self, output): + '''deal with one section output message format as + + Port .................................... 2 + ''' + pat = '(.*) \.+ (.*)\r' + data = re.findall(pat, output, re.M) + stats = dict(data) + num_pat = '^\d+$' + for key in stats.keys(): + value = stats[key] + flg = re.match(num_pat, value) + if flg: + stats[key] = int(value) + return stats + + def filter_ports_attr(self, content): + '''deal with multiple section output message format as + + Port .................................... 1 + + Port .................................... 2 + ''' + info = {} + sections =content.split("\r\n\r\n")[1].split(" ") + for output in sections: + stats = self.filter_one_attrs(output) + if not stats: + continue + info[stats['Port']] = stats + + return info + + def filter_pchs_attr(self, content): + '''deal with multiple section output message format as + + Port Channel ............................ lag4000 + ''' + info = {} + sections =content.split("\r\n\r\n")[1].split(" ") + for output in sections: + stats = self.filter_one_attrs(output) + if not stats: + continue + info[stats['Port Channel']] = stats + + return info + + def filter_one_section(self, content): + '''deal with one running-config message section''' + info = content.split(" ") + _info = [ item.strip() for item in info if item.strip()] + attrs = map(lambda x: x.replace(" ", "").splitlines(), _info) + return attrs + + def filter_interfaces_config(self, output): + '''deal with multiple section running-config message''' + patIntf = r'(?<= interface).+?(?=exit)' + sections = re.findall(patIntf, output, re.S) + infos = {} + for section in sections: + info = self.filter_one_section(section) + name = info[0][0].strip() + infos[name] = info[1:] + return infos + ######################################################################## + #### switch mode select + ######################################################################## + def get_current_mode(self): + '''get current switch mode + + quanta ons system has no command to show shell layer. Currently use + help output message to judge current shell level + ''' + modes = { + # shell level : prompt, help + "user": 'enable Enter to the privileged mode', + "privileged": ('configure ' + 'Enter configuration mode'), + "configure": 'interface Configure interfaces', + "port config": + ('channel-group ' + 'Ethernet channel group bundling configuration'), + "port-channel config": + ('lacp ' + 'Determines whether this LAG processes Received LACPDUs'), + } + mode = "unknown" + try: + output = self.console(["help", 'Switch']) + for name, value in modes.iteritems(): + if value in output: + mode = name + break + except Exception as e: + print e + finally: + pass + + msg = "current on {0} mode".format(mode) + self.logger.info(msg) + return mode + + def enter_user(self): + '''enter user mode''' + mode = self.get_current_mode() + if mode == "user": + return + elif mode == "privileged": + cmds = ['exit', 'Switch >'] + elif mode == "configure": + cmds = ["exit", "Switch #"] + else: + cmds = [["exit", "Switch \(config\)#"], + ["exit", "Switch #"]] + + self.console(cmds) + + def enter_privileged(self): + '''enter privileged mode''' + mode = self.get_current_mode() + if mode == "user": + cmds = ["enable", "Switch #"] + elif mode == "privileged": + return + elif mode == "configure": + cmds = ["exit", "Switch #"] + else: + cmds = [["exit", "Switch \(config\)#"], + ["exit", "Switch #"]] + + self.console(cmds) + + def enter_configure(self): + '''enter configure mode''' + mode = self.get_current_mode() + if mode == "user": + cmds = [["enable", "Switch #"], + ["configure", "Switch \(config\)#"],] + elif mode == "privileged": + cmds = ["configure", "Switch \(config\)#"] + elif mode == "configure": + return + else: + cmds = ["exit", "Switch \(config\)#"] + self.console(cmds) + ######################################################################## + #### global effective method + ######################################################################## + def clear_switch_stats(self): + ''' + quanta switch doesn't support clear specified port channel. When + multiple scenes are running, don't run this method + ''' + msg = "clear switch all ports statistics data" + self.logger.warning(msg) + cmds = [['clear statistics', 'Proceed with clear operation'], + ['yes', '#', 2]] + + self.enter_privileged() + self.console(cmds) + ######################################################################## + #### interface method + ######################################################################## + def intf_get_config(self, ports): + '''get interface configuration by port name or port name list + ''' + if isinstance(ports, (tuple, list)): + _ports = ",".join(ports) + elif isinstance(ports, str): + _ports = ports + else: + msg = "ports param are wrong" + raise SwitchException(msg) + + cmds = ["show running-config interface {0}".format(_ports), '#'] + + self.enter_privileged() + output = self.console(cmds) + configs = self.filter_interfaces_config(output) + + return configs + + def intf_reset_config(self, port, attrs): + '''reset interface configurationt to default status''' + cmds = ["interface {0}".format(port), '#'] + + self.enter_privileged() + self.console(cmds) + + cmds = [] + for attr in attrs: + if not attr.startswith('no '): + cmds.append(["no {0}".format(attr), '#']) + else: + cmds.append([attr[3:], "# "]) + self.console(cmds) + + def intf_get_vlan_attr(self, port_id): + '''get interface vlan ids''' + prompt = 'Switch #' + cmd = ["show running-config interface {0}".format(port_id), prompt] + + self.enter_privileged() + output = self.console(cmd) + pat = "switchport vlan add (.*) .*" + vlan_ids = re.findall(pat, output) + return vlan_ids + + def intf_set_vlan_attr(self, vlan_id, port_id, scene='normal'): + '''remove port from previous vlan and add port to new vlan''' + prompt = 'Switch \(config-if {0}\)#'.format(port_id) + + if scene == 'lacp': + _cmds =[ + "interface {0}".format(port_id), + "no switchport pvid", + "switchport pvid {0}".format(vlan_id),] + else: + vlans = self.intf_get_vlan_attr(port_id) + clear_vlan_cmd = "" if not vlans \ + else "no switchport vlan add {0}".format(",".join(vlans)) + _cmds =[ + "interface {0}".format(port_id), + # clear old vlan + "no switchport pvid", + clear_vlan_cmd, + # set vlan attribute + "switchport vlan add {0} untagged".format(vlan_id), + "switchport pvid {0}".format(vlan_id),] + cmds = map(lambda x: [x, prompt], _cmds) + + self.enter_configure() + self.console(cmds) + + def intf_get_pch_id(self, port_id): + '''get interface port channel id. + + If not binding to any port channel, return None + ''' + attrs = self.intf_get_config([port_id]) + for attr_section in attrs.values()[0]: + if attr_section[0] and "channel-group" in attr_section[0]: + pch_attr = attr_section + break + else: + return None + pat = "channel-group (\d+) .*" + ch_grp_id = re.findall(pat, pch_attr[0]) + return int(ch_grp_id[0]) + + def intf_unbind_pch(self, ch_grp_id, port_id): + '''unbind interface from port channel''' + prompt = 'Switch \(config-if {0}\)#'.format(port_id) + _cmds =[ + "interface {0}".format(port_id), + "no channel-group {0}".format(ch_grp_id),] + cmds = map(lambda x: [x, prompt], _cmds) + ret_mode= [['exit', '#']] + + self.enter_configure() + self.console(cmds + ret_mode) + + def intf_close_lldp(self, port_id): + '''close interface lldp transmit/receive''' + prompt = 'Switch \(config-if {0}\)#'.format(port_id) + _cmds =[ + "interface {0}".format(port_id), + "no lldp receive", + "no lldp transmit"] + cmds = map(lambda x: [x, prompt], _cmds) + ret_mode= [['exit', '#']] + + self.enter_configure() + self.console(cmds + ret_mode) + + def intf_open_lldp(self, port_id): + '''open interface lldp transmit/receive''' + prompt = 'Switch \(config-if {0}\)#'.format(port_id) + _cmds =[ + "interface {0}".format(port_id), + "lldp receive", + "lldp transmit"] + cmds = map(lambda x: [x, prompt], _cmds) + ret_mode= [['exit', '#']] + + self.enter_configure() + self.console(cmds + ret_mode) + + def intf_get_brief(self, port): + '''get interface brief information''' + cmds = ['show interface {0}'.format(port), '#', 3] + + self.enter_privileged() + output = self.console(cmds) + if not output: + return None + attrs = self.filter_ports_attr(output) + return attrs + + def intf_get_transceiver(self, port): + '''get interface transceiver information''' + cmds = ["show interface {0} transceiver".format(port), '#'] + + self.enter_privileged() + output = self.console(cmds) + if not output: + return None + attrs = self.filter_ports_attr(output) + return attrs + + def intf_shutdown(self, port_id): + '''set interface link down + + Quanta swith don't support set shutdown when the port is a member + of port channel, it herit the port status of port channel + ''' + self.enter_configure() + cmds = [["interface {0}".format(port_id), '#'], + ["shutdown", '#']] + self.console(cmds) + + def intf_no_shutdown(self, port_id): + '''set interface link up + + Quanta swith don't support set shutdown when the port is a member + of port channel, it herit the port status of port channel + ''' + self.enter_configure() + cmds = [["interface {0}".format(port_id), '#'], + ["no shutdown", '#']] + self.console(cmds) + + def intf_get_stats(self, port): + '''get interface statistics data''' + cmds = ['show statistics interface {0}'.format(port), '#', 5] + + self.enter_privileged() + output = self.console(cmds) + return self.filter_one_attrs(output) + + def intf_clear_stats(self, port): + '''clear interface statistics data + + only apply on the interface not binding to any port-channel + ''' + cmds = [ + ['clear statistics {0}'.format(port), 'Proceed with clear operation'], + ['yes', '#', 2]] + + self.enter_privileged() + self.console(cmds) + + ######################################################################## + #### port channel/channel group method + ######################################################################## + def create_port_channel(self, ch_grp_id): + '''create a new port channel''' + if self.pch_is_exist(ch_grp_id): + msg = "port channel {0} has created".format(ch_grp_id) + self.logger.info(msg) + return + # create a new one + prompt = 'Switch \(config-if\)#' + cmds = [['interface port-channel {0}'.format(ch_grp_id), prompt], + ['shutdown', prompt], + ['exit', '#']] + + self.enter_configure() + self.console(cmds) + # check status + if self.pch_is_exist(ch_grp_id): + msg = "port channel {0} create successful".format(ch_grp_id) + self.logger.info(msg) + else: + msg = "port channel {0} create failed".format(ch_grp_id) + raise SwitchException(msg) + + def pch_is_exist(self, ch_grp_id): + '''check if port channel has been created''' + cmds = ['show interface port-channel', '#'] + + self.enter_privileged() + output = self.console(cmds) + infos = self.filter_pchs_attr(output) + name = "lag{0}".format(ch_grp_id) + if name in infos.keys(): + return True + else: + return False + + def pch_id_in_range(self, ch_grp_id): + '''check if port channel id is in the right range + + The range is set by sub class + ''' + if not self.PORT_CHANNEL_RANGE: + msg = "PORT_CHANNEL_RANGE should be set by subclass" + raise SwitchException(msg) + + flag = ch_grp_id >= self.PORT_CHANNEL_RANGE[0] and \ + ch_grp_id <= self.PORT_CHANNEL_RANGE[1] + return flag + + def pch_set_load_balance(self, mode): + '''set port channel load balance + + It is a global effective status flag + + @param mode: + dscp Load balancing by IP DSCP. + dst-ip Load balancing by destination IP address. + dst-mac Load balancing by destination MAC address. + ether-type Load balancing by ethertype. + inner-vlan-id Load balancing by in VLAN ID. + inner-vlan-pri Load balancing by in VLAN priority. + ip-protocol Load balancing by IP protocol. + ip6-flow Load balancing by IPv6 traffic flow. + l4-dst-port Load balancing by layer 4 destination port. + l4-src-port Load balancing by layer 4 source port. + outer-vlan-id Load balancing by out VLAN ID. + outer-vlan-pri Load balancing by out VLAN priority. + src-ip Load balancing by source IP address. + src-mac Load balancing by source MAC address. + ''' + supports = [ + 'dscp', 'dst-ip', 'dst-mac', 'ether-type', 'inner-vlan-id', + 'inner-vlan-pri', 'ip-protocol', 'ip6-flow', 'l4-dst-port', + 'l4-src-port', 'outer-vlan-id', 'outer-vlan-pri', 'src-ip', + 'src-mac'] + if mode not in supports: + msg = "load balance {0} is not supported".format(mode) + raise SwitchException(msg) + + prompt = 'Switch \(config\)#' + cmds = ['port-channel load-balance {0}'.format(mode), prompt] + + self.enter_configure() + self.console(cmds) + + def pch_remove_port(self, ch_grp_id, port): + '''unbind a port from port channel + + only remove port in channel group, not change other configuration + ''' + ch = ch_grp_id + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = [ + ['interface {0}'.format(port), prompt, 2], + ['no channel-group {0}'.format(ch), prompt, 2], + ['exit', "Switch \(config\)#"], + ['exit', "Switch #"]] + + self.enter_configure() + self.console(cmds) + + def pch_add_port(self, ch_grp_id, port): + '''bind a port to a port channel + + only add port in channel group, not change other configuration + ''' + ch = ch_grp_id + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = [ + ['interface {0}'.format(port), prompt, 2], + ['channel-group {0}'.format(ch), prompt], + ['exit', "Switch \(config\)#"], + ['exit', "Switch #"]] + + self.enter_configure() + self.console(cmds) + + def pch_remove_ports(self, ports): + '''unbind a list of port from any port channel''' + for port in ports: + ch = self.intf_get_pch_id(port) + if not ch: + continue + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = [ + ['interface {0}'.format(port), prompt, 2], + ['no channel-group {0}'.format(ch), prompt, 2], + ['exit', "Switch \(config\)#", 2]] + + self.enter_configure() + self.console(cmds) + + def pch_get_ports_config(self, ch_grp_id): + '''get ports' configuration belong to a port channel + + quanta switch not support query all ports config in one command, so it + will be very slow here + ''' + ports = self.pch_get_member(ch_grp_id) + members = {} + self.enter_privileged() + for port_name in ports: + cmds = ["show running-config interface " + port_name, '#'] + output = self.console(cmds) + if output: + members[port_name] = output + + return members + + def pch_reset_port_config(self, port, attrs): + '''release interface from port channel''' + # not done here + return False + cmds = ["interface {0}".format(port), '#'] + + self.enter_privileged() + self.console(cmds) + + cmds = [] + for attr in attrs: + if not attr.startswith('no '): + cmds.append(["no {0}".format(attr), '#']) + else: + cmds.append([attr[3:], "# "]) + self.console(cmds) + + def pch_set_ip_igmp(self, ch_grp_id): + '''set port channel ip igmp''' + prompt = 'Switch \(config-if\)#' + cmds = [['interface port-channel {0}'.format(ch_grp_id), prompt], + ['ip igmp', prompt], + ['exit', '#']] + + self.enter_configure() + self.console(cmds) + + def pch_close_ip_igmp(self, ch_grp_id): + '''close port channel ip igmp''' + prompt = 'Switch \(config-if\)#' + cmds = [['interface port-channel {0}'.format(ch_grp_id), prompt], + ['no ip igmp', prompt], + ['exit', '#']] + + self.enter_configure() + self.console(cmds) + + def pch_no_shutdown(self, ch_grp_id): + '''set port channel on ``no shutdown`` status''' + prompt = 'Switch \(config-if\)#' + cmds = [['interface port-channel {0}'.format(ch_grp_id), prompt], + ['no shutdown', prompt], + ['ip igmp', prompt], # TBD + ['exit', '#']] + + self.enter_configure() + self.console(cmds) + + def pch_shutdown(self, ch_grp_id): + '''set port channel on ``no shutdown`` status''' + prompt = 'Switch \(config-if\)#' + cmds = [['interface port-channel {0}'.format(ch_grp_id), prompt], + ['shutdown', prompt], + ['no ip igmp', prompt], + ['exit', '#']] + + self.enter_configure() + self.console(cmds) + + def pch_get_member(self, ch_grp_id): + '''get port member of a port channel''' + details = self.pch_get_group_info(ch_grp_id) + members = sorted(details.keys()) + + return members + + def pch_get_member_status(self, ch_grp_id): + '''get port members' status of a port channel''' + members = self.pch_get_member(ch_grp_id) + if not members: + msg = "no member attached on port-channel {0}".format(ch_grp_id) + self.logger.error(msg) + return False + + err_port = [] + for name, attrs in members.iteritems(): + if attrs['Mode'] != 'Active': + err_port.append(name) + if err_port: + msg = "{0} not at Active mode".format(" ".join(err_port)) + self.logger.error(msg) + return False + else: + return True + + def pch_get_group_info(self, ch_grp_id): + '''get port member status of a channel group''' + cmd = ["show channel-group {0}".format(ch_grp_id), "#"] + + self.enter_privileged() + output = self.console(cmd) + if "Error! Invalid interface range has been specified" in output: + return [] + infos = self.filter_ports_attr(output) + return infos + + def pch_get_grp_remote_info(self, ch_grp_id): + '''get all ports remote information of a channel group''' + cmd = ["show channel-group {0} remote".format(ch_grp_id), "#"] + + self.enter_privileged() + output = self.console(cmd) + infos = self.filter_ports_attr(output) + return infos + + def pch_get_grp_admin_info(self, ch_grp_id): + '''get all ports admin information of a channel group''' + cmd = ["show channel-group {0} admin".format(ch_grp_id), "#"] + + self.enter_privileged() + output = self.console(cmd) + infos = self.filter_ports_attr(output) + return infos + + def pch_get_ports_stats(self, ports): + '''get all ports statistics of a channel group''' + stats_grp = {} + for port_id in ports: + stats = self.intf_get_stats(port_id) + stats_grp[port_id] = stats + return stats_grp + + def pch_clear_stats(self, ch_grp_id, ports): + '''clear all ports statistics data of a port channel + + quanta switch doesn't support clear specified port channel statistics. + An interface should unbind from a port channel before clear its + statistics data + ''' + for port_id in ports: + self.pch_remove_port(ch_grp_id, port_id) + self.intf_clear_stats(port_id) + self.pch_add_port(ch_grp_id, port_id) + + ######################################################################## + #### vlan method + ######################################################################## + def create_vlan(self, vlan_id): + '''create a new vlan''' + if self.vlan_is_exist(vlan_id): + msg = "vlan {0} has created".format(vlan_id) + self.logger.info(msg) + return + # create a new one + cmds = [['vlan-database', 'Switch \(vlan\)#'], + ['vlan {0}'.format(vlan_id), 'Switch \(vlan\)#'], + ['exit', '#', 2]] + + self.enter_privileged() + self.console(cmds) + if self.vlan_is_exist(vlan_id): + msg = "vlan {0} create successful".format(vlan_id) + self.logger.info(msg) + else: + msg = "vlan {0} create failed".format(vlan_id) + raise SwitchException(msg) + + def vlan_is_exist(self, vlan_id): + '''check if vlan has been created''' + cmds = ['show vlan', '#'] + + self.enter_privileged() + output = self.console(cmds) + name = str(vlan_id) + " " + if name in output: + return True + else: + return False + + def vlan_id_in_range(self, vlan_id): + '''check if vlan id is in the right range''' + if not self.VLAN_RANGE: + msg = "VLAN_RANGE should be set by subclass" + raise SwitchException(msg) + + flag = vlan_id >= self.VLAN_RANGE[0] and \ + vlan_id <= self.VLAN_RANGE[1] + return flag + + def vlan_remove_ports(self, ports): + '''reset port vlan to default 1''' + pass + ######################################################################## + #### lacp method + ######################################################################## + def lacp_set_port_channel(self, vlan_id, ch_grp_id): + '''set a new port channel for lacp scene''' + prompt = 'Switch \(config-if\)#' + _cmds =['interface port-channel {0}'.format(ch_grp_id), + 'no switchport vlan add 1', + 'switchport vlan add {0} untagged'.format(vlan_id), + 'switchport pvid {0}'.format(vlan_id), + 'lacp', + 'shutdown'] + cmds = map(lambda x: [x, prompt], _cmds) + [['exit', '#']] + + self.enter_configure() + self.console(cmds) + + def lacp_init_ports(self, vlan_id, ch_grp_id, ports): + '''initialize lacp configuration + + add ports into one port channel, set it on correct lacp + configuration and check if port has been added successfully. + ''' + # reset port configuration + for port in ports: + _ch_grp_id = self.intf_get_pch_id(port) + if not _ch_grp_id: + continue + if _ch_grp_id == ch_grp_id: + continue + self.intf_unbind_pch(_ch_grp_id, port) + # release ports bind to port channel + members = self.pch_get_member(ch_grp_id) + self.pch_remove_ports(members) + # add ports + for port in ports: + self.intf_set_vlan_attr(vlan_id, port, 'lacp') + self.pch_add_port(ch_grp_id, port) + # set lacp configuration + for port in ports: + self.lacp_intf_set_attr(vlan_id, ch_grp_id, port) + # close lldp + for port in ports: + self.intf_close_lldp(port) + # check if port channel members are the desired interfaces + members = self.pch_get_member(ch_grp_id) + if cmp(sorted(members), sorted(ports)) == 0: + msg = "add ports success" + self.logger.info(msg) + else: + msg = "fail to add ports" + self.logger.error(msg) + raise SwitchException(msg) + + def lacp_add_ports(self, vlan, ch_grp_id, ports): + '''add ports to a lacp + + add ports into one port channel, set it on correct lacp + configuration and check if port has been added successfully. + ''' + _cmds =[ + 'interface {port_id}', + 'channel-group {ch_grp_id}', + 'channel-group {ch_grp_id} mode passive', + 'no channel-group {ch_grp_id} collecting', + 'no channel-group {ch_grp_id} distributing', + #'channel-group {ch_grp_id} synchronization' + ] + ret_mode= [['exit', '#']] + # set port lacp configuration + for port in ports: + #attrs = self.intf_get_config(port) + #self.intf_reset_config(port, attrs) + attrs = {'port_id': port, + 'ch_grp_id': ch_grp_id,} + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = map(lambda x: [x.format(**attrs), prompt], _cmds) + + self.enter_configure() + self.console(cmds + ret_mode) + self.intf_close_lldp(port) + # check lacp channel group create successfully + members = self.pch_get_member(ch_grp_id) + if cmp(sorted(members), sorted(ports)) == 0: + msg = "add ports success" + self.logger.info(msg) + else: + msg = "fail to add ports" + self.logger.error(msg) + raise SwitchException(msg) + + def lacp_intf_set_attr(self, vlan_id, ch_grp_id, port_id): + '''set interface lacp configuration''' + attrs = {'port_id': port_id, + 'ch_grp_id': ch_grp_id,} + _cmds =[ + "interface {port_id}", + # config actor, set port in passive status + "channel-group {ch_grp_id} mode passive", + "channel-group {ch_grp_id} timeout long", + "channel-group {ch_grp_id} aggregation multiple", + "channel-group {ch_grp_id} lacp port-priority 128", + #"channel-group {ch_grp_id} key {0}", + "no channel-group {ch_grp_id} synchronization", + "channel-group {ch_grp_id} expired", + "no channel-group {ch_grp_id} collecting", + "no channel-group {ch_grp_id} defaulting", + "no channel-group {ch_grp_id} distributing", + # config parter + "channel-group {ch_grp_id} partner key 0", + "channel-group {ch_grp_id} partner number 1", + "channel-group {ch_grp_id} partner priority 32768", + "channel-group {ch_grp_id} partner system priority 8" + ] + prompt = 'Switch \(config-if {0}\)#'.format(port_id) + cmds = map(lambda x: [x.format(**attrs), prompt], _cmds) + ret_mode= [['exit', '#']] + + self.enter_configure() + self.console(cmds + ret_mode) + + def lacp_close_ports(self, ch_grp_id, ports): + ret_mode= [['exit', '#']] + #--------------------------------------- + # close lacp port channel + _cmds =[ + 'interface {port_id}', + 'channel-group {ch_grp_id} mode passive', + 'no channel-group {ch_grp_id} collecting', + 'no channel-group {ch_grp_id} distributing'] + self.enter_configure() + for port in sorted(ports): + attrs = {'port_id': port, + 'ch_grp_id': ch_grp_id,} + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = map(lambda x: [x.format(**attrs), prompt], _cmds) + self.console(cmds + ret_mode) + time.sleep(5) + + def lacp_set_port_ready(self, ch_grp_id, ports): + #--------------------------------------- + # close lacp port channel + self.lacp_close_ports(ch_grp_id, ports) + #--------------------------------------- + ret_mode= [['exit', '#']] + # reset lacp port channel + _cmds =[ + 'interface {port_id}', + #'channel-group {ch_grp_id} lacp port-priority 128', + "no channel-group {ch_grp_id} defaulting", + 'channel-group {ch_grp_id} timeout long', + 'channel-group {ch_grp_id} mode active', + #'channel-group {ch_grp_id} partner key 0', + #'channel-group {ch_grp_id} partner number 1, + ] + for port in sorted(ports): + attrs = {'port_id': port, + 'ch_grp_id': ch_grp_id,} + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = map(lambda x: [x.format(**attrs), prompt], _cmds) + self.console(cmds + ret_mode) + # wait 5 second to make sure port ready to work + time.sleep(5) + _cmds = ['interface {port_id}', + 'channel-group {ch_grp_id} collecting', + 'channel-group {ch_grp_id} distributing', + 'channel-group {ch_grp_id} synchronization',] + for port in sorted(ports): + attrs = {'port_id': port, + 'ch_grp_id': ch_grp_id,} + prompt = 'Switch \(config-if {0}\)#'.format(port) + cmds = map(lambda x: [x.format(**attrs), prompt], _cmds) + self.console(cmds + ret_mode) + # wait default long timeout interval to make sure lacp sync with + # parter successful + time.sleep(30) + + def lacp_get_info(self, ch_grp_id): + infos = self.pch_get_group_info(ch_grp_id) + return infos + + def lacp_get_remote_info(self, ch_grp_id): + infos = self.pch_get_grp_remote_info(ch_grp_id) + return infos + + def lacp_get_admin_info(self, ch_grp_id): + infos = self.pch_get_grp_admin_info(ch_grp_id) + return infos + + def lacp_is_ready(self, ch_grp_id): + max_try = 3 + time_interval = 10 + while max_try: + details = self.lacp_get_info(ch_grp_id) + msgs = [] + for port, value in details.iteritems(): + # check if interface lacp is on enable status + if value['LACP Operational Status'] != 'Enabled': + msg = "interface {0} lacp not ready".format(port) + msgs.append(msg) + # check if interface is ready for traffic + if 'Churn Detection Status' != 'True': + msg = "interface {0} traffic not ready".format(port) + msgs.append(msg) + else: + return True + time.sleep(time_interval) + max_try -= 1 + else: + error = os.linesep.join(msgs) + raise SwitchException(error) + + def lacp_filter_intf_attrs(self, content, patStr): + pat = re.compile(patStr) + result = pat.findall(content) + if not result: + msg = "have not found expected content" + self.logger.error(msg) + return None + info = result[0] + attrs = {} + for cnt in range(1, len(info), 2): + attrs.update({info[cnt]: info[cnt+1]}) + attrs.update({'id': info[0]}) + return attrs + ######################################################################## + ##### use for debug/unit test + ######################################################################## + def test_debug_cmds(self): + self.pch_add_ports(100, 4000, ['xe1', 'xe2', 'xe3', 'xe4']) + self.pch_get_ports_config(4000) + self.pch_get_member_status(4000) + self.intf_get_config(["xe1"]) + self.intf_shutdown("xe10") + self.intf_no_shutdown("xe10") + + def test_get_current_mode(self): + self.enter_configure() + self.console(["interface xe1", "Switch"]) + self.enter_configure() + self.console(["exit", "Switch"]) + self.enter_configure() + +class QuantaT3048Iz1(QuantaONS): + '''Quanta switch T3048-Iz1''' + # quanta 3048H switch maximum port number + MAX_PORTS = 48 + VLAN_RANGE = [1, 4094] + PORT_CHANNEL_RANGE = [3800, 4094] + def __init__(self, *args, **kwargs): + super(QuantaT3048Iz1, self).__init__(*args, **kwargs) + +class LacpSceneBase(object): + def __init__(self): + pass + + def set_interface_down(self, port): + raise NotImplementedError + + def set_interface_up(self, port): + raise NotImplementedError + + def get_interface_stats(self): + raise NotImplementedError + + def get_all_statistics(self): + raise NotImplementedError + + def clear_all_statistics(self): + raise NotImplementedError + + @abstractmethod + def create_scene(self): + '''create lacp scene''' + raise NotImplementedError + + @abstractmethod + def init_scene(self): + '''initialize lacp scene''' + raise NotImplementedError + + @abstractmethod + def start_scene(self): + '''start lacp scene''' + raise NotImplementedError + + @abstractmethod + def reset_scene(self): + '''close lacp scene''' + raise NotImplementedError + + @abstractmethod + def stop_scene(self): + '''close lacp scene''' + raise NotImplementedError + + @abstractmethod + def delete_scene(self): + '''delete lacp scene, release all resource''' + raise NotImplementedError + + @abstractmethod + def close(self): + raise NotImplementedError + +class QuantaLacpScene(LacpSceneBase): + '''Quanta switch lacp scene''' + + CLS ={'t3048-iz1': QuantaT3048Iz1} + def __init__(self, cls_name, *args, **kwargs): + model = kwargs.get('model') + if not model: + msg = "desired switch {0} is not supported".format(model) + raise SwitchException(msg) + self.logger = kwargs.get('logger') + self.logger.info("use switch <{model}>".format(**kwargs)) + #self.sw = cls_name(*args, **kwargs) + self.sw = self.CLS[model](*args, **kwargs) + ######################################################################## + # switch logic method for lacp + ######################################################################## + def lacp_bind_ports(self, vlan, ch_grp_id, ports): + self.sw.lacp_add_ports(vlan, ch_grp_id, ports) + + def lacp_unbind_ports(self, vlan, ch_grp_id, ports): + self.sw.pch_remove_ports(vlan, ch_grp_id, ports) + + def lacp_get_interface_info(self, port_id): + '''get port status related with lacp''' + pass + ######################################################################## + ##### switch abstract method for user + ######################################################################## + def get_member(self): + ''' get lacp members ''' + return self.sw.pch_get_member(self.ch_grp_id) + + def get_all_statistics(self): + ''' get lacp members' statistics ''' + return self.sw.pch_get_ports_stats(self.ports) + + def clear_all_statistics(self): + '''clear lacp members' statistics + + Run it before creating lacp channel with testpmd + ''' + self.sw.pch_shutdown(self.ch_grp_id) + self.sw.pch_clear_stats(self.ch_grp_id, self.ports) + #self.sw.pch_no_shutdown(self.ch_grp_id) + # here long time wait for port rebind to port channel + + def set_ready(self): + ''' set lacp members on ready status ''' + self.sw.lacp_set_port_ready(self.ch_grp_id, self.ports) + self.sw.lacp_is_ready(self.ch_grp_id) + + def set_interface_down(self, port): + '''set a interface on link down''' + msg = "quanta switch don't support shutdown port at lacp mode" + self.logger.warning(msg) + + def set_interface_up(self, port): + '''set a interface on link up''' + msg = "quanta switch don't support no shutdown port at lacp mode" + self.logger.warning(msg) + + def create_scene(self, vlan_id, ch_grp_id, ports): + '''create lacp scene on switch''' + self.sw.create_vlan(vlan_id) + self.sw.create_port_channel(ch_grp_id) + self.sw.lacp_set_port_channel(vlan_id, ch_grp_id) + #self.sw.pch_clear_ports(ports) + self.sw.lacp_init_ports(vlan_id, ch_grp_id, ports) + self.sw.pch_no_shutdown(ch_grp_id) + + def init_scene(self, auto_config=False, **kwargs): + '''create a desired lacp scene + + @param auto_config: + ``True`` create a desired lacp scene automatically + ``False`` use a lacp scene set by user manually + ''' + # get lacp scene configuration + _id = kwargs.get('vlan id') + self.vlan_id = int(_id) if isinstance(_id, (str, unicode)) else _id + if not self.vlan_id or not self.sw.vlan_id_in_range(self.vlan_id): + msg = 'vlan id not set' + raise SwitchException(msg) + + _id = kwargs.get('channel group id') + self.ch_grp_id = int(_id) if isinstance(_id, (str, unicode)) else _id + if not self.ch_grp_id or not self.sw.pch_id_in_range(self.ch_grp_id): + msg = 'channel group id not set' + raise SwitchException(msg) + + self.ports = kwargs.get('port member') + if not self.ports: + msg = 'port member not set' + raise SwitchException(msg) + # create lacp scene on switch + if auto_config: + self.create_scene(self.vlan_id, self.ch_grp_id, self.ports) + + def clear_scene(self): + '''clear lacp scene + + clear interface statistics data and LPDU Counter data + ''' + self.clear_all_statistics() + # testpmd 802.3ad long timeout is 90 second, so wait longer than that + # to make sure parter/actor sync successful + time.sleep(120) + + def start_scene(self): + '''start lacp scene''' + self.sw.pch_no_shutdown(self.ch_grp_id) + time.sleep(60) + + def reset_scene(self): + '''reset lacp scene''' + #self.clear_all_statistics() + self.set_ready() + #self.sw.pch_shutdown(self.ch_grp_id) + #time.sleep(10) + #self.sw.pch_no_shutdown(self.ch_grp_id) + #time.sleep(10) + #self.set_ready() + # testpmd 802.3ad long timeout is 90 second, so wait longer than that + # to make sure parter/actor sync successful + time.sleep(120) + + def stop_scene(self): + '''stop lacp scene''' + self.sw.pch_shutdown(self.ch_grp_id) + self.sw.lacp_close_ports(self.ch_grp_id, self.ports) + time.sleep(60) + + def delete_scene(self): + '''delete lacp scene''' + pass + +class CiscoLacpScene(): pass + +class SwitchScene(SSHConnection): + ''' + : create a switch work scene base + ''' + # define different work scene based on switch manufacturer/model + CLS = {'quanta lacp': [QuantaLacpScene, '# '], + 'cisco lacp': [CiscoLacpScene, '#']} + def __init__(self, *arg): + # get switch configuration + (self.name, self.switch_name, self.model, ip, user, passwd, + self.scene, self.vlan_id, self.port_channel_id, self.ports_list, + auto_config) = arg + name = " ".join([self.switch_name, self.scene]) + switch_CLS = self.CLS[name][0] + self.default_prompt = self.CLS[name][1] + # initialize switch session/logger + self.logger = getLogger(self.name) + super(SwitchScene, self).__init__(ip, self.name, passwd, + user, node_type="switch") + super(SwitchScene, self).init_log(self.logger) + # initialize switch equipment + self.say("initialize") + configs = {"console": self.console, + "model": self.model, + 'logger': self.logger} + self.switch = switch_CLS(self.switch_name, *[], **configs) + lacp_scene = {'vlan id': self.vlan_id, + 'channel group id': self.port_channel_id, + 'port member': self.ports_list} + self.switch.init_scene(auto_config=auto_config, **lacp_scene) + + def say(self, action): + msg_fmt = "{0} <{1}> [{2} {3}] '{4}' work scene".format + self.logger.info(msg_fmt(action, self.name, self.switch_name, + self.model, self.scene)) + ######################################################################## + ##### console method used to run switch command + ######################################################################## + def check_disired_msg(self, output, expected_items): + self.logger.info(output) + expected_output = expected_items[1] + check_type = True if len(expected_items) == 2 else expected_items[2] + if check_type and expected_output in output: + msg = "expected '{0}' is in output".format(expected_output) + self.logger.info(msg) + elif not check_type and expected_output not in output: + msg = "unexpected '{0}' is not in output".format( + expected_output) + self.logger.info(msg) + else: + status = "isn't in" if check_type else "is in" + msg = "[{0}] {1} output".format(expected_output, status) + self.logger.error(msg) + raise SwitchException(msg) + + def console(self, cmds): + if len(cmds) == 0: + return + # check if cmds is string + if isinstance(cmds, str): + timeout = 15 + cmds = [[cmds, '', timeout]] + # check if cmds is only one command + if not isinstance(cmds[0], list): + cmds = [cmds] + output_list = [] + for item in cmds: + cmd = item[0] + expected_items = item[1] + if expected_items and isinstance(expected_items, (list, tuple)): + check_output = True + expected_str = expected_items[0] or self.default_prompt + else: + check_output = False + expected_str = expected_items or self.default_prompt + #---------------- + timeout = int(item[2]) if len(item) == 3 else 5 + #---------------- + # run command on session + try: + output = self.send_expect(cmd, expected_str, timeout) + output = self.session.get_output_all() \ + if not output.strip() else output + except TimeoutException: + # quanta switch software don't display all output when output + # is more than one pagepage, so use space charater to show the + # left content + if self.switch_name == 'quanta': + output_1 = self.session.get_output_all() + try: + if " - help" in output_1: + output_1 ='\r\n'.join(output_1.splitlines()[:-1])+\ + '\r\n' + output_2 = self.send_expect(" ", expected_str, + timeout) + output = output_1 + output_2 + except Exception as e: + output = output_1 + finally: + pass + else: + msg = "execute '{0}' timeout".format(cmd) + raise TimeoutException(cmd, msg) + except Exception as e: + exc_info = sys.exc_info() + traceback.print_exception(exc_info[0], exc_info[1], + exc_info[2], None, sys.stderr) + finally: + pass + # when user set expected string, check it on output + if check_output and len(expected_items) >= 2: + self.check_disired_msg(output, expected_items) + else: + output_list.append(output) + time.sleep(2) + retOutput = output_list[0] if len(cmds) == 1 else output_list + return retOutput + ######################################################################## + ##### methods for dts framework + ######################################################################## + def get_ports(self): + """ get switch ports """ + plist = list() + if self.session is None: + msg = "switch session failed to create" + self.logger.warning(msg) + return plist + for port in self.ports_list: + plist.append({'type': 'switch', + 'pci': 'SWITCH:{0}'.format(port)}) + return plist + ######################################################################## + ##### switch methods for user + ######################################################################## + def _get_mac(self, port): + return self.switch.get_mac(port) + + def vlan(self): + """ return lacp scene vlan id """ + return self.vlan_id + + def port_channel(self): + """ return lacp scene port channel id """ + return self.port_channel_id + + def ports(self): + """ return lacp scene ports """ + return self.ports_list + + def get_stats(self): + ''' get lacp members' statistics ''' + results = self.switch.get_all_statistics() + return results + + def clear_stats(self): + ''' clear lacp members' statistics ''' + self.switch.clear_all_statistics() + + def set_intf_down(self, port_id): + ''' set interface down ''' + self.switch.set_interface_down(port_id) + + def set_intf_up(self, port_id): + ''' set interface up ''' + self.switch.set_interface_up(port_id) + + def get_member(self): + ''' get interfaces belong a lacp configuration ''' + return self.switch.get_member() + + def clear(self): + '''clear switch work scene''' + self.say('start') + self.switch.clear_scene() + + def start(self): + '''activate switch work scene''' + self.say('start') + self.switch.start_scene() + + def reset(self): + '''reset switch work scene to original status''' + self.say('reset') + self.switch.reset_scene() + + def stop(self): + '''stop switch work scene''' + self.say('stop') + self.switch.stop_scene() + + def quit(self): + '''quit switch work scene''' + self.say('quit') + self.switch.stop_scene() + self.close(force=True) + +class Switch(object): + '''use for switch work scene creation + + support multiple work scenes creation, every work scene configuration + is in dts/conf/switch.cfg. Each ``SwitchScene`` class instance manage + its own resources/status of work scene. + ''' + def __init__(self): + self.switch_grp = {} + + def init_scenes(self, tester): + for name, config in self.get_config(tester): + self.switch_grp[name] = SwitchScene(*config) + + def get_config(self, tester): + '''get switch configuration from cfg file''' + switchRef = tester.get_switch_crb() + switchcfg = SwitchConf() + switchConfigs = switchcfg.load_switch_config() + if not switchRef or not switchConfigs: + msg = "wrong switch configuration" + raise ConfigParseException(msg) + + for name, switch_config in switchConfigs.iteritems(): + # under default status, switch scene will config resources status + # automatically + if 'auto_config' not in switch_config: + auto_config = True + else: + value = switch_config['auto_config'].lower() + auto_config = True if value == 'true' else False + config = [name, + # 'manufacturer' + switch_config["manufacturer"], + # 'model' + switch_config["model"], + # switch ip address/user/passwd + # 'ip': + switch_config["ip"], + # 'user': + switch_config["user"], + # 'passwd': + switch_config["passwd"], + # configuration scene + # 'scene': + switch_config["scene"], + # 'vlan_id': + switch_config["vlan"], + # 'port_channel_id': + switch_config["port-channel"], + # 'ports': + switch_config["ports"], + auto_config] + + yield name, config \ No newline at end of file -- 1.9.3