summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJames Turnbull <james@lovedthanlost.net>2011-04-12 05:15:00 +1000
committerJames Turnbull <james@lovedthanlost.net>2011-04-12 05:15:00 +1000
commitdce851cac79393f86950f4ebfc48b9ac67dcd8f7 (patch)
treecd2a4a92183b43eba985633f34de6557937b9e37 /lib
parent46d67fd86819d1dfe4813f22b192213985e3a587 (diff)
parent49dcc24195d262437cc35997a811eb724d65b48b (diff)
downloadpuppet-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.rb4
-rw-r--r--lib/puppet/provider/interface/base.rb0
-rw-r--r--lib/puppet/provider/interface/cisco.rb33
-rw-r--r--lib/puppet/provider/network_device.rb59
-rw-r--r--lib/puppet/provider/vlan/cisco.rb34
-rw-r--r--lib/puppet/type/interface.rb107
-rw-r--r--lib/puppet/type/router.rb14
-rw-r--r--lib/puppet/type/vlan.rb24
-rw-r--r--lib/puppet/util/network_device.rb2
-rw-r--r--lib/puppet/util/network_device/base.rb29
-rw-r--r--lib/puppet/util/network_device/cisco.rb4
-rw-r--r--lib/puppet/util/network_device/cisco/device.rb246
-rw-r--r--lib/puppet/util/network_device/cisco/interface.rb82
-rw-r--r--lib/puppet/util/network_device/ipcalc.rb68
-rw-r--r--lib/puppet/util/network_device/transport.rb5
-rw-r--r--lib/puppet/util/network_device/transport/base.rb26
-rw-r--r--lib/puppet/util/network_device/transport/ssh.rb115
-rw-r--r--lib/puppet/util/network_device/transport/telnet.rb42
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