diff --git a/lib/puppet/provider/interface/cisco.rb b/lib/puppet/provider/interface/cisco.rb index 795a7f1ac..a0f82c7de 100644 --- a/lib/puppet/provider/interface/cisco.rb +++ b/lib/puppet/provider/interface/cisco.rb @@ -1,27 +1,27 @@ require 'puppet/provider/cisco' Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for interface." mk_resource_methods def self.lookup(device, name) interface = nil - device.command do |ng| - interface = device.interface(name) + device.command do |dev| + interface = dev.interface(name) end interface end def initialize(device, *args) super end def flush - device.command do |device| - device.new_interface(name).update(former_properties, properties) + device.command do |dev| + dev.new_interface(name).update(former_properties, properties) end super end end diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb index 3421d35b0..6d03764d2 100644 --- a/lib/puppet/provider/vlan/cisco.rb +++ b/lib/puppet/provider/vlan/cisco.rb @@ -1,28 +1,28 @@ require 'puppet/provider/cisco' Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for vlans." mk_resource_methods def self.lookup(device, id) vlans = {} - device.command do |d| - vlans = d.parse_vlans || {} + device.command do |dev| + vlans = dev.parse_vlans || {} end vlans[id] end def initialize(device, *args) super end # Clear out the cached values. def flush - device.command do |device| - device.update_vlan(resource[:name], former_properties, properties) + device.command do |dev| + dev.update_vlan(resource[:name], former_properties, properties) end super end end diff --git a/lib/puppet/util/network_device.rb b/lib/puppet/util/network_device.rb index 7fb8e2ff3..2d0f94f4f 100644 --- a/lib/puppet/util/network_device.rb +++ b/lib/puppet/util/network_device.rb @@ -1,17 +1,17 @@ class Puppet::Util::NetworkDevice class << self attr_reader :current end def self.init(device) require "puppet/util/network_device/#{device.provider}/device" - @current = Puppet::Util::NetworkDevice.const_get(device.provider.capitalize).const_get(:Device).new(device.url) + @current = Puppet::Util::NetworkDevice.const_get(device.provider.capitalize).const_get(:Device).new(device.url, device.options) rescue => detail raise "Can't load #{device.provider} for #{device.name}: #{detail}" end # Should only be used in tests def self.teardown @current = nil end end diff --git a/lib/puppet/util/network_device/base.rb b/lib/puppet/util/network_device/base.rb index d2fbfccb2..8f971efdf 100644 --- a/lib/puppet/util/network_device/base.rb +++ b/lib/puppet/util/network_device/base.rb @@ -1,27 +1,27 @@ require 'puppet/util/autoload' require 'uri' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' class Puppet::Util::NetworkDevice::Base attr_accessor :url, :transport - def initialize(url) + def initialize(url, options = {}) @url = URI.parse(url) @autoloader = Puppet::Util::Autoload.new( self, "puppet/util/network_device/transport", :wrap => false ) if @autoloader.load(@url.scheme) - @transport = Puppet::Util::NetworkDevice::Transport.const_get(@url.scheme.capitalize).new + @transport = Puppet::Util::NetworkDevice::Transport.const_get(@url.scheme.capitalize).new(options[:debug]) @transport.host = @url.host @transport.port = @url.port || case @url.scheme ; when "ssh" ; 22 ; when "telnet" ; 23 ; end @transport.user = @url.user @transport.password = @url.password end end end diff --git a/lib/puppet/util/network_device/cisco/device.rb b/lib/puppet/util/network_device/cisco/device.rb index 588c8504d..e7b9368d4 100644 --- a/lib/puppet/util/network_device/cisco/device.rb +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -1,257 +1,264 @@ 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 'puppet/util/network_device/cisco/facts' 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) + super(url, options) @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}") + def connect transport.connect login transport.command("terminal length 0") do |out| enable if out =~ />\s?\z/n end find_capabilities + end + + def disconnect + transport.close + end + + def command(cmd = nil) + connect out = execute(cmd) if cmd yield self if block_given? - transport.close + disconnect 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") + out = execute("sh vlan brief") lines = out.split("\n") lines.shift; lines.pop @support_vlan_brief = ! (lines.first =~ /^%/) end - IF={ + IF = { :FastEthernet => %w{FastEthernet FastEth Fast FE Fa F}, :GigabitEthernet => %w{GigabitEthernet GigEthernet GigEth GE Gi G}, :TenGigabitEthernet => %w{TenGigabitEthernet TE Te}, :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 } found = /^#{found}(.+)\Z/i.match(interface) return "#{k.to_s}#{found[1]}".gsub(/\s+/,'') end end interface end def facts @facts ||= Puppet::Util::NetworkDevice::Cisco::Facts.new(transport) facts = {} command do |ng| facts = @facts.retrieve end facts 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}") + out = execute("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") + out = execute("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") + out = execute(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 = { :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[: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") + execute("conf t") + execute("no vlan #{id}") + execute("exit") return end # We're creating or updating an entry - transport.command("conf t") - transport.command("vlan #{id}") + execute("conf t") + execute("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]}") + execute("name #{should[property]}") end - transport.command("exit") - transport.command("exit") + execute("exit") + execute("exit") end def parse_trunking(interface) trunking = {} - out = transport.command("sh interface #{interface} switchport") + out = execute("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/lib/puppet/util/network_device/config.rb b/lib/puppet/util/network_device/config.rb index 6480d5970..cf28761ba 100644 --- a/lib/puppet/util/network_device/config.rb +++ b/lib/puppet/util/network_device/config.rb @@ -1,93 +1,96 @@ require 'ostruct' require 'puppet/util/loadedfile' require 'puppet/util/network_device' class Puppet::Util::NetworkDevice::Config < Puppet::Util::LoadedFile def self.main @main ||= self.new end def self.devices main.devices || [] end attr_reader :devices def exists? FileTest.exists?(@file) end def initialize() @file = Puppet[:deviceconfig] raise Puppet::DevError, "No device config file defined" unless @file return unless self.exists? super(@file) @devices = {} read(true) # force reading at start end # Read the configuration file. def read(force = false) return unless FileTest.exists?(@file) parse if force or changed? end private def parse begin devices = {} device = nil File.open(@file) { |f| count = 1 f.each { |line| case line when /^\s*#/ # skip comments count += 1 next when /^\s*$/ # skip blank lines count += 1 next when /^\[([\w.-]+)\]\s*$/ # [device.fqdn] name = $1 name.chomp! raise Puppet::Error, "Duplicate device found at line #{count}, already found at #{device.line}" if devices.include?(name) device = OpenStruct.new device.name = name device.line = count + device.options = { :debug => false } Puppet.debug "found device: #{device.name} at #{device.line}" devices[name] = device - when /^\s*(type|url)\s+(.+)$/ - parse_directive(device, $1, $2, count) + when /^\s*(type|url|debug)(\s+(.+))*$/ + parse_directive(device, $1, $3, count) else raise Puppet::Error, "Invalid line #{count}: #{line}" end count += 1 } } rescue Errno::EACCES => detail Puppet.err "Configuration error: Cannot read #{@file}; cannot serve" #raise Puppet::Error, "Cannot read #{@config}" rescue Errno::ENOENT => detail Puppet.err "Configuration error: '#{@file}' does not exit; cannot serve" end @devices = devices end def parse_directive(device, var, value, count) case var when "type" device.provider = value when "url" device.url = value + when "debug" + device.options[:debug] = true else raise Puppet::Error, "Invalid argument '#{var}' at line #{count}" end end end diff --git a/lib/puppet/util/network_device/ipcalc.rb b/lib/puppet/util/network_device/ipcalc.rb index b2e3aa673..8ca5295a7 100644 --- a/lib/puppet/util/network_device/ipcalc.rb +++ b/lib/puppet/util/network_device/ipcalc.rb @@ -1,68 +1,68 @@ - require 'puppet/util/network_device' + module Puppet::Util::NetworkDevice::IPCalc # This is a rip-off of authstore Octet = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])' IPv4 = "#{Octet}\.#{Octet}\.#{Octet}\.#{Octet}" IPv6_full = "_:_:_:_:_:_:_:_|_:_:_:_:_:_::_?|_:_:_:_:_::((_:)?_)?|_:_:_:_::((_:){0,2}_)?|_:_:_::((_:){0,3}_)?|_:_::((_:){0,4}_)?|_::((_:){0,5}_)?|::((_:){0,6}_)?" IPv6_partial = "_:_:_:_:_:_:|_:_:_:_::(_:)?|_:_::(_:){0,2}|_::(_:){0,3}" IP = "#{IPv4}|#{IPv6_full}".gsub(/_/,'([0-9a-fA-F]{1,4})').gsub(/\(/,'(?:') def parse(value) case value when /^(#{IP})\/(\d+)$/ # 12.34.56.78/24, a001:b002::efff/120, c444:1000:2000::9:192.168.0.1/112 [$2.to_i,IPAddr.new($1)] when /^(#{IP})$/ # 10.20.30.40, value = IPAddr.new(value) [bits(value.family),value] end end def bits(family) family == Socket::AF_INET6 ? 128 : 32 end def fullmask(family) (1 << bits(family)) - 1 end def mask(family, length) (1 << (bits(family) - length)) - 1 end # returns ip address netmask from prefix length def netmask(family, length) IPAddr.new(fullmask(family) & ~mask(family, length) , family) end # returns an IOS wildmask def wildmask(family, length) IPAddr.new(mask(family, length) , family) end # returns ip address prefix length from netmask def prefix_length(netmask) mask_addr = netmask.to_i return 0 if mask_addr == 0 length=32 if (netmask.ipv6?) length=128 end mask = mask_addr < 2**length ? length : 128 mask.times do if ((mask_addr & 1) == 1) break end mask_addr = mask_addr >> 1 mask = mask - 1 end mask end def linklocal?(ip) end end diff --git a/lib/puppet/util/network_device/transport/ssh.rb b/lib/puppet/util/network_device/transport/ssh.rb index dca5600bd..daee1d3a3 100644 --- a/lib/puppet/util/network_device/transport/ssh.rb +++ b/lib/puppet/util/network_device/transport/ssh.rb @@ -1,121 +1,122 @@ require 'puppet/util/network_device' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' # This is an adaptation/simplification of gem net-ssh-telnet, which aims to have # a sane interface to Net::SSH. Credits goes to net-ssh-telnet authors class Puppet::Util::NetworkDevice::Transport::Ssh < Puppet::Util::NetworkDevice::Transport::Base - attr_accessor :buf, :ssh, :channel, :verbose + attr_accessor :buf, :ssh, :channel - def initialize - super + def initialize(verbose = false) + super() + @verbose = verbose unless Puppet.features.ssh? raise 'Connecting with ssh to a network device requires the \'net/ssh\' ruby library' end end def handles_login? true end def eof? !! @eof end def connect(&block) @output = [] @channel_data = "" begin Puppet.debug("connecting to #{host} as #{user}") @ssh = Net::SSH.start(host, user, :port => port, :password => password, :timeout => timeout) rescue TimeoutError raise TimeoutError, "timed out while opening an ssh connection to the host" rescue Net::SSH::AuthenticationFailed raise Puppet::Error, "SSH authentication failure connecting to #{host} as #{user}" rescue Net::SSH::Exception => detail raise Puppet::Error, "SSH connection failure to #{host}" end @buf = "" @eof = false @channel = nil @ssh.open_channel do |channel| channel.request_pty { |ch,success| raise "failed to open pty" unless success } channel.send_channel_request("shell") do |ch, success| raise "failed to open ssh shell channel" unless success ch.on_data { |ch,data| @buf << data } ch.on_extended_data { |ch,type,data| @buf << data if type == 1 } ch.on_close { @eof = true } @channel = ch expect(default_prompt, &block) # this is a little bit unorthodox, we're trying to escape # the ssh loop there while still having the ssh connection up # otherwise we wouldn't be able to return ssh stdout/stderr # for a given call of command. return end end @ssh.loop end def close @channel.close if @channel @channel = nil @ssh.close if @ssh end def expect(prompt) line = '' sock = @ssh.transport.socket while not @eof break if line =~ prompt and @buf == '' break if sock.closed? IO::select([sock], [sock], nil, nil) process_ssh # at this point we have accumulated some data in @buf # or the channel has been closed if @buf != "" line += @buf.gsub(/\r\n/no, "\n") @buf = '' yield line if block_given? elsif @eof # channel has been closed break if line =~ prompt if line == '' line = nil yield nil if block_given? end break end end Puppet.debug("ssh: expected #{line}") if @verbose line end def send(line) Puppet.debug("ssh: send #{line}") if @verbose @channel.send_data(line + "\n") end def process_ssh while @buf == "" and not eof? begin @channel.connection.process(0.1) rescue IOError @eof = true end end end end diff --git a/lib/puppet/util/network_device/transport/telnet.rb b/lib/puppet/util/network_device/transport/telnet.rb index e9322f81b..b25ce688e 100644 --- a/lib/puppet/util/network_device/transport/telnet.rb +++ b/lib/puppet/util/network_device/transport/telnet.rb @@ -1,42 +1,44 @@ require 'puppet/util/network_device' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' require 'net/telnet' class Puppet::Util::NetworkDevice::Transport::Telnet < Puppet::Util::NetworkDevice::Transport::Base - def initialize - super + def initialize(verbose = false) + super() + @verbose = verbose end def handles_login? false end def connect @telnet = Net::Telnet::new("Host" => host, "Port" => port || 23, "Timeout" => 10, "Prompt" => default_prompt) end def close @telnet.close if @telnet @telnet = nil end def expect(prompt) @telnet.waitfor(prompt) do |out| yield out if block_given? end end def command(cmd, options = {}) send(cmd) expect(options[:prompt] || default_prompt) do |output| yield output if block_given? end end def send(line) + Puppet.debug("telnet: send #{line}") if @verbose @telnet.puts(line) 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 bd4489da3..50e710fab 100755 --- a/spec/unit/util/network_device/cisco/device_spec.rb +++ b/spec/unit/util/network_device/cisco/device_spec.rb @@ -1,408 +1,419 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/cisco/device' +require 'puppet/util/network_device/transport/telnet' 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 + + it "should find the debug mode from the options" do + Puppet::Util::NetworkDevice::Transport::Telnet.expects(:new).with(true).returns(@transport) + cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23", :debug => true) + end + + it "should set the debug mode to false by default" do + Puppet::Util::NetworkDevice::Transport::Telnet.expects(:new).with(false).returns(@transport) + cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23") + 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" => "GigabitEthernet1", "Te2" => "TenGigabitEthernet2", "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"], :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 describe "when finding device facts" do it "should delegate to the cisco facts entity" do facts = stub 'facts' Puppet::Util::NetworkDevice::Cisco::Facts.expects(:new).returns(facts) facts.expects(:retrieve).returns(:facts) @cisco.facts.should == :facts end end end diff --git a/spec/unit/util/network_device/config_spec.rb b/spec/unit/util/network_device/config_spec.rb index eea3de2dc..25ff6b220 100755 --- a/spec/unit/util/network_device/config_spec.rb +++ b/spec/unit/util/network_device/config_spec.rb @@ -1,110 +1,124 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/config' describe Puppet::Util::NetworkDevice::Config do include PuppetSpec::Files before(:each) do Puppet[:deviceconfig] = make_absolute("/dummy") FileTest.stubs(:exists?).with(make_absolute("/dummy")).returns(true) end describe "when initializing" do before :each do Puppet::Util::NetworkDevice::Config.any_instance.stubs(:read) end it "should use the deviceconfig setting as pathname" do Puppet.expects(:[]).with(:deviceconfig).returns(make_absolute("/dummy")) Puppet::Util::NetworkDevice::Config.new end it "should raise an error if no file is defined finally" do Puppet.expects(:[]).with(:deviceconfig).returns(nil) lambda { Puppet::Util::NetworkDevice::Config.new }.should raise_error(Puppet::DevError) end it "should read and parse the file" do Puppet::Util::NetworkDevice::Config.any_instance.expects(:read) Puppet::Util::NetworkDevice::Config.new end end describe "when parsing device" do before :each do @config = Puppet::Util::NetworkDevice::Config.new @config.stubs(:changed?).returns(true) @fd = stub 'fd' File.stubs(:open).yields(@fd) end it "should skip comments" do @fd.stubs(:each).yields(' # comment') OpenStruct.expects(:new).never @config.read end it "should increment line number even on commented lines" do @fd.stubs(:each).multiple_yields(' # comment','[router.puppetlabs.com]') @config.read @config.devices.should be_include('router.puppetlabs.com') end it "should skip blank lines" do @fd.stubs(:each).yields(' ') @config.read @config.devices.should be_empty end it "should produce the correct line number" do @fd.stubs(:each).multiple_yields(' ', '[router.puppetlabs.com]') @config.read @config.devices['router.puppetlabs.com'].line.should == 2 end it "should throw an error if the current device already exists" do @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', '[router.puppetlabs.com]') lambda { @config.read }.should raise_error end it "should accept device certname containing dashes" do @fd.stubs(:each).yields('[router-1.puppetlabs.com]') @config.read @config.devices.should include('router-1.puppetlabs.com') end it "should create a new device for each found device line" do @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', '[swith.puppetlabs.com]') @config.read @config.devices.size.should == 2 end it "should parse the device type" do @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco') @config.read @config.devices['router.puppetlabs.com'].provider.should == 'cisco' end it "should parse the device url" do @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/') @config.read @config.devices['router.puppetlabs.com'].url.should == 'ssh://test/' end + + it "should parse the debug mode" do + @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/', 'debug') + + @config.read + @config.devices['router.puppetlabs.com'].options.should == { :debug => true } + end + + it "should set the debug mode to false by default" do + @fd.stubs(:each).multiple_yields('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/') + + @config.read + @config.devices['router.puppetlabs.com'].options.should == { :debug => false } + end end end diff --git a/spec/unit/util/network_device_spec.rb b/spec/unit/util/network_device_spec.rb index 57c26674b..beb6a7ea5 100644 --- a/spec/unit/util/network_device_spec.rb +++ b/spec/unit/util/network_device_spec.rb @@ -1,50 +1,50 @@ #! /usr/bin/env ruby require 'spec_helper' require 'ostruct' require 'puppet/util/network_device' describe Puppet::Util::NetworkDevice do before(:each) do - @device = OpenStruct.new(:name => "name", :provider => "test") + @device = OpenStruct.new(:name => "name", :provider => "test", :url => "telnet://admin:password@127.0.0.1", :options => { :debug => false }) end after(:each) do Puppet::Util::NetworkDevice.teardown end class Puppet::Util::NetworkDevice::Test class Device - def initialize(device) + def initialize(device, options) end end end describe "when initializing the remote network device singleton" do it "should load the network device code" do Puppet::Util::NetworkDevice.expects(:require) Puppet::Util::NetworkDevice.init(@device) end it "should create a network device instance" do Puppet::Util::NetworkDevice.stubs(:require) - Puppet::Util::NetworkDevice::Test::Device.expects(:new) + Puppet::Util::NetworkDevice::Test::Device.expects(:new).with("telnet://admin:password@127.0.0.1", :debug => false) Puppet::Util::NetworkDevice.init(@device) end it "should raise an error if the remote device instance can't be created" do Puppet::Util::NetworkDevice.stubs(:require).raises("error") lambda { Puppet::Util::NetworkDevice.init(@device) }.should raise_error end it "should let caller to access the singleton device" do device = stub 'device' Puppet::Util::NetworkDevice.stubs(:require) Puppet::Util::NetworkDevice::Test::Device.expects(:new).returns(device) Puppet::Util::NetworkDevice.init(@device) Puppet::Util::NetworkDevice.current.should == device end end end