diff options
author | Pieter van de Bruggen <pieter@puppetlabs.com> | 2011-04-18 13:40:31 -0700 |
---|---|---|
committer | Pieter van de Bruggen <pieter@puppetlabs.com> | 2011-04-18 14:08:32 -0700 |
commit | 07b677c5f6af8def03c5c30393fd83bc3986239a (patch) | |
tree | b28d3376b4ea835a2348ebfea898d674cb593e34 | |
parent | b142973a94ced6c0ff43da882189abe806c18c68 (diff) | |
download | puppet-07b677c5f6af8def03c5c30393fd83bc3986239a.tar.gz puppet-07b677c5f6af8def03c5c30393fd83bc3986239a.tar.xz puppet-07b677c5f6af8def03c5c30393fd83bc3986239a.zip |
Merge remote-tracking branch 'community/feature/puppet-device' into 2.7.x
Reviewed-By: Mike Stahnke
38 files changed, 1370 insertions, 210 deletions
diff --git a/lib/puppet/application/device.rb b/lib/puppet/application/device.rb new file mode 100644 index 000000000..df5bac26a --- /dev/null +++ b/lib/puppet/application/device.rb @@ -0,0 +1,255 @@ +require 'puppet/application' +require 'puppet/util/network_device' + + +class Puppet::Application::Device < Puppet::Application + + should_parse_config + run_mode :agent + + attr_accessor :args, :agent, :host + + def preinit + # Do an initial trap, so that cancels don't get a stack trace. + trap(:INT) do + $stderr.puts "Cancelling startup" + exit(0) + end + + { + :waitforcert => nil, + :detailed_exitcodes => false, + :verbose => false, + :debug => false, + :centrallogs => false, + :setdest => false, + }.each do |opt,val| + options[opt] = val + end + + @args = {} + end + + option("--centrallogging") + option("--debug","-d") + option("--verbose","-v") + + option("--detailed-exitcodes") do |arg| + options[:detailed_exitcodes] = true + end + + option("--logdest DEST", "-l DEST") do |arg| + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + puts detail.backtrace if Puppet[:debug] + $stderr.puts detail.to_s + end + end + + option("--waitforcert WAITFORCERT", "-w") do |arg| + options[:waitforcert] = arg.to_i + end + + option("--port PORT","-p") do |arg| + @args[:Port] = arg + end + + def help + <<-HELP + +puppet-device(8) -- Manage remote network devices +======== + +SYNOPSIS +-------- +Retrieves all configurations from the puppet master and apply +them to the remote devices configured in /etc/puppet/device.conf. + +Currently must be run out periodically, using cron or something similar. + +USAGE +----- + puppet device [-d|--debug] [--detailed-exitcodes] [-V|--version] + [-h|--help] [-l|--logdest syslog|<file>|console] + [-v|--verbose] [-w|--waitforcert <seconds>] + + +DESCRIPTION +----------- +Once the client has a signed certificate for a given remote device, it will +retrieve its configuration and apply it. + +USAGE NOTES +----------- +One need a /etc/puppet/device.conf file with the following content: + +[remote.device.fqdn] +type <type> +url <url> + +where: + * type: the current device type (the only value at this time is cisco) + * url: an url allowing to connect to the device + +Supported url must conforms to: + scheme://user:password@hostname/?query + + with: + * scheme: either ssh or telnet + * user: username, can be omitted depending on the switch/router configuration + * password: the connection password + * query: this is device specific. Cisco devices supports an enable parameter whose + value would be the enable password. + +OPTIONS +------- +Note that any configuration parameter that's valid in the configuration file +is also a valid long argument. For example, 'server' is a valid configuration +parameter, so you can specify '--server <servername>' as an argument. + +* --debug: + Enable full debugging. + +* --detailed-exitcodes: + Provide transaction information via exit codes. If this is enabled, an + exit code of '2' means there were changes, and an exit code of '4' means + that there were failures during the transaction. This option only makes + sense in conjunction with --onetime. + +* --help: + Print this help message + +* --logdest: + Where to send messages. Choose between syslog, the console, and a log file. + Defaults to sending messages to syslog, or the console if debugging or + verbosity is enabled. + +* --verbose: + Turn on verbose reporting. + +* --waitforcert: + This option only matters for daemons that do not yet have certificates + and it is enabled by default, with a value of 120 (seconds). This causes + +puppet agent+ to connect to the server every 2 minutes and ask it to sign a + certificate request. This is useful for the initial setup of a puppet + client. You can turn off waiting for certificates by specifying a time + of 0. + +EXAMPLE +------- + $ puppet device --server puppet.domain.com + +AUTHOR +------ +Brice Figureau + + +COPYRIGHT +--------- +Copyright (c) 2011 Puppet Labs, LLC +Licensed under the Apache 2.0 License + HELP + end + + + def main + vardir = Puppet[:vardir] + confdir = Puppet[:confdir] + certname = Puppet[:certname] + + # find device list + require 'puppet/util/network_device/config' + devices = Puppet::Util::NetworkDevice::Config.devices + if devices.empty? + Puppet.err "No device found in #{Puppet[:deviceconfig]}" + exit(1) + end + devices.each_value do |device| + begin + Puppet.info "starting applying configuration to #{device.name} at #{device.url}" + + # override local $vardir and $certname + Puppet.settings.set_value(:confdir, File.join(Puppet[:devicedir], device.name), :cli) + Puppet.settings.set_value(:vardir, File.join(Puppet[:devicedir], device.name), :cli) + Puppet.settings.set_value(:certname, device.name, :cli) + + # this will reload and recompute default settings and create the devices sub vardir, or we hope so :-) + Puppet.settings.use :main, :agent, :ssl + + # this init the device singleton, so that the facts terminus + # and the various network_device provider can use it + Puppet::Util::NetworkDevice.init(device) + + # ask for a ssl cert if needed, but at least + # setup the ssl system for this device. + setup_host + + require 'puppet/configurer' + configurer = Puppet::Configurer.new + report = configurer.run(:network_device => true) + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err detail.to_s + ensure + Puppet.settings.set_value(:vardir, vardir, :cli) + Puppet.settings.set_value(:confdir, confdir, :cli) + Puppet.settings.set_value(:certname, certname, :cli) + end + end + end + + # Handle the logging settings. + def setup_logs + if options[:debug] or options[:verbose] + Puppet::Util::Log.newdestination(:console) + if options[:debug] + Puppet::Util::Log.level = :debug + else + Puppet::Util::Log.level = :info + end + end + + Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] + end + + def setup_host + @host = Puppet::SSL::Host.new + waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) + cert = @host.wait_for_cert(waitforcert) + end + + def setup + setup_logs + + args[:Server] = Puppet[:server] + if options[:centrallogs] + logdest = args[:Server] + + logdest += ":" + args[:Port] if args.include?(:Port) + Puppet::Util::Log.newdestination(logdest) + end + + Puppet.settings.use :main, :agent, :device, :ssl + + # Always ignoreimport for agent. It really shouldn't even try to import, + # but this is just a temporary band-aid. + Puppet[:ignoreimport] = true + + # We need to specify a ca location for all of the SSL-related i + # indirected classes to work; in fingerprint mode we just need + # access to the local files and we don't need a ca. + Puppet::SSL::Host.ca_location = :remote + + Puppet::Transaction::Report.indirection.terminus_class = :rest + + # Override the default; puppetd needs this, usually. + # You can still override this on the command-line with, e.g., :compiler. + Puppet[:catalog_terminus] = :rest + + Puppet[:facts_terminus] = :network_device + + Puppet::Resource::Catalog.indirection.cache_class = :yaml + end +end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 680762b94..dbd5a9437 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -487,6 +487,11 @@ module Puppet This should match how often the hosts report back to the server."] ) + setdefaults(:device, + :devicedir => {:default => "$vardir/devices", :mode => "750", :desc => "The root directory of devices' $vardir"}, + :deviceconfig => ["$confdir/device.conf","Path to the device config file for puppet device"] + ) + setdefaults(:agent, :localconfig => { :default => "$statedir/localconfig", :owner => "root", diff --git a/lib/puppet/indirector/facts/network_device.rb b/lib/puppet/indirector/facts/network_device.rb new file mode 100644 index 000000000..c9bac803e --- /dev/null +++ b/lib/puppet/indirector/facts/network_device.rb @@ -0,0 +1,25 @@ +require 'puppet/node/facts' +require 'puppet/indirector/code' + +class Puppet::Node::Facts::NetworkDevice < Puppet::Indirector::Code + desc "Retrieve facts from a network device." + + # Look a device's facts up through the current device. + def find(request) + result = Puppet::Node::Facts.new(request.key, Puppet::Util::NetworkDevice.current.facts) + + result.add_local_facts + result.stringify + result.downcase_if_necessary + + result + end + + def destroy(facts) + raise Puppet::DevError, "You cannot destroy facts in the code store; it is only used for getting facts from a remote device" + end + + def save(facts) + raise Puppet::DevError, "You cannot save facts to the code store; it is only used for getting facts from a remote device" + end +end
\ No newline at end of file diff --git a/lib/puppet/provider/cisco.rb b/lib/puppet/provider/cisco.rb new file mode 100644 index 000000000..918982f59 --- /dev/null +++ b/lib/puppet/provider/cisco.rb @@ -0,0 +1,9 @@ +require 'puppet/util/network_device/cisco/device' +require 'puppet/provider/network_device' + +# This is the base class of all prefetched cisco device providers +class Puppet::Provider::Cisco < Puppet::Provider::NetworkDevice + def self.device(url) + Puppet::Util::NetworkDevice::Cisco::Device.new(url) + end +end diff --git a/lib/puppet/provider/interface/cisco.rb b/lib/puppet/provider/interface/cisco.rb index f3bd202e9..795a7f1ac 100644 --- a/lib/puppet/provider/interface/cisco.rb +++ b/lib/puppet/provider/interface/cisco.rb @@ -1,22 +1,20 @@ -require 'puppet/util/network_device/cisco/device' -require 'puppet/provider/network_device' +require 'puppet/provider/cisco' -Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::NetworkDevice do +Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for interface." mk_resource_methods - def self.lookup(url, name) + def self.lookup(device, name) interface = nil - network_gear = Puppet::Util::NetworkDevice::Cisco::Device.new(url) - network_gear.command do |ng| - interface = network_gear.interface(name) + device.command do |ng| + interface = device.interface(name) end interface end - def initialize(*args) + def initialize(device, *args) super end @@ -26,8 +24,4 @@ Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Netwo 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 index 58865fddc..b178df977 100644 --- a/lib/puppet/provider/network_device.rb +++ b/lib/puppet/provider/network_device.rb @@ -2,17 +2,22 @@ # This is the base class of all prefetched network device provider class Puppet::Provider::NetworkDevice < Puppet::Provider - def self.lookup(url, name) + def self.device(url) + raise "This provider doesn't implement the necessary device method" + end + + def self.lookup(device, 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) + device = Puppet::Util::NetworkDevice.current || device(resource[:device_url]) + if result = lookup(device, name) result[:ensure] = :present - resource.provider = new(result) + resource.provider = new(device, result) else - resource.provider = new(:ensure => :absent) + resource.provider = new(device, :ensure => :absent) end end end @@ -21,8 +26,12 @@ class Puppet::Provider::NetworkDevice < Puppet::Provider @property_hash[:ensure] != :absent end - def initialize(*args) - super + attr_accessor :device + + def initialize(device, *args) + super(*args) + + @device = device # Make a duplicate, so that we have a copy for comparison # at the end. diff --git a/lib/puppet/provider/vlan/cisco.rb b/lib/puppet/provider/vlan/cisco.rb index 46e172c73..3421d35b0 100644 --- a/lib/puppet/provider/vlan/cisco.rb +++ b/lib/puppet/provider/vlan/cisco.rb @@ -1,22 +1,20 @@ -require 'puppet/util/network_device/cisco/device' -require 'puppet/provider/network_device' +require 'puppet/provider/cisco' -Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::NetworkDevice do +Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for vlans." mk_resource_methods - def self.lookup(url, id) + def self.lookup(device, 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) + def initialize(device, *args) super end @@ -27,8 +25,4 @@ Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::NetworkDev end super end - - def device - @device ||= Puppet::Util::NetworkDevice::Cisco::Device.new(resource[:device_url]) - end end diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb index 98c29657e..a6cff9bdc 100644 --- a/lib/puppet/resource/catalog.rb +++ b/lib/puppet/resource/catalog.rb @@ -133,6 +133,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph transaction.report = options[:report] if options[:report] transaction.tags = options[:tags] if options[:tags] transaction.ignoreschedules = true if options[:ignoreschedules] + transaction.for_network_device = options[:network_device] transaction.add_times :config_retrieval => self.retrieval_duration || 0 diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index d7845fbc9..3728a2fff 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -12,7 +12,7 @@ class Puppet::Transaction require 'puppet/transaction/resource_harness' require 'puppet/resource/status' - attr_accessor :component, :catalog, :ignoreschedules + attr_accessor :component, :catalog, :ignoreschedules, :for_network_device attr_accessor :configurator # The report, once generated. @@ -339,6 +339,8 @@ class Puppet::Transaction resource.warning "Skipping because of failed dependencies" elsif resource.virtual? resource.debug "Skipping because virtual" + elsif resource.appliable_to_device? ^ for_network_device + resource.debug "Skipping #{resource.appliable_to_device? ? 'device' : 'host'} resources because running on a #{for_network_device ? 'device' : 'host'}" else return false end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index c0e5d390b..656b8f264 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -116,6 +116,26 @@ class Type ens end + def self.apply_to_device + @apply_to = :device + end + + def self.apply_to_host + @apply_to = :host + end + + def self.apply_to_all + @apply_to = :both + end + + def self.apply_to + @apply_to ||= :host + end + + def self.can_apply_to(target) + [ target == :device ? :device : :host, :both ].include?(apply_to) + end + # Deal with any options passed into parameters. def self.handle_param_options(name, options) # If it's a boolean parameter, create a method to test the value easily @@ -1895,6 +1915,14 @@ class Type def virtual?; !!@virtual; end def exported?; !!@exported; end + + def appliable_to_device? + self.class.can_apply_to(:device) + end + + def appliable_to_host? + self.class.can_apply_to(:host) + end end end diff --git a/lib/puppet/type/interface.rb b/lib/puppet/type/interface.rb index 7560a0552..d3b5cb06f 100644 --- a/lib/puppet/type/interface.rb +++ b/lib/puppet/type/interface.rb @@ -10,6 +10,8 @@ Puppet::Type.newtype(:interface) do interface mode (access or trunking, native vlan and encapsulation), switchport characteristics (speed, duplex)." + apply_to_device + ensurable do defaultvalues diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb index 5fb008f6f..f60f96fd2 100755 --- a/lib/puppet/type/schedule.rb +++ b/lib/puppet/type/schedule.rb @@ -43,6 +43,8 @@ module Puppet This will cause resources to be applied every 30 minutes by default. " + apply_to_all + newparam(:name) do desc "The name of the schedule. This name is used to retrieve the schedule when assigning it to an object: diff --git a/lib/puppet/type/vlan.rb b/lib/puppet/type/vlan.rb index 6708ea4f5..e39daf994 100644 --- a/lib/puppet/type/vlan.rb +++ b/lib/puppet/type/vlan.rb @@ -5,6 +5,8 @@ Puppet::Type.newtype(:vlan) do @doc = "This represents a router or switch vlan." + apply_to_device + ensurable newparam(:name) do diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index a884b8658..714d03f74 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -14,7 +14,8 @@ module Puppet 'queue' => 'puppetqd', 'resource' => 'ralsh', 'kick' => 'puppetrun', - 'master' => 'puppetmasterd' + 'master' => 'puppetmasterd', + 'device' => 'puppetdevice' ) def initialize(zero = $0, argv = ARGV, stdin = STDIN) diff --git a/lib/puppet/util/network_device.rb b/lib/puppet/util/network_device.rb index bca66016b..d9c1aa34d 100644 --- a/lib/puppet/util/network_device.rb +++ b/lib/puppet/util/network_device.rb @@ -1,2 +1,12 @@ -module Puppet::Util::NetworkDevice +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) + rescue => detail + raise "Can't load #{device.provider} for #{device.name}: #{detail}" + end 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 index ff96c8693..7d6c3fc44 100644 --- a/lib/puppet/util/network_device/base.rb +++ b/lib/puppet/util/network_device/base.rb @@ -3,27 +3,25 @@ require 'uri' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' -module Puppet::Util::NetworkDevice - class Base +class Puppet::Util::NetworkDevice::Base - attr_accessor :url, :transport + attr_accessor :url, :transport - def initialize(url) - @url = URI.parse(url) + def initialize(url) + @url = URI.parse(url) - @autoloader = Puppet::Util::Autoload.new( - self, - "puppet/util/network_device/transport", - :wrap => false - ) + @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 + 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
\ No newline at end of file diff --git a/lib/puppet/util/network_device/cisco/device.rb b/lib/puppet/util/network_device/cisco/device.rb index 1f350991a..005470e13 100644 --- a/lib/puppet/util/network_device/cisco/device.rb +++ b/lib/puppet/util/network_device/cisco/device.rb @@ -3,6 +3,7 @@ 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 @@ -91,6 +92,15 @@ class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice:: 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) diff --git a/lib/puppet/util/network_device/cisco/facts.rb b/lib/puppet/util/network_device/cisco/facts.rb new file mode 100644 index 000000000..40e2b37c2 --- /dev/null +++ b/lib/puppet/util/network_device/cisco/facts.rb @@ -0,0 +1,72 @@ + +require 'puppet/util/network_device/cisco' +require 'puppet/util/network_device/ipcalc' + +# this retrieves facts from a cisco device +class Puppet::Util::NetworkDevice::Cisco::Facts + + attr_reader :transport + + def initialize(transport) + @transport = transport + end + + def retrieve + facts = {} + facts.merge(parse_show_ver) + end + + def parse_show_ver + facts = {} + out = @transport.command("sh ver") + lines = out.split("\n") + lines.shift; lines.pop + lines.each do |l| + case l + # cisco WS-C2924C-XL (PowerPC403GA) processor (revision 0x11) with 8192K/1024K bytes of memory. + # Cisco 1841 (revision 5.0) with 355328K/37888K bytes of memory. + # Cisco 877 (MPC8272) processor (revision 0x200) with 118784K/12288K bytes of memory. + # cisco WS-C2960G-48TC-L (PowerPC405) processor (revision C0) with 61440K/4088K bytes of memory. + # cisco WS-C2950T-24 (RC32300) processor (revision R0) with 19959K bytes of memory. + when /[cC]isco ([\w-]+) (?:\(([\w-]+)\) processor )?\(revision (.+)\) with (\d+[KMG])(?:\/(\d+[KMG]))? bytes of memory\./ + facts[:hardwaremodel] = $1 + facts[:processor] = $2 if $2 + facts[:hardwarerevision] = $3 + facts[:memorysize] = $4 + # uptime + # Switch uptime is 1 year, 12 weeks, 6 days, 22 hours, 32 minutes + # c2950 uptime is 3 weeks, 1 day, 23 hours, 36 minutes + # c2960 uptime is 2 years, 27 weeks, 5 days, 21 hours, 30 minutes + # router uptime is 5 weeks, 1 day, 3 hours, 30 minutes + when /^\s*([\w-]+)\s+uptime is (.*?)$/ + facts[:hostname] = $1 + facts[:uptime] = $2 + facts[:uptime_seconds] = uptime_to_seconds($2) + facts[:uptime_days] = facts[:uptime_seconds] / 86400 + # "IOS (tm) C2900XL Software (C2900XL-C3H2S-M), Version 12.0(5)WC10, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.0(5)WC10", :operatingsystemmajrelease => "12.0", :operatingsystemfeature => "C3H2S"}, + # "IOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA8a, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.1(22)EA8a", :operatingsystemmajrelease => "12.1", :operatingsystemfeature => "I6K2L2Q4"}, + # "Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(44)SE, RELEASE SOFTWARE (fc1)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.2(44)SE", :operatingsystemmajrelease => "12.2", :operatingsystemfeature => "LANBASEK9"}, + # "Cisco IOS Software, C870 Software (C870-ADVIPSERVICESK9-M), Version 12.4(11)XJ4, RELEASE SOFTWARE (fc2)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(11)XJ40", :operatingsystemmajrelease => "12.4XJ", :operatingsystemfeature => "ADVIPSERVICESK9"}, + # "Cisco IOS Software, 1841 Software (C1841-ADVSECURITYK9-M), Version 12.4(24)T4, RELEASE SOFTWARE (fc2)" =>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(24)T4", :operatingsystemmajrelease => "12.4T", :operatingsystemfeature => "ADVSECURITYK9"}, + when /(?:Cisco )?(IOS)\s*(?:\(tm\) |Software, )?(?:\w+)\s+Software\s+\(\w+-(\w+)-\w+\), Version ([0-9.()A-Za-z]+),/ + facts[:operatingsystem] = $1 + facts[:operatingsystemrelease] = $3 + facts[:operatingsystemmajrelease] = ios_major_version(facts[:operatingsystemrelease]) + facts[:operatingsystemfeature] = $2 + end + end + facts + end + + def ios_major_version(version) + version.gsub(/^(\d+)\.(\d+)\(.+\)([A-Z]+)([\da-z]+)?/, '\1.\2\3') + end + + def uptime_to_seconds(uptime) + captures = (uptime.match /^(?:(?:(?:(?:(\d+) years?,)?\s*(\d+) weeks?,)?\s*(\d+) days?,)?\s*(\d+) hours?,)?\s*(\d+) minutes?$/).captures + seconds = captures.zip([31536000, 604800, 86400, 3600, 60]).inject(0) do |total, (x,y)| + total + (x.nil? ? 0 : x.to_i * y) + end + end + +end
\ No newline at end of file diff --git a/lib/puppet/util/network_device/config.rb b/lib/puppet/util/network_device/config.rb new file mode 100644 index 000000000..17f4e254c --- /dev/null +++ b/lib/puppet/util/network_device/config.rb @@ -0,0 +1,93 @@ +require 'ostruct' +require 'puppet/util/loadedfile' + +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 ConfigurationError, "Duplicate device found at line #{count}, already found at #{device.line}" if devices.include?(name) + device = OpenStruct.new + device.name = name + device.line = count + Puppet.debug "found device: #{device.name} at #{device.line}" + devices[name] = device + when /^\s*(type|url)\s+(.+)$/ + parse_directive(device, $1, $2, count) + else + raise ConfigurationError, "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 + else + raise ConfigurationError, + "Invalid argument '#{var}' at line #{count}" + end + 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 index e64fe9b69..cef8f3859 100644 --- a/lib/puppet/util/network_device/transport.rb +++ b/lib/puppet/util/network_device/transport.rb @@ -1,5 +1,3 @@ # stub -module Puppet::Util::NetworkDevice - module Transport - end +module Puppet::Util::NetworkDevice::Transport 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 index b3cf51b8a..bf0e7193c 100644 --- a/lib/puppet/util/network_device/transport/ssh.rb +++ b/lib/puppet/util/network_device/transport/ssh.rb @@ -31,6 +31,10 @@ class Puppet::Util::NetworkDevice::Transport::Ssh < Puppet::Util::NetworkDevice: @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 = "" diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index 41a1ebac3..78d62fc51 100755 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -75,6 +75,62 @@ describe Puppet::Transaction do transaction.evaluate end + it "should not apply device resources on normal host" do + catalog = Puppet::Resource::Catalog.new + resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" + catalog.add_resource resource + + transaction = Puppet::Transaction.new(catalog) + transaction.for_network_device = false + + transaction.expects(:apply).never.with(resource, nil) + + transaction.evaluate + transaction.resource_status(resource).should be_skipped + end + + it "should not apply host resources on device" do + catalog = Puppet::Resource::Catalog.new + resource = Puppet::Type.type(:file).new :path => "/foo/bar", :backup => false + catalog.add_resource resource + + transaction = Puppet::Transaction.new(catalog) + transaction.for_network_device = true + + transaction.expects(:apply).never.with(resource, nil) + + transaction.evaluate + transaction.resource_status(resource).should be_skipped + end + + it "should apply device resources on device" do + catalog = Puppet::Resource::Catalog.new + resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" + catalog.add_resource resource + + transaction = Puppet::Transaction.new(catalog) + transaction.for_network_device = true + + transaction.expects(:apply).with(resource, nil) + + transaction.evaluate + transaction.resource_status(resource).should_not be_skipped + end + + it "should apply resources appliable on host and device on a device" do + catalog = Puppet::Resource::Catalog.new + resource = Puppet::Type.type(:schedule).new :name => "test" + catalog.add_resource resource + + transaction = Puppet::Transaction.new(catalog) + transaction.for_network_device = true + + transaction.expects(:apply).with(resource, nil) + + transaction.evaluate + transaction.resource_status(resource).should_not be_skipped + end + # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. it "should propagate events from a contained resource through its container to its dependent container's contained resources" do diff --git a/spec/unit/application/device_spec.rb b/spec/unit/application/device_spec.rb new file mode 100644 index 000000000..c3c220918 --- /dev/null +++ b/spec/unit/application/device_spec.rb @@ -0,0 +1,349 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/application/device' +require 'puppet/util/network_device/config' +require 'ostruct' +require 'puppet/configurer' + +describe Puppet::Application::Device do + before :each do + @device = Puppet::Application[:device] +# @device.stubs(:puts) + @device.preinit + Puppet::Util::Log.stubs(:newdestination) + Puppet::Util::Log.stubs(:level=) + + Puppet::Node.stubs(:terminus_class=) + Puppet::Node.stubs(:cache_class=) + Puppet::Node::Facts.stubs(:terminus_class=) + end + + it "should operate in agent run_mode" do + @device.class.run_mode.name.should == :agent + end + + it "should ask Puppet::Application to parse Puppet configuration file" do + @device.should_parse_config?.should be_true + end + + it "should declare a main command" do + @device.should respond_to(:main) + end + + it "should declare a preinit block" do + @device.should respond_to(:preinit) + end + + describe "in preinit" do + before :each do + @device.stubs(:trap) + end + + it "should catch INT" do + @device.expects(:trap).with { |arg,block| arg == :INT } + + @device.preinit + end + end + + describe "when handling options" do + before do + @device.command_line.stubs(:args).returns([]) + end + + [:centrallogging, :debug, :verbose,].each do |option| + it "should declare handle_#{option} method" do + @device.should respond_to("handle_#{option}".to_sym) + end + + it "should store argument value when calling handle_#{option}" do + @device.options.expects(:[]=).with(option, 'arg') + @device.send("handle_#{option}".to_sym, 'arg') + end + end + + it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do + Puppet[:onetime] = true + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0) + @device.setup_host + end + + it "should use supplied waitforcert when --onetime is specified" do + Puppet[:onetime] = true + @device.handle_waitforcert(60) + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60) + @device.setup_host + end + + it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do + Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120) + @device.setup_host + end + + it "should set the log destination with --logdest" do + @device.options.stubs(:[]=).with { |opt,val| opt == :setdest } + Puppet::Log.expects(:newdestination).with("console") + + @device.handle_logdest("console") + end + + it "should put the setdest options to true" do + @device.options.expects(:[]=).with(:setdest,true) + + @device.handle_logdest("console") + end + + it "should parse the log destination from the command line" do + @device.command_line.stubs(:args).returns(%w{--logdest /my/file}) + + Puppet::Util::Log.expects(:newdestination).with("/my/file") + + @device.parse_options + end + + it "should store the waitforcert options with --waitforcert" do + @device.options.expects(:[]=).with(:waitforcert,42) + + @device.handle_waitforcert("42") + end + + it "should set args[:Port] with --port" do + @device.handle_port("42") + @device.args[:Port].should == "42" + end + + end + + describe "during setup" do + before :each do + @device.options.stubs(:[]) + Puppet.stubs(:info) + FileTest.stubs(:exists?).returns(true) + Puppet[:libdir] = "/dev/null/lib" + Puppet::SSL::Host.stubs(:ca_location=) + Puppet::Transaction::Report.stubs(:terminus_class=) + Puppet::Resource::Catalog.stubs(:terminus_class=) + Puppet::Resource::Catalog.stubs(:cache_class=) + Puppet::Node::Facts.stubs(:terminus_class=) + @host = stub_everything 'host' + Puppet::SSL::Host.stubs(:new).returns(@host) + Puppet.stubs(:settraps) + end + + it "should call setup_logs" do + @device.expects(:setup_logs) + @device.setup + end + + describe "when setting up logs" do + before :each do + Puppet::Util::Log.stubs(:newdestination) + end + + it "should set log level to debug if --debug was passed" do + @device.options.stubs(:[]).with(:debug).returns(true) + + Puppet::Util::Log.expects(:level=).with(:debug) + + @device.setup_logs + end + + it "should set log level to info if --verbose was passed" do + @device.options.stubs(:[]).with(:verbose).returns(true) + + Puppet::Util::Log.expects(:level=).with(:info) + + @device.setup_logs + end + + [:verbose, :debug].each do |level| + it "should set console as the log destination with level #{level}" do + @device.options.stubs(:[]).with(level).returns(true) + + Puppet::Util::Log.expects(:newdestination).with(:console) + + @device.setup_logs + end + end + + it "should set syslog as the log destination if no --logdest" do + @device.options.stubs(:[]).with(:setdest).returns(false) + + Puppet::Util::Log.expects(:newdestination).with(:syslog) + + @device.setup_logs + end + + end + + it "should set a central log destination with --centrallogs" do + @device.options.stubs(:[]).with(:centrallogs).returns(true) + Puppet[:server] = "puppet.reductivelabs.com" + Puppet::Util::Log.stubs(:newdestination).with(:syslog) + + Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") + + @device.setup + end + + it "should use :main, :agent, :device and :ssl config" do + Puppet.settings.expects(:use).with(:main, :agent, :device, :ssl) + + @device.setup + end + + it "should install a remote ca location" do + Puppet::SSL::Host.expects(:ca_location=).with(:remote) + + @device.setup + end + + it "should tell the report handler to use REST" do + Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) + + @device.setup + end + + it "should change the catalog_terminus setting to 'rest'" do + Puppet[:catalog_terminus] = :foo + @device.setup + Puppet[:catalog_terminus].should == :rest + end + + it "should tell the catalog handler to use cache" do + Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) + + @device.setup + end + + it "should change the facts_terminus setting to 'network_device'" do + Puppet[:facts_terminus] = :foo + + @device.setup + Puppet[:facts_terminus].should == :network_device + end + end + + describe "when initializing each devices SSL" do + before(:each) do + @host = stub_everything 'host' + Puppet::SSL::Host.stubs(:new).returns(@host) + end + + it "should create a new ssl host" do + Puppet::SSL::Host.expects(:new).returns(@host) + @device.setup_host + end + + it "should wait for a certificate" do + @device.options.stubs(:[]).with(:waitforcert).returns(123) + @host.expects(:wait_for_cert).with(123) + + @device.setup_host + end + end + + + describe "when running" do + before :each do + @device.options.stubs(:[]).with(:fingerprint).returns(false) + Puppet.stubs(:notice) + @device.options.stubs(:[]).with(:client) + Puppet::Util::NetworkDevice::Config.stubs(:devices).returns({}) + end + + it "should dispatch to main" do + @device.stubs(:main) + @device.run_command + end + + it "should get the device list" do + device_hash = stub_everything 'device hash' + Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash) + @device.main + end + + it "should exit if the device list is empty" do + @device.expects(:exit).with(1) + @device.main + end + + describe "for each device" do + before(:each) do + Puppet[:vardir] = "/dummy" + Puppet[:confdir] = "/dummy" + Puppet[:certname] = "certname" + @device_hash = { + "device1" => OpenStruct.new(:name => "device1", :url => "url", :provider => "cisco"), + "device2" => OpenStruct.new(:name => "device2", :url => "url", :provider => "cisco"), + } + Puppet::Util::NetworkDevice::Config.stubs(:devices).returns(@device_hash) + Puppet.settings.stubs(:set_value) + Puppet.settings.stubs(:use) + @device.stubs(:setup_host) + Puppet::Util::NetworkDevice.stubs(:init) + @configurer = stub_everything 'configurer' + Puppet::Configurer.stubs(:new).returns(@configurer) + end + + it "should set vardir to the device vardir" do + Puppet.settings.expects(:set_value).with(:vardir, "/dummy/devices/device1", :cli) + @device.main + end + + it "should set confdir to the device confdir" do + Puppet.settings.expects(:set_value).with(:confdir, "/dummy/devices/device1", :cli) + @device.main + end + + it "should set certname to the device certname" do + Puppet.settings.expects(:set_value).with(:certname, "device1", :cli) + Puppet.settings.expects(:set_value).with(:certname, "device2", :cli) + @device.main + end + + it "should make sure all the required folders and files are created" do + Puppet.settings.expects(:use).with(:main, :agent, :ssl).twice + @device.main + end + + it "should initialize the device singleton" do + Puppet::Util::NetworkDevice.expects(:init).with(@device_hash["device1"]).then.with(@device_hash["device2"]) + @device.main + end + + it "should setup the SSL context" do + @device.expects(:setup_host).twice + @device.main + end + + it "should launch a configurer for this device" do + @configurer.expects(:run).twice + @device.main + end + + [:vardir, :confdir].each do |setting| + it "should cleanup the #{setting} setting after the run" do + configurer = states('configurer').starts_as('notrun') + Puppet.settings.expects(:set_value).with(setting, "/dummy/devices/device1", :cli).when(configurer.is('notrun')) + @configurer.expects(:run).twice.then(configurer.is('run')) + Puppet.settings.expects(:set_value).with(setting, "/dummy", :cli).when(configurer.is('run')) + + @device.main + end + end + + it "should cleanup the certname setting after the run" do + configurer = states('configurer').starts_as('notrun') + Puppet.settings.expects(:set_value).with(:certname, "device1", :cli).when(configurer.is('notrun')) + @configurer.expects(:run).twice.then(configurer.is('run')) + Puppet.settings.expects(:set_value).with(:certname, "certname", :cli).when(configurer.is('run')) + + @device.main + end + + end + end +end diff --git a/spec/unit/indirector/facts/network_device_spec.rb b/spec/unit/indirector/facts/network_device_spec.rb new file mode 100644 index 000000000..302a810e8 --- /dev/null +++ b/spec/unit/indirector/facts/network_device_spec.rb @@ -0,0 +1,89 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/facts/network_device' + +describe Puppet::Node::Facts::NetworkDevice do + it "should be a subclass of the Code terminus" do + Puppet::Node::Facts::NetworkDevice.superclass.should equal(Puppet::Indirector::Code) + end + + it "should have documentation" do + Puppet::Node::Facts::NetworkDevice.doc.should_not be_nil + end + + it "should be registered with the configuration store indirection" do + indirection = Puppet::Indirector::Indirection.instance(:facts) + Puppet::Node::Facts::NetworkDevice.indirection.should equal(indirection) + end + + it "should have its name set to :facter" do + Puppet::Node::Facts::NetworkDevice.name.should == :network_device + end +end + +describe Puppet::Node::Facts::NetworkDevice do + before :each do + @remote_device = stub 'remote_device', :facts => {} + Puppet::Util::NetworkDevice.stubs(:current).returns(@remote_device) + @device = Puppet::Node::Facts::NetworkDevice.new + @name = "me" + @request = stub 'request', :key => @name + end + + describe Puppet::Node::Facts::NetworkDevice, " when finding facts" do + it "should return a Facts instance" do + @device.find(@request).should be_instance_of(Puppet::Node::Facts) + end + + it "should return a Facts instance with the provided key as the name" do + @device.find(@request).name.should == @name + end + + it "should return the device facts as the values in the Facts instance" do + @remote_device.expects(:facts).returns("one" => "two") + facts = @device.find(@request) + facts.values["one"].should == "two" + end + + it "should add local facts" do + facts = Puppet::Node::Facts.new("foo") + Puppet::Node::Facts.expects(:new).returns facts + facts.expects(:add_local_facts) + + @device.find(@request) + end + + it "should convert all facts into strings" do + facts = Puppet::Node::Facts.new("foo") + Puppet::Node::Facts.expects(:new).returns facts + facts.expects(:stringify) + + @device.find(@request) + end + + it "should call the downcase hook" do + facts = Puppet::Node::Facts.new("foo") + Puppet::Node::Facts.expects(:new).returns facts + facts.expects(:downcase_if_necessary) + + @device.find(@request) + end + end + + describe Puppet::Node::Facts::NetworkDevice, " when saving facts" do + it "should fail" do + proc { @device.save(@facts) }.should raise_error(Puppet::DevError) + end + end + + describe Puppet::Node::Facts::NetworkDevice, " when destroying facts" do + it "should fail" do + proc { @device.destroy(@facts) }.should raise_error(Puppet::DevError) + end + end +end diff --git a/spec/unit/provider/cisco_spec.rb b/spec/unit/provider/cisco_spec.rb new file mode 100644 index 000000000..08320731c --- /dev/null +++ b/spec/unit/provider/cisco_spec.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/provider/cisco' + +describe Puppet::Provider::Cisco do + it "should implement a device class method" do + Puppet::Provider::Cisco.should respond_to(:device) + end + + it "should create a cisco device instance" do + Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).returns :device + Puppet::Provider::Cisco.device(:url).should == :device + end +end
\ No newline at end of file diff --git a/spec/unit/provider/interface/cisco_spec.rb b/spec/unit/provider/interface/cisco_spec.rb index d1f70609f..c18f87cf8 100755 --- a/spec/unit/provider/interface/cisco_spec.rb +++ b/spec/unit/provider/interface/cisco_spec.rb @@ -8,12 +8,13 @@ provider_class = Puppet::Type.type(:interface).provider(:cisco) describe provider_class do before do + @device = stub_everything 'device' @resource = stub("resource", :name => "Fa0/1") - @provider = provider_class.new(@resource) + @provider = provider_class.new(@device, @resource) end - it "should have a parent of Puppet::Provider::NetworkDevice" do - provider_class.should < Puppet::Provider::NetworkDevice + it "should have a parent of Puppet::Provider::Cisco" do + provider_class.should < Puppet::Provider::Cisco end it "should have an instances method" do @@ -22,31 +23,24 @@ describe provider_class do describe "when looking up instances at prefetch" do before do - @device = stub_everything 'device' - Puppet::Util::NetworkDevice::Cisco::Device.stubs(:new).returns(@device) @device.stubs(:command).yields(@device) end - it "should initialize the network device with the given url" do - Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).with(:url).returns(@device) - provider_class.lookup(:url, "Fa0/1") - end - it "should delegate to the device interface fetcher" do @device.expects(:interface) - provider_class.lookup("", "Fa0/1") + provider_class.lookup(@device, "Fa0/1") end it "should return the given interface data" do @device.expects(:interface).returns({ :description => "thisone", :mode => :access}) - provider_class.lookup("", "Fa0").should == {:description => "thisone", :mode => :access } + provider_class.lookup(@device, "Fa0").should == {:description => "thisone", :mode => :access } end end describe "when an instance is being flushed" do it "should call the device interface update method with current and past properties" do - @instance = provider_class.new(:ensure => :present, :name => "Fa0/1", :description => "myinterface") + @instance = provider_class.new(@device, :ensure => :present, :name => "Fa0/1", :description => "myinterface") @instance.description = "newdesc" @instance.resource = @resource @resource.stubs(:[]).with(:name).returns("Fa0/1") diff --git a/spec/unit/provider/network_device_spec.rb b/spec/unit/provider/network_device_spec.rb index 83d2bdc01..e2a87cf4e 100755 --- a/spec/unit/provider/network_device_spec.rb +++ b/spec/unit/provider/network_device_spec.rb @@ -3,10 +3,15 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/provider/network_device' +require 'ostruct' Puppet::Type.type(:vlan).provide :test, :parent => Puppet::Provider::NetworkDevice do mk_resource_methods - def self.lookup(device_url, name) + def self.lookup(device, name) + end + + def self.device(url) + :device end end @@ -34,7 +39,7 @@ describe provider_class do end it "should lookup an entry for each passed resource" do - provider_class.expects(:lookup).with(nil, "200").returns nil + provider_class.expects(:lookup).with{ |device,value| value == "200" }.returns nil provider_class.stubs(:new) @resource.stubs(:provider=) @@ -44,7 +49,7 @@ describe provider_class do describe "resources that do not exist" do it "should create a provider with :ensure => :absent" do provider_class.stubs(:lookup).returns(nil) - provider_class.expects(:new).with(:ensure => :absent).returns "myprovider" + provider_class.expects(:new).with(:device, :ensure => :absent).returns "myprovider" @resource.expects(:provider=).with("myprovider") provider_class.prefetch(@resources) end @@ -54,7 +59,7 @@ describe provider_class do it "should create a provider with the results of the find and ensure at present" do provider_class.stubs(:lookup).returns({ :name => "200", :description => "myvlan"}) - provider_class.expects(:new).with(:name => "200", :description => "myvlan", :ensure => :present).returns "myprovider" + provider_class.expects(:new).with(:device, :name => "200", :description => "myvlan", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") provider_class.prefetch(@resources) @@ -74,7 +79,7 @@ describe provider_class do end it "should store a copy of the hash as its vlan_properties" do - instance = provider_class.new(:one => :two) + instance = provider_class.new(:device, :one => :two) instance.former_properties.should == {:one => :two} end end @@ -82,7 +87,7 @@ describe provider_class do describe "when an instance" do before do - @instance = provider_class.new + @instance = provider_class.new(:device) @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:description] @@ -98,12 +103,12 @@ describe provider_class do end it "should indicate when the instance already exists" do - @instance = provider_class.new(:ensure => :present) + @instance = provider_class.new(:device, :ensure => :present) @instance.exists?.should be_true end it "should indicate when the instance does not exist" do - @instance = provider_class.new(:ensure => :absent) + @instance = provider_class.new(:device, :ensure => :absent) @instance.exists?.should be_false end diff --git a/spec/unit/provider/vlan/cisco_spec.rb b/spec/unit/provider/vlan/cisco_spec.rb index bb243a75e..a67290eb4 100755 --- a/spec/unit/provider/vlan/cisco_spec.rb +++ b/spec/unit/provider/vlan/cisco_spec.rb @@ -8,12 +8,13 @@ provider_class = Puppet::Type.type(:vlan).provider(:cisco) describe provider_class do before do + @device = stub_everything 'device' @resource = stub("resource", :name => "200") - @provider = provider_class.new(@resource) + @provider = provider_class.new(@device, @resource) end - it "should have a parent of Puppet::Provider::NetworkDevice" do - provider_class.should < Puppet::Provider::NetworkDevice + it "should have a parent of Puppet::Provider::Cisco" do + provider_class.should < Puppet::Provider::Cisco end it "should have an instances method" do @@ -22,31 +23,24 @@ describe provider_class do describe "when looking up instances at prefetch" do before do - @device = stub_everything 'device' - Puppet::Util::NetworkDevice::Cisco::Device.stubs(:new).returns(@device) @device.stubs(:command).yields(@device) end - it "should initialize the network device with the given url" do - Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).with(:url).returns(@device) - provider_class.lookup(:url, "200") - end - it "should delegate to the device vlans" do @device.expects(:parse_vlans) - provider_class.lookup("", "200") + provider_class.lookup(@device, "200") end it "should return only the given vlan" do @device.expects(:parse_vlans).returns({"200" => { :description => "thisone" }, "1" => { :description => "nothisone" }}) - provider_class.lookup("", "200").should == {:description => "thisone" } + provider_class.lookup(@device, "200").should == {:description => "thisone" } end end describe "when an instance is being flushed" do it "should call the device update_vlan method with its vlan id, current attributes, and desired attributes" do - @instance = provider_class.new(:ensure => :present, :name => "200", :description => "myvlan") + @instance = provider_class.new(@device, :ensure => :present, :name => "200", :description => "myvlan") @instance.description = "myvlan2" @instance.resource = @resource @resource.stubs(:[]).with(:name).returns("200") diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb index ae65aa91a..ebf523eab 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -592,6 +592,7 @@ describe Puppet::Resource::Catalog, "when compiling" do Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:add_times) + @transaction.stubs(:for_network_device=) Puppet.settings.stubs(:use) end diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index 4157e58ac..d7788c06c 100755 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -270,6 +270,24 @@ describe Puppet::Transaction do @resource.stubs(:virtual?).returns true @transaction.should be_skip(@resource) end + + it "should skip device only resouce on normal host" do + @resource.stubs(:appliable_to_device?).returns true + @transaction.for_network_device = false + @transaction.should be_skip(@resource) + end + + it "should not skip device only resouce on remote device" do + @resource.stubs(:appliable_to_device?).returns true + @transaction.for_network_device = true + @transaction.should_not be_skip(@resource) + end + + it "should skip host resouce on device" do + @resource.stubs(:appliable_to_device?).returns false + @transaction.for_network_device = true + @transaction.should be_skip(@resource) + end end describe "when determining if tags are missing" do diff --git a/spec/unit/type/interface_spec.rb b/spec/unit/type/interface_spec.rb index 68f4c765f..12ba225d9 100755 --- a/spec/unit/type/interface_spec.rb +++ b/spec/unit/type/interface_spec.rb @@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:interface) do + it "should have a 'name' parameter'" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1")[:name].should == "FastEthernet 0/1" end @@ -15,6 +16,10 @@ describe Puppet::Type.type(:interface) do Puppet::Type.type(:interface).attrtype(:ensure).should == :property end + it "should be applied on device" do + Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1").should be_appliable_to_device + end + [:description, :speed, :duplex, :native_vlan, :encapsulation, :mode, :allowed_trunk_vlans, :etherchannel, :ipaddress].each do |p| it "should have a #{p} property" do Puppet::Type.type(:interface).attrtype(p).should == :property diff --git a/spec/unit/type/schedule_spec.rb b/spec/unit/type/schedule_spec.rb index 7599411e4..33064ca6f 100755 --- a/spec/unit/type/schedule_spec.rb +++ b/spec/unit/type/schedule_spec.rb @@ -36,6 +36,14 @@ describe Puppet::Type.type(:schedule) do describe Puppet::Type.type(:schedule) do include ScheduleTesting + it "should apply to device" do + @schedule.should be_appliable_to_device + end + + it "should apply to host" do + @schedule.should be_appliable_to_host + end + it "should default to :distance for period-matching" do @schedule[:periodmatch].should == :distance end diff --git a/spec/unit/type/vlan_spec.rb b/spec/unit/type/vlan_spec.rb index 2983a58e9..3bee14bbd 100755 --- a/spec/unit/type/vlan_spec.rb +++ b/spec/unit/type/vlan_spec.rb @@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:vlan) do + it "should have a 'name' parameter'" do Puppet::Type.type(:vlan).new(:name => "200")[:name].should == "200" end @@ -11,6 +12,10 @@ describe Puppet::Type.type(:vlan) do Puppet::Type.type(:vlan).new(:name => "200", :device_url => :device)[:device_url].should == :device end + it "should be applied on device" do + Puppet::Type.type(:vlan).new(:name => "200").should be_appliable_to_device + end + it "should have an ensure property" do Puppet::Type.type(:vlan).attrtype(:ensure).should == :property end diff --git a/spec/unit/util/network_device/cisco/device_spec.rb b/spec/unit/util/network_device/cisco/device_spec.rb index 82b0666e6..33242a1ab 100755 --- a/spec/unit/util/network_device/cisco/device_spec.rb +++ b/spec/unit/util/network_device/cisco/device_spec.rb @@ -392,130 +392,17 @@ eos @cisco.parse_interface_config("Gi0/17").should == {:etherchannel=>"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 -# static access -# Switch#sh interfaces FastEthernet 0/1 switchport -# Name: Fa0/1 -# Switchport: Enabled -# Administrative mode: static access -# Operational Mode: static access -# Administrative Trunking Encapsulation: isl -# Operational Trunking Encapsulation: isl -# Negotiation of Trunking: Disabled -# Access Mode VLAN: 100 (SHDSL) -# Trunking Native Mode VLAN: 1 (default) -# Trunking VLANs Enabled: NONE -# Pruning VLANs Enabled: NONE -# -# Priority for untagged frames: 0 -# Override vlan tag priority: FALSE -# Voice VLAN: none -# Appliance trust: none -# Self Loopback: No -# Switch# - -# c2960#sh interfaces GigabitEthernet 0/1 switchport -# Name: Gi0/1 -# Switchport: Enabled -# Administrative Mode: trunk -# Operational Mode: trunk -# Administrative Trunking Encapsulation: dot1q -# Operational Trunking Encapsulation: dot1q -# Negotiation of Trunking: On -# Access Mode VLAN: 1 (default) -# Trunking Native Mode VLAN: 1 (default) -# Administrative Native VLAN tagging: enabled -# Voice VLAN: none -# Administrative private-vlan host-association: none -# Administrative private-vlan mapping: none -# Administrative private-vlan trunk native VLAN: none -# Administrative private-vlan trunk Native VLAN tagging: enabled -# Administrative private-vlan trunk encapsulation: dot1q -# Administrative private-vlan trunk normal VLANs: none -# Administrative private-vlan trunk associations: none -# Administrative private-vlan trunk mappings: none -# Operational private-vlan: none -# Trunking VLANs Enabled: 1,99 -# Pruning VLANs Enabled: 2-1001 -# Capture Mode Disabled -# Capture VLANs Allowed: ALL -# -# Protected: false -# Unknown unicast blocked: disabled -# Unknown multicast blocked: disabled -# Appliance trust: none -# c2960# - -# c2960#sh interfaces GigabitEthernet 0/2 switchport -# Name: Gi0/2 -# Switchport: Enabled -# Administrative Mode: static access -# Operational Mode: static access -# Administrative Trunking Encapsulation: dot1q -# Operational Trunking Encapsulation: native -# Negotiation of Trunking: Off -# Access Mode VLAN: 99 (MGMT) -# Trunking Native Mode VLAN: 1 (default) -# Administrative Native VLAN tagging: enabled -# Voice VLAN: none -# Administrative private-vlan host-association: none -# Administrative private-vlan mapping: none -# Administrative private-vlan trunk native VLAN: none -# Administrative private-vlan trunk Native VLAN tagging: enabled -# Administrative private-vlan trunk encapsulation: dot1q -# Administrative private-vlan trunk normal VLANs: none -# Administrative private-vlan trunk associations: none -# Administrative private-vlan trunk mappings: none -# Operational private-vlan: none -# Trunking VLANs Enabled: ALL -# Pruning VLANs Enabled: 2-1001 -# Capture Mode Disabled -# Capture VLANs Allowed: ALL -# -# Protected: false -# Unknown unicast blocked: disabled -# Unknown multicast blocked: disabled -# Appliance trust: none -# c2960# - -# c877#sh interfaces FastEthernet 1 switchport -# Name: Fa1 -# Switchport: Enabled -# Administrative Mode: trunk -# Operational Mode: trunk -# Administrative Trunking Encapsulation: dot1q -# Operational Trunking Encapsulation: dot1q -# Negotiation of Trunking: Disabled -# Access Mode VLAN: 0 ((Inactive)) -# Trunking Native Mode VLAN: 1 (default) -# Trunking VLANs Enabled: ALL -# Trunking VLANs Active: 1 -# Protected: false -# Priority for untagged frames: 0 -# Override vlan tag priority: FALSE -# Voice VLAN: none -# Appliance trust: none - - -# c2960#sh etherchannel summary -# Flags: D - down P - bundled in port-channel -# I - stand-alone s - suspended -# H - Hot-standby (LACP only) -# R - Layer3 S - Layer2 -# U - in use f - failed to allocate aggregator -# -# M - not in use, minimum links not met -# u - unsuitable for bundling -# w - waiting to be aggregated -# d - default port -# -# -# Number of channel-groups in use: 1 -# Number of aggregators: 1 -# -# Group Port-channel Protocol Ports -# ------+-------------+-----------+----------------------------------------------- -# 1 Po1(SU) LACP Gi0/17(P) Gi0/18(P) -# -# c2960# diff --git a/spec/unit/util/network_device/cisco/facts_spec.rb b/spec/unit/util/network_device/cisco/facts_spec.rb new file mode 100644 index 000000000..bb29ac292 --- /dev/null +++ b/spec/unit/util/network_device/cisco/facts_spec.rb @@ -0,0 +1,63 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../../spec_helper' + +require 'puppet/util/network_device' +require 'puppet/util/network_device/cisco/facts' + +describe Puppet::Util::NetworkDevice::Cisco::Facts do + before(:each) do + @transport = stub_everything 'transport' + @facts = Puppet::Util::NetworkDevice::Cisco::Facts.new(@transport) + end + + { + "cisco WS-C2924C-XL (PowerPC403GA) processor (revision 0x11) with 8192K/1024K bytes of memory." => {:hardwaremodel => "WS-C2924C-XL", :memorysize => "8192K", :processor => "PowerPC403GA", :hardwarerevision => "0x11" }, + "Cisco 1841 (revision 5.0) with 355328K/37888K bytes of memory." => {:hardwaremodel=>"1841", :memorysize => "355328K", :hardwarerevision => "5.0" }, + "Cisco 877 (MPC8272) processor (revision 0x200) with 118784K/12288K bytes of memory." => {:hardwaremodel=>"877", :memorysize => "118784K", :processor => "MPC8272", :hardwarerevision => "0x200" }, + "cisco WS-C2960G-48TC-L (PowerPC405) processor (revision C0) with 61440K/4088K bytes of memory." => {:hardwaremodel=>"WS-C2960G-48TC-L", :memorysize => "61440K", :processor => "PowerPC405", :hardwarerevision => "C0" }, + "cisco WS-C2950T-24 (RC32300) processor (revision R0) with 19959K bytes of memory." => {:hardwaremodel=>"WS-C2950T-24", :memorysize => "19959K", :processor => "RC32300", :hardwarerevision => "R0" } + }.each do |ver, expected| + it "should parse show ver output for hardware device facts" do + @transport.stubs(:command).with("sh ver").returns(<<eos) +Switch>sh ver +#{ver} +Switch> +eos + @facts.parse_show_ver.should == expected + end + end + + { +"Switch uptime is 1 year, 12 weeks, 6 days, 22 hours, 32 minutes" => { :hostname => "Switch", :uptime => "1 year, 12 weeks, 6 days, 22 hours, 32 minutes", :uptime_seconds => 39393120, :uptime_days => 455 }, +"c2950 uptime is 3 weeks, 1 day, 23 hours, 36 minutes" => { :hostname => "c2950", :uptime => "3 weeks, 1 day, 23 hours, 36 minutes", :uptime_days => 22, :uptime_seconds => 1985760}, +"router uptime is 5 weeks, 1 day, 3 hours, 30 minutes" => { :hostname => "router", :uptime => "5 weeks, 1 day, 3 hours, 30 minutes", :uptime_days => 36, :uptime_seconds => 3123000 }, +"c2950 uptime is 1 minute" => { :hostname => "c2950", :uptime => "1 minute", :uptime_days => 0, :uptime_seconds => 60 } + }.each do |ver, expected| + it "should parse show ver output for device uptime facts" do + @transport.stubs(:command).with("sh ver").returns(<<eos) +Switch>sh ver +#{ver} +Switch> +eos + @facts.parse_show_ver.should == expected + end + end + + { +"IOS (tm) C2900XL Software (C2900XL-C3H2S-M), Version 12.0(5)WC10, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.0(5)WC10", :operatingsystemmajrelease => "12.0WC", :operatingsystemfeature => "C3H2S"}, +"IOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA8a, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.1(22)EA8a", :operatingsystemmajrelease => "12.1EA", :operatingsystemfeature => "I6K2L2Q4"}, +"Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(44)SE, RELEASE SOFTWARE (fc1)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.2(44)SE", :operatingsystemmajrelease => "12.2SE", :operatingsystemfeature => "LANBASEK9"}, +"Cisco IOS Software, C870 Software (C870-ADVIPSERVICESK9-M), Version 12.4(11)XJ4, RELEASE SOFTWARE (fc2)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(11)XJ4", :operatingsystemmajrelease => "12.4XJ", :operatingsystemfeature => "ADVIPSERVICESK9"}, +"Cisco IOS Software, 1841 Software (C1841-ADVSECURITYK9-M), Version 12.4(24)T4, RELEASE SOFTWARE (fc2)" =>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(24)T4", :operatingsystemmajrelease => "12.4T", :operatingsystemfeature => "ADVSECURITYK9"}, + }.each do |ver, expected| + it "should parse show ver output for device software version facts" do + @transport.stubs(:command).with("sh ver").returns(<<eos) +Switch>sh ver +#{ver} +Switch> +eos + @facts.parse_show_ver.should == expected + end + end +end diff --git a/spec/unit/util/network_device/config_spec.rb b/spec/unit/util/network_device/config_spec.rb new file mode 100644 index 000000000..52796f30b --- /dev/null +++ b/spec/unit/util/network_device/config_spec.rb @@ -0,0 +1,102 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/util/network_device/config' + +describe Puppet::Util::NetworkDevice::Config do + before(:each) do + Puppet[:deviceconfig] = "/dummy" + FileTest.stubs(:exists?).with("/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("/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 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 + end + +end
\ No newline at end of file diff --git a/spec/unit/util/network_device/transport/ssh_spec.rb b/spec/unit/util/network_device/transport/ssh_spec.rb index 0e91ed9f9..18d22a953 100755 --- a/spec/unit/util/network_device/transport/ssh_spec.rb +++ b/spec/unit/util/network_device/transport/ssh_spec.rb @@ -30,6 +30,14 @@ describe Puppet::Util::NetworkDevice::Transport::Ssh, :if => Puppet.features.ssh @transport.connect end + it "should raise a Puppet::Error when encountering an authentication failure" do + Net::SSH.expects(:start).raises Net::SSH::AuthenticationFailed + @transport.host = "localhost" + @transport.user = "user" + + lambda { @transport.connect }.should raise_error Puppet::Error + end + describe "when connected" do before(:each) do @ssh = stub_everything 'ssh' diff --git a/spec/unit/util/network_device_spec.rb b/spec/unit/util/network_device_spec.rb new file mode 100644 index 000000000..70cb509b4 --- /dev/null +++ b/spec/unit/util/network_device_spec.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env rspec +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") + end + + class Puppet::Util::NetworkDevice::Test + class Device + def initialize(device) + 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.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
\ No newline at end of file |