diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb new file mode 100644 index 000000000..46e172c73 --- /dev/null +++ b/lib/puppet/provider/vlan/cisco.rb @@ -0,0 +1,34 @@ +require 'puppet/util/network_device/cisco/device' +require 'puppet/provider/network_device' + +Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::NetworkDevice do + + desc "Cisco switch/router provider for vlans." + + mk_resource_methods + + def self.lookup(url, id) + vlans = {} + device = Puppet::Util::NetworkDevice::Cisco::Device.new(url) + device.command do |d| + vlans = d.parse_vlans || {} + end + vlans[id] + end + + def initialize(*args) + super + end + + # Clear out the cached values. + def flush + device.command do |device| + device.update_vlan(resource[:name], former_properties, properties) + end + super + end + + def device + @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url]) + end +end diff --git a/lib/puppet/type/vlan.rb b/lib/puppet/type/vlan.rb new file mode 100644 index 000000000..6708ea4f5 --- /dev/null +++ b/lib/puppet/type/vlan.rb @@ -0,0 +1,24 @@ +# +# Manages a Vlan on a given router or switch +# + +Puppet::Type.newtype(:vlan) do + @doc = "This represents a router or switch vlan." + + ensurable + + newparam(:name) do + desc "Vlan id. It must be a number" + isnamevar + + newvalues(/^\d+/) + end + + newproperty(:description) do + desc "Vlan name" + end + + newparam(:device_url) do + desc "Url to connect to a router or switch." + end +end \ No newline at end of file diff --git a/lib/puppet/util/network_device/cisco/device.rb b/lib/puppet/util/network_device/cisco/device.rb index 97489bd8c..1f350991a 100644 --- a/lib/puppet/util/network_device/cisco/device.rb +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -1,225 +1,246 @@ require 'puppet' require 'puppet/util' require 'puppet/util/network_device/base' require 'puppet/util/network_device/ipcalc' require 'puppet/util/network_device/cisco/interface' require 'ipaddr' class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice::Base include Puppet::Util::NetworkDevice::IPCalc attr_accessor :enable_password def initialize(url, options = {}) super(url) @enable_password = options[:enable_password] || parse_enable(@url.query) transport.default_prompt = /[#>]\s?\z/n end def parse_enable(query) return $1 if query =~ /enable=(.*)/ end def command(cmd=nil) Puppet.debug("command #{cmd}") transport.connect login transport.command("terminal length 0") do |out| enable if out =~ />\s?\z/n end find_capabilities out = execute(cmd) if cmd yield self if block_given? transport.close out end def execute(cmd) transport.command(cmd) end def login return if transport.handles_login? if @url.user != '' transport.command(@url.user, :prompt => /^Password:/) else transport.expect(/^Password:/) end transport.command(@url.password) end def enable raise "Can't issue \"enable\" to enter privileged, no enable password set" unless enable_password transport.command("enable", :prompt => /^Password:/) transport.command(enable_password) end def support_vlan_brief? !! @support_vlan_brief end def find_capabilities out = transport.command("sh vlan brief") lines = out.split("\n") lines.shift; lines.pop @support_vlan_brief = ! (lines.first =~ /^%/) end IF={ :FastEthernet => %w{FastEthernet FastEth Fast FE Fa F}, :GigEthernet => %w{GigabitEthernet GigEthernet GigEth GE Gi G}, :Ethernet => %w{Ethernet Eth E}, :Serial => %w{Serial Se S}, :PortChannel => %w{PortChannel Port-Channel Po}, :POS => %w{POS P}, :VLAN => %w{VLAN VL V}, :Loopback => %w{Loopback Loop Lo}, :ATM => %w{ATM AT A}, :Dialer => %w{Dialer Dial Di D}, :VirtualAccess => %w{Virtual-Access Virtual-A Virtual Virt} } def canonalize_ifname(interface) IF.each do |k,ifnames| if found = ifnames.find { |ifname| interface =~ /^#{ifname}\s*\d/i } interface =~ /^#{found}(.+)\b/i return "#{k.to_s}#{$1}".gsub(/\s+/,'') end end interface end def interface(name) ifname = canonalize_ifname(name) interface = parse_interface(ifname) return { :ensure => :absent } if interface.empty? interface.merge!(parse_trunking(ifname)) interface.merge!(parse_interface_config(ifname)) end def new_interface(name) Puppet::Util::NetworkDevice::Cisco::Interface.new(canonalize_ifname(name), transport) end def parse_interface(name) resource = {} out = transport.command("sh interface #{name}") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| if l =~ /#{name} is (.+), line protocol is / resource[:ensure] = ($1 == 'up' ? :present : :absent); end if l =~ /Auto Speed \(.+\),/ or l =~ /Auto Speed ,/ or l =~ /Auto-speed/ resource[:speed] = :auto end if l =~ /, (.+)Mb\/s/ resource[:speed] = $1 end if l =~ /\s+Auto-duplex \((.{4})\),/ resource[:duplex] = :auto end if l =~ /\s+(.+)-duplex/ resource[:duplex] = $1 == "Auto" ? :auto : $1.downcase.to_sym end if l =~ /Description: (.+)/ resource[:description] = $1 end end resource end def parse_interface_config(name) resource = Hash.new { |hash, key| hash[key] = Array.new ; } out = transport.command("sh running-config interface #{name} | begin interface") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| if l =~ /ip address (#{IP}) (#{IP})\s+secondary\s*$/ resource[:ipaddress] << [prefix_length(IPAddr.new($2)), IPAddr.new($1), 'secondary'] end if l =~ /ip address (#{IP}) (#{IP})\s*$/ resource[:ipaddress] << [prefix_length(IPAddr.new($2)), IPAddr.new($1), nil] end if l =~ /ipv6 address (#{IP})\/(\d+) (eui-64|link-local)/ resource[:ipaddress] << [$2.to_i, IPAddr.new($1), $3] end if l =~ /channel-group\s+(\d+)/ resource[:etherchannel] = $1 end end resource end def parse_vlans vlans = {} out = transport.command(support_vlan_brief? ? "sh vlan brief" : "sh vlan-switch brief") lines = out.split("\n") lines.shift; lines.shift; lines.shift; lines.pop vlan = nil lines.each do |l| case l # vlan name status when /^(\d+)\s+(\w+)\s+(\w+)\s+([a-zA-Z0-9,\/. ]+)\s*$/ - vlan = { :id => $1, :name => $2, :status => $3, :interfaces => [] } + vlan = { :name => $1, :description => $2, :status => $3, :interfaces => [] } if $4.strip.length > 0 vlan[:interfaces] = $4.strip.split(/\s*,\s*/).map{ |ifn| canonalize_ifname(ifn) } end - vlans[vlan[:id]] = vlan + vlans[vlan[:name]] = vlan when /^\s+([a-zA-Z0-9,\/. ]+)\s*$/ raise "invalid sh vlan summary output" unless vlan if $1.strip.length > 0 vlan[:interfaces] += $1.strip.split(/\s*,\s*/).map{ |ifn| canonalize_ifname(ifn) } end else end end vlans end + def update_vlan(id, is = {}, should = {}) + if should[:ensure] == :absent + Puppet.info "Removing #{id} from device vlan" + transport.command("conf t") + transport.command("no vlan #{id}") + transport.command("exit") + return + end + + # We're creating or updating an entry + transport.command("conf t") + transport.command("vlan #{id}") + [is.keys, should.keys].flatten.uniq.each do |property| + Puppet.debug("trying property: #{property}: #{should[property]}") + next if property != :description + transport.command("name #{should[property]}") + end + transport.command("exit") + transport.command("exit") + end + def parse_trunking(interface) trunking = {} out = transport.command("sh interface #{interface} switchport") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| case l when /^Administrative mode:\s+(.*)$/i case $1 when "trunk" trunking[:mode] = :trunk when "static access" trunking[:mode] = :access else raise "Unknown switchport mode: #{$1} for #{interface}" end when /^Administrative Trunking Encapsulation:\s+(.*)$/ case $1 when "dot1q","isl" trunking[:encapsulation] = $1.to_sym if trunking[:mode] == :trunk else raise "Unknown switchport encapsulation: #{$1} for #{interface}" end when /^Access Mode VLAN:\s+(.*) \(\(Inactive\)\)$/ # nothing when /^Access Mode VLAN:\s+(.*) \(.*\)$/ trunking[:native_vlan] = $1 if trunking[:mode] == :access when /^Trunking VLANs Enabled:\s+(.*)$/ next if trunking[:mode] == :access vlans = $1 trunking[:allowed_trunk_vlans] = case vlans when /all/i :all when /none/i :none else vlans end end end trunking end end diff --git a/spec/unit/provider/vlan/cisco_spec.rb b/spec/unit/provider/vlan/cisco_spec.rb new file mode 100644 index 000000000..0951367e6 --- /dev/null +++ b/spec/unit/provider/vlan/cisco_spec.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/vlan/cisco' + +provider_class = Puppet::Type.type(:vlan).provider(:cisco) + +describe provider_class do + before do + @resource = stub("resource", :name => "200") + @provider = provider_class.new(@resource) + end + + it "should have a parent of Puppet::Provider::NetworkDevice" do + provider_class.should < Puppet::Provider::NetworkDevice + end + + it "should have an instances method" do + provider_class.should respond_to(:instances) + end + + describe "when looking up instances at prefetch" do + before do + @device = stub_everything 'device' + Puppet::Util::NetworkDevice::Cisco::Device.stubs(:new).returns(@device) + @device.stubs(:command).yields(@device) + end + + it "should initialize the network device with the given url" do + Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).with(:url).returns(@device) + provider_class.lookup(:url, "200") + end + + it "should delegate to the device vlans" do + @device.expects(:parse_vlans) + provider_class.lookup("", "200") + end + + it "should return only the given vlan" do + @device.expects(:parse_vlans).returns({"200" => { :description => "thisone" }, "1" => { :description => "nothisone" }}) + provider_class.lookup("", "200").should == {:description => "thisone" } + end + + end + + describe "when an instance is being flushed" do + it "should call the device update_vlan method with its vlan id, current attributes, and desired attributes" do + @instance = provider_class.new(:ensure => :present, :name => "200", :description => "myvlan") + @instance.description = "myvlan2" + @instance.resource = @resource + @resource.stubs(:[]).with(:name).returns("200") + device = stub_everything 'device' + @instance.stubs(:device).returns(device) + device.expects(:command).yields(device) + device.expects(:update_vlan).with(@instance.name, {:ensure => :present, :name => "200", :description => "myvlan"}, + {:ensure => :present, :name => "200", :description => "myvlan2"}) + + @instance.flush + end + end +end diff --git a/spec/unit/type/vlan_spec.rb b/spec/unit/type/vlan_spec.rb new file mode 100644 index 000000000..607d7116d --- /dev/null +++ b/spec/unit/type/vlan_spec.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Type.type(:vlan) do + it "should have a 'name' parameter'" do + Puppet::Type.type(:vlan).new(:name => "200")[:name].should == "200" + end + + it "should have a 'device_url' parameter'" do + Puppet::Type.type(:vlan).new(:name => "200", :device_url => :device)[:device_url].should == :device + end + + it "should have an ensure property" do + Puppet::Type.type(:vlan).attrtype(:ensure).should == :property + end + + it "should have a description property" do + Puppet::Type.type(:vlan).attrtype(:description).should == :property + end + + describe "when validating attribute values" do + before do + @provider = stub 'provider', :class => Puppet::Type.type(:vlan).defaultprovider, :clear => nil + Puppet::Type.type(:vlan).defaultprovider.stubs(:new).returns(@provider) + end + + it "should support :present as a value to :ensure" do + Puppet::Type.type(:vlan).new(:name => "200", :ensure => :present) + end + + it "should support :absent as a value to :ensure" do + Puppet::Type.type(:vlan).new(:name => "200", :ensure => :absent) + end + + it "should fail if vlan name is not a number" do + lambda { Puppet::Type.type(:vlan).new(:name => "notanumber", :ensure => :present) }.should raise_error + end + end +end diff --git a/spec/unit/util/network_device/cisco/device_spec.rb b/spec/unit/util/network_device/cisco/device_spec.rb index 9021bbd2c..31aec920e 100644 --- a/spec/unit/util/network_device/cisco/device_spec.rb +++ b/spec/unit/util/network_device/cisco/device_spec.rb @@ -1,501 +1,521 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/util/network_device/cisco/device' describe Puppet::Util::NetworkDevice::Cisco::Device do before(:each) do @transport = stub_everything 'transport', :is_a? => true, :command => "" @cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/") @cisco.transport = @transport end describe "when creating the device" do it "should find the enable password from the url" do cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password") cisco.enable_password.should == "enable_password" end it "should find the enable password from the options" do cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password", :enable_password => "mypass") cisco.enable_password.should == "mypass" end end describe "when connecting to the physical device" do it "should connect to the transport" do @transport.expects(:connect) @cisco.command end it "should attempt to login" do @cisco.expects(:login) @cisco.command end it "should tell the device to not page" do @transport.expects(:command).with("terminal length 0") @cisco.command end it "should enter the enable password if returned prompt is not privileged" do @transport.stubs(:command).yields("Switch>").returns("") @cisco.expects(:enable) @cisco.command end it "should find device capabilities" do @cisco.expects(:find_capabilities) @cisco.command end it "should execute given command" do @transport.expects(:command).with("mycommand") @cisco.command("mycommand") end it "should yield to the command block if one is provided" do @transport.expects(:command).with("mycommand") @cisco.command do |c| c.command("mycommand") end end it "should close the device transport" do @transport.expects(:close) @cisco.command end describe "when login in" do it "should not login if transport handles login" do @transport.expects(:handles_login?).returns(true) @transport.expects(:command).never @transport.expects(:expect).never @cisco.login end it "should send username if one has been provided" do @transport.expects(:command).with("user", :prompt => /^Password:/) @cisco.login end it "should send password after the username" do @transport.expects(:command).with("user", :prompt => /^Password:/) @transport.expects(:command).with("password") @cisco.login end it "should expect the Password: prompt if no user was sent" do @cisco.url.user = '' @transport.expects(:expect).with(/^Password:/) @transport.expects(:command).with("password") @cisco.login end end describe "when entering enable password" do it "should raise an error if no enable password has been set" do @cisco.enable_password = nil lambda{ @cisco.enable }.should raise_error end it "should send the enable command and expect an enable prompt" do @cisco.enable_password = 'mypass' @transport.expects(:command).with("enable", :prompt => /^Password:/) @cisco.enable end it "should send the enable password" do @cisco.enable_password = 'mypass' @transport.stubs(:command).with("enable", :prompt => /^Password:/) @transport.expects(:command).with("mypass") @cisco.enable end end end describe "when finding network device capabilities" do it "should try to execute sh vlan brief" do @transport.expects(:command).with("sh vlan brief").returns("") @cisco.find_capabilities end it "should detect errors" do @transport.stubs(:command).with("sh vlan brief").returns(< "FastEthernet0/1", "Fa0/1" => "FastEthernet0/1", "FastEth 0/1" => "FastEthernet0/1", "Gi1" => "GigEthernet1", "Di9" => "Dialer9", "Ethernet 0/0/1" => "Ethernet0/0/1", "E0" => "Ethernet0", "ATM 0/1.1" => "ATM0/1.1", "VLAN99" => "VLAN99" }.each do |input,expected| it "should canonicalize #{input} to #{expected}" do @cisco.canonalize_ifname(input).should == expected end end + describe "when updating device vlans" do + describe "when removing a vlan" do + it "should issue the no vlan command" do + @transport.expects(:command).with("no vlan 200") + @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :absent}) + end + end + + describe "when updating a vlan" do + it "should issue the vlan command to enter global vlan modifications" do + @transport.expects(:command).with("vlan 200") + @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :present, :name => "200"}) + end + + it "should issue the name command to modify the vlan description" do + @transport.expects(:command).with("name myvlan") + @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :present, :name => "200", :description => "myvlan"}) + end + end + end describe "when parsing interface" do it "should parse interface output" do @cisco.expects(:parse_interface).returns({ :ensure => :present }) @cisco.interface("FastEthernet0/1").should == { :ensure => :present } end it "should parse trunking and merge results" do @cisco.stubs(:parse_interface).returns({ :ensure => :present }) @cisco.expects(:parse_trunking).returns({ :native_vlan => "100" }) @cisco.interface("FastEthernet0/1").should == { :ensure => :present, :native_vlan => "100" } end it "should return an absent interface if parse_interface returns nothing" do @cisco.stubs(:parse_interface).returns({}) @cisco.interface("FastEthernet0/1").should == { :ensure => :absent } end it "should parse ip address information and merge results" do @cisco.stubs(:parse_interface).returns({ :ensure => :present }) @cisco.expects(:parse_interface_config).returns({ :ipaddress => [24,IPAddr.new('192.168.0.24'), nil] }) @cisco.interface("FastEthernet0/1").should == { :ensure => :present, :ipaddress => [24,IPAddr.new('192.168.0.24'), nil] } end it "should parse the sh interface command" do @transport.stubs(:command).with("sh interface FastEthernet0/1").returns(< :absent, :duplex => :auto, :speed => :auto } end it "should be able to parse the sh vlan brief command output" do @cisco.stubs(:support_vlan_brief?).returns(true) @transport.stubs(:command).with("sh vlan brief").returns(<{:status=>"active", :interfaces=>["FastEthernet0/1", "FastEthernet0/2"], :name=>"management", :id=>"100"}, "1"=>{:status=>"active", :interfaces=>["FastEthernet0/3", "FastEthernet0/4", "FastEthernet0/5", "FastEthernet0/6", "FastEthernet0/7", "FastEthernet0/8", "FastEthernet0/9", "FastEthernet0/10", "FastEthernet0/11", "FastEthernet0/12", "FastEthernet0/13", "FastEthernet0/14", "FastEthernet0/15", "FastEthernet0/16", "FastEthernet0/17", "FastEthernet0/18", "FastEthernet0/23", "FastEthernet0/24"], :name=>"default", :id=>"1"}, "10"=>{:status=>"active", :interfaces=>[], :name=>"VLAN0010", :id=>"10"}} + @cisco.parse_vlans.should == {"100"=>{:status=>"active", :interfaces=>["FastEthernet0/1", "FastEthernet0/2"], :description=>"management", :name=>"100"}, "1"=>{:status=>"active", :interfaces=>["FastEthernet0/3", "FastEthernet0/4", "FastEthernet0/5", "FastEthernet0/6", "FastEthernet0/7", "FastEthernet0/8", "FastEthernet0/9", "FastEthernet0/10", "FastEthernet0/11", "FastEthernet0/12", "FastEthernet0/13", "FastEthernet0/14", "FastEthernet0/15", "FastEthernet0/16", "FastEthernet0/17", "FastEthernet0/18", "FastEthernet0/23", "FastEthernet0/24"], :description=>"default", :name=>"1"}, "10"=>{:status=>"active", :interfaces=>[], :description=>"VLAN0010", :name=>"10"}} end it "should parse trunk switchport information" do @transport.stubs(:command).with("sh interface FastEthernet0/21 switchport").returns(< :trunk, :encapsulation => :dot1q, :allowed_trunk_vlans=>:all, } end it "should parse trunk switchport information with allowed vlans" do @transport.stubs(:command).with("sh interface GigabitEthernet 0/1 switchport").returns(< :trunk, :encapsulation => :dot1q, :allowed_trunk_vlans=>"1,99", } end it "should parse access switchport information" do @transport.stubs(:command).with("sh interface FastEthernet0/1 switchport").returns(< :access, :native_vlan => "100" } end it "should parse ip addresses" do @transport.stubs(:command).with("sh running-config interface Vlan 1 | begin interface").returns(<[[24, IPAddr.new('192.168.0.24'), 'secondary'], [24, IPAddr.new('192.168.0.1'), nil], [64, IPAddr.new('2001:07a8:71c1::'), "eui-64"]]} end it "should parse etherchannel membership" do @transport.stubs(:command).with("sh running-config interface Gi0/17 | begin interface").returns(<"1"} end end end # static access # Switch#sh interfaces FastEthernet 0/1 switchport # Name: Fa0/1 # Switchport: Enabled # Administrative mode: static access # Operational Mode: static access # Administrative Trunking Encapsulation: isl # Operational Trunking Encapsulation: isl # Negotiation of Trunking: Disabled # Access Mode VLAN: 100 (SHDSL) # Trunking Native Mode VLAN: 1 (default) # Trunking VLANs Enabled: NONE # Pruning VLANs Enabled: NONE # # Priority for untagged frames: 0 # Override vlan tag priority: FALSE # Voice VLAN: none # Appliance trust: none # Self Loopback: No # Switch# # c2960#sh interfaces GigabitEthernet 0/1 switchport # Name: Gi0/1 # Switchport: Enabled # Administrative Mode: trunk # Operational Mode: trunk # Administrative Trunking Encapsulation: dot1q # Operational Trunking Encapsulation: dot1q # Negotiation of Trunking: On # Access Mode VLAN: 1 (default) # Trunking Native Mode VLAN: 1 (default) # Administrative Native VLAN tagging: enabled # Voice VLAN: none # Administrative private-vlan host-association: none # Administrative private-vlan mapping: none # Administrative private-vlan trunk native VLAN: none # Administrative private-vlan trunk Native VLAN tagging: enabled # Administrative private-vlan trunk encapsulation: dot1q # Administrative private-vlan trunk normal VLANs: none # Administrative private-vlan trunk associations: none # Administrative private-vlan trunk mappings: none # Operational private-vlan: none # Trunking VLANs Enabled: 1,99 # Pruning VLANs Enabled: 2-1001 # Capture Mode Disabled # Capture VLANs Allowed: ALL # # Protected: false # Unknown unicast blocked: disabled # Unknown multicast blocked: disabled # Appliance trust: none # c2960# # c2960#sh interfaces GigabitEthernet 0/2 switchport # Name: Gi0/2 # Switchport: Enabled # Administrative Mode: static access # Operational Mode: static access # Administrative Trunking Encapsulation: dot1q # Operational Trunking Encapsulation: native # Negotiation of Trunking: Off # Access Mode VLAN: 99 (MGMT) # Trunking Native Mode VLAN: 1 (default) # Administrative Native VLAN tagging: enabled # Voice VLAN: none # Administrative private-vlan host-association: none # Administrative private-vlan mapping: none # Administrative private-vlan trunk native VLAN: none # Administrative private-vlan trunk Native VLAN tagging: enabled # Administrative private-vlan trunk encapsulation: dot1q # Administrative private-vlan trunk normal VLANs: none # Administrative private-vlan trunk associations: none # Administrative private-vlan trunk mappings: none # Operational private-vlan: none # Trunking VLANs Enabled: ALL # Pruning VLANs Enabled: 2-1001 # Capture Mode Disabled # Capture VLANs Allowed: ALL # # Protected: false # Unknown unicast blocked: disabled # Unknown multicast blocked: disabled # Appliance trust: none # c2960# # c877#sh interfaces FastEthernet 1 switchport # Name: Fa1 # Switchport: Enabled # Administrative Mode: trunk # Operational Mode: trunk # Administrative Trunking Encapsulation: dot1q # Operational Trunking Encapsulation: dot1q # Negotiation of Trunking: Disabled # Access Mode VLAN: 0 ((Inactive)) # Trunking Native Mode VLAN: 1 (default) # Trunking VLANs Enabled: ALL # Trunking VLANs Active: 1 # Protected: false # Priority for untagged frames: 0 # Override vlan tag priority: FALSE # Voice VLAN: none # Appliance trust: none # c2960#sh etherchannel summary # Flags: D - down P - bundled in port-channel # I - stand-alone s - suspended # H - Hot-standby (LACP only) # R - Layer3 S - Layer2 # U - in use f - failed to allocate aggregator # # M - not in use, minimum links not met # u - unsuitable for bundling # w - waiting to be aggregated # d - default port # # # Number of channel-groups in use: 1 # Number of aggregators: 1 # # Group Port-channel Protocol Ports # ------+-------------+-----------+----------------------------------------------- # 1 Po1(SU) LACP Gi0/17(P) Gi0/18(P) # # c2960#