diff options
Diffstat (limited to 'lib/puppet/util')
-rw-r--r-- | lib/puppet/util/network_device.rb | 2 | ||||
-rw-r--r-- | lib/puppet/util/network_device/base.rb | 29 | ||||
-rw-r--r-- | lib/puppet/util/network_device/cisco.rb | 4 | ||||
-rw-r--r-- | lib/puppet/util/network_device/cisco/device.rb | 225 | ||||
-rw-r--r-- | lib/puppet/util/network_device/cisco/interface.rb | 82 |
5 files changed, 342 insertions, 0 deletions
diff --git a/lib/puppet/util/network_device.rb b/lib/puppet/util/network_device.rb new file mode 100644 index 000000000..bca66016b --- /dev/null +++ b/lib/puppet/util/network_device.rb @@ -0,0 +1,2 @@ +module Puppet::Util::NetworkDevice +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/base.rb b/lib/puppet/util/network_device/base.rb new file mode 100644 index 000000000..ff96c8693 --- /dev/null +++ b/lib/puppet/util/network_device/base.rb @@ -0,0 +1,29 @@ +require 'puppet/util/autoload' +require 'uri' +require 'puppet/util/network_device/transport' +require 'puppet/util/network_device/transport/base' + +module Puppet::Util::NetworkDevice + class Base + + attr_accessor :url, :transport + + def initialize(url) + @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.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 +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/cisco.rb b/lib/puppet/util/network_device/cisco.rb new file mode 100644 index 000000000..c03a00104 --- /dev/null +++ b/lib/puppet/util/network_device/cisco.rb @@ -0,0 +1,4 @@ + +module Puppet::Util::NetworkDevice::Cisco + +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 new file mode 100644 index 000000000..97489bd8c --- /dev/null +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -0,0 +1,225 @@ +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 => [] } + if $4.strip.length > 0 + vlan[:interfaces] = $4.strip.split(/\s*,\s*/).map{ |ifn| canonalize_ifname(ifn) } + end + vlans[vlan[:id]] = 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 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/lib/puppet/util/network_device/cisco/interface.rb b/lib/puppet/util/network_device/cisco/interface.rb new file mode 100644 index 000000000..63d5492a7 --- /dev/null +++ b/lib/puppet/util/network_device/cisco/interface.rb @@ -0,0 +1,82 @@ +require 'puppet/util/network_device/cisco' +require 'puppet/util/network_device/ipcalc' + +# this manages setting properties to an interface in a cisco switch or router +class Puppet::Util::NetworkDevice::Cisco::Interface + + include Puppet::Util::NetworkDevice::IPCalc + extend Puppet::Util::NetworkDevice::IPCalc + + attr_reader :transport, :name + + def initialize(name, transport) + @name = name + @transport = transport + end + + COMMANDS = { + # property => order, ios command/block/array + :description => [1, "description %s"], + :speed => [2, "speed %s"], + :duplex => [3, "duplex %s"], + :native_vlan => [4, "switchport access vlan %s"], + :encapsulation => [5, "switchport trunk encapsulation %s"], + :mode => [6, "switchport mode %s"], + :allowed_trunk_vlans => [7, "switchport trunk allowed vlan %s"], + :etherchannel => [8, ["channel-group %s", "port group %s"]], + :ipaddress => [9, + lambda do |prefix,ip,option| + ip.ipv6? ? "ipv6 address #{ip.to_s}/#{prefix} #{option}" : + "ip address #{ip.to_s} #{netmask(Socket::AF_INET,prefix)}" + end], + :ensure => [10, lambda { |value| value == :present ? "no shutdown" : "shutdown" } ] + } + + def update(is={}, should={}) + Puppet.debug("Updating interface #{name}") + command("conf t") + command("interface #{name}") + + # apply changes in a defined orders for cisco IOS devices + [is.keys, should.keys].flatten.uniq.sort {|a,b| COMMANDS[a][0] <=> COMMANDS[b][0] }.each do |property| + # They're equal, so do nothing. + next if is[property] == should[property] + + # We're deleting it + if should[property] == :absent or should[property].nil? + execute(property, is[property], "no ") + next + end + + # We're replacing an existing value or creating a new one + execute(property, should[property]) + end + + command("exit") + command("exit") + end + + def execute(property, value, prefix='') + case COMMANDS[property][1] + when Array + COMMANDS[property][1].each do |command| + transport.command(prefix + command % value) do |out| + break unless out =~ /^%/ + end + end + when String + command(prefix + COMMANDS[property][1] % value) + when Proc + value = [value] unless value.is_a?(Array) + value.each do |value| + command(prefix + COMMANDS[property][1].call(*value)) + end + end + end + + def command(command) + transport.command(command) do |out| + Puppet.err "Error while executing #{command}, device returned #{out}" if out =~ /^%/mo + end + end +end
\ No newline at end of file |