diff options
author | James Turnbull <james@lovedthanlost.net> | 2011-04-12 05:15:00 +1000 |
---|---|---|
committer | James Turnbull <james@lovedthanlost.net> | 2011-04-12 05:15:00 +1000 |
commit | dce851cac79393f86950f4ebfc48b9ac67dcd8f7 (patch) | |
tree | cd2a4a92183b43eba985633f34de6557937b9e37 /lib | |
parent | 46d67fd86819d1dfe4813f22b192213985e3a587 (diff) | |
parent | 49dcc24195d262437cc35997a811eb724d65b48b (diff) | |
download | puppet-dce851cac79393f86950f4ebfc48b9ac67dcd8f7.tar.gz puppet-dce851cac79393f86950f4ebfc48b9ac67dcd8f7.tar.xz puppet-dce851cac79393f86950f4ebfc48b9ac67dcd8f7.zip |
Merge branch 'tickets/master/7021' into next
* tickets/master/7021:
Updated confine in Spec test for RSpec 2
Add management of router/switchs global vlans
Cisco Switch/Router Interface management
Base class for network device based providers
Ssh transport for network device management
Telnet transport to connect to remote network device
Remote Network Device transport system
Introduce a module for some IP computations
Diffstat (limited to 'lib')
-rw-r--r-- | lib/puppet/feature/ssh.rb | 4 | ||||
-rw-r--r-- | lib/puppet/provider/interface/base.rb | 0 | ||||
-rw-r--r-- | lib/puppet/provider/interface/cisco.rb | 33 | ||||
-rw-r--r-- | lib/puppet/provider/network_device.rb | 59 | ||||
-rw-r--r-- | lib/puppet/provider/vlan/cisco.rb | 34 | ||||
-rw-r--r-- | lib/puppet/type/interface.rb | 107 | ||||
-rw-r--r-- | lib/puppet/type/router.rb | 14 | ||||
-rw-r--r-- | lib/puppet/type/vlan.rb | 24 | ||||
-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 | 246 | ||||
-rw-r--r-- | lib/puppet/util/network_device/cisco/interface.rb | 82 | ||||
-rw-r--r-- | lib/puppet/util/network_device/ipcalc.rb | 68 | ||||
-rw-r--r-- | lib/puppet/util/network_device/transport.rb | 5 | ||||
-rw-r--r-- | lib/puppet/util/network_device/transport/base.rb | 26 | ||||
-rw-r--r-- | lib/puppet/util/network_device/transport/ssh.rb | 115 | ||||
-rw-r--r-- | lib/puppet/util/network_device/transport/telnet.rb | 42 |
18 files changed, 894 insertions, 0 deletions
diff --git a/lib/puppet/feature/ssh.rb b/lib/puppet/feature/ssh.rb new file mode 100644 index 000000000..82fe19882 --- /dev/null +++ b/lib/puppet/feature/ssh.rb @@ -0,0 +1,4 @@ +require 'puppet/util/feature' + +Puppet.features.rubygems? +Puppet.features.add(:ssh, :libs => %{net/ssh}) diff --git a/lib/puppet/provider/interface/base.rb b/lib/puppet/provider/interface/base.rb new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/lib/puppet/provider/interface/base.rb diff --git a/lib/puppet/provider/interface/cisco.rb b/lib/puppet/provider/interface/cisco.rb new file mode 100644 index 000000000..f3bd202e9 --- /dev/null +++ b/lib/puppet/provider/interface/cisco.rb @@ -0,0 +1,33 @@ +require 'puppet/util/network_device/cisco/device' +require 'puppet/provider/network_device' + +Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::NetworkDevice do + + desc "Cisco switch/router provider for interface." + + mk_resource_methods + + def self.lookup(url, name) + interface = nil + network_gear = Puppet::Util::NetworkDevice::Cisco::Device.new(url) + network_gear.command do |ng| + interface = network_gear.interface(name) + end + interface + end + + def initialize(*args) + super + end + + def flush + device.command do |device| + device.new_interface(name).update(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/provider/network_device.rb b/lib/puppet/provider/network_device.rb new file mode 100644 index 000000000..58865fddc --- /dev/null +++ b/lib/puppet/provider/network_device.rb @@ -0,0 +1,59 @@ + +# This is the base class of all prefetched network device provider +class Puppet::Provider::NetworkDevice < Puppet::Provider + + def self.lookup(url, name) + raise "This provider doesn't implement the necessary lookup method" + end + + def self.prefetch(resources) + resources.each do |name, resource| + if result = lookup(resource[:device_url], name) + result[:ensure] = :present + resource.provider = new(result) + else + resource.provider = new(:ensure => :absent) + end + end + end + + def exists? + @property_hash[:ensure] != :absent + end + + def initialize(*args) + super + + # Make a duplicate, so that we have a copy for comparison + # at the end. + @properties = @property_hash.dup + end + + def create + @property_hash[:ensure] = :present + self.class.resource_type.validproperties.each do |property| + if val = resource.should(property) + @property_hash[property] = val + end + end + end + + def destroy + @property_hash[:ensure] = :absent + end + + def flush + @property_hash.clear + end + + def self.instances + end + + def former_properties + @properties.dup + end + + def properties + @property_hash.dup + end +end
\ No newline at end of file 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/interface.rb b/lib/puppet/type/interface.rb new file mode 100644 index 000000000..7560a0552 --- /dev/null +++ b/lib/puppet/type/interface.rb @@ -0,0 +1,107 @@ +# +# Manages an interface on a given router or switch +# + +require 'puppet/util/network_device/ipcalc' + +Puppet::Type.newtype(:interface) do + + @doc = "This represents a router or switch interface. It is possible to manage + interface mode (access or trunking, native vlan and encapsulation), + switchport characteristics (speed, duplex)." + + ensurable do + defaultvalues + + aliasvalue :shutdown, :absent + aliasvalue :no_shutdown, :present + + defaultto { :no_shutdown } + end + + newparam(:name) do + desc "Interface name" + end + + newparam(:device_url) do + desc "Url to connect to a router or switch." + end + + newproperty(:description) do + desc "Interface description." + + defaultto { @resource[:name] } + end + + newproperty(:speed) do + desc "Interface speed." + newvalues(:auto, /^\d+/) + end + + newproperty(:duplex) do + desc "Interface duplex." + newvalues(:auto, :full, :half) + end + + newproperty(:native_vlan) do + desc "Interface native vlan (for access mode only)." + newvalues(/^\d+/) + end + + newproperty(:encapsulation) do + desc "Interface switchport encapsulation." + newvalues(:none, :dot1q, :isl ) + end + + newproperty(:mode) do + desc "Interface switchport mode." + newvalues(:access, :trunk) + end + + newproperty(:allowed_trunk_vlans) do + desc "Allowed list of Vlans that this trunk can forward." + newvalues(:all, /./) + end + + newproperty(:etherchannel) do + desc "Channel group this interface is part of." + newvalues(/^\d+/) + end + + newproperty(:ipaddress, :array_matching => :all) do + include Puppet::Util::NetworkDevice::IPCalc + + desc "IP Address of this interface (it might not be possible to set an interface IP address + it depends on the interface type and device type). + Valid format of ip addresses are: + * IPV4, like 127.0.0.1 + * IPV4/prefixlength like 127.0.1.1/24 + * IPV6/prefixlength like FE80::21A:2FFF:FE30:ECF0/128 + * an optional suffix for IPV6 addresses from this list: eui-64, link-local + It is also possible to use an array of values. + " + + validate do |values| + values = [values] unless values.is_a?(Array) + values.each do |value| + self.fail "Invalid interface ip address" unless parse(value.gsub(/\s*(eui-64|link-local)\s*$/,'')) + end + end + + munge do |value| + option = value =~ /eui-64|link-local/i ? value.gsub(/^.*?\s*(eui-64|link-local)\s*$/,'\1') : nil + [parse(value.gsub(/\s*(eui-64|link-local)\s*$/,'')), option].flatten + end + + def value_to_s(value) + value = [value] unless value.is_a?(Array) + value.map{ |v| "#{v[1].to_s}/#{v[0]} #{v[2]}"}.join(",") + end + + def change_to_s(currentvalue, newvalue) + currentvalue = value_to_s(currentvalue) if currentvalue != :absent + newvalue = value_to_s(newvalue) + super(currentvalue, newvalue) + end + end +end diff --git a/lib/puppet/type/router.rb b/lib/puppet/type/router.rb new file mode 100644 index 000000000..648389d39 --- /dev/null +++ b/lib/puppet/type/router.rb @@ -0,0 +1,14 @@ +# +# Manage a router abstraction +# + +module Puppet + newtype(:router) do + @doc = "Manages connected router." + + newparam(:url) do + desc "An URL to access the router of the form (ssh|telnet)://user:pass:enable@host/." + isnamevar + end + 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.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..1f350991a --- /dev/null +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -0,0 +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 = { :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") + 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/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 diff --git a/lib/puppet/util/network_device/ipcalc.rb b/lib/puppet/util/network_device/ipcalc.rb new file mode 100644 index 000000000..2b4f360b7 --- /dev/null +++ b/lib/puppet/util/network_device/ipcalc.rb @@ -0,0 +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
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport.rb b/lib/puppet/util/network_device/transport.rb new file mode 100644 index 000000000..e64fe9b69 --- /dev/null +++ b/lib/puppet/util/network_device/transport.rb @@ -0,0 +1,5 @@ +# stub +module Puppet::Util::NetworkDevice + module Transport + end +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport/base.rb b/lib/puppet/util/network_device/transport/base.rb new file mode 100644 index 000000000..1d62209cb --- /dev/null +++ b/lib/puppet/util/network_device/transport/base.rb @@ -0,0 +1,26 @@ + +require 'puppet/util/network_device' +require 'puppet/util/network_device/transport' + +class Puppet::Util::NetworkDevice::Transport::Base + attr_accessor :user, :password, :host, :port + attr_accessor :default_prompt, :timeout + + def initialize + @timeout = 10 + end + + def send(cmd) + end + + def expect(prompt) + end + + def command(cmd, options = {}) + send(cmd) + expect(options[:prompt] || default_prompt) do |output| + yield output if block_given? + end + end + +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport/ssh.rb b/lib/puppet/util/network_device/transport/ssh.rb new file mode 100644 index 000000000..b3cf51b8a --- /dev/null +++ b/lib/puppet/util/network_device/transport/ssh.rb @@ -0,0 +1,115 @@ + +require 'puppet/util/network_device' +require 'puppet/util/network_device/transport' +require 'puppet/util/network_device/transport/base' +require 'net/ssh' + +# 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 + + def initialize + super + 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" + 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
\ No newline at end of file diff --git a/lib/puppet/util/network_device/transport/telnet.rb b/lib/puppet/util/network_device/transport/telnet.rb new file mode 100644 index 000000000..e55079e06 --- /dev/null +++ b/lib/puppet/util/network_device/transport/telnet.rb @@ -0,0 +1,42 @@ +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 + end + + def handles_login? + false + end + + def connect + @telnet = Net::Telnet::new("Host" => host, "Port" => port || 23, + "Timeout" => 10, + "Prompt" => default_prompt, "Output_log" => "/tmp/out.log") + 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) + @telnet.puts(line) + end +end
\ No newline at end of file |