From 9b49d100662a2868db21d2fee93ff17aa923cea3 Mon Sep 17 00:00:00 2001 From: sai chintalapudi Date: Tue, 22 Dec 2015 11:03:30 -0800 Subject: [PATCH 1/7] interface_port_channel and portchannel_global addition --- .../cmd_ref/interface_port_channel.yaml | 56 ++++ .../cmd_ref/portchannel_global.yaml | 79 +++++ .../interface_port_channel.rb | 161 ++++++++++ lib/cisco_node_utils/portchannel_global.rb | 303 ++++++++++++++++++ tests/test_interface_port_channel.rb | 113 +++++++ tests/test_portchannel_global.rb | 187 +++++++++++ 6 files changed, 899 insertions(+) create mode 100644 lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml create mode 100644 lib/cisco_node_utils/cmd_ref/portchannel_global.yaml create mode 100644 lib/cisco_node_utils/interface_port_channel.rb create mode 100644 lib/cisco_node_utils/portchannel_global.rb create mode 100644 tests/test_interface_port_channel.rb create mode 100644 tests/test_portchannel_global.rb diff --git a/lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml b/lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml new file mode 100644 index 00000000..21d64da3 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml @@ -0,0 +1,56 @@ +# interface_port_channel +--- +_template: + config_set: ["interface %s"] + config_get_token: '/^interface %s$/i' + config_get: "show running interface all" + +create: + config_set: "interface %s" + +destroy: + config_set: "no interface %s" + +lacp_graceful_convergence: + kind: boolean + auto_default: false + config_get_token_append: ['/^lacp graceful.convergence$/'] + config_set_append: ["%s lacp graceful-convergence"] + default_value: true + +lacp_max_bundle: + kind: int + config_get_token_append: ['/^lacp max.bundle (\d+)$/'] + config_set_append: ["%s lacp max-bundle %s"] + /N3K/: + default_value: 32 + /N9K/: + default_value: 32 + else: + default_value: 16 + +lacp_min_links: + kind: int + config_get_token_append: ['/^lacp min.links (\d+)$/'] + config_set_append: ["%s lacp min-links %s"] + default_value: 1 + +lacp_suspend_individual: + kind: boolean + auto_default: false + config_get_token_append: ['/^lacp suspend.individual$/'] + config_set_append: ["%s lacp suspend-individual"] + default_value: true + +port_hash_distribution: + _exclude: [/N6K/, /N5K/] + config_get_token_append: ['/^port-channel port hash.distribution ?(.*)$/'] + config_set: ["terminal dont-ask", "interface %s", "%s port-channel port hash-distribution %s", "end"] + default_value: false + +port_load_defer: + _exclude: [/N6K/, /N5K/] + kind: boolean + config_get_token_append: ['/^port-channel port load.defer$/'] + config_set_append: ["%s port-channel port load-defer"] + default_value: false diff --git a/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml b/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml new file mode 100644 index 00000000..29ba6362 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml @@ -0,0 +1,79 @@ +# portchannel_global +--- +_template: + config_get: "show running all" + +asymmetric: + _exclude: [/N6K/, /N5K/, /N3K/, /N9K/] + default_value: false + +bundle_hash: + /N5K/: + default_value: 'ip' + /N6K/: + default_value: 'ip' + /N7/: + default_value: 'ip' + /N3K/: + default_value: 'ip-l4port' + /N9K/: + default_value: 'ip-l4port' + +bundle_select: + default_value: 'src-dst' + +concatenation: + _exclude: [/N7/, /N5K/, /N6K/] + default_value: false + +hash_distribution: + _exclude: [/N6K/, /N5K/, /N3K/, /N9K/] + config_get_token: ['/^port.channel hash.distribution ?(.*)$/'] + config_set: ["terminal dont-ask", "port-channel hash-distribution %s", "end"] + default_value: 'adaptive' + +hash_poly: + _exclude: [/N7/, /N3K/, /N9K/] + default_value: 'CRC10b' + +load_balance_type: + kind: string + /N5K/: + default_only: "ethernet" + /N6K/: + default_only: "ethernet" + /N7/: + default_only: "asymmetric" + /N3K/: + default_only: "symmetry" + /N9K/: + default_only: "symmetry" + +load_defer: + _exclude: [/N6K/, /N5K/, /N3K/, /N9K/] + kind: int + config_get_token: ['/^port.channel load.defer (\d+)$/'] + config_set: ["port-channel load-defer %s"] + default_value: 120 + +port_channel_load_balance: + multiple: + config_get_token: '/^port-channel load-balance (.*)$/' + config_set: "port-channel load-balance %s %s %s %s %s %s" + +resilient: + _exclude: [/N6K/, /N5K/, /N7/] + kind: boolean + config_get: "show running-config all" + config_get_token: '/^port-channel load-balance resilient$/' + config_set: "%s port-channel load-balance resilient" + default_value: false + +rotate: + _exclude: [/N5K/, /N6K/] + kind: int + default_value: 0 + +symmetry: + _exclude: [/N7/, /N3K/, /N5K/, /N6K/] + default_value: false diff --git a/lib/cisco_node_utils/interface_port_channel.rb b/lib/cisco_node_utils/interface_port_channel.rb new file mode 100644 index 00000000..38d290d9 --- /dev/null +++ b/lib/cisco_node_utils/interface_port_channel.rb @@ -0,0 +1,161 @@ +# December 2015, Sai Chintalapudi +# +# Copyright (c) 2015 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'node_util' +require_relative 'interface' + +# Add some interface-specific constants to the Cisco namespace +module Cisco + # InterfacePortChannel - node utility class for port channel config management + class InterfacePortChannel < NodeUtil + attr_reader :name + + def initialize(name, instantiate=true) + fail TypeError unless name.is_a?(String) + fail ArgumentError unless name.length > 0 + @name = name.downcase + fail ArgumentError unless name.start_with?('port-channel') + + create if instantiate + end + + def self.interfaces + hash = {} + intf_list = config_get('interface', 'all_interfaces') + return hash if intf_list.nil? + + intf_list.each do |id| + id = id.downcase + next unless id.start_with?('port-channel') + hash[id] = InterfacePortChannel.new(id, false) + end + hash + end + + def create + config_set('interface_port_channel', 'create', @name) + end + + def destroy + config_set('interface_port_channel', 'destroy', @name) + end + + ######################################################## + # PROPERTIES # + ######################################################## + + def lacp_graceful_convergence + config_get('interface_port_channel', 'lacp_graceful_convergence', @name) + end + + def lacp_graceful_convergence=(state) + fail TypeError unless state == true || state == false + no_cmd = (state ? '' : 'no') + config_set('interface_port_channel', + 'lacp_graceful_convergence', @name, no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_graceful_convergence + config_get_default('interface_port_channel', 'lacp_graceful_convergence') + end + + def lacp_max_bundle + config_get('interface_port_channel', 'lacp_max_bundle', @name) + end + + def lacp_max_bundle=(val) + config_set('interface_port_channel', 'lacp_max_bundle', @name, '', val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_max_bundle + config_get_default('interface_port_channel', 'lacp_max_bundle') + end + + def lacp_min_links + config_get('interface_port_channel', 'lacp_min_links', @name) + end + + def lacp_min_links=(val) + config_set('interface_port_channel', 'lacp_min_links', @name, '', val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_min_links + config_get_default('interface_port_channel', 'lacp_min_links') + end + + def lacp_suspend_individual + config_get('interface_port_channel', 'lacp_suspend_individual', @name) + end + + def lacp_suspend_individual=(state) + fail TypeError unless state == true || state == false + no_cmd = (state ? '' : 'no') + config_set('interface_port_channel', + 'lacp_suspend_individual', @name, no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_suspend_individual + config_get_default('interface_port_channel', 'lacp_suspend_individual') + end + + def port_hash_distribution + config_get('interface_port_channel', 'port_hash_distribution', @name) + end + + def port_hash_distribution=(val) + if val + state = '' + value = val + else + state = 'no' + value = '' + end + config_set('interface_port_channel', + 'port_hash_distribution', @name, state, value) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_port_hash_distribution + config_get_default('interface_port_channel', 'port_hash_distribution') + end + + def port_load_defer + config_get('interface_port_channel', 'port_load_defer', @name) + end + + def port_load_defer=(state) + fail TypeError unless state == true || state == false + no_cmd = (state ? '' : 'no') + config_set('interface_port_channel', + 'port_load_defer', @name, no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_port_load_defer + config_get_default('interface_port_channel', 'port_load_defer') + end + end # Class +end # Module diff --git a/lib/cisco_node_utils/portchannel_global.rb b/lib/cisco_node_utils/portchannel_global.rb new file mode 100644 index 00000000..bf3020c6 --- /dev/null +++ b/lib/cisco_node_utils/portchannel_global.rb @@ -0,0 +1,303 @@ +# December 2015, Sai Chintalapudi +# +# Copyright (c) 2015 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'node_util' + +module Cisco + # node_utils class for portchannel_global + class PortChannelGlobal < NodeUtil + attr_reader :name + + def initialize(name, instantiate=true) + fail TypeError unless name.is_a?(String) + fail ArgumentError unless name == 'default' + @name = name.downcase + + create if instantiate + end + + def self.globals + hash = {} + # if the key already exists, no need create instance + hash['default'] = + PortChannelGlobal.new('default', false) unless hash.key?('default') + hash + end + + # create and destroy are not needed for this as + # portchannel global commands do not have any + # pre-requisites + def create + end + + def destroy + @name = nil + end + + ######################################################## + # PROPERTIES # + ######################################################## + + def hash_distribution + config_get('portchannel_global', 'hash_distribution') + end + + def hash_distribution=(val) + config_set('portchannel_global', + 'hash_distribution', val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_hash_distribution + config_get_default('portchannel_global', 'hash_distribution') + end + + def load_defer + config_get('portchannel_global', 'load_defer') + end + + def load_defer=(val) + config_set('portchannel_global', 'load_defer', val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_load_defer + config_get_default('portchannel_global', 'load_defer') + end + + def resilient + config_get('portchannel_global', 'resilient') + end + + def resilient=(state) + fail TypeError unless state == true || state == false + no_cmd = (state ? '' : 'no') + config_set('portchannel_global', 'resilient', no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_resilient + config_get_default('portchannel_global', 'resilient') + end + + # port-channel load-balance is a complicated command + # and this has so many forms in the same device and in + # different devices. + # For ex: + # port-channel load-balance src-dst ip rotate 4 concatenation symmetric + # port-channel load-balance resilient + # port-channel load-balance ethernet destination-mac CRC10c + # port-channel load-balance ethernet source-ip + # port-channel load-balance dst ip-l4port rotate 4 asymmetric + # port-channel load-balance dst ip-l4port-vlan module 9 + # port-channel load-balance hash-modulo + # we need to eliminate all the clutter and get the correct + # line of config first and after that get index of each property + # and get the property value because some properties may or + # may not be present always. + # This method returns a list + # 1st item is bundle_select + # 2nd item is bundle_hash + # 3rd item is hash_poly + # 4th item is asymmetric + # 5th item is symmetry + # 6th item is rotate + # 7th item is concatenation + def port_channel_load_balance + lb = config_get('portchannel_global', + 'port_channel_load_balance') + list = [] + lb.each do |line| + next if line.include? 'internal' + next if line.include? 'resilient' + next if line.include? 'module' + next if line.include? 'fex' + next if line.include? 'hash' + params = line.split(' ') + lb_type = config_get('portchannel_global', 'load_balance_type') + case lb_type.to_sym + when :ethernet # n6k + _parse_ethernet_params(list, params) + when :asymmetric # n7k + _parse_asymmetric_params(list, params, line) + when :symmetry # n9k + _parse_symmetry_params(list, params, line) + end + end + list + end + + def asymmetric + array = port_channel_load_balance + array[3] + end + + def default_asymmetric + config_get_default('portchannel_global', + 'asymmetric') + end + + def bundle_hash + array = port_channel_load_balance + array[1] + end + + def default_bundle_hash + config_get_default('portchannel_global', + 'bundle_hash') + end + + def bundle_select + array = port_channel_load_balance + array[0] + end + + def default_bundle_select + config_get_default('portchannel_global', + 'bundle_select') + end + + def concatenation + array = port_channel_load_balance + array[6] + end + + def default_concatenation + config_get_default('portchannel_global', + 'concatenation') + end + + def hash_poly + array = port_channel_load_balance + array[2] + end + + def default_hash_poly + config_get_default('portchannel_global', + 'hash_poly') + end + + def rotate + array = port_channel_load_balance + array[5] + end + + def default_rotate + config_get_default('portchannel_global', + 'rotate') + end + + def symmetry + array = port_channel_load_balance + array[4] + end + + def default_symmetry + config_get_default('portchannel_global', + 'symmetry') + end + + def port_channel_load_balance=(bselect, bhash, hpoly, asy, sy, conc, rot) + lb_type = config_get('portchannel_global', 'load_balance_type') + case lb_type.to_sym + when :ethernet # n6k + if bselect == 'src' + sel = 'source' + elsif bselect == 'dst' + sel = 'destination' + else + sel = 'source-dest' + end + sel_hash = sel + '-' + bhash + # port-channel load-balance ethernet destination-mac CRC10c + config_set('portchannel_global', 'port_channel_load_balance', + 'ethernet', sel_hash, hpoly, '', '', '') + when :asymmetric # n7k + if asy == true + asym = 'asymmetric' + else + asym = '' + end + # port-channel load-balance dst ip-l4port rotate 4 asymmetric + config_set('portchannel_global', 'port_channel_load_balance', + bselect, bhash, 'rotate', rot.to_s, asym, '') + when :symmetry # n9k + sym = sy ? 'symmetric' : '' + concat = conc ? 'concatenation' : '' + rot_str = rot.zero? ? '' : 'rotate' + rot_val = rot.zero? ? '' : rot.to_s + # port-channel load-balance src-dst ip rotate 4 concatenation symmetric + config_set('portchannel_global', 'port_channel_load_balance', + bselect, bhash, rot_str, rot_val, concat, sym) + end + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + # on n6k, the bundle hash and bundle select are + # merged into one and so we need to break them apart, + # also they are called source and destination instead of + # src and dst as in other devices, so we convert them + def _parse_ethernet_params(list, params) + hash_poly = params[2] + # hash_poly is not shown on the running config + # if it is default under some circumstatnces + hash_poly = hash_poly.nil? ? 'CRC10b' : hash_poly + select_hash = params[1] + lparams = select_hash.split('-') + if lparams[0].downcase == 'destination' + bselect = 'dst' + bhash = lparams[1] + else + if select_hash.include? '-dest-' + bselect = 'src-dst' + bhash = lparams[2] + # there are bundles hashes like ip-only and + # port-only specific to src-dst + bhash += '-only' if select_hash.include? 'only' + else + bselect = 'src' + bhash = lparams[1] + end + end + list << bselect << bhash << hash_poly << nil << nil << nil << nil + end + + def _parse_asymmetric_params(list, params, line) + bselect = params[0] + bhash = params[1] + # asymmetric keyword does not show up if it is false + asym = (line.include? 'asymmetric') ? true : false + ri = params.index('rotate') + rotate = params[ri + 1].to_i + list << bselect << bhash << nil << asym << nil << rotate << nil + end + + def _parse_symmetry_params(list, params, line) + bselect = params[0] + bhash = params[1] + ri = params.index('rotate') + rotate = params[ri + 1].to_i + # concatenation and symmetry keywords do not show up if false + concat = (line.include? 'concatenation') ? true : false + sym = (line.include? 'symmetric') ? true : false + list << bselect << bhash << nil << nil << sym << rotate << concat + end + end # class +end # module diff --git a/tests/test_interface_port_channel.rb b/tests/test_interface_port_channel.rb new file mode 100644 index 00000000..bb8187c9 --- /dev/null +++ b/tests/test_interface_port_channel.rb @@ -0,0 +1,113 @@ +# Copyright (c) 2014-2015 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'ciscotest' +require_relative '../lib/cisco_node_utils/interface' +require_relative '../lib/cisco_node_utils/interface_port_channel' + +# TestX__CLASS_NAME__X - Minitest for X__CLASS_NAME__X node utility class +class TestInterfacePortChannel < CiscoTestCase + # TESTS + + DEFAULT_NAME = 'port-channel134' + + def setup + super + config "no interface #{DEFAULT_NAME}" + end + + def teardown + config "no interface #{DEFAULT_NAME}" + super + end + + def n7k_platform? + /N7K/ =~ node.product_id + end + + def n9k_platform? + /N3K/ =~ node.product_id || /N9K/ =~ node.product_id + end + + def n6k_platform? + /N5K/ =~ node.product_id || /N6K/ =~ node.product_id + end + + def create_port_channel(ifname=DEFAULT_NAME) + InterfacePortChannel.new(ifname) + end + + def test_get_set_port_hash_distribution + return if n6k_platform? + interface = create_port_channel + interface.port_hash_distribution = 'adaptive' + assert_equal('adaptive', interface.port_hash_distribution) + interface.port_hash_distribution = 'fixed' + assert_equal('fixed', interface.port_hash_distribution) + interface.port_hash_distribution = + interface.default_port_hash_distribution + assert_equal(interface.default_port_hash_distribution, + interface.port_hash_distribution) + end + + def test_get_set_lacp_graceful_convergence + interface = create_port_channel + interface.lacp_graceful_convergence = false + assert_equal(false, interface.lacp_graceful_convergence) + interface.lacp_graceful_convergence = + interface.default_lacp_graceful_convergence + assert_equal(interface.default_lacp_graceful_convergence, + interface.lacp_graceful_convergence) + end + + def test_get_set_lacp_min_links + interface = create_port_channel + interface.lacp_min_links = 5 + assert_equal(5, interface.lacp_min_links) + interface.lacp_min_links = interface.default_lacp_min_links + assert_equal(interface.default_lacp_min_links, + interface.lacp_min_links) + end + + def test_get_set_lacp_max_bundle + interface = create_port_channel + interface.lacp_max_bundle = 10 + assert_equal(10, interface.lacp_max_bundle) + interface.lacp_max_bundle = + interface.default_lacp_max_bundle + assert_equal(interface.default_lacp_max_bundle, + interface.lacp_max_bundle) + end + + def test_get_set_lacp_suspend_individual + interface = create_port_channel + interface.lacp_suspend_individual = false + assert_equal(false, interface.lacp_suspend_individual) + interface.lacp_suspend_individual = + interface.default_lacp_suspend_individual + assert_equal(interface.default_lacp_suspend_individual, + interface.lacp_suspend_individual) + end + + def test_get_set_port_load_defer + return if n6k_platform? + interface = create_port_channel + interface.port_load_defer = true + assert_equal(true, interface.port_load_defer) + interface.port_load_defer = + interface.default_port_load_defer + assert_equal(interface.default_port_load_defer, + interface.port_load_defer) + end +end diff --git a/tests/test_portchannel_global.rb b/tests/test_portchannel_global.rb new file mode 100644 index 00000000..50cad768 --- /dev/null +++ b/tests/test_portchannel_global.rb @@ -0,0 +1,187 @@ +# Copyright (c) 2014-2015 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'ciscotest' +require_relative '../lib/cisco_node_utils/portchannel_global' + +# TestX__CLASS_NAME__X - Minitest for X__CLASS_NAME__X node utility class +class TestPortchannelGlobal < CiscoTestCase + # TESTS + + DEFAULT_NAME = 'default' + + def setup + super + config 'no port-channel load-balance' unless n6k_platform? + config 'no port-channel load-balance ethernet' unless + n9k_platform? || n7k_platform? + end + + def teardown + config 'no port-channel load-balance' unless n6k_platform? + config 'no port-channel load-balance ethernet' unless + n9k_platform? || n7k_platform? + super + end + + def n7k_platform? + /N7K/ =~ node.product_id + end + + def n9k_platform? + /N3K/ =~ node.product_id || /N9K/ =~ node.product_id + end + + def n6k_platform? + /N5K/ =~ node.product_id || /N6K/ =~ node.product_id + end + + def create_portchannel_global(name=DEFAULT_NAME) + PortChannelGlobal.new(name) + end + + def test_get_hash_distribution + return if n9k_platform? || n6k_platform? + @global = create_portchannel_global + @global.hash_distribution = 'fixed' + assert_equal('fixed', @global.hash_distribution) + @global.hash_distribution = + @global.default_hash_distribution + assert_equal(@global.default_hash_distribution, + @global.hash_distribution) + end + + def test_get_set_load_defer + return if n9k_platform? || n6k_platform? + @global = create_portchannel_global + @global.load_defer = 1000 + assert_equal(1000, @global.load_defer) + @global.load_defer = + @global.default_load_defer + assert_equal(@global.default_load_defer, + @global.load_defer) + end + + def test_get_set_resilient + return if n7k_platform? || n6k_platform? + @global = create_portchannel_global + @global.resilient = true + assert_equal(true, @global.resilient) + @global.resilient = @global.default_resilient + assert_equal(@global.default_resilient, @global.resilient) + end + + def test_get_set_port_channel_load_balance_sym_concat_rot + return if n7k_platform? || n6k_platform? + @global = create_portchannel_global + @global.send(:port_channel_load_balance=, + 'src-dst', 'ip-l4port', nil, nil, true, true, 4) + assert_equal('src-dst', + @global.bundle_select) + assert_equal('ip-l4port', + @global.bundle_hash) + assert_equal(true, @global.symmetry) + assert_equal(true, @global.concatenation) + assert_equal(4, @global.rotate) + @global.send( + :port_channel_load_balance=, + @global.default_bundle_select, + @global.default_bundle_hash, + nil, + nil, + @global.default_symmetry, + @global.default_concatenation, + @global.default_rotate) + assert_equal( + @global.default_bundle_select, + @global.bundle_select) + assert_equal( + @global.default_bundle_hash, + @global.bundle_hash) + assert_equal( + @global.default_symmetry, + @global.symmetry) + assert_equal( + @global.default_concatenation, + @global.concatenation) + assert_equal(@global.default_rotate, + @global.rotate) + end + + def test_get_set_port_channel_load_balance_hash_poly + return if n7k_platform? || n9k_platform? + @global = create_portchannel_global + @global.send(:port_channel_load_balance=, + 'src-dst', 'ip-only', 'CRC10c', nil, nil, nil, nil) + assert_equal('src-dst', + @global.bundle_select) + assert_equal('ip-only', + @global.bundle_hash) + assert_equal('CRC10c', @global.hash_poly) + @global.send(:port_channel_load_balance=, + 'dst', 'mac', 'CRC10a', nil, nil, nil, nil) + assert_equal('dst', + @global.bundle_select) + assert_equal('mac', + @global.bundle_hash) + assert_equal('CRC10a', @global.hash_poly) + @global.send( + :port_channel_load_balance=, + @global.default_bundle_select, + @global.default_bundle_hash, + @global.default_hash_poly, + nil, nil, nil, nil) + assert_equal( + @global.default_bundle_select, + @global.bundle_select) + assert_equal( + @global.default_bundle_hash, + @global.bundle_hash) + assert_equal(@global.default_hash_poly, + @global.hash_poly) + end + + def test_get_set_port_channel_load_balance_asym_rot + return if n9k_platform? || n6k_platform? + @global = create_portchannel_global + @global.send(:port_channel_load_balance=, + 'src-dst', 'ip-vlan', nil, true, nil, nil, 4) + assert_equal('src-dst', + @global.bundle_select) + assert_equal('ip-vlan', + @global.bundle_hash) + assert_equal(true, @global.asymmetric) + assert_equal(4, @global.rotate) + @global.send( + :port_channel_load_balance=, + @global.default_bundle_select, + @global.default_bundle_hash, + nil, + @global.default_asymmetric, + nil, + nil, + @global.default_rotate) + assert_equal( + @global.default_bundle_select, + @global.bundle_select) + assert_equal( + @global.default_bundle_hash, + @global.bundle_hash) + assert_equal( + @global.default_asymmetric, + @global.asymmetric) + assert_equal(@global.default_rotate, + @global.rotate) + end +end From 8966e1fb8a722b02e366d2ba770c39d79208045a Mon Sep 17 00:00:00 2001 From: saichint Date: Tue, 22 Dec 2015 11:10:12 -0800 Subject: [PATCH 2/7] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90043bab..b1a16e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ Changelog * pim (@smigopal) * pim_group_list (@smigopal) * pim_rp_address (@smigopal) +* Port Channel + * interface_port_channel (@saichint) + * portchannel_global (@saichint) * VDC * vdc (@chrisvanheuveln) * VXLAN From fdba9f35c028b5fef3334ce42a4f64348bb4ee12 Mon Sep 17 00:00:00 2001 From: saichint Date: Tue, 22 Dec 2015 15:12:22 -0800 Subject: [PATCH 3/7] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a16e56..baf43b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ Changelog * pim_group_list (@smigopal) * pim_rp_address (@smigopal) * Port Channel - * interface_port_channel (@saichint) + * interface_portchannel (@saichint) * portchannel_global (@saichint) * VDC * vdc (@chrisvanheuveln) From fb1c5af6ae4d6399d443ffef97f1bcd43c5a38aa Mon Sep 17 00:00:00 2001 From: sai chintalapudi Date: Wed, 23 Dec 2015 10:42:10 -0800 Subject: [PATCH 4/7] pull req comments --- .../cmd_ref/interface_port_channel.yaml | 56 ------ .../cmd_ref/portchannel_global.yaml | 34 ++-- .../interface_port_channel.rb | 161 ------------------ lib/cisco_node_utils/portchannel_global.rb | 94 ++++------ tests/test_interface_port_channel.rb | 113 ------------ tests/test_portchannel_global.rb | 24 ++- 6 files changed, 63 insertions(+), 419 deletions(-) delete mode 100644 lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml delete mode 100644 lib/cisco_node_utils/interface_port_channel.rb delete mode 100644 tests/test_interface_port_channel.rb diff --git a/lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml b/lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml deleted file mode 100644 index 21d64da3..00000000 --- a/lib/cisco_node_utils/cmd_ref/interface_port_channel.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# interface_port_channel ---- -_template: - config_set: ["interface %s"] - config_get_token: '/^interface %s$/i' - config_get: "show running interface all" - -create: - config_set: "interface %s" - -destroy: - config_set: "no interface %s" - -lacp_graceful_convergence: - kind: boolean - auto_default: false - config_get_token_append: ['/^lacp graceful.convergence$/'] - config_set_append: ["%s lacp graceful-convergence"] - default_value: true - -lacp_max_bundle: - kind: int - config_get_token_append: ['/^lacp max.bundle (\d+)$/'] - config_set_append: ["%s lacp max-bundle %s"] - /N3K/: - default_value: 32 - /N9K/: - default_value: 32 - else: - default_value: 16 - -lacp_min_links: - kind: int - config_get_token_append: ['/^lacp min.links (\d+)$/'] - config_set_append: ["%s lacp min-links %s"] - default_value: 1 - -lacp_suspend_individual: - kind: boolean - auto_default: false - config_get_token_append: ['/^lacp suspend.individual$/'] - config_set_append: ["%s lacp suspend-individual"] - default_value: true - -port_hash_distribution: - _exclude: [/N6K/, /N5K/] - config_get_token_append: ['/^port-channel port hash.distribution ?(.*)$/'] - config_set: ["terminal dont-ask", "interface %s", "%s port-channel port hash-distribution %s", "end"] - default_value: false - -port_load_defer: - _exclude: [/N6K/, /N5K/] - kind: boolean - config_get_token_append: ['/^port-channel port load.defer$/'] - config_set_append: ["%s port-channel port load-defer"] - default_value: false diff --git a/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml b/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml index 29ba6362..113de126 100644 --- a/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml +++ b/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml @@ -4,53 +4,43 @@ _template: config_get: "show running all" asymmetric: - _exclude: [/N6K/, /N5K/, /N3K/, /N9K/] + _exclude: [/N6/, /N5/, /N3/, /N9/] default_value: false bundle_hash: - /N5K/: + /N(5|6|7)/: default_value: 'ip' - /N6K/: - default_value: 'ip' - /N7/: - default_value: 'ip' - /N3K/: - default_value: 'ip-l4port' - /N9K/: + /N(3|9)/: default_value: 'ip-l4port' bundle_select: default_value: 'src-dst' concatenation: - _exclude: [/N7/, /N5K/, /N6K/] + _exclude: [/N7/, /N5/, /N6/] default_value: false hash_distribution: - _exclude: [/N6K/, /N5K/, /N3K/, /N9K/] + _exclude: [/N6/, /N5/, /N3/, /N9/] config_get_token: ['/^port.channel hash.distribution ?(.*)$/'] config_set: ["terminal dont-ask", "port-channel hash-distribution %s", "end"] default_value: 'adaptive' hash_poly: - _exclude: [/N7/, /N3K/, /N9K/] + _exclude: [/N7/, /N3/, /N9/] default_value: 'CRC10b' load_balance_type: kind: string - /N5K/: - default_only: "ethernet" - /N6K/: + /N(5|6)/: default_only: "ethernet" /N7/: default_only: "asymmetric" - /N3K/: - default_only: "symmetry" - /N9K/: + /N(3|9)/: default_only: "symmetry" load_defer: - _exclude: [/N6K/, /N5K/, /N3K/, /N9K/] + _exclude: [/N6/, /N5/, /N3/, /N9/] kind: int config_get_token: ['/^port.channel load.defer (\d+)$/'] config_set: ["port-channel load-defer %s"] @@ -62,7 +52,7 @@ port_channel_load_balance: config_set: "port-channel load-balance %s %s %s %s %s %s" resilient: - _exclude: [/N6K/, /N5K/, /N7/] + _exclude: [/N6/, /N5/, /N7/] kind: boolean config_get: "show running-config all" config_get_token: '/^port-channel load-balance resilient$/' @@ -70,10 +60,10 @@ resilient: default_value: false rotate: - _exclude: [/N5K/, /N6K/] + _exclude: [/N5/, /N6/] kind: int default_value: 0 symmetry: - _exclude: [/N7/, /N3K/, /N5K/, /N6K/] + _exclude: [/N7/, /N3/, /N5/, /N6/] default_value: false diff --git a/lib/cisco_node_utils/interface_port_channel.rb b/lib/cisco_node_utils/interface_port_channel.rb deleted file mode 100644 index 38d290d9..00000000 --- a/lib/cisco_node_utils/interface_port_channel.rb +++ /dev/null @@ -1,161 +0,0 @@ -# December 2015, Sai Chintalapudi -# -# Copyright (c) 2015 Cisco and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require_relative 'node_util' -require_relative 'interface' - -# Add some interface-specific constants to the Cisco namespace -module Cisco - # InterfacePortChannel - node utility class for port channel config management - class InterfacePortChannel < NodeUtil - attr_reader :name - - def initialize(name, instantiate=true) - fail TypeError unless name.is_a?(String) - fail ArgumentError unless name.length > 0 - @name = name.downcase - fail ArgumentError unless name.start_with?('port-channel') - - create if instantiate - end - - def self.interfaces - hash = {} - intf_list = config_get('interface', 'all_interfaces') - return hash if intf_list.nil? - - intf_list.each do |id| - id = id.downcase - next unless id.start_with?('port-channel') - hash[id] = InterfacePortChannel.new(id, false) - end - hash - end - - def create - config_set('interface_port_channel', 'create', @name) - end - - def destroy - config_set('interface_port_channel', 'destroy', @name) - end - - ######################################################## - # PROPERTIES # - ######################################################## - - def lacp_graceful_convergence - config_get('interface_port_channel', 'lacp_graceful_convergence', @name) - end - - def lacp_graceful_convergence=(state) - fail TypeError unless state == true || state == false - no_cmd = (state ? '' : 'no') - config_set('interface_port_channel', - 'lacp_graceful_convergence', @name, no_cmd) - rescue Cisco::CliError => e - raise "[#{@name}] '#{e.command}' : #{e.clierror}" - end - - def default_lacp_graceful_convergence - config_get_default('interface_port_channel', 'lacp_graceful_convergence') - end - - def lacp_max_bundle - config_get('interface_port_channel', 'lacp_max_bundle', @name) - end - - def lacp_max_bundle=(val) - config_set('interface_port_channel', 'lacp_max_bundle', @name, '', val) - rescue Cisco::CliError => e - raise "[#{@name}] '#{e.command}' : #{e.clierror}" - end - - def default_lacp_max_bundle - config_get_default('interface_port_channel', 'lacp_max_bundle') - end - - def lacp_min_links - config_get('interface_port_channel', 'lacp_min_links', @name) - end - - def lacp_min_links=(val) - config_set('interface_port_channel', 'lacp_min_links', @name, '', val) - rescue Cisco::CliError => e - raise "[#{@name}] '#{e.command}' : #{e.clierror}" - end - - def default_lacp_min_links - config_get_default('interface_port_channel', 'lacp_min_links') - end - - def lacp_suspend_individual - config_get('interface_port_channel', 'lacp_suspend_individual', @name) - end - - def lacp_suspend_individual=(state) - fail TypeError unless state == true || state == false - no_cmd = (state ? '' : 'no') - config_set('interface_port_channel', - 'lacp_suspend_individual', @name, no_cmd) - rescue Cisco::CliError => e - raise "[#{@name}] '#{e.command}' : #{e.clierror}" - end - - def default_lacp_suspend_individual - config_get_default('interface_port_channel', 'lacp_suspend_individual') - end - - def port_hash_distribution - config_get('interface_port_channel', 'port_hash_distribution', @name) - end - - def port_hash_distribution=(val) - if val - state = '' - value = val - else - state = 'no' - value = '' - end - config_set('interface_port_channel', - 'port_hash_distribution', @name, state, value) - rescue Cisco::CliError => e - raise "[#{@name}] '#{e.command}' : #{e.clierror}" - end - - def default_port_hash_distribution - config_get_default('interface_port_channel', 'port_hash_distribution') - end - - def port_load_defer - config_get('interface_port_channel', 'port_load_defer', @name) - end - - def port_load_defer=(state) - fail TypeError unless state == true || state == false - no_cmd = (state ? '' : 'no') - config_set('interface_port_channel', - 'port_load_defer', @name, no_cmd) - rescue Cisco::CliError => e - raise "[#{@name}] '#{e.command}' : #{e.clierror}" - end - - def default_port_load_defer - config_get_default('interface_port_channel', 'port_load_defer') - end - end # Class -end # Module diff --git a/lib/cisco_node_utils/portchannel_global.rb b/lib/cisco_node_utils/portchannel_global.rb index bf3020c6..59040a7e 100644 --- a/lib/cisco_node_utils/portchannel_global.rb +++ b/lib/cisco_node_utils/portchannel_global.rb @@ -21,32 +21,20 @@ module Cisco class PortChannelGlobal < NodeUtil attr_reader :name - def initialize(name, instantiate=true) + def initialize(name) fail TypeError unless name.is_a?(String) fail ArgumentError unless name == 'default' @name = name.downcase - - create if instantiate end def self.globals hash = {} # if the key already exists, no need create instance hash['default'] = - PortChannelGlobal.new('default', false) unless hash.key?('default') + PortChannelGlobal.new('default', false) hash end - # create and destroy are not needed for this as - # portchannel global commands do not have any - # pre-requisites - def create - end - - def destroy - @name = nil - end - ######################################################## # PROPERTIES # ######################################################## @@ -111,41 +99,29 @@ def default_resilient # line of config first and after that get index of each property # and get the property value because some properties may or # may not be present always. - # This method returns a list - # 1st item is bundle_select - # 2nd item is bundle_hash - # 3rd item is hash_poly - # 4th item is asymmetric - # 5th item is symmetry - # 6th item is rotate - # 7th item is concatenation + # This method returns a hash def port_channel_load_balance lb = config_get('portchannel_global', 'port_channel_load_balance') - list = [] + hash = {} lb.each do |line| - next if line.include? 'internal' - next if line.include? 'resilient' - next if line.include? 'module' - next if line.include? 'fex' - next if line.include? 'hash' - params = line.split(' ') + next if line[/(internal|resilient|module|fex|hash)/] + params = line.split lb_type = config_get('portchannel_global', 'load_balance_type') case lb_type.to_sym when :ethernet # n6k - _parse_ethernet_params(list, params) + _parse_ethernet_params(hash, params) when :asymmetric # n7k - _parse_asymmetric_params(list, params, line) + _parse_asymmetric_params(hash, params, line) when :symmetry # n9k - _parse_symmetry_params(list, params, line) + _parse_symmetry_params(hash, params, line) end end - list + hash end def asymmetric - array = port_channel_load_balance - array[3] + port_channel_load_balance[:asymmetric] end def default_asymmetric @@ -154,8 +130,7 @@ def default_asymmetric end def bundle_hash - array = port_channel_load_balance - array[1] + port_channel_load_balance[:bundle_hash] end def default_bundle_hash @@ -164,8 +139,7 @@ def default_bundle_hash end def bundle_select - array = port_channel_load_balance - array[0] + port_channel_load_balance[:bundle_select] end def default_bundle_select @@ -174,8 +148,7 @@ def default_bundle_select end def concatenation - array = port_channel_load_balance - array[6] + port_channel_load_balance[:concatenation] end def default_concatenation @@ -184,8 +157,7 @@ def default_concatenation end def hash_poly - array = port_channel_load_balance - array[2] + port_channel_load_balance[:hash_poly] end def default_hash_poly @@ -194,8 +166,7 @@ def default_hash_poly end def rotate - array = port_channel_load_balance - array[5] + port_channel_load_balance[:rotate] end def default_rotate @@ -204,8 +175,7 @@ def default_rotate end def symmetry - array = port_channel_load_balance - array[4] + port_channel_load_balance[:symmetry] end def default_symmetry @@ -229,11 +199,7 @@ def port_channel_load_balance=(bselect, bhash, hpoly, asy, sy, conc, rot) config_set('portchannel_global', 'port_channel_load_balance', 'ethernet', sel_hash, hpoly, '', '', '') when :asymmetric # n7k - if asy == true - asym = 'asymmetric' - else - asym = '' - end + asym = (asy == true) ? 'asymmetric' : '' # port-channel load-balance dst ip-l4port rotate 4 asymmetric config_set('portchannel_global', 'port_channel_load_balance', bselect, bhash, 'rotate', rot.to_s, asym, '') @@ -254,7 +220,7 @@ def port_channel_load_balance=(bselect, bhash, hpoly, asy, sy, conc, rot) # merged into one and so we need to break them apart, # also they are called source and destination instead of # src and dst as in other devices, so we convert them - def _parse_ethernet_params(list, params) + def _parse_ethernet_params(hash, params) hash_poly = params[2] # hash_poly is not shown on the running config # if it is default under some circumstatnces @@ -276,20 +242,27 @@ def _parse_ethernet_params(list, params) bhash = lparams[1] end end - list << bselect << bhash << hash_poly << nil << nil << nil << nil + hash[:bundle_select] = bselect + hash[:bundle_hash] = bhash + hash[:hash_poly] = hash_poly + hash end - def _parse_asymmetric_params(list, params, line) + def _parse_asymmetric_params(hash, params, line) bselect = params[0] bhash = params[1] # asymmetric keyword does not show up if it is false asym = (line.include? 'asymmetric') ? true : false ri = params.index('rotate') rotate = params[ri + 1].to_i - list << bselect << bhash << nil << asym << nil << rotate << nil + hash[:bundle_select] = bselect + hash[:bundle_hash] = bhash + hash[:asymmetric] = asym + hash[:rotate] = rotate + hash end - def _parse_symmetry_params(list, params, line) + def _parse_symmetry_params(hash, params, line) bselect = params[0] bhash = params[1] ri = params.index('rotate') @@ -297,7 +270,12 @@ def _parse_symmetry_params(list, params, line) # concatenation and symmetry keywords do not show up if false concat = (line.include? 'concatenation') ? true : false sym = (line.include? 'symmetric') ? true : false - list << bselect << bhash << nil << nil << sym << rotate << concat + hash[:bundle_select] = bselect + hash[:bundle_hash] = bhash + hash[:symmetry] = sym + hash[:rotate] = rotate + hash[:concatenation] = concat + hash end end # class end # module diff --git a/tests/test_interface_port_channel.rb b/tests/test_interface_port_channel.rb deleted file mode 100644 index bb8187c9..00000000 --- a/tests/test_interface_port_channel.rb +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2014-2015 Cisco and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require_relative 'ciscotest' -require_relative '../lib/cisco_node_utils/interface' -require_relative '../lib/cisco_node_utils/interface_port_channel' - -# TestX__CLASS_NAME__X - Minitest for X__CLASS_NAME__X node utility class -class TestInterfacePortChannel < CiscoTestCase - # TESTS - - DEFAULT_NAME = 'port-channel134' - - def setup - super - config "no interface #{DEFAULT_NAME}" - end - - def teardown - config "no interface #{DEFAULT_NAME}" - super - end - - def n7k_platform? - /N7K/ =~ node.product_id - end - - def n9k_platform? - /N3K/ =~ node.product_id || /N9K/ =~ node.product_id - end - - def n6k_platform? - /N5K/ =~ node.product_id || /N6K/ =~ node.product_id - end - - def create_port_channel(ifname=DEFAULT_NAME) - InterfacePortChannel.new(ifname) - end - - def test_get_set_port_hash_distribution - return if n6k_platform? - interface = create_port_channel - interface.port_hash_distribution = 'adaptive' - assert_equal('adaptive', interface.port_hash_distribution) - interface.port_hash_distribution = 'fixed' - assert_equal('fixed', interface.port_hash_distribution) - interface.port_hash_distribution = - interface.default_port_hash_distribution - assert_equal(interface.default_port_hash_distribution, - interface.port_hash_distribution) - end - - def test_get_set_lacp_graceful_convergence - interface = create_port_channel - interface.lacp_graceful_convergence = false - assert_equal(false, interface.lacp_graceful_convergence) - interface.lacp_graceful_convergence = - interface.default_lacp_graceful_convergence - assert_equal(interface.default_lacp_graceful_convergence, - interface.lacp_graceful_convergence) - end - - def test_get_set_lacp_min_links - interface = create_port_channel - interface.lacp_min_links = 5 - assert_equal(5, interface.lacp_min_links) - interface.lacp_min_links = interface.default_lacp_min_links - assert_equal(interface.default_lacp_min_links, - interface.lacp_min_links) - end - - def test_get_set_lacp_max_bundle - interface = create_port_channel - interface.lacp_max_bundle = 10 - assert_equal(10, interface.lacp_max_bundle) - interface.lacp_max_bundle = - interface.default_lacp_max_bundle - assert_equal(interface.default_lacp_max_bundle, - interface.lacp_max_bundle) - end - - def test_get_set_lacp_suspend_individual - interface = create_port_channel - interface.lacp_suspend_individual = false - assert_equal(false, interface.lacp_suspend_individual) - interface.lacp_suspend_individual = - interface.default_lacp_suspend_individual - assert_equal(interface.default_lacp_suspend_individual, - interface.lacp_suspend_individual) - end - - def test_get_set_port_load_defer - return if n6k_platform? - interface = create_port_channel - interface.port_load_defer = true - assert_equal(true, interface.port_load_defer) - interface.port_load_defer = - interface.default_port_load_defer - assert_equal(interface.default_port_load_defer, - interface.port_load_defer) - end -end diff --git a/tests/test_portchannel_global.rb b/tests/test_portchannel_global.rb index 50cad768..ba663bf2 100644 --- a/tests/test_portchannel_global.rb +++ b/tests/test_portchannel_global.rb @@ -36,15 +36,15 @@ def teardown end def n7k_platform? - /N7K/ =~ node.product_id + /N7/ =~ node.product_id end def n9k_platform? - /N3K/ =~ node.product_id || /N9K/ =~ node.product_id + /N(3|9)/ =~ node.product_id end def n6k_platform? - /N5K/ =~ node.product_id || /N6K/ =~ node.product_id + /N(5|6)/ =~ node.product_id end def create_portchannel_global(name=DEFAULT_NAME) @@ -52,7 +52,8 @@ def create_portchannel_global(name=DEFAULT_NAME) end def test_get_hash_distribution - return if n9k_platform? || n6k_platform? + skip('Platform does not support this property') if n6k_platform? || + n9k_platform? @global = create_portchannel_global @global.hash_distribution = 'fixed' assert_equal('fixed', @global.hash_distribution) @@ -63,7 +64,8 @@ def test_get_hash_distribution end def test_get_set_load_defer - return if n9k_platform? || n6k_platform? + skip('Platform does not support this property') if n6k_platform? || + n9k_platform? @global = create_portchannel_global @global.load_defer = 1000 assert_equal(1000, @global.load_defer) @@ -74,7 +76,8 @@ def test_get_set_load_defer end def test_get_set_resilient - return if n7k_platform? || n6k_platform? + skip('Platform does not support this property') if n6k_platform? || + n7k_platform? @global = create_portchannel_global @global.resilient = true assert_equal(true, @global.resilient) @@ -83,7 +86,8 @@ def test_get_set_resilient end def test_get_set_port_channel_load_balance_sym_concat_rot - return if n7k_platform? || n6k_platform? + skip('Platform does not support this property') if n6k_platform? || + n7k_platform? @global = create_portchannel_global @global.send(:port_channel_load_balance=, 'src-dst', 'ip-l4port', nil, nil, true, true, 4) @@ -120,7 +124,8 @@ def test_get_set_port_channel_load_balance_sym_concat_rot end def test_get_set_port_channel_load_balance_hash_poly - return if n7k_platform? || n9k_platform? + skip('Platform does not support this property') if n7k_platform? || + n9k_platform? @global = create_portchannel_global @global.send(:port_channel_load_balance=, 'src-dst', 'ip-only', 'CRC10c', nil, nil, nil, nil) @@ -153,7 +158,8 @@ def test_get_set_port_channel_load_balance_hash_poly end def test_get_set_port_channel_load_balance_asym_rot - return if n9k_platform? || n6k_platform? + skip('Platform does not support this property') if n6k_platform? || + n9k_platform? @global = create_portchannel_global @global.send(:port_channel_load_balance=, 'src-dst', 'ip-vlan', nil, true, nil, nil, 4) From 0f7c84cb7a7c1f3b67cd7da46d895a88bd6234e1 Mon Sep 17 00:00:00 2001 From: sai chintalapudi Date: Wed, 23 Dec 2015 10:42:41 -0800 Subject: [PATCH 5/7] pull req comments --- .../cmd_ref/interface_portchannel.yaml | 54 ++++++ lib/cisco_node_utils/interface_portchannel.rb | 157 ++++++++++++++++++ tests/test_interface_portchannel.rb | 113 +++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml create mode 100644 lib/cisco_node_utils/interface_portchannel.rb create mode 100644 tests/test_interface_portchannel.rb diff --git a/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml b/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml new file mode 100644 index 00000000..95ddc0b5 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml @@ -0,0 +1,54 @@ +# interface_portchannel +--- +_template: + config_set: ["interface %s"] + config_get_token: '/^interface %s$/i' + config_get: "show running interface all" + +create: + config_set: "interface %s" + +destroy: + config_set: "no interface %s" + +lacp_graceful_convergence: + kind: boolean + auto_default: false + config_get_token_append: ['/^lacp graceful.convergence$/'] + config_set_append: ["%s lacp graceful-convergence"] + default_value: true + +lacp_max_bundle: + kind: int + config_get_token_append: ['/^lacp max.bundle (\d+)$/'] + config_set_append: ["lacp max-bundle %s"] + /N(3|9)/: + default_value: 32 + else: + default_value: 16 + +lacp_min_links: + kind: int + config_get_token_append: ['/^lacp min.links (\d+)$/'] + config_set_append: ["lacp min-links %s"] + default_value: 1 + +lacp_suspend_individual: + kind: boolean + auto_default: false + config_get_token_append: ['/^lacp suspend.individual$/'] + config_set_append: ["%s lacp suspend-individual"] + default_value: true + +port_hash_distribution: + _exclude: [/N6/, /N5/] + config_get_token_append: ['/^port-channel port hash.distribution (.*)$/'] + config_set: ["terminal dont-ask", "interface %s", "%s port-channel port hash-distribution %s", "end"] + default_value: false + +port_load_defer: + _exclude: [/N6/, /N5/] + kind: boolean + config_get_token_append: ['/^port-channel port load.defer$/'] + config_set_append: ["%s port-channel port load-defer"] + default_value: false diff --git a/lib/cisco_node_utils/interface_portchannel.rb b/lib/cisco_node_utils/interface_portchannel.rb new file mode 100644 index 00000000..c65c2a9d --- /dev/null +++ b/lib/cisco_node_utils/interface_portchannel.rb @@ -0,0 +1,157 @@ +# December 2015, Sai Chintalapudi +# +# Copyright (c) 2015 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'node_util' +require_relative 'interface' + +# Add some interface-specific constants to the Cisco namespace +module Cisco + # InterfacePortChannel - node utility class for port channel config management + class InterfacePortChannel < NodeUtil + attr_reader :name + + def initialize(name, instantiate=true) + fail TypeError unless name.is_a?(String) + fail ArgumentError unless name.length > 0 + @name = name.downcase + fail ArgumentError unless @name.start_with?('port-channel') + + create if instantiate + end + + def self.interfaces + hash = {} + intf_list = config_get('interface', 'all_interfaces') + return hash if intf_list.nil? + + intf_list.each do |id| + id = id.downcase + next unless id.start_with?('port-channel') + hash[id] = InterfacePortChannel.new(id, false) + end + hash + end + + def create + config_set('interface_portchannel', 'create', @name) + end + + def destroy + config_set('interface_portchannel', 'destroy', @name) + end + + ######################################################## + # PROPERTIES # + ######################################################## + + def lacp_graceful_convergence + config_get('interface_portchannel', 'lacp_graceful_convergence', @name) + end + + def lacp_graceful_convergence=(state) + no_cmd = (state ? '' : 'no') + config_set('interface_portchannel', + 'lacp_graceful_convergence', @name, no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_graceful_convergence + config_get_default('interface_portchannel', 'lacp_graceful_convergence') + end + + def lacp_max_bundle + config_get('interface_portchannel', 'lacp_max_bundle', @name) + end + + def lacp_max_bundle=(val) + config_set('interface_portchannel', 'lacp_max_bundle', @name, val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_max_bundle + config_get_default('interface_portchannel', 'lacp_max_bundle') + end + + def lacp_min_links + config_get('interface_portchannel', 'lacp_min_links', @name) + end + + def lacp_min_links=(val) + config_set('interface_portchannel', 'lacp_min_links', @name, val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_min_links + config_get_default('interface_portchannel', 'lacp_min_links') + end + + def lacp_suspend_individual + config_get('interface_portchannel', 'lacp_suspend_individual', @name) + end + + def lacp_suspend_individual=(state) + no_cmd = (state ? '' : 'no') + config_set('interface_portchannel', + 'lacp_suspend_individual', @name, no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_lacp_suspend_individual + config_get_default('interface_portchannel', 'lacp_suspend_individual') + end + + def port_hash_distribution + config_get('interface_portchannel', 'port_hash_distribution', @name) + end + + def port_hash_distribution=(val) + if val + state = '' + else + state = 'no' + val = '' + end + config_set('interface_portchannel', + 'port_hash_distribution', @name, state, val) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_port_hash_distribution + config_get_default('interface_portchannel', 'port_hash_distribution') + end + + def port_load_defer + config_get('interface_portchannel', 'port_load_defer', @name) + end + + def port_load_defer=(state) + no_cmd = (state ? '' : 'no') + config_set('interface_portchannel', + 'port_load_defer', @name, no_cmd) + rescue Cisco::CliError => e + raise "[#{@name}] '#{e.command}' : #{e.clierror}" + end + + def default_port_load_defer + config_get_default('interface_portchannel', 'port_load_defer') + end + end # Class +end # Module diff --git a/tests/test_interface_portchannel.rb b/tests/test_interface_portchannel.rb new file mode 100644 index 00000000..683b7439 --- /dev/null +++ b/tests/test_interface_portchannel.rb @@ -0,0 +1,113 @@ +# Copyright (c) 2014-2015 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative 'ciscotest' +require_relative '../lib/cisco_node_utils/interface' +require_relative '../lib/cisco_node_utils/interface_portchannel' + +# TestX__CLASS_NAME__X - Minitest for X__CLASS_NAME__X node utility class +class TestInterfacePortChannel < CiscoTestCase + # TESTS + + DEFAULT_NAME = 'port-channel134' + + def setup + super + config "no interface #{DEFAULT_NAME}" + end + + def teardown + config "no interface #{DEFAULT_NAME}" + super + end + + def n7k_platform? + /N7K/ =~ node.product_id + end + + def n9k_platform? + /N3K/ =~ node.product_id || /N9K/ =~ node.product_id + end + + def n6k_platform? + /N5K/ =~ node.product_id || /N6K/ =~ node.product_id + end + + def create_port_channel(ifname=DEFAULT_NAME) + InterfacePortChannel.new(ifname) + end + + def test_get_set_port_hash_distribution + skip('Platform does not support this property') if n6k_platform? + interface = create_port_channel + interface.port_hash_distribution = 'adaptive' + assert_equal('adaptive', interface.port_hash_distribution) + interface.port_hash_distribution = 'fixed' + assert_equal('fixed', interface.port_hash_distribution) + interface.port_hash_distribution = + interface.default_port_hash_distribution + assert_equal(interface.default_port_hash_distribution, + interface.port_hash_distribution) + end + + def test_get_set_lacp_graceful_convergence + interface = create_port_channel + interface.lacp_graceful_convergence = false + assert_equal(false, interface.lacp_graceful_convergence) + interface.lacp_graceful_convergence = + interface.default_lacp_graceful_convergence + assert_equal(interface.default_lacp_graceful_convergence, + interface.lacp_graceful_convergence) + end + + def test_get_set_lacp_min_links + interface = create_port_channel + interface.lacp_min_links = 5 + assert_equal(5, interface.lacp_min_links) + interface.lacp_min_links = interface.default_lacp_min_links + assert_equal(interface.default_lacp_min_links, + interface.lacp_min_links) + end + + def test_get_set_lacp_max_bundle + interface = create_port_channel + interface.lacp_max_bundle = 10 + assert_equal(10, interface.lacp_max_bundle) + interface.lacp_max_bundle = + interface.default_lacp_max_bundle + assert_equal(interface.default_lacp_max_bundle, + interface.lacp_max_bundle) + end + + def test_get_set_lacp_suspend_individual + interface = create_port_channel + interface.lacp_suspend_individual = false + assert_equal(false, interface.lacp_suspend_individual) + interface.lacp_suspend_individual = + interface.default_lacp_suspend_individual + assert_equal(interface.default_lacp_suspend_individual, + interface.lacp_suspend_individual) + end + + def test_get_set_port_load_defer + skip('Platform does not support this property') if n6k_platform? + interface = create_port_channel + interface.port_load_defer = true + assert_equal(true, interface.port_load_defer) + interface.port_load_defer = + interface.default_port_load_defer + assert_equal(interface.default_port_load_defer, + interface.port_load_defer) + end +end From bdb3ce44f351fe523b006dcc6566c88133696db2 Mon Sep 17 00:00:00 2001 From: sai chintalapudi Date: Wed, 23 Dec 2015 10:51:45 -0800 Subject: [PATCH 6/7] review comment --- tests/test_interface_portchannel.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/test_interface_portchannel.rb b/tests/test_interface_portchannel.rb index 683b7439..c42fe342 100644 --- a/tests/test_interface_portchannel.rb +++ b/tests/test_interface_portchannel.rb @@ -32,16 +32,8 @@ def teardown super end - def n7k_platform? - /N7K/ =~ node.product_id - end - - def n9k_platform? - /N3K/ =~ node.product_id || /N9K/ =~ node.product_id - end - def n6k_platform? - /N5K/ =~ node.product_id || /N6K/ =~ node.product_id + /N(5|6)/ =~ node.product_id end def create_port_channel(ifname=DEFAULT_NAME) From 7478ab95c1d791e2b363c39f3f5b55426d9a6ba8 Mon Sep 17 00:00:00 2001 From: sai chintalapudi Date: Wed, 23 Dec 2015 12:10:16 -0800 Subject: [PATCH 7/7] removed optional space from hash distribution --- lib/cisco_node_utils/cmd_ref/portchannel_global.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml b/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml index 113de126..559db253 100644 --- a/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml +++ b/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml @@ -22,7 +22,7 @@ concatenation: hash_distribution: _exclude: [/N6/, /N5/, /N3/, /N9/] - config_get_token: ['/^port.channel hash.distribution ?(.*)$/'] + config_get_token: ['/^port.channel hash.distribution (.*)$/'] config_set: ["terminal dont-ask", "port-channel hash-distribution %s", "end"] default_value: 'adaptive'