diff --git a/CHANGELOG.md b/CHANGELOG.md index b580c6b..c88f90c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ Ruby Client for eAPI ==================== +## v0.4.0, November, 2015 + +- New users API +- New routemap API +- New vrrp API +- BGP API: Add support for maximum_paths and maximum_ecmp_paths +- System API: add support for managing the global EOS ‘ip routing’ setting +- Updated RPM/SWIX packaging to handle Puppet All-In-One (AIO) agent paths + New package names are: rbeapi, rbeapi-puppet3 (formerly pe-puppet), + and rbeapi-puppet-aio +- Fixed port-channel get_members() issue with EOS 4.15 and above. +- Fixed issue with the eapi.conf wildcard connection +- Fixed issue that would cause a traceback when searching for eapi.conf if + $HOME was not set + + ## v0.3.0, August, 2015 - API Change: Eliminated overloading the value option in command_builder. When diff --git a/Gemfile b/Gemfile index 57415cc..4d206b8 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ gem 'net_http_unix' gem 'netaddr' group :development do + gem 'rubocop', '>=0.35.1' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -13,6 +14,7 @@ group :development do end group :development, :test do + gem 'listen', '<=3.0.3' gem 'rake', '~> 10.1.0' gem 'rspec', '~> 3.0.0' gem 'rspec-mocks', '~> 3.0.0' @@ -22,7 +24,7 @@ group :development, :test do gem 'pry', require: false gem 'pry-doc', require: false gem 'pry-stack_explorer', require: false - gem 'rbeapi', '0.3.0', path: '.' + gem 'rbeapi', '0.4.0', path: '.' gem 'ci_reporter_rspec', require: false gem 'simplecov-json', require: false gem 'simplecov-rcov', require: false diff --git a/Guardfile b/Guardfile index 992071b..4778dcc 100644 --- a/Guardfile +++ b/Guardfile @@ -5,8 +5,8 @@ guard :rspec, cmd: 'bundle exec rspec' do watch(%r{^spec\/.+_spec\.rb$}) - watch(%r{^lib\/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { 'spec' } + watch(%r{^lib\/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } end guard :rubocop do diff --git a/README.md b/README.md index 4dc6ebb..e0790cb 100644 --- a/README.md +++ b/README.md @@ -214,8 +214,10 @@ and uploaded to [RubyGems](https://rubygems.org/). by the build. NOTE: [PuppetLabs](https://puppetlabs.com/misc/pe-files) provides a puppet agent SWIX which includes Ruby 1.9.3 in /opt/puppet/bin/ which is different from where you might otherwise install - Ruby. If you have installed the puppet-enterprise SWIX, then you should - build and use the ``pe-rbeapi`` swix, below. Otherwise, if you have installed + Ruby. If you have installed the puppet-enterprise 3.x SWIX, then you should + build and use the ``rbeapi-puppet3`` swix, below. If you have installed + the puppet-enterprise 2015.x SWIX, then you should build and use the + ``rbeapi-puppet-aio`` swix, below. Otherwise, if you have installed at least Ruby 1.9.3 in the standard system location, then the ``rbeapi`` SWIX may be used. @@ -223,20 +225,29 @@ and uploaded to [RubyGems](https://rubygems.org/). $ bundle install --path .bundle/gems/ $ bundle exec rake all_rpms ... - RPMs are available in rpms/noarch/ - Copy the RPMs to an EOS device then run the 'swix create' command. - Examples: - Puppet Open Source: - cd /mnt/flash; swix create rbeapi-0.3.0-1.swix \ - rubygem-rbeapi-0.3.0-1.eos4.noarch.rpm \ - rubygem-inifile-3.0.0-2.eos4.noarch.rpm \ - rubygem-netaddr-1.5.0-1.eos4.noarch.rpm \ - rubygem-net_http_unix-0.2.1-2.eos4.noarch.rpm - Puppet-enterprise: - cd/mnt/flash; swix create pe-rbeapi-0.3.0-1.swix \ - pe-rubygem-rbeapi-0.3.0-1.eos4.noarch.rpm \ - pe-rubygem-inifile-3.0.0-2.eos4.noarch.rpm \ - pe-rubygem-netaddr-1.5.0-1.eos4.noarch.rpm +RPMs are available in rpms/noarch/ +Copy the RPMs to an EOS device then run the 'swix create' command. + Examples: + Puppet Open Source: + cd /mnt/flash; \ + swix create rbeapi-0.4.0-1.swix \ + rubygem-rbeapi-0.4.0-1.eos4.noarch.rpm \ + rubygem-inifile-3.0.0-3.eos4.noarch.rpm \ + rubygem-netaddr-1.5.0-2.eos4.noarch.rpm \ + rubygem-net_http_unix-0.2.1-3.eos4.noarch.rpm + Puppet-enterprise agent (3.x): + cd/mnt/flash; \ + swix create rbeapi-puppet3-0.4.0-1.swix \ + rubygem-rbeapi-puppet3-0.4.0-1.eos4.noarch.rpm \ + rubygem-inifile-puppet3-3.0.0-3.eos4.noarch.rpm \ + rubygem-netaddr-puppet3-1.5.0-2.eos4.noarch.rpm + Puppet-All-in-one agent (2015.x/4.x): + cd/mnt/flash; \ + swix create rbeapi-puppet-aio-0.4.0-1.swix \ + rubygem-rbeapi-puppet-aio-0.4.0-1.eos4.noarch.rpm \ + rubygem-inifile-puppet-aio-3.0.0-3.eos4.noarch.rpm \ + rubygem-netaddr-puppet-aio-1.5.0-2.eos4.noarch.rpm \ + rubygem-net_http_unix-puppet-aio-0.2.1-3.eos4.noarch.rpm ``` On EOS: @@ -244,13 +255,13 @@ and uploaded to [RubyGems](https://rubygems.org/). Arista# copy flash: Arista# bash -bash-4.1# cd /mnt/flash/ - -bash-4.1# swix create pe-rbeapi-0.3.0-1.swix \ - pe-rubygem-rbeapi-0.1.0-1.eos4.noarch.rpm \ - pe-rubygem-inifile-3.0.0-1.eos4.noarch.rpm \ - pe-rubygem-netaddr-1.5.0-1.eos4.noarch.rpm + -bash-4.1# swix create rbeapi-puppet3-0.4.0-1.swix \ + rubygem-rbeapi-puppet3-0.4.0-1.eos4.noarch.rpm \ + rubygem-inifile-puppet3-3.0.0-1.eos4.noarch.rpm \ + rubygem-netaddr-puppet3-1.5.0-1.eos4.noarch.rpm -bash-4.1# exit - Arista# copy flash:pe-rbeapi-0.3.0-1.swix extension: - Arista# extension pe-rbeapi-0.3.0-1.swix + Arista# copy flash:rbeapi-puppet3-0.4.0-1.swix extension: + Arista# extension rbeapi-puppet3-0.4.0-1.swix Arista# copy installed-extensions boot-extensions ``` @@ -258,8 +269,8 @@ and uploaded to [RubyGems](https://rubygems.org/). On EOS: ``` - Arista# no extension pe-rbeapi-0.2.0-1.swix - Arista# extension pe-rbeapi-0.3.0-1.swix + Arista# no extension pe-rbeapi-0.3.0-1.swix + Arista# extension rbeapi-puppet3-0.4.0-1.swix Arista# copy installed-extensions boot-extensions ``` diff --git a/Rakefile b/Rakefile index f8c2a94..27626c2 100644 --- a/Rakefile +++ b/Rakefile @@ -15,7 +15,6 @@ task rpm: :build do system "sed -e 's/^Version:.*/Version: #{Rbeapi::VERSION}/g' " \ 'rbeapi.spec.tmpl > rbeapi.spec' system "rpmbuild #{RPM_OPTS} rbeapi.spec" - system "rpmbuild #{RPM_OPTS} --define 'enterprise 1' rbeapi.spec" RPMS = `find rpms/noarch -name "*rbeapi*rpm"` puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" @@ -33,8 +32,6 @@ task :inifile do system "sed -e 's/^Version:.*/Version: #{INIFILE_VERSION}/g' " \ 'gems/inifile/inifile.spec.tmpl > gems/inifile/inifile.spec' system "rpmbuild #{RPM_OPTS} gems/inifile/inifile.spec" - system "rpmbuild #{RPM_OPTS} --define 'enterprise 1' " \ - 'gems/inifile/inifile.spec' RPMS = `find rpms/noarch -name "*inifile*rpm"` puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" @@ -70,8 +67,6 @@ task :netaddr do system "sed -e 's/^Version:.*/Version: #{NETADDR_VERSION}/g' " \ 'gems/netaddr/netaddr.spec.tmpl > gems/netaddr/netaddr.spec' system "rpmbuild #{RPM_OPTS} gems/netaddr/netaddr.spec" - system "rpmbuild #{RPM_OPTS} --define 'enterprise 1' " \ - 'gems/netaddr/netaddr.spec' RPMS = `find rpms/noarch -name "*netaddr*rpm"` puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" @@ -87,19 +82,54 @@ task all_rpms: :build do puts 'RPMs are available in rpms/noarch/' puts "Copy the RPMs to an EOS device then run the 'swix create' command." puts ' Examples: ' - puts ' Puppet Open Source: ' - puts ' cd /mnt/flash; \\' - puts " swix create rbeapi-#{Rbeapi::VERSION}-1.swix \\" - puts " rubygem-rbeapi-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \\" - puts ' rubygem-inifile-3.0.0-2.eos4.noarch.rpm \\' - puts ' rubygem-netaddr-1.5.0-1.eos4.noarch.rpm \\' - puts ' rubygem-net_http_unix-0.2.1-2.eos4.noarch.rpm' - puts ' Puppet-enterprise: ' - puts ' cd/mnt/flash; \\' - puts " swix create pe-rbeapi-#{Rbeapi::VERSION}-1.swix \\" - puts " pe-rubygem-rbeapi-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \\" - puts ' pe-rubygem-inifile-3.0.0-2.eos4.noarch.rpm \\' - puts ' pe-rubygem-netaddr-1.5.0-1.eos4.noarch.rpm' + puts ' Puppet Open Source: ' + puts ' cd /mnt/flash; \\' + puts " swix create rbeapi-#{Rbeapi::VERSION}-1.swix \\" + puts " rubygem-rbeapi-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \\" + puts ' rubygem-inifile-3.0.0-3.eos4.noarch.rpm \\' + puts ' rubygem-netaddr-1.5.0-2.eos4.noarch.rpm \\' + puts ' rubygem-net_http_unix-0.2.1-3.eos4.noarch.rpm' + puts ' Puppet-enterprise agent (3.x): ' + puts ' cd/mnt/flash; \\' + puts " swix create rbeapi-puppet3-#{Rbeapi::VERSION}-1.swix \\" + puts " rubygem-rbeapi-puppet3-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \\" + puts ' rubygem-inifile-puppet3-3.0.0-3.eos4.noarch.rpm \\' + puts ' rubygem-netaddr-puppet3-1.5.0-2.eos4.noarch.rpm' + puts ' Puppet-All-in-one agent (2015.x/4.x): ' + puts ' cd/mnt/flash; \\' + puts " swix create rbeapi-puppet-aio-#{Rbeapi::VERSION}-1.swix \\" + puts " rubygem-rbeapi-puppet-aio-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \\" + puts ' rubygem-inifile-puppet-aio-3.0.0-3.eos4.noarch.rpm \\' + puts ' rubygem-netaddr-puppet-aio-1.5.0-2.eos4.noarch.rpm \\' + puts ' rubygem-net_http_unix-puppet-aio-0.2.1-3.eos4.noarch.rpm' +end + +desc 'Generate SWIX files from RPMs' +task swix: :all_rpms do + SWIX = 'PYTHONPATH=${PYTHONPATH}:/nfs/misc/tools/swix \ + /nfs/misc/tools/swix/swix' + system "cd rpms/noarch; + rm -f rbeapi-#{Rbeapi::VERSION}-1.swix; + #{SWIX} create rbeapi-#{Rbeapi::VERSION}-1.swix \ + rubygem-rbeapi-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \ + rubygem-inifile-3.0.0-3.eos4.noarch.rpm \ + rubygem-netaddr-1.5.0-2.eos4.noarch.rpm" + system "cd rpms/noarch; + rm -f rbeapi-puppet3-#{Rbeapi::VERSION}-1.swix; + #{SWIX} create rbeapi-puppet3-#{Rbeapi::VERSION}-1.swix \ + rubygem-rbeapi-puppet3-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \ + rubygem-inifile-puppet3-3.0.0-3.eos4.noarch.rpm \ + rubygem-netaddr-puppet3-1.5.0-2.eos4.noarch.rpm" + system "cd rpms/noarch; + rm -f rbeapi-puppet-aio-#{Rbeapi::VERSION}-1.swix; + #{SWIX} create rbeapi-puppet-aio-#{Rbeapi::VERSION}-1.swix \ + rubygem-rbeapi-puppet-aio-#{Rbeapi::VERSION}-1.eos4.noarch.rpm \ + rubygem-inifile-puppet-aio-3.0.0-3.eos4.noarch.rpm \ + rubygem-netaddr-puppet-aio-1.5.0-2.eos4.noarch.rpm" + SWIXS = `find rpms/noarch -name "rbeapi*swix" -ls` + puts "\n################################################\n#" + puts "The following artifacts are in rpms/noarch/\n#{SWIXS}" + puts "#\n################################################\n\n" end task release: :build do diff --git a/gems/inifile/inifile.spec.tmpl b/gems/inifile/inifile.spec.tmpl index 40d2b38..3df8a05 100644 --- a/gems/inifile/inifile.spec.tmpl +++ b/gems/inifile/inifile.spec.tmpl @@ -3,7 +3,7 @@ Name: %{?enterprise:pe-}rubygem-%{gem_name} Version: 3.0.0 -Release: 2.eos4 +Release: 3.eos4 Summary: INI file reader and writer Group: Development/Languages @@ -11,19 +11,8 @@ License: Unknown URL: http://rubygems.org/gems/inifile Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem -%if 0%{?enterprise:1} == 1 -# Use these settings for Puppet Enterprise -%global gem /opt/puppet/bin/gem -Requires: pe-ruby -Requires: pe-rubygems -Provides: pe-rubygem(%{gem_name}) = %{version} -Provides: pe-rubygem-%{gem_name} = %{version} -%else -# Use these settings for all other installs -%global gem gem Requires: ruby(abi) = %{rubyabi} Provides: ruby(%{gem_name}) = %{version}-%{release} -%endif BuildArch: noarch @@ -60,6 +49,25 @@ are also possible var1 = baz var2 = shoodle. +%package puppet3 +Summary: Inifile rubygem packaged for Puppet 3.x on Arista EOS +Group: Development/Languages +Requires: pe-ruby +Requires: pe-rubygems +Provides: pe-rubygem(%{gem_name}) = %{version} +Provides: pe-rubygem-%{gem_name} = %{version} +%description puppet3 +The inifile rubygem packaged for Puppet 3.x on Arista EOS. + +%package puppet-aio +Summary: Inifile rubygem packaged for Puppet 2015 (4.x) on Arista EOS +Group: Development/Languages +# Use these settings for all other installs +Requires: puppet >= 4.0.0 +Provides: rubygem-%{gem_name} = %{version} +%description puppet-aio +The inifile rubygem packaged for Puppet 2015 (4.x) on Arista EOS. + %prep %setup -q -D -T -n . @@ -70,13 +78,41 @@ install %{SOURCE0} %{buildroot}/ %files /%{gem_name}-%{version}.gem +%files puppet3 +/%{gem_name}-%{version}.gem + +%files puppet-aio +/%{gem_name}-%{version}.gem + %post -%{gem} install --local /%{gem_name}-%{version}.gem > /dev/null 2>&1 +GEM_OPTS="--no-document --local" +gem install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 %preun -%{gem} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 +gem uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet3 +GEM=/opt/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet3 +GEM=/opt/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 %changelog +* Fri Oct 30 2015 Jere Julian - 3.0.0-3 +- Detect the location of the puppet-agent's gem install + * Thu May 21 2015 Jere Julian - 3.0.0-2 - Ubuntu requires we manually create the buildroot diff --git a/gems/net_http_unix/net_http_unix.spec.tmpl b/gems/net_http_unix/net_http_unix.spec.tmpl index dcc8aef..1c97b26 100644 --- a/gems/net_http_unix/net_http_unix.spec.tmpl +++ b/gems/net_http_unix/net_http_unix.spec.tmpl @@ -3,7 +3,7 @@ Name: %{?enterprise:pe-}rubygem-%{gem_name} Version: 0.2.1 -Release: 2.eos4 +Release: 3.eos4 Summary: Wrapper around Net::HTTP with AF_UNIX support Group: Development/Languages @@ -11,25 +11,30 @@ License: Apache 2.0 URL: http://github.com/puppetlabs/net_http_unix Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem -%if 0%{?enterprise:1} == 1 -# Use these settings for Puppet Enterprise -%global gem /opt/puppet/bin/gem -Requires: pe-ruby -Requires: pe-rubygems -Provides: pe-rubygem(%{gem_name}) = %{version} -Provides: pe-rubygem-%{gem_name} = %{version} -%else -# Use these settings for all other installs -%global gem gem Requires: ruby(abi) = %{rubyabi} Provides: ruby(%{gem_name}) = %{version}-%{release} -%endif BuildArch: noarch %description Wrapper around Net::HTTP with AF_UNIX support. +%package puppet3 +Group: Development/Languages +Summary: Net_http_unix rubygem packaged for Puppet Enterprise 3.x agents +Requires: pe-ruby +Requires: pe-rubygems +Provides: pe-rubygem(%{gem_name}) = %{version} +Provides: pe-rubygem-%{gem_name} = %{version} +%description puppet3 + +%package puppet-aio +Group: Development/Languages +Summary: Net_http_unix rubygem packaged for Puppet All-In-One (4.x) agents +Requires: puppet >= 4.0.0 +Provides: rubygem-%{gem_name} = %{version} +%description puppet-aio + %prep %setup -q -D -T -n . @@ -40,14 +45,42 @@ install %{SOURCE0} %{buildroot}/ %files /%{gem_name}-%{version}.gem +%files puppet3 +/%{gem_name}-%{version}.gem + +%files puppet-aio +/%{gem_name}-%{version}.gem + %post -%{gem} install --local /%{gem_name}-%{version}.gem > /dev/null 2>&1 +GEM_OPTS="--no-document --local" +gem install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 %preun -%{gem} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 +gem uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet3 +GEM=/opt/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet3 +GEM=/opt/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1pckage puppet3 %changelog -* Tue May 21 2015 Jere Julian - 0.2.1-2 +* Fri Oct 30 2015 Jere Julian - 0.2.1-3 +- Detect the location of the puppet-agent's gem install + +* Thu May 21 2015 Jere Julian - 0.2.1-2 - Ubuntu requires we manually create the buildroot * Tue Mar 17 2015 Jere Julian - 0.2.1-1 diff --git a/gems/netaddr/netaddr.spec.tmpl b/gems/netaddr/netaddr.spec.tmpl index b740960..cf1dee8 100644 --- a/gems/netaddr/netaddr.spec.tmpl +++ b/gems/netaddr/netaddr.spec.tmpl @@ -3,31 +3,36 @@ Name: %{?enterprise:pe-}rubygem-%{gem_name} Version: 1.5.0 -Release: 1.eos4 +Release: 2.eos4 Summary: A package for manipulating network addresses Group: Development/Languages License: Unknown Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem -%if 0%{?enterprise:1} == 1 -# Use these settings for Puppet Enterprise -%global gem /opt/puppet/bin/gem -Requires: pe-ruby -Requires: pe-rubygems -Provides: pe-rubygem(%{gem_name}) = %{version} -Provides: pe-rubygem-%{gem_name} = %{version} -%else -# Use these settings for all other installs -%global gem gem Requires: ruby(abi) = %{rubyabi} Provides: ruby(%{gem_name}) = %{version}-%{release} -%endif BuildArch: noarch + %description A package for manipulating network addresses. +%package puppet3 +Summary: NetAddr rubygem packaged for Puppet Enterprise 3.x agents +Group: Development/Languages +Requires: pe-ruby +Requires: pe-rubygems +Provides: pe-rubygem(%{gem_name}) = %{version} +Provides: pe-rubygem-%{gem_name} = %{version} +%description puppet3 + +%package puppet-aio +Summary: NetAddr rubygem packaged for Puppet All-In-One (4.x) agents +Group: Development/Languages +Requires: puppet >= 4.0.0 +Provides: rubygem-%{gem_name} = %{version} +%description puppet-aio %prep %setup -q -D -T -n . @@ -39,12 +44,40 @@ install %{SOURCE0} %{buildroot}/ %files /%{gem_name}-%{version}.gem +%files puppet3 +/%{gem_name}-%{version}.gem + +%files puppet-aio +/%{gem_name}-%{version}.gem + %post -%{gem} install --local /%{gem_name}-%{version}.gem > /dev/null 2>&1 +GEM_OPTS="--no-document --local" +gem install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 %preun -%{gem} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 +gem uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet3 +GEM=/opt/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet3 +GEM=/opt/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 %changelog +* Fri Oct 30 2015 Jere Julian - 1.5.0-2 +- Detect the location of the puppet-agent's gem install + * Tue Jul 07 2015 Jere Julian - 1.5.0-1 - Initial package diff --git a/lib/rbeapi/api/bgp.rb b/lib/rbeapi/api/bgp.rb index f1cc972..079e897 100644 --- a/lib/rbeapi/api/bgp.rb +++ b/lib/rbeapi/api/bgp.rb @@ -55,11 +55,12 @@ def initialize(node) # Hash. def get config = get_block('^router bgp .*') - return {} unless config + return nil unless config response = Bgp.parse_bgp_as(config) response.merge!(parse_router_id(config)) response.merge!(parse_shutdown(config)) + response.merge!(parse_maximum_paths(config)) response.merge!(parse_networks(config)) response[:neighbors] = @neighbors.getall response @@ -110,6 +111,21 @@ def parse_shutdown(config) end private :parse_shutdown + ## + # parse_maximum_paths scans the BGP routing configuration for the + # maximum paths and maximum ecmp paths. + # + # @api private + # + # @param [String] :config The switch config. + # + # @return [Hash] resource hash attribute + def parse_maximum_paths(config) + values = config.scan(/maximum-paths\s+(\d+)\s+ecmp\s+(\d+)/).first + { maximum_paths: values[0].to_i, maximum_ecmp_paths: values[1].to_i } + end + private :parse_maximum_paths + ## # parse_networks scans the BGP routing configuration for all # the network entries. @@ -133,6 +149,8 @@ def parse_networks(config) ## # create will create a new instance of BGP routing on the node. + # Optional parameters can be passed in to initialize BGP specific + # settings. # # @commands # router bgp @@ -140,10 +158,48 @@ def parse_networks(config) # @param [String] :bgp_as The BGP autonomous system number to be # configured for the local BGP routing instance. # + # + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :router_id The BGP routing process router-id + # value. When no ID has been specified (i.e. value not set), the + # local router ID is set to the following: + # * The loopback IP address when a single loopback interface is + # configured. + # * The loopback with the highest IP address when multiple loopback + # interfaces are configured. + # * The highest IP address on a physical interface when no loopback + # interfaces are configure + # + # @option :opts [Integer] :maximum_paths Maximum number of equal cost + # paths. + # + # @option :opts [Integer] :maximum_ecmp_paths Maximum number of installed + # ECMP routes. The maximum_paths option must be set if + # maximum_ecmp_paths is set. + # + # @option :opts [Boolean] :enable If true then the BGP router is enabled. + # If false then the BGP router is disabled. + # # @return [Boolean] returns true if the command completed successfully - def create(bgp_as) - value = bgp_as - configure("router bgp #{value}") + def create(bgp_as, opts = {}) + if opts[:maximum_ecmp_paths] && !opts[:maximum_paths] + message = 'maximum_paths must be set if maximum_ecmp_paths is set' + fail ArgumentError, message + end + cmds = ["router bgp #{bgp_as}"] + if opts.key?(:enable) + cmds << (opts[:enable] == true ? 'no shutdown' : 'shutdown') + end + cmds << "router-id #{opts[:router_id]}" if opts.key?(:router_id) + if opts.key?(:maximum_paths) + cmd = "maximum-paths #{opts[:maximum_paths]}" + if opts.key?(:maximum_ecmp_paths) + cmd << " ecmp #{opts[:maximum_ecmp_paths]}" + end + cmds << cmd + end + configure(cmds) end ## @@ -250,6 +306,46 @@ def set_shutdown(opts = {}) configure_bgp(command_builder('shutdown', opts)) end + ## + # set_maximum_paths sets the maximum number of equal cost paths and + # the maximum number of installed ECMP routes. + # + # @commands + # router bgp + # {no | default} + # maximum-paths [ecmp ] + # + # @param [Integer] :maximum_paths Maximum number of equal cost paths. + # + # @param [Integer] :maximum_ecmp_paths Maximum number of installed ECMP + # routes. + # + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the maximum paths using + # the default keyword + # + # @return [Boolean] returns true if the command complete successfully + def set_maximum_paths(maximum_paths, maximum_ecmp_paths, opts = {}) + enable = opts.fetch(:enable, true) + default = opts[:default] || false + + case default + when true + cmd = 'default maximum-paths' + when false + if enable + cmd = "maximum-paths #{maximum_paths} ecmp #{maximum_ecmp_paths}" + else + cmd = 'no maximum-paths' + end + end + configure_bgp(cmd) + end + ## # add_network creates a new instance of a BGP network on the node. # diff --git a/lib/rbeapi/api/interfaces.rb b/lib/rbeapi/api/interfaces.rb index d44046b..d154219 100644 --- a/lib/rbeapi/api/interfaces.rb +++ b/lib/rbeapi/api/interfaces.rb @@ -155,7 +155,7 @@ def parse_description(config) # @return [Hash] resource hash attribute def parse_shutdown(config) value = /no shutdown/ =~ config - { shutdown: value.nil? } + { shutdown: value.nil? } end private :parse_shutdown @@ -640,7 +640,7 @@ def get(name) def parse_members(name) grpid = name.scan(/(?<=Port-Channel)\d+/)[0] command = "show port-channel #{grpid} all-ports" - config = node.enable(command, format: 'text') + config = node.enable(command, encoding: 'text') values = config.first[:result]['output'].scan(/\bEthernet[^\s]+/) { members: values } end @@ -664,7 +664,7 @@ def parse_lacp_mode(name) return { lacp_mode: DEFAULT_LACP_MODE } unless members config = get_block("interface #{members.first}") mdata = /channel-group \d+ mode (\w+)/.match(config) - { lacp_mode: mdata ? mdata[1] : DEFAULT_LACP_MODE } + { lacp_mode: mdata ? mdata[1] : DEFAULT_LACP_MODE } end private :parse_lacp_mode @@ -911,8 +911,7 @@ def set_lacp_fallback(name, opts = {}) # @param [Hash] :opts optional keyword arguments # # @option :opts [String] :value Specifies the value to configure for - # the port-channel lacp fallback timeout. Valid values range from - # 1 to 100 seconds + # the port-channel lacp fallback timeout. # # @option :opts [Boolean] :enable If false then the command is # negated. Default is true. diff --git a/lib/rbeapi/api/radius.rb b/lib/rbeapi/api/radius.rb index b864ebd..86c1481 100644 --- a/lib/rbeapi/api/radius.rb +++ b/lib/rbeapi/api/radius.rb @@ -148,7 +148,7 @@ def parse_global_key # @return [Array>] Array of resource hashes def parse_servers tuples = config.scan(SERVER_REGEXP) - tuples.map do |(host, vrf, authp, acctp, tout, tries, keyfm, key)| + tuples.map do |(host, vrf, authp, acctp, tout, tries, keyfm, key)| hsh = {} hsh[:hostname] = host hsh[:vrf] = vrf diff --git a/lib/rbeapi/api/routemaps.rb b/lib/rbeapi/api/routemaps.rb index 114e7c7..ca6494c 100644 --- a/lib/rbeapi/api/routemaps.rb +++ b/lib/rbeapi/api/routemaps.rb @@ -43,63 +43,431 @@ module Api # the basis of such criteria as route metrics, access control lists, next # hop addresses, and route tags. # - # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/ClassLength # class Routemaps < Entity + ## + # get returns a hash of routemap configurations for the given name + # + # @example + # { + # : { + # : { + # match: , + # set: , + # continue: , + # description: + # }, + # : { + # match: , + # set: , + # continue: , + # description: + # } + # }, + # : { + # : { + # match: , + # set: , + # continue: , + # description: + # }, + # : { + # match: , + # set: , + # continue: , + # description: + # } + # } + # } + # + # @param [String] name The routemap name to return a resource for from the + # nodes configuration + # + # @return [nil, Hash] Returns the routemap resource as a + # Hash. If the specified name is not found in the nodes current + # configuration a nil object is returned def get(name) + parse_entries(name) + end + + ## + # getall returns a collection of routemap resource hashes from the nodes + # running configuration. The routemap resource collection hash is keyed + # by the unique routemap name. + # + # @example + # { + # : { + # : { + # : { + # match: , + # set: , + # continue: , + # description: + # }, + # : { + # match: , + # set: , + # continue: , + # description: + # } + # }, + # : { + # : { + # match: , + # set: , + # continue: , + # description: + # }, + # : { + # match: , + # set: , + # continue: , + # description: + # } + # } + # }, + # : { + # : { + # : { + # match: , + # set: , + # continue: , + # description: + # }, + # : { + # match: , + # set: , + # continue: , + # description: + # } + # }, + # : { + # : { + # match: , + # set: , + # continue: , + # description: + # }, + # : { + # match: , + # set: , + # continue: , + # description: + # } + # } + # } + # } + # + # @return [nil, Hash] returns a hash that represents the + # entire routemap collection from the nodes running configuration. If + # there are no routemap names configured, this method will return nil. + def getall + routemaps = config.scan(/(?<=^route-map\s)[^\s]+/) + return nil if routemaps.empty? + routemaps.each_with_object({}) do |name, response| + response[name] = parse_entries(name) + end + end + + ## + # parse entries is a private method to get the routemap rules. + # + # @return [nil, Hash] returns a hash that represents the + # rules for routemaps from the nodes running configuration. If + # there are no routemaps configured, this method will return nil. + # + def parse_entries(name) entries = config.scan(/^route-map\s#{name}\s.+$/) + return nil if entries.empty? + entries.each_with_object({}) do |rm, response| + mdata = /^route-map\s(.+)\s(.+)\s(\d+)$/.match(rm) + rule_hsh = parse_rules(get_block(rm)) + if response[mdata[2]] + response[mdata[2]].merge!(mdata[3].to_i => rule_hsh) + else + response[mdata[2]] = { mdata[3].to_i => rule_hsh } + end + end + end + private :parse_entries + + ## + # parse rule is a private method to parse a rule. + # + # @return [Hash] returns a hash that represents the + # rules for routemaps from the nodes running configuration. If + # there are no routemaps configured, this method will return an empty + # hash. + # + def parse_rules(rules) + rules.split("\n").each_with_object({}) do |rule, rule_hsh| + mdata = /\s{3}(\w+)\s/.match(rule) + case mdata.nil? ? nil : mdata[1] + when 'match' + rule_hsh[:match] = [] unless rule_hsh.include?(:match) + rule_hsh[:match] << rule.sub('match', '').strip + when 'set' + rule_hsh[:set] = [] unless rule_hsh.include?(:set) + rule_hsh[:set] << rule.sub('set', '').strip + when 'continue' + rule_hsh[:continue] = nil unless rule_hsh.include?(:continue) + rule_hsh[:continue] = rule.sub('continue', '').strip.to_i + when 'description' + rule_hsh[:description] = nil unless rule_hsh.include?(:description) + rule_hsh[:description] = rule.sub('description', '').strip + end + end + end + private :parse_rules - entries.each_with_object([]) do |rm, arry| - mdata = /route-map\s(.+)\s(.+)\s(\d+)$/.match(rm) - rules = get_block(rm) - rule_hsh = { 'action' => mdata[2], 'seqno' => mdata[3], - 'match_rules' => [], 'set_rules' => [], - 'continue_rules' => [] } + ## + # name_commands is utilized to initially prepare the routemap + def name_commands(name, action, seqno, opts = {}) + if opts[:default] == true + cmd = "default route-map #{name}" + elsif opts[:enable] == false + cmd = "no route-map #{name}" + else + cmd = "route-map #{name}" + end + cmd << " #{action}" + cmd << " #{seqno}" + [cmd] + end + private :name_commands - parsed = rules.split("\n").each_with_object({}) do |rule, hsh| - mdata = /\s{3}(\w+)\s/.match(rule) - case mdata.nil? ? nil : mdata[1] - when 'match' - hsh['match_rules'] = [] unless hsh.include?('match') - hsh['match_rules'] << rule.strip - when 'set' - hsh['set_rules'] = [] unless hsh.include?('set') - hsh['set_rules'] << rule.strip - when 'continue' - hsh['continue_rules'] = [] unless hsh.include?('continue') - hsh['continue_rules'] << rule.strip + ## + # create will create a new routemap with the specified name. + # + # rubocop:disable Metrics/MethodLength + # + # @commands + # route-map action seqno description + # match set continue + # + # @param [String] :name The name of the routemap to create + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [Boolean] :default Set routemap to default + # + # @option :opts [String] :description A description for the routemap + # + # @option :opts [Array] :match routemap match rule + # + # @option :opts [String] :set Sets route attribute + # + # @option :opts [String] :continue The routemap sequence number to + # continue on. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the routemap to default. + # + # @return [Boolean] returns true if the command completed successfully + def create(name, action, seqno, opts = {}) + if opts.empty? + cmds = name_commands(name, action, seqno) + else + cmds = name_commands(name, action, seqno, opts) + if opts[:description] + cmds << 'no description' + cmds << "description #{opts[:description]}" + end + if opts[:continue] + cmds << 'no continue' + cmds << "continue #{opts[:continue]}" + end + if opts[:match] + remove_match_statements(name, action, seqno, cmds) + opts[:match].each do |options| + cmds << "match #{options}" + end + end + if opts[:set] + remove_set_statements(name, action, seqno, cmds) + opts[:set].each do |options| + cmds << "set #{options}" end end - rule_hsh.update(parsed) - arry << rule_hsh end + configure(cmds) end - def getall - maps = config.scan(/(?<=^route-map\s)[^\s]+/) - maps.each_with_object({}) do |name, hsh| - hsh[name] = get name unless hsh.include?(name) + ## + # remove_match_statemements removes all match rules for the + # specified routemap + def remove_match_statements(name, action, seqno, cmds) + entries = parse_entries(name) + return nil unless entries + entries.each do |entry| + next unless entry[0] == action && entry[1].assoc(seqno) && \ + entry[1].assoc(seqno)[0] == seqno + Array(entry[1].assoc(seqno)[1][:match]).each do |options| + cmds << "no match #{options}" + end end end + private :remove_match_statements - def create(name) - configure "route-map #{name}" + ## + # remove_set_statemements removes all set rules for the + # specified routemap + def remove_set_statements(name, action, seqno, cmds) + entries = parse_entries(name) + return nil unless entries + entries.each do |entry| + next unless entry[0] == action && entry[1].assoc(seqno) && \ + entry[1].assoc(seqno)[0] == seqno + Array(entry[1].assoc(seqno)[1][:set]).each do |options| + cmds << "no set #{options}" + end + end end + private :remove_set_statements - def delete(name) - configure "no route-map #{name}" + ## + # delete will delete an existing routemap name from the nodes current + # running configuration. If the delete method is called and the + # routemap name does not exist, this method will succeed. + # + # @commands + # no route-map + # + # @param [String] :name The routemap name to delete from the node. + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @return [Boolean] returns true if the command completed successfully + def delete(name, action, seqno) + configure(["no route-map #{name} #{action} #{seqno}"]) + end + + ## + # This method will attempt to default the routemap from the nodes + # operational config. Since routemaps do not exist by default, + # the default action is essentially a negation and the result will + # be the removal of the routemap clause. + # If the routemap does not exist then this + # method will not perform any changes but still return True + # + # @commands + # no route-map + # + # @param [String] :name The routemap name to set to default. + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @return [Boolean] returns true if the command completed successfully + def default(name, action, seqno) + configure(["default route-map #{name} #{action} #{seqno}"]) + end + + ## + # set_match_statements will set the match values for a specified routemap. + # If the specified routemap does not exist, it will be created. + # + # @commands + # route-map action seqno match + # + # @param [String] :name The name of the routemap to create + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @param [Array] :value The routemap match rules + # + # @return [Boolean] returns true if the command completed successfully + def set_match_statements(name, action, seqno, value) + cmds = ["route-map #{name} #{action} #{seqno}"] + remove_match_statements(name, action, seqno, cmds) + Array(value).each do |options| + cmds << "match #{options}" + end + configure(cmds) + end + + ## + # set_set_statements will set the set values for a specified routemap. + # If the specified routemap does not exist, it will be created. + # + # @commands + # route-map action seqno set + # + # @param [String] :name The name of the routemap to create + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @param [Array] :value The routemap set rules + # + # @return [Boolean] returns true if the command completed successfully + def set_set_statements(name, action, seqno, value) + cmds = ["route-map #{name} #{action} #{seqno}"] + remove_set_statements(name, action, seqno, cmds) + Array(value).each do |options| + cmds << "set #{options}" + end + configure(cmds) end - def add_rule(name, action, rule, seqno = nil) - cmd = "route-map #{name} #{action}" - cmd << " #{seqno}" if seqno - cmds = [*cmds] - cmds << rule - configure cmds + ## + # set_continue will set the continue value for a specified routemap. + # If the specified routemap does not exist, it will be created. + # + # @commands + # route-map action seqno continue + # + # @param [String] :name The name of the routemap to create + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @param [Integer] :value The continue value + # + # @return [Boolean] returns true if the command completed successfully + def set_continue(name, action, seqno, value) + cmds = ["route-map #{name} #{action} #{seqno}"] + cmds << 'no continue' + cmds << "continue #{value}" + configure(cmds) end - def remove_rule(name, action, seqno) - configure "no route-map #{name} #{action} #{seqno}" + ## + # set_description will set the description for a specified routemap. + # If the specified routemap does not exist, it will be created. + # + # @commands + # route-map action seqno description + # + # @param [String] :name The name of the routemap to create + # + # @param [String] :action Either permit or deny + # + # @param [Integer] :seqno The sequence number + # + # @param [String] :value The description value + # + # @return [Boolean] returns true if the command completed successfully + def set_description(name, action, seqno, value) + cmds = ["route-map #{name} #{action} #{seqno}"] + cmds << 'no description' + cmds << "description #{value}" + configure(cmds) end end end diff --git a/lib/rbeapi/api/system.rb b/lib/rbeapi/api/system.rb index 4449158..34b0e06 100644 --- a/lib/rbeapi/api/system.rb +++ b/lib/rbeapi/api/system.rb @@ -54,6 +54,7 @@ class System < Entity def get response = {} response.merge!(parse_hostname(config)) + response.merge!(parse_iprouting(config)) response end @@ -62,6 +63,11 @@ def parse_hostname(config) { hostname: mdata.nil? ? '' : mdata[1] } end + def parse_iprouting(config) + mdata = /no\sip\srouting/.match(config) + { iprouting: mdata.nil? ? true : false } + end + ## # Configures the system hostname value in the running-config # @@ -76,6 +82,21 @@ def set_hostname(opts = {}) cmd = command_builder('hostname', opts) configure(cmd) end + + ## + # Configures the state of global ip routing + # + # @param [Hash] opts The configuration parameters + # @option :opts [Boolean] :enable True if ip routing should be enabled + # or False if ip routing should be disabled. Default is true. + # @option opts [Boolean] :default Controls the use of the default + # keyword. Default is false. + # + # @return [Boolean] returns true if the command completed successfully + def set_iprouting(opts = {}) + cmd = command_builder('ip routing', opts) + configure(cmd) + end end end end diff --git a/lib/rbeapi/api/users.rb b/lib/rbeapi/api/users.rb new file mode 100644 index 0000000..c546f9a --- /dev/null +++ b/lib/rbeapi/api/users.rb @@ -0,0 +1,361 @@ +# +# Copyright (c) 2015, Arista Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of Arista Networks nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +require 'rbeapi/api' + +## +# Rbeapi toplevel namespace +module Rbeapi + ## + # Api is module namesapce for working with the EOS command API + module Api + ## + # The Users class provides configuration of local user resources for + # an EOS node. + class Users < Entity + def initialize(node) + super(node) + # The regex used here parses the running configuration to find all + # username entries. There is extra logic in the regular expression + # to store the username as 'user' and then creates a back reference + # to find a following configuration line that might contain the + # users sshkey. + @users_re = Regexp.new(/^username\s+(?[^\s]+)\s+ + privilege\s+(?\d+) + (\s+role\s+(?\S+))? + (?:\s+(?(nopassword)))? + (\s+secret\s+(?0|5|7|sha512)\s+ + (?\S+))?.*$\n + (username\s+\k\s+ + sshkey\s+(?.*)$)?/x) + + @encryption_map = { 'cleartext' => '0', + 'md5' => '5', + 'sha512' => 'sha512' } + end + + ## + # get returns the local user configuration + # + # @example + # { + # name: , + # privilege: , + # role: , + # nopassword: , + # encryption: <'cleartext', 'md5', 'sha512'> + # secret: , + # sshkey: + # } + # + # @param [String] name The user name to return a resource for from the + # nodes configuration + # + # @return [nil, Hash] Returns the user resource as a + # Hash. If the specified user name is not found in the nodes current + # configuration a nil object is returned + def get(name) + # The regex used here parses the running configuration to find one + # username entry. + user_re = Regexp.new(/^username\s+(?#{name})\s+ + privilege\s+(?\d+) + (\s+role\s+(?\S+))? + (?:\s+(?(nopassword)))? + (\s+secret\s+(?0|5|7|sha512)\s+ + (?\S+))?.*$\n + (username\s+#{name}\s+ + sshkey\s+(?.*)$)?/x) + user = config.scan(user_re) + return nil unless user + parse_user_entry(user[0]) + end + + ## + # getall returns a collection of user resource hashes from the nodes + # running configuration. The user resource collection hash is keyed + # by the unique user name. + # + # @example + # [ + # : { + # name: , + # privilege: , + # role: , + # nopassword: , + # encryption: <'cleartext', 'md5', 'sha512'> + # secret: , + # sshkey: + # }, + # ... + # ] + # + # @return [Hash] returns a hash that represents the + # entire user collection from the nodes running configuration. If + # there are no user names configured, this method will return an empty + # hash. + def getall + entries = config.scan(@users_re) + response = {} + entries.each do |user| + response[user[0]] = parse_user_entry(user) + end + response + end + + ## + # parse_user_entry maps the tokens find to the hash entries. + # + # @api private + # + # @param [Array] :user An array of values returned from the regular + # expression scan of the nodes configuration. + # + # @return [Hash] resource hash attribute + def parse_user_entry(user) + hsh = {} + hsh[:name] = user[0] + hsh[:privilege] = user[1].to_i + hsh[:role] = user[2] + hsh[:nopassword] = user[3] ? true : false + # Map the encryption value if set, if there is no mapping then + # just return the value. + if user[4] + @encryption_map.each do |key, value| + if value == user[4] + user[4] = key + break + end + end + end + hsh[:encryption] = user[4] + hsh[:secret] = user[5] + hsh[:sshkey] = user[6] + hsh + end + private :parse_user_entry + + ## + # create will create a new user name resource in the nodes current + # configuration with the specified user name. Creating users require + # either a secret (password) or the nopassword keyword to be specified. + # Optional parameters can be passed in to initialize user name specific + # settings. + # + # @eos_version 4.13.7M + # + # @commands + # username nopassword privilege role + # username secret [0,5,sha512] ... + # + # @param [String] :name The name of the user to create + # + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [Boolean] :nopassword Configures the user to be able to + # authenticate without a password challenge + # + # @option :opts [String] :secret The secret (password) to assign to this + # user + # + # @option :opts [String] :encryption Specifies how the secret is encoded. + # Valid values are "cleartext", "md5", "sha512". The default is + # "cleartext" + # + # @option :opts [String] :privilege The privilege value to assign to + # the user + # + # @option :opts [String] :role The role value to assign to the user + # + # @option :opts [String] :sshkey The sshkey value to assign to the user + # + # @return [Boolean] returns true if the command completed successfully + def create(name, opts = {}) + cmd = "username #{name}" + cmd << " privilege #{opts[:privilege]}" if opts[:privilege] + cmd << " role #{opts[:role]}" if opts[:role] + if opts[:nopassword] == :true + cmd << ' nopassword' + else + # Map the encryption value if set, if there is no mapping then + # just return the value. + enc = opts.fetch(:encryption, 'cleartext') + unless @encryption_map[enc] + fail ArgumentError, "invalid encryption value: #{enc}" + end + enc = @encryption_map[enc] + + unless opts[:secret] + fail ArgumentError, + 'secret must be specified if nopassword is false' + end + cmd << " secret #{enc} #{opts[:secret]}" + end + cmds = [cmd] + cmds << "username #{name} sshkey #{opts[:sshkey]}" if opts[:sshkey] + configure(cmds) + end + + ## + # delete will delete an existing user name from the nodes current + # running configuration. If the delete method is called and the user + # name does not exist, this method will succeed. + # + # @eos_version 4.13.7M + # + # @commands + # no username + # + # @param [String] :name The user name to delete from the node. + # + # @return [Boolean] returns true if the command completed successfully + def delete(name) + configure("no username #{name}") + end + + ## + # default will configure the user name using the default keyword. This + # command has the same effect as deleting the user name from the nodes + # running configuration. + # + # @eos_version 4.13.7M + # + # @commands + # default username + # + # @param [String] :name The user name to default in the nodes + # configuration. + # + # @return [Boolean] returns true if the command complete successfully + def default(name) + configure("default username #{name}") + end + + ## + # set_privilege configures the user privilege value for the specified user + # name in the nodes running configuration. If enable is false in the + # opts keyword Hash then the name value is negated using the no + # keyword. If the default keyword is set to true, then the privilege value + # is defaulted using the default keyword. The default keyword takes + # precedence over the enable keyword + # + # @eos_version 4.13.7M + # + # @commands + # username privilege + # no username privilege + # default username privilege + # + # @param [String] :name The user name to default in the nodes + # configuration. + # + # @param [Hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The privilege value to assign to the user + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the user privilege value + # using the default keyword + # + # @return [Boolean] returns true if the command completed successfully + def set_privilege(name, opts = {}) + configure(command_builder("username #{name} privilege", opts)) + end + + ## + # set_role configures the user role value for the specified user + # name in the nodes running configuration. If enable is false in the + # opts keyword Hash then the name value is negated using the no + # keyword. If the default keyword is set to true, then the role value + # is defaulted using the default keyword. The default keyword takes + # precedence over the enable keyword + # + # @eos_version 4.13.7M + # + # @commands + # username role + # no username role + # default username role + # + # @param [String] :name The user name to default in the nodes + # configuration. + # + # @param [Hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The role value to assign to the user + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the user role value + # using the default keyword + # + # @return [Boolean] returns true if the command completed successfully + def set_role(name, opts = {}) + configure(command_builder("username #{name} role", opts)) + end + + ## + # set_sshkey configures the user sshkey value for the specified user + # name in the nodes running configuration. If enable is false in the + # opts keyword Hash then the name value is negated using the no + # keyword. If the default keyword is set to true, then the sshkey value + # is defaulted using the default keyword. The default keyword takes + # precedence over the enable keyword + # + # @eos_version 4.13.7M + # + # @commands + # username sshkey + # no username sshkey + # default username sshkey + # + # @param [String] :name The user name to default in the nodes + # configuration. + # + # @param [Hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The sshkey value to assign to the user + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the user sshkey value + # using the default keyword + # + # @return [Boolean] returns true if the command completed successfully + def set_sshkey(name, opts = {}) + configure(command_builder("username #{name} sshkey", opts)) + end + end + end +end diff --git a/lib/rbeapi/api/varp.rb b/lib/rbeapi/api/varp.rb index a493d7a..47d6d53 100644 --- a/lib/rbeapi/api/varp.rb +++ b/lib/rbeapi/api/varp.rb @@ -54,15 +54,19 @@ class Varp < Entity # key / value pairs. def get response = {} + response.merge!(parse_mac_address(config)) + response[:interfaces] = interfaces.getall + response + end - regex = /(?<=^ip\svirtual-router\smac-address\s) - ((?:[a-f0-9]{2}:){5}[a-f0-9]{2})$/x - + def parse_mac_address(config) + # ip virtual-router mac-address value will always + # be stored in aa:bb:cc:dd:ee:ff format + regex = /mac-address ((?:[a-f0-9]{2}:){5}[a-f0-9]{2})$/ mdata = regex.match(config) - response['mac_address'] = mdata.nil? ? '' : mdata[1] - response['interfaces'] = interfaces.getall - response + { mac_address: mdata.nil? ? '' : mdata[1] } end + private :parse_mac_address def interfaces return @interfaces if @interfaces @@ -95,7 +99,6 @@ class VarpInterfaces < Entity # # Example # { - # "name": , # "addresses": array # } # @@ -108,8 +111,8 @@ class VarpInterfaces < Entity def get(name) config = get_block("^interface #{name}") return nil unless config - addrs = config.scan(/(?<=\s{3}ip\svirtual-router\saddress\s).+$/) - { 'addresses' => addrs } + response = parse_addresses(config) + response end ## @@ -118,8 +121,8 @@ def get(name) # # Example # { - # : {...}, - # : {...} + # "name": {...}, + # "name": {...} # } # # @return [nil, Hash] A Ruby hash that represents the @@ -127,12 +130,20 @@ def get(name) # interfaces are configured. def getall interfaces = config.scan(/(?<=^interface\s)(Vl.+)$/) - interfaces.first.each_with_object({}) do |name, resp| - data = get(name) - resp[name] = data if data + return nil unless interfaces + + interfaces.each_with_object({}) do |name, resp| + data = get(name[0]) + resp[name.first] = data if data end end + def parse_addresses(config) + addrs = config.scan(/(?<=\s{3}ip\svirtual-router\saddress\s).+$/) + { addresses: addrs } + end + private :parse_addresses + ## # The set_addresses method assigns one or more virtual IPv4 address # to the specified VLAN interface. All existing addresses are @@ -153,29 +164,46 @@ def set_addresses(name, opts = {}) value = opts[:value] enable = opts.fetch(:enable, true) default = opts[:default] || false + cmds = ["interface #{name}"] case default when true - configure(["interface #{name}", 'default ip virtual-router address']) + cmds << 'default ip virtual-router address' when false - get(name)['addresses'].each do |addr| - result = remove_address(name, addr) - return result unless result - end + cmds << 'no ip virtual-router address' if enable + fail ArgumentError, + 'no values for addresses provided' unless value value.each do |addr| - result = add_address(name, addr) - return result unless result + cmds << "ip virtual-router address #{addr}" end end end - true + configure(cmds) end + ## + # The add_address method assigns one virtual IPv4 address + # + # @param [String] :name The name of the interface. The + # name argument must be the full interface name. Valid interfaces + # are restricted to VLAN interfaces + # @param [string] :address The virtual router address to add + # + # @return [Boolean] True if the commands succeeds otherwise False def add_address(name, value) configure(["interface #{name}", "ip virtual-router address #{value}"]) end + ## + # The remove_address method removes one virtual IPv4 address + # + # @param [String] :name The name of the interface. The + # name argument must be the full interface name. Valid interfaces + # are restricted to VLAN interfaces + # @param [string] :address The virtual router address to remove + # + # @return [Boolean] True if the commands succeeds otherwise False def remove_address(name, value) configure(["interface #{name}", "no ip virtual-router address #{value}"]) diff --git a/lib/rbeapi/api/vrrp.rb b/lib/rbeapi/api/vrrp.rb new file mode 100644 index 0000000..69cf370 --- /dev/null +++ b/lib/rbeapi/api/vrrp.rb @@ -0,0 +1,1072 @@ +# +# Copyright (c) 2015, Arista Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of Arista Networks nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +require 'rbeapi/api' + +## +# Eos is the toplevel namespace for working with Arista EOS nodes +module Rbeapi + ## + # Api is module namespace for working with the EOS command API + module Api + ## + # The Vrrp class manages the set of virtual routers. + # rubocop:disable Metrics/ClassLength + class Vrrp < Entity + def initialize(node) + super(node) + end + + ## + # get returns the all the virtual router IPs for the given layer 3 + # interface name from the nodes current configuration. + # + # rubocop:disable Metrics/MethodLength + # + # @example + # { + # 1: { + # enable: + # primary_ip: + # priority: + # description: + # secondary_ip: [ , ] + # ip_version: + # timers_advertise: + # mac_addr_adv_interval: + # preempt: + # preempt_delay_min: + # preempt_delay_reload: + # delay_reload: + # track: [ + # { name: 'Ethernet3', action: 'decrement', amount: 33 }, + # { name: 'Ethernet2', action: 'decrement', amount: 22 }, + # { name: 'Ethernet2', action: 'shutdown' } + # ] + # } + # } + # + # @param [String] :name The layer 3 interface name. + # + # @return [nil, Hash] Returns the VRRP resource as a + # Hash with the virtual router ID as the key. If the interface name + # does not exist then a nil object is returned. + def get(name) + config = get_block("^interface #{name}") + return nil unless config + + response = {} + + vrids = config.scan(/^\s+(?:no |)vrrp (\d+)/) + vrids.uniq.each do |vrid_arr| + # Parse the vrrp configuration for the vrid(s) in the list + entry = {} + vrid = vrid_arr[0] + entry.merge!(parse_delay_reload(config, vrid)) + entry.merge!(parse_description(config, vrid)) + entry.merge!(parse_enable(config, vrid)) + entry.merge!(parse_ip_version(config, vrid)) + entry.merge!(parse_mac_addr_adv_interval(config, vrid)) + entry.merge!(parse_preempt(config, vrid)) + entry.merge!(parse_preempt_delay_min(config, vrid)) + entry.merge!(parse_preempt_delay_reload(config, vrid)) + entry.merge!(parse_primary_ip(config, vrid)) + entry.merge!(parse_priority(config, vrid)) + entry.merge!(parse_secondary_ip(config, vrid)) + entry.merge!(parse_timers_advertise(config, vrid)) + entry.merge!(parse_track(config, vrid)) + + response[vrid.to_i] = entry unless entry.nil? + end + response + end + + ## + # getall returns the collection of virtual router IPs for all the + # layer 3 interfaces from the nodes running configuration as a hash. + # The resource collection hash is keyed by the ACL name. + # + # @example + # { + # 'Vlan100': { + # 1: { data }, + # 250: { data }, + # }, + # 'Vlan200': { + # 2: { data }, + # 250: { data }, + # } + # } + # + # @return [nil, Hash] Returns a hash that represents + # the entire virtual router IPs collection for all the layer 3 + # interfaces from the nodes running configuration. If there are no + # virtual routers configured, this method will return an empty hash. + def getall + interfaces = config.scan(/(?<=^interface\s).+$/) + interfaces.each_with_object({}) do |name, hsh| + data = get(name) + hsh[name] = data if data + end + end + + ## + # parse_primary_ip scans the nodes configurations for the given + # virtual router id and extracts the primary IP. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'primary_ip', String>] Where string is the IPv4 + # address or nil if the value is not set. + def parse_primary_ip(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} ip (\d+\.\d+\.\d+\.\d+)$/) + if match.empty? + fail 'Did not get a default value for primary_ip' + else + value = match[0][0] + end + { primary_ip: value } + end + private :parse_primary_ip + + ## + # parse_priority scans the nodes configurations for the given + # virtual router id and extracts the priority value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'priority', Integer>] The priority is between + # <1-255> or nil if the value is not set. + def parse_priority(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} priority (\d+)$/) + if match.empty? + fail 'Did not get a default value for priority' + else + value = match[0][0].to_i + end + { priority: value } + end + private :parse_priority + + ## + # parse_timers_advertise scans the nodes configurations for the given + # virtual router id and extracts the timers advertise value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [nil, Hash<'timers_advertise', Integer>] The timers_advertise + # is between <1-255> or nil if the value is not set. + def parse_timers_advertise(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} timers advertise (\d+)$/) + if match.empty? + fail 'Did not get a default value for timers advertise' + else + value = match[0][0].to_i + end + { timers_advertise: value } + end + private :parse_timers_advertise + + ## + # parse_preempt scans the nodes configurations for the given + # virtual router id and extracts the preempt value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [nil, Hash<'preempt', Integer>] The preempt is + # between <1-255> or nil if the value is not set. + def parse_preempt(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} preempt$/) + if match.empty? + value = false + else + value = true + end + { preempt: value } + end + private :parse_preempt + + ## + # parse_enable scans the nodes configurations for the given + # virtual router id and extracts the enable value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'enable', Boolean>] + def parse_enable(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} shutdown$/) + if match.empty? + value = true + else + value = false + end + { enable: value } + end + private :parse_enable + + ## + # parse_secondary_ip scans the nodes configurations for the given + # virtual router id and extracts the secondary_ip value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [nil, Hash<'secondary_ip', Array>] Returns an empty + # array if the value is not set. + def parse_secondary_ip(config, vrid) + regex = "vrrp #{vrid} ip" + matches = config.scan(/^\s+#{regex} (\d+\.\d+\.\d+\.\d+) secondary$/) + response = [] + matches.each do |ip| + response << ip[0] + end + { secondary_ip: response } + end + private :parse_secondary_ip + + ## + # parse_description scans the nodes configurations for the given + # virtual router id and extracts the description. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [nil, Hash<'secondary_ip', String>] Returns nil if the + # value is not set. + def parse_description(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} description\s+(.*)\s*$/) + if match.empty? + value = nil + else + value = match[0][0] + end + { description: value } + end + private :parse_description + + ## + # parse_track scans the nodes configurations for the given + # virtual router id and extracts the track entries. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'track', Array] Returns an empty array if the + # value is not set. An example array of hashes follows: + # { name: 'Ethernet3', action: 'decrement', amount: 33 }, + # { name: 'Ethernet2', action: 'decrement', amount: 22 }, + # { name: 'Ethernet2', action: 'shutdown' } + def parse_track(config, vrid) + pre = "vrrp #{vrid} track " + matches = \ + config.scan(/^\s+#{pre}(\S+) (decrement|shutdown)\s*(?:(\d+$|$))/) + response = [] + matches.each do |name, action, amount| + hsh = { name: name, action: action } + hsh[:amount] = amount.to_i if action == 'decrement' + response << hsh + end + { track: response } + end + private :parse_track + + ## + # parse_ip_version scans the nodes configurations for the given + # virtual router id and extracts the IP version. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'ip_version', Integer>] Returns nil if the + # value is not set. + def parse_ip_version(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} ip version (\d+)$/) + if match.empty? + fail 'Did not get a default value for ip version' + else + value = match[0][0].to_i + end + { ip_version: value } + end + private :parse_ip_version + + ## + # parse_mac_addr_adv_interval scans the nodes configurations for the + # given virtual router id and extracts the mac address advertisement + # interval. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'mac_addr_adv_interval', Integer>] Returns nil if the + # value is not set. + def parse_mac_addr_adv_interval(config, vrid) + regex = "vrrp #{vrid} mac-address advertisement-interval" + match = config.scan(/^\s+#{regex} (\d+)$/) + if match.empty? + fail 'Did not get a default value for mac address ' \ + 'advertisement interval' + else + value = match[0][0].to_i + end + { mac_addr_adv_interval: value } + end + private :parse_mac_addr_adv_interval + + ## + # parse_preempt_delay_min scans the nodes configurations for the given + # virtual router id and extracts the preempt delay minimum value.. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'preempt_delay_min', Integer>] Returns nil if the + # value is not set. + def parse_preempt_delay_min(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} preempt delay minimum (\d+)$/) + if match.empty? + fail 'Did not get a default value for preempt delay minimum' + else + value = match[0][0].to_i + end + { preempt_delay_min: value } + end + private :parse_preempt_delay_min + + ## + # parse_preempt_delay_reload scans the nodes configurations for the + # given virtual router id and extracts the preempt delay reload value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'preempt_delay_reload', Integer>] Returns nil if the + # value is not set. + def parse_preempt_delay_reload(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} preempt delay reload (\d+)$/) + if match.empty? + fail 'Did not get a default value for preempt delay reload' + else + value = match[0][0].to_i + end + { preempt_delay_reload: value } + end + private :parse_preempt_delay_reload + + ## + # parse_delay_reload scans the nodes configurations for the given + # virtual router id and extracts the delay reload value. + # + # @api private + # + # @param [String] :config The interface config. + # @param [String] :vrid The virtual router id. + # + # @return [Hash<'delay_reload', Integer>] Returns empty hash if the + # value is not set. + def parse_delay_reload(config, vrid) + match = config.scan(/^\s+vrrp #{vrid} delay reload (\d+)$/) + if match.empty? + fail 'Did not get a default value for delay reload' + else + value = match[0][0].to_i + end + { delay_reload: value } + end + private :parse_delay_reload + + ## + # create will create a new virtual router ID resource for the interface + # in the nodes current. If the create method is called and the virtual + # router ID already exists for the interface, this method will still + # return true. Create takes optional parameters, but at least one + # parameter needs to be set or the command will fail. + # + # @eos_version 4.13.7M + # + # @commands + # interface + # vrrp ... + # + # @param [String] :name The layer 3 interface name. + # + # @param [String] :vrid The virtual router id. + # + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [Boolean] :enable Enable the virtual router. + # + # @option :opts [String] :primary_ip The primary IPv4 address. + # + # @option :opts [Integer] :priority The priority setting for a virtual + # router. + # + # @option :opts [String] :description Associates a text string to a + # virtual router. + # + # @option :opts [Array] :secondary_ip The secondary IPv4 + # address to the specified virtual router. + # + # @option :opts [Integer] :ip_version Configures the VRRP version for + # the VRRP router. + # + # @option :opts [Integer] :timers_advertise The interval between + # successive advertisement messages that the switch sends to routers + # in the specified virtual router ID. + # + # @option :opts [Integer] :mac_addr_adv_interval Specifies interval in + # seconds between advertisement packets sent to VRRP group members. + # + # @option :opts [Boolean] :preempt A virtual router preempt mode + # setting. When preempt mode is enabled, if the switch has a higher + # priority it will preempt the current master virtual router. When + # preempt mode is disabled, the switch can become the master virtual + # router only when a master virtual router is not present on the + # subnet, regardless of priority settings. + # + # @option :opts [Integer] :preempt_delay_min Interval in seconds between + # VRRP preempt event and takeover. Minimum delays takeover when VRRP + # is fully implemented. + # + # @option :opts [Integer] :preempt_delay_reload Interval in seconds + # between VRRP preempt event and takeover. Reload delays takeover + # after initialization following a switch reload. + # + # @option :opts [Integer] :delay_reload Delay between system reboot and + # VRRP initialization. + # + # @option :opts [Array] :track The track hash contains the + # name of an interface to track, the action to take on state-change + # of the tracked interface, and the amount to decrement the priority. + # + # @return [Boolean] returns true if the command completed successfully + def create(name, vrid, opts = {}) + fail ArgumentError, 'create has no options set' if opts.empty? + cmds = [] + if opts.key?(:enable) + if opts[:enable] + cmds << "no vrrp #{vrid} shutdown" + else + cmds << "vrrp #{vrid} shutdown" + end + end + cmds << "vrrp #{vrid} ip #{opts[:primary_ip]}" if opts.key?(:primary_ip) + if opts.key?(:priority) + cmds << "vrrp #{vrid} priority #{opts[:priority]}" + end + if opts.key?(:description) + cmds << "vrrp #{vrid} description #{opts[:description]}" + end + if opts.key?(:secondary_ip) + cmds += build_secondary_ip_cmd(name, vrid, opts[:secondary_ip]) + end + if opts.key?(:ip_version) + cmds << "vrrp #{vrid} ip version #{opts[:ip_version]}" + end + if opts.key?(:timers_advertise) + cmds << "vrrp #{vrid} timers advertise #{opts[:timers_advertise]}" + end + if opts.key?(:mac_addr_adv_interval) + val = opts[:mac_addr_adv_interval] + cmds << "vrrp #{vrid} mac-address advertisement-interval #{val}" + end + if opts.key?(:preempt) + if opts[:preempt] + cmds << "vrrp #{vrid} preempt" + else + cmds << "no vrrp #{vrid} preempt" + end + end + if opts.key?(:preempt_delay_min) + val = opts[:preempt_delay_min] + cmds << "vrrp #{vrid} preempt delay minimum #{val}" + end + if opts.key?(:preempt_delay_reload) + val = opts[:preempt_delay_reload] + cmds << "vrrp #{vrid} preempt delay reload #{val}" + end + if opts.key?(:delay_reload) + cmds << "vrrp #{vrid} delay reload #{opts[:delay_reload]}" + end + cmds += build_tracks_cmd(name, vrid, opts[:track]) if opts.key?(:track) + configure_interface(name, cmds) + end + + ## + # delete will delete the virtual router ID on the interface from the + # nodes current running configuration. If the delete method is called + # and the virtual router id does not exist on the interface, this + # method will succeed. + # + # @eos_version 4.13.7M + # + # @commands + # interface + # no vrrp + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # + # @return [Boolean] returns true if the command completed successfully + def delete(name, vrid) + configure_interface(name, "no vrrp #{vrid}") + end + + ## + # default will default the virtual router ID on the interface from the + # nodes current running configuration. This command has the same effect + # as deleting the virtual router id from the interface in the nodes + # running configuration. If the default method is called and the + # virtual router id does not exist on the interface, this method will + # succeed. + # + # @eos_version 4.13.7M + # + # @commands + # interface + # default vrrp + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # + # @return [Boolean] returns true if the command complete successfully + def default(name, vrid) + configure_interface(name, "default vrrp #{vrid}") + end + + ## + # set_shutdown enables and disables the virtual router. + # + # @commands + # interface + # {no | default} vrrp shutdown + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [Boolean] :enable If enable is true then the virtual + # router is administratively enabled for the interface and if enable + # is false then the virtual router is administratively disabled + # for the interface. Default is true. + # + # @option :opts [Boolean] :default Configure shutdown using + # the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_shutdown(name, vrid, opts = {}) + fail 'set_shutdown has the value option set' if opts[:value] + # Shutdown semantics are opposite of enable semantics so invert enable + enable = opts.fetch(:enable, true) + opts.merge!(enable: !enable) + cmd = "vrrp #{vrid} shutdown" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_primary_ip sets the primary IP address for the virtual router. + # + # @commands + # interface + # {no | default} vrrp ip + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The primary IPv4 address. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the primary IP address using + # the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_primary_ip(name, vrid, opts = {}) + cmd = "vrrp #{vrid} ip" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_priority sets the priority for a virtual router. + # + # @commands + # interface + # {no | default} vrrp priority + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The priority value. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the priority using + # the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_priority(name, vrid, opts = {}) + cmd = "vrrp #{vrid} priority" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_description sets the description for a virtual router. + # + # @commands + # interface + # {no | default} vrrp description + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The description value. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the description using + # the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_description(name, vrid, opts = {}) + cmd = "vrrp #{vrid} description" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # build_secondary_ip_cmd builds the array of commands required + # to update the secondary IP addresses. This method allows the + # create methods to leverage the code in the setter. + # + # @api private + # + # @param [String] :name The layer 3 interface name. + # + # @param [Integer] :vrid The virtual router ID. + # + # @param [Array] :ip_addrs Array of secondary IPv4 address. + # An empty array will remove all secondary IPv4 addresses set for + # the virtual router on the specified layer 3 interface. + # + # @return [Array] Returns the array of commands. The + # array could be empty. + def build_secondary_ip_cmd(name, vrid, ip_addrs) + ip_addrs = Set.new ip_addrs + + # Get the current secondary IP address set for the virtual router + # A return of nil means that nothing has been configured for + # the virtual router. + vrrp = get(name) + vrrp = [] if vrrp.nil? + + if vrrp.key?(vrid) + current_addrs = Set.new vrrp[vrid][:secondary_ip] + else + current_addrs = Set.new [] + end + + cmds = [] + # Add commands to delete any secondary IP addresses that are + # currently set for the virtual router but not in ip_addrs. + current_addrs.difference(ip_addrs).each do |addr| + cmds << "no vrrp #{vrid} ip #{addr} secondary" + end + + # Add commands to add any secondary IP addresses that are + # not currently set for the virtual router but are in ip_addrs. + ip_addrs.difference(current_addrs).each do |addr| + cmds << "vrrp #{vrid} ip #{addr} secondary" + end + cmds + end + private :build_secondary_ip_cmd + + # set_secondary_ips configures the set of secondary IP addresses + # associated with the virtual router. The ip_addrs value passed + # should be an array of IP Addresses. This method will remove + # secondary IP addresses that are currently set for the virtual + # router but not included in the ip_addrs array value passed in. + # The method will then add secondary IP addresses that are not + # currently set for the virtual router but are included in the + # ip_addrs array value passed in. + # + # @commands + # interface + # {no} vrrp ip secondary + # + # @param [String] :name The layer 3 interface name. + # + # @param [Integer] :vrid The virtual router ID. + # + # @param [Array] :ip_addrs Array of secondary IPv4 address. + # An empty array will remove all secondary IPv4 addresses set for + # the virtual router on the specified layer 3 interface. + # + # @return [Boolean] returns true if the command complete successfully + def set_secondary_ip(name, vrid, ip_addrs) + cmds = build_secondary_ip_cmd(name, vrid, ip_addrs) + return true if cmds.empty? + configure_interface(name, cmds) + end + + ## + # set_ip_version sets the VRRP version for a virtual router. + # + # @commands + # interface + # {no | default} vrrp ip version + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The VRRP version. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the VRRP version using + # the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_ip_version(name, vrid, opts = {}) + cmd = "vrrp #{vrid} ip version" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_timers_advertise sets the interval between successive + # advertisement messages that the switch sends to routers in the + # specified virtual router ID. + # + # @commands + # interface + # {no | default} vrrp timers advertise + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The timer value in seconds. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the timer advertise value + # using the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_timers_advertise(name, vrid, opts = {}) + cmd = "vrrp #{vrid} timers advertise" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_mac_addr_adv_interval sets the interval in seconds between + # advertisement packets sent to VRRP group members for the + # specified virtual router ID. + # + # @commands + # interface + # {no | default} vrrp mac-address advertisement-interval + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The mac address advertisement interval + # value in seconds. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the timer advertise value + # using the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_mac_addr_adv_interval(name, vrid, opts = {}) + cmd = "vrrp #{vrid} mac-address advertisement-interval" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_preempt sets the virtual router's preempt mode setting. When + # preempt mode is enabled, if the switch has a higher priority it + # will preempt the current master virtual router. When preempt mode + # is disabled, the switch can become the master virtual router only + # when a master virtual router is not present on the subnet, + # regardless of priority settings. + # + # @commands + # interface + # {no | default} vrrp preempt + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [Boolean] :enable If enable is true then the virtual + # router preempt mode is administratively enabled for the interface + # and if enable is false then the virtual router preempt mode is + # administratively disabled for the interface. Default is true. + # + # @option :opts [Boolean] :default Configure the timer advertise value + # using the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_preempt(name, vrid, opts = {}) + fail 'set_preempt has the value option set' if opts[:value] + cmd = "vrrp #{vrid} preempt" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_preempt_delay_min sets the minimum time in seconds for the + # virtual router to wait before taking over the active role. + # + # @commands + # interface + # {no | default} vrrp preempt delay minimum + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The preempt delay minimum value. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the preempt delay minimum + # value using the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_preempt_delay_min(name, vrid, opts = {}) + cmd = "vrrp #{vrid} preempt delay minimum" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_preempt_delay_reload sets the preemption delay after a reload + # only. This delay period applies only to the first interface-up + # event after the virtual router has reloaded. + # + # @commands + # interface + # {no | default} vrrp preempt delay reload + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The preempt delay reload value. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the preempt delay reload + # value using the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_preempt_delay_reload(name, vrid, opts = {}) + cmd = "vrrp #{vrid} preempt delay reload" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # set_delay_reload sets the delay between system reboot and VRRP + # initialization for the virtual router. + # + # @commands + # interface + # {no | default} vrrp delay reload + # + # @param [String] :name The layer 3 interface name. + # @param [Integer] :vrid The virtual router ID. + # @param [hash] :opts Optional keyword arguments + # + # @option :opts [String] :value The delay reload value. + # + # @option :opts [Boolean] :enable If false then the command is + # negated. Default is true. + # + # @option :opts [Boolean] :default Configure the delay reload + # value using the default keyword. + # + # @return [Boolean] returns true if the command complete successfully + def set_delay_reload(name, vrid, opts = {}) + cmd = "vrrp #{vrid} delay reload" + configure_interface(name, command_builder(cmd, opts)) + end + + ## + # build_tracks_cmd builds the array of commands required + # to update the tracks. This method allows the + # create methods to leverage the code in the setter. + # + # @api private + # + # @param [String] :name The layer 3 interface name. + # + # @param [Integer] :vrid The virtual router ID. + # + # @param [Array] :tracks Array of a hash of track information. + # Hash format: { name: 'Eth2', action: 'decrement', amount: 33 }, + # The name and action key are required. The amount key should only + # be specified if the action is shutdown. The valid actions are + # 'decrement' and 'shutdown'. An empty array will remove all tracks + # set for the virtual router on the specified layer 3 interface. + # + # @return [Array] Returns the array of commands. The + # array could be empty. + def build_tracks_cmd(name, vrid, tracks) + # Validate the track hash + valid_keys = [:name, :action, :amount] + # rubocop:disable Style/Next + tracks.each do |track| + track.keys do |key| + unless valid_keys.include?(key) + fail ArgumentError, 'Key: #{key} invalid in track hash' + end + end + unless track.key?(:name) && track.key?(:action) + fail ArgumentError, 'Must specify :name and :action in track hash' + end + unless track[:action] == 'decrement' || track[:action] == 'shutdown' + fail ArgumentError, "Action must be 'decrement' or 'shutdown'" + end + if track.key?(:amount) && track[:action] != 'decrement' + fail ArgumentError, "Action must be 'decrement' to set amount" + end + if track.key?(:amount) + track[:amount] = track[:amount].to_i + if track[:amount] < 0 + fail ArgumentError, 'Amount must be greater than zero' + end + end + end + + tracks = Set.new tracks + + # Get the current tracks set for the virtual router + # A return of nil means that nothing has been configured for + # the virtual router. + vrrp = get(name) + vrrp = [] if vrrp.nil? + + if vrrp.key?(vrid) + current_tracks = Set.new vrrp[vrid][:track] + else + current_tracks = Set.new [] + end + + cmds = [] + # Add commands to delete any tracks that are + # currently set for the virtual router but not in tracks. + current_tracks.difference(tracks).each do |tk| + cmds << "no vrrp #{vrid} track #{tk[:name]} #{tk[:action]}" + end + + # Add commands to add any tracks that are + # not currently set for the virtual router but are in tracks. + tracks.difference(current_tracks).each do |tk| + cmd = "vrrp #{vrid} track #{tk[:name]} #{tk[:action]}" + cmd << " #{tk[:amount]}" if tk.key?(:amount) + cmds << cmd + end + cmds + end + private :build_tracks_cmd + + # set_tracks configures the set of track settings associated with + # the virtual router. The tracks value passed should be an array of + # hashes, each hash containing a track entry. This method will remove + # tracks that are currently set for the virtual router but not included + # in the tracks array value passed in. The method will then add + # tracks that are not currently set for the virtual router but are + # included in the tracks array value passed in. + # + # @commands + # interface + # {no} vrrp track [] + # + # @param [String] :name The layer 3 interface name. + # + # @param [Integer] :vrid The virtual router ID. + # + # @param [Array] :tracks Array of a hash of track information. + # Hash format: { name: 'Eth2', action: 'decrement', amount: 33 }, + # An empty array will remove all tracks set for + # the virtual router on the specified layer 3 interface. + # + # @return [Boolean] returns true if the command complete successfully + def set_tracks(name, vrid, tracks) + cmds = build_tracks_cmd(name, vrid, tracks) + return true if cmds.empty? + configure_interface(name, cmds) + end + end + end +end diff --git a/lib/rbeapi/client.rb b/lib/rbeapi/client.rb index 912b615..640c0dd 100644 --- a/lib/rbeapi/client.rb +++ b/lib/rbeapi/client.rb @@ -268,6 +268,7 @@ def add_connection(name, values) # for handling both enable mode and config mode commands class Node attr_reader :connection + attr_accessor :dry_run ## # Save the connection and set autorefresh to true. @@ -277,6 +278,7 @@ class Node def initialize(connection) @connection = connection @autorefresh = true + @dry_run = false end ## @@ -331,12 +333,18 @@ def config(commands, opts = {}) commands = [*commands] unless commands.respond_to?('each') commands.insert(0, 'configure') - response = run_commands(commands, opts) - refresh if @autorefresh + if @dry_run + puts '[rbeapi dry-run commands]' + puts commands + else + response = run_commands(commands, opts) - response.shift - response + refresh if @autorefresh + + response.shift + response + end end ## diff --git a/lib/rbeapi/eapilib.rb b/lib/rbeapi/eapilib.rb index f09d781..c16bce2 100644 --- a/lib/rbeapi/eapilib.rb +++ b/lib/rbeapi/eapilib.rb @@ -301,7 +301,7 @@ def send(data, opts) # adds the list of commands to the exception message def execute(commands, opts = {}) @error = nil - request = request(commands, opts) + request = request(commands, opts) response = send(request, opts) return response['result'] rescue ConnectionError, CommandError => exc diff --git a/lib/rbeapi/version.rb b/lib/rbeapi/version.rb index a8463de..d179c37 100644 --- a/lib/rbeapi/version.rb +++ b/lib/rbeapi/version.rb @@ -33,5 +33,5 @@ # # # Rbeapi toplevel namespace module Rbeapi - VERSION = '0.3.0' + VERSION = '0.4.0' end diff --git a/rbeapi.spec.tmpl b/rbeapi.spec.tmpl index 08a048f..a2faa07 100644 --- a/rbeapi.spec.tmpl +++ b/rbeapi.spec.tmpl @@ -11,31 +11,10 @@ License: New BSD URL: https://github.com/arista-eosplus/rbeapi Source0: %{gem_name}-%{version}.gem -%if 0%{?enterprise:1} == 1 -# Use these settings for Puppet Enterprise -%global gem /opt/puppet/bin/gem -Requires: pe-rubygems -Requires: pe-ruby -Requires: pe-rubygem(net-http-unix) -Requires: pe-rubygem(inifile) -Requires: pe-rubygem(netaddr) -Provides: pe-rubygem(%{gem_name}) = %{version} -Provides: pe-rubygem-%{gem_name} = %{version} -%else -# Use these settings for all other installs -%global gem gem -Requires: ruby(abi) = %{rubyabi} -Requires: ruby(net-http-unix) -Requires: ruby(inifile) -Requires: ruby(netaddr) -Provides: ruby(%{gem_name}) = %{version}-%{release} -%endif - - BuildArch: noarch %description -The Ruby Cliet for eAPI provides a native Ruby implementation for programming +The Ruby Client for eAPI provides a native Ruby implementation for programming Arista EOS network devices using Ruby. The Ruby client provides the ability to build native applications in Ruby that can communicate with EOS either locally via Unix domain sockets (on-box) or remotely over a HTTP/S transport @@ -52,6 +31,31 @@ The library is freely provided to the open source community for building robust applications using Arista EOS eAPI. Support is provided as best effort through Github iusses. +%package puppet3 +Summary: Arista eAPI Ruby Library Puppet Enterprise 3.x agents +Group: Development/Languages +# Use these settings for Puppet Enterprise +Requires: pe-rubygems +Requires: pe-ruby +Requires: pe-rubygem(net-http-unix) +Requires: pe-rubygem(inifile) +Requires: pe-rubygem(netaddr) +Provides: pe-rubygem(%{gem_name}) = %{version} +Provides: pe-rubygem-%{gem_name} = %{version} +%description puppet3 +The Ruby eAPI Client for Puppet Enterprise 3.x agents + +%package puppet-aio +Summary: Arista eAPI Ruby Library for Puppet All-In-One (4.x) agents +Group: Development/Languages +Requires: puppet >= 4.0.0 +Requires: rubygem-net_http_unix +Requires: rubygem-inifile +Requires: rubygem-netaddr +Provides: rubygem-%{gem_name} = %{version} +%description puppet-aio +The Ruby eAPI Client for Puppet All-In-One (4.x) agents + %prep %setup -q -D -T -n . @@ -62,14 +66,42 @@ install %{SOURCE0} %{buildroot}/ %files /%{gem_name}-%{version}.gem +%files puppet3 +/%{gem_name}-%{version}.gem + +%files puppet-aio +/%{gem_name}-%{version}.gem + %post -%{gem} install --local /%{gem_name}-%{version}.gem > /dev/null 2>&1 +GEM_OPTS="--no-document --local" +gem install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 %preun -%{gem} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 +gem uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet3 +GEM=/opt/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet3 +GEM=/opt/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 + +%post puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +GEM_OPTS="--no-document --local" +${GEM} install ${GEM_OPTS} /%{gem_name}-%{version}.gem > /dev/null 2>&1 + +%preun puppet-aio +GEM=/opt/puppetlabs/puppet/bin/gem +${GEM} uninstall %{gem_name} --version '= %{version}' > /dev/null 2>&1 %changelog -* Tue May 21 2015 Jere Julian - 0.1.0-2 +* Fri Oct 30 2015 Jere Julian - 0.4.0-1 +- Detect the location of the puppet-agent's gem install + +* Thu May 21 2015 Jere Julian - 0.1.0-2 - Ubuntu requires we manually create the buildroot * Tue Mar 17 2015 Jere Julian - 0.1.0-1 diff --git a/spec/system/api_varp_spec.rb b/spec/system/api_varp_spec.rb deleted file mode 100644 index 6041604..0000000 --- a/spec/system/api_varp_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'spec_helper' - -require 'rbeapi/client' -require 'rbeapi/api/varp' - -describe Rbeapi::Api::Varp do - subject { described_class.new(node) } - - let(:config) { Rbeapi::Client::Config.new(filename: get_fixture('dut.conf')) } - let(:node) { Rbeapi::Client.connect_to('dut') } - - describe '#get' do - it 'returns a varp resource instance' do - expect(subject.get).to be_a_kind_of(Hash) - end - - it 'has a key for mac_address' do - expect(subject.get).to include('mac_address') - end - - it 'has a key for interfaces' do - expect(subject.get).to include('interfaces') - end - end - - describe '#interfaces' do - it 'is a kind of VarpInterfaces' do - expect(subject.interfaces).to be_a_kind_of(Rbeapi::Api::VarpInterfaces) - end - end - - describe '#set_router_id' do - before { node.config('no ip virtual-router mac-address') } - - it 'configures the ip varp mac-address' do - expect(subject.get['mac_address']).to be_empty - expect(subject.set_mac_address(value: 'aa:bb:cc:dd:ee:ff')).to be_truthy - expect(subject.get['mac_address']).to eq('aa:bb:cc:dd:ee:ff') - end - end -end diff --git a/spec/system/rbeapi/api/dns_spec.rb b/spec/system/rbeapi/api/dns_spec.rb index 63c9414..eb683cb 100644 --- a/spec/system/rbeapi/api/dns_spec.rb +++ b/spec/system/rbeapi/api/dns_spec.rb @@ -44,7 +44,7 @@ end describe '#set_name_servers' do - let(:servers) { %w(1.2.3.4 5.6.7.8 9.10.11.12) } + let(:servers) { %w(1.2.3.4 5.6.7.8 9.10.11.12) } before { node.config('no ip name-server') } @@ -94,7 +94,7 @@ end describe '#set_domain_list' do - let(:servers) { %w(foo bar baz) } + let(:servers) { %w(foo bar baz) } before do node.config(['no ip domain-list foo', diff --git a/spec/system/rbeapi/api/routemaps_spec.rb b/spec/system/rbeapi/api/routemaps_spec.rb new file mode 100644 index 0000000..4b6edca --- /dev/null +++ b/spec/system/rbeapi/api/routemaps_spec.rb @@ -0,0 +1,344 @@ +require 'spec_helper' + +require 'rbeapi/client' +require 'rbeapi/api/routemaps' + +describe Rbeapi::Api::Routemaps do + subject { described_class.new(node) } + + let(:node) do + Rbeapi::Client.config.read(fixture_file('dut.conf')) + Rbeapi::Client.connect_to('dut') + end + + describe '#get' do + let(:resource) { subject.get } + + before do + node.config(['no route-map test', 'no route-map test1', + 'no route-map test2', 'no route-map test3', + 'route-map test permit 10', + 'route-map test permit 20', 'description descript', + 'match ip address prefix-list MYLOOPBACK', + 'match interface Loopback0', + 'set community internet 5555:5555', 'continue 99']) + end + + it 'returns a routemap resource instance' do + expect(subject.get('test')).to be_a_kind_of(Hash) + end + + it 'has a key for description' do + expect(subject.get('test').assoc('permit')[1].assoc(20)[1]) + .to include(:description) + end + + it 'has a key for continue' do + expect(subject.get('test').assoc('permit')[1].assoc(20)[1]) + .to include(:continue) + end + + it 'has a key for match' do + expect(subject.get('test').assoc('permit')[1].assoc(20)[1]) + .to include(:match) + end + + it 'has a key for set' do + expect(subject.get('test').assoc('permit')[1].assoc(20)[1]) + .to include(:set) + end + end + + describe '#getall' do + let(:resource) { subject.getall } + + before do + node.config(['no route-map test', 'no route-map test1', + 'route-map test1 permit 10', 'continue 99', + 'route-map test permit 10', + 'route-map test permit 20', 'description descript', + 'match ip address prefix-list MYLOOPBACK', + 'match interface Loopback0', + 'set community internet 5555:5555', 'continue 99']) + end + + let(:test1_entries) do + { + 'test1' => { + 'permit' => { + 10 => { + continue: 99 + } + } + }, + 'test' => { + 'permit' => { + 10 => {}, + 20 => { + continue: 99, + description: 'descript', + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'], + set: ['community internet 5555:5555'] + } + } + } + } + end + + it 'returns a routemap resource instance' do + expect(subject.getall).to be_a_kind_of(Hash) + end + + it 'has a key for description' do + expect(subject.getall.count).to eq(2) + end + + it 'returns the routemap collection' do + expect(subject.getall).to include(test1_entries) + end + end + + describe '#create' do + let(:test_entry) do + { + 'permit' => { + 20 => { + continue: 99, + description: 'descript', + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'], + set: ['community internet 5555:5555'] + } + } + } + end + + before do + node.config(['no route-map test', 'no route-map test1']) + end + + it 'creates the routemap with all options' do + expect(subject.get('test')).to eq(nil) + expect(subject + .create('test', 'permit', 20, + continue: 99, description: 'descript', + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'], + set: ['community internet 5555:5555']) + ).to be_truthy + expect(subject.get('test')).to eq(test_entry) + end + + it 'creates the routemap with no options' do + expect(subject.get('test1')).to eq(nil) + expect(subject.create('test1', 'permit', 10)).to be_truthy + expect(subject.get('test1')).to be_truthy + expect(subject.get('test1').assoc('permit')[0]).to eq('permit') + expect( + subject.get('test1').assoc('permit')[1].assoc(10)[0]).to eq(10) + expect( + subject.get('test1').assoc('permit')[1].assoc(10)[1][:continue] + ).to eq(nil) + expect( + subject.get('test1').assoc('permit')[1].assoc(10)[1][:description] + ).to eq(nil) + expect( + subject.get('test1').assoc('permit')[1].assoc(10)[1][:match] + ).to eq(nil) + expect( + subject.get('test1').assoc('permit')[1].assoc(10)[1][:set] + ).to eq(nil) + end + end + + describe '#delete' do + before do + node.config(['route-map test', + 'route-map test1 permit 20', + 'route-map test1 permit 10', + 'route-map test2 permit 10', + 'route-map test2 permit 20']) + end + + it 'removes the routemap' do + expect(subject.get('test')).to eq('permit' => { 10 => {} }) + expect(subject.delete('test', 'permit', 10)).to be_truthy + expect(subject.get('test')).to eq(nil) + end + + it 'removes multiple routemaps with same name' do + expect(subject.get('test1')) + .to eq('permit' => { 10 => {}, 20 => {} }) + expect(subject.delete('test1', 'permit', 20)).to be_truthy + expect(subject.get('test1')).to eq('permit' => { 10 => {} }) + end + end + + describe '#delete' do + before do + node.config(['route-map test', + 'route-map test1 permit 20', + 'route-map test1 permit 10', + 'route-map test2 permit 10', + 'route-map test2 permit 20']) + end + + it 'removes the routemap' do + expect(subject.get('test')).to eq('permit' => { 10 => {} }) + expect(subject.delete('test', 'permit', 10)).to be_truthy + expect(subject.get('test')).to eq(nil) + end + + it 'removes multiple routemaps with same name' do + expect(subject.get('test1')) + .to eq('permit' => { 10 => {}, 20 => {} }) + expect(subject.delete('test1', 'permit', 20)).to be_truthy + expect(subject.get('test1')).to eq('permit' => { 10 => {} }) + end + end + + describe '#set_match_statements' do + before do + node.config(['route-map test permit 10', + 'no match ip address prefix-list MYLOOPBACK', + 'no match interface Vlan100', + 'no match interface Loopback1', + 'match interface Loopback1', + 'no route-map test1']) + end + + it 'sets match statements on an existing routemap' do + expect(subject.get('test')) + .to eq('permit' => { 10 => { match: ['interface Loopback1'] } }) + expect( + subject.set_match_statements('test', 'permit', 10, + ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'])).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'] } }) + end + + it 'adds more match statements' do + expect(subject.get('test')) + .to eq('permit' => { 10 => { match: ['interface Loopback1'] } }) + expect(subject.set_match_statements('test', 'permit', 10, + ['interface Vlan100'])).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { match: ['interface Vlan100'] } }) + expect(subject + .set_match_statements('test', 'permit', 10, + ['interface Vlan100', + 'ip address prefix-list MYLOOPBACK']) + ).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { + match: ['ip address prefix-list MYLOOPBACK', + 'interface Vlan100'] } }) + expect(subject + .set_match_statements('test', 'permit', 10, + ['interface Vlan100'])).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { match: ['interface Vlan100'] } }) + end + + it 'adds match statements to a new seqno' do + expect(subject.get('test')) + .to eq('permit' => { 10 => { match: ['interface Loopback1'] } }) + expect(subject.set_match_statements('test', 'permit', 20, + ['interface Vlan100'])).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { match: ['interface Loopback1'] }, + 20 => { match: ['interface Vlan100'] } }) + end + + it 'set match statements on a new routemap' do + expect(subject.get('test1')).to eq(nil) + expect(subject.set_match_statements('test1', 'permit', 10, + ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'])).to be_truthy + expect(subject.get('test1')) + .to eq('permit' => { 10 => { + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'] } }) + end + end + + describe '#set_set_statements' do + before do + node.config(['no route-map test', 'no route-map test1', + 'route-map test permit 10', + 'set community internet 3333:3333']) + end + + it 'set set statements on an existing routemap' do + expect(subject.get('test')) + .to eq('permit' => { 10 => { set: ['community internet 3333:3333'] } }) + expect(subject + .set_set_statements('test', 'permit', 10, + ['origin igp'])).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { set: ['origin igp'] } }) + end + + it 'set set statements on a new routemap' do + expect(subject.get('test1')).to eq(nil) + expect(subject + .set_set_statements('test1', 'permit', 10, + ['community internet 5555:5555', + 'community internet 4444:4444'])).to be_truthy + expect(subject.get('test1')) + .to eq('permit' => { 10 => { + set: ['community internet 4444:4444 5555:5555'] } }) + end + end + + describe '#set_continue' do + before do + node.config(['no route-map test', 'no route-map test1', + 'route-map test permit 10', 'continue 50']) + end + + it 'set continue on an existing routemap' do + expect(subject.get('test')) + .to eq('permit' => { 10 => { continue: 50 } }) + expect(subject.set_continue('test', 'permit', 10, 99)).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { continue: 99 } }) + end + + it 'set continue on a new routemap' do + expect(subject.get('test1')).to eq(nil) + expect(subject.set_continue('test1', 'permit', 10, 99)).to be_truthy + expect(subject.get('test1')) + .to eq('permit' => { 10 => { continue: 99 } }) + end + end + + describe '#set_description' do + before do + node.config(['no route-map test', 'no route-map test1', + 'route-map test permit 10', 'description temp']) + end + + it 'set description on an existing routemap' do + expect(subject.get('test')) + .to eq('permit' => { 10 => { description: 'temp' } }) + expect(subject + .set_description('test', 'permit', 10, 'descript')).to be_truthy + expect(subject.get('test')) + .to eq('permit' => { 10 => { description: 'descript' } }) + end + + it 'set description on a new routemap' do + expect(subject.get('test1')).to eq(nil) + expect(subject + .set_description('test1', 'permit', 10, + 'descript')).to be_truthy + expect(subject.get('test1')) + .to eq('permit' => { 10 => { description: 'descript' } }) + end + end +end diff --git a/spec/system/rbeapi/api/switchports_spec.rb b/spec/system/rbeapi/api/switchports_spec.rb index 715aa8b..dff9316 100644 --- a/spec/system/rbeapi/api/switchports_spec.rb +++ b/spec/system/rbeapi/api/switchports_spec.rb @@ -112,7 +112,7 @@ end describe '#set_access_vlan' do - before { node.config(['default interface Ethernet1', 'vlan 100']) } + before { node.config(['default interface Ethernet1', 'vlan 100']) } it 'sets the access vlan value to 100' do expect(subject.get('Ethernet1')[:access_vlan]).to eq('1') diff --git a/spec/system/rbeapi/api/system_spec.rb b/spec/system/rbeapi/api/system_spec.rb index 3d1b3a1..f828f78 100644 --- a/spec/system/rbeapi/api/system_spec.rb +++ b/spec/system/rbeapi/api/system_spec.rb @@ -13,18 +13,18 @@ describe '#get' do let(:entity) do - { hostname: 'localhost' } + { hostname: 'localhost', iprouting: true } end - before { node.config('hostname localhost') } + before { node.config(['hostname localhost', 'ip routing']) } it 'returns the snmp resource' do expect(subject.get).to eq(entity) end end - describe '#set_system' do - before { node.config('hostname localhost') } + describe '#set_hostname' do + before { node.config(['hostname localhost']) } it 'configures the system hostname value' do expect(subject.get[:hostname]).to eq('localhost') @@ -47,5 +47,45 @@ expect(subject.set_hostname(default: true)).to be_truthy expect(subject.get[:hostname]).to be_empty end + + it 'configures the system hostname value' do + expect(subject.get[:iprouting]).to eq(true) + expect(subject.set_iprouting(enable: true)).to be_truthy + expect(subject.get[:iprouting]).to eq(true) + end + end + + describe '#set_iprouting' do + describe 'negates ip routing' do + before { node.config(['ip routing']) } + + it 'negates ip routing' do + expect(subject.get[:iprouting]).to eq(true) + expect(subject.set_iprouting(enable: false)).to be_truthy + expect(subject.get[:iprouting]).to eq(false) + end + + it 'defaults ip routing' do + expect(subject.get[:iprouting]).to eq(true) + expect(subject.set_iprouting(default: true)).to be_truthy + expect(subject.get[:iprouting]).to eq(false) + end + end + + describe 'enables ip routing' do + before { node.config(['no ip routing']) } + + it 'negates ip routing' do + expect(subject.get[:iprouting]).to eq(false) + expect(subject.set_iprouting(enable: true)).to be_truthy + expect(subject.get[:iprouting]).to eq(true) + end + + it 'defaults ip routing' do + expect(subject.get[:iprouting]).to eq(false) + expect(subject.set_iprouting(default: false)).to be_truthy + expect(subject.get[:iprouting]).to eq(true) + end + end end end diff --git a/spec/system/api_varp_interfaces_spec.rb b/spec/system/rbeapi/api/varp_interfaces_spec.rb similarity index 58% rename from spec/system/api_varp_interfaces_spec.rb rename to spec/system/rbeapi/api/varp_interfaces_spec.rb index de0d0fa..276dae9 100644 --- a/spec/system/api_varp_interfaces_spec.rb +++ b/spec/system/rbeapi/api/varp_interfaces_spec.rb @@ -6,18 +6,21 @@ describe Rbeapi::Api::VarpInterfaces do subject { described_class.new(node) } - let(:config) { Rbeapi::Client::Config.new(filename: get_fixture('dut.conf')) } - let(:node) { Rbeapi::Client.connect_to('dut') } + let(:node) do + Rbeapi::Client.config.read(fixture_file('dut.conf')) + Rbeapi::Client.connect_to('dut') + end describe '#get' do before do node.config(['ip virtual-router mac-address aabb.ccdd.eeff', - 'default interface vlan 100', 'interface vlan 100', + 'no interface Vlan99', 'no interface Vlan100', + 'default interface Vlan100', 'interface Vlan100', 'ip address 99.99.99.99/24', 'ip virtual-router address 99.99.99.98', 'exit']) end - it 'returns an instance for vlan 100' do + it 'returns an instance for Vlan100' do expect(subject.get('Vlan100')).not_to be_nil end @@ -29,12 +32,13 @@ describe '#getall' do before do node.config(['ip virtual-router mac-address aabb.ccdd.eeff', - 'default interface vlan 100', 'interface vlan 100', + 'no interface Vlan99', 'no interface Vlan100', + 'default interface Vlan100', 'interface Vlan100', 'ip address 99.99.99.99/24', 'ip virtual-router address 99.99.99.98', 'exit']) end - it 'returns a collection that includes vlan 100' do + it 'returns a collection that includes Vlan100' do expect(subject.getall).to include('Vlan100') end @@ -46,40 +50,45 @@ describe '#set_addresses' do before do node.config(['ip virtual-router mac-address aabb.ccdd.eeff', - 'default interface vlan 100', 'interface vlan 100', + 'no interface Vlan99', 'no interface Vlan100', + 'default interface Vlan100', 'interface Vlan100', 'ip address 99.99.99.99/24', 'exit']) end it 'adds new address to the list of addresses' do - expect(subject.get('Vlan100')['addresses']).not_to include('99.99.99.98') + expect(subject.get('Vlan100')[:addresses]).not_to include('99.99.99.98') expect(subject.set_addresses('Vlan100', value: ['99.99.99.98'])) .to be_truthy - expect(subject.get('Vlan100')['addresses']).to include('99.99.99.98') + expect(subject.get('Vlan100')[:addresses]).to include('99.99.99.98') end - it 'removes address to the list of addresses' do + it 'removes address from the list of addresses' do node.config(['interface vlan 100', 'ip address 99.99.99.99/24', 'ip virtual-router address 99.99.99.98']) - expect(subject.get('Vlan100')['addresses']).to include('99.99.99.98') + expect(subject.get('Vlan100')[:addresses]).to include('99.99.99.98') expect(subject.set_addresses('Vlan100', value: ['99.99.99.97'])) .to be_truthy - expect(subject.get('Vlan100')['addresses']).not_to include('99.99.99.98') + expect(subject.get('Vlan100')[:addresses]).not_to include('99.99.99.98') end it 'negate the list of addresses' do expect(subject.set_addresses('Vlan100', value: ['99.99.99.98'])) .to be_truthy - expect(subject.get('Vlan100')['addresses']).to include('99.99.99.98') + expect(subject.get('Vlan100')[:addresses]).to include('99.99.99.98') expect(subject.set_addresses('Vlan100', enable: false)).to be_truthy - expect(subject.get('Vlan100')['addresses']).to be_empty + expect(subject.get('Vlan100')[:addresses]).to be_empty end it 'default the list of addresses' do expect(subject.set_addresses('Vlan100', value: ['99.99.99.98'])) .to be_truthy - expect(subject.get('Vlan100')['addresses']).to include('99.99.99.98') + expect(subject.get('Vlan100')[:addresses]).to include('99.99.99.98') expect(subject.set_addresses('Vlan100', default: true)).to be_truthy - expect(subject.get('Vlan100')['addresses']).to be_empty + expect(subject.get('Vlan100')[:addresses]).to be_empty + end + + it 'can not evaluate without addresses' do + expect { subject.set_addresses('Vlan100') }.to raise_error ArgumentError end end end diff --git a/spec/system/rbeapi/api/varp_spec.rb b/spec/system/rbeapi/api/varp_spec.rb new file mode 100644 index 0000000..bf7773f --- /dev/null +++ b/spec/system/rbeapi/api/varp_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +require 'rbeapi/client' +require 'rbeapi/api/varp' + +describe Rbeapi::Api::Varp do + subject { described_class.new(node) } + + let(:node) do + Rbeapi::Client.config.read(fixture_file('dut.conf')) + Rbeapi::Client.connect_to('dut') + end + + describe '#get' do + let(:resource) { subject.get } + + before do + node.config(['no ip virtual-router mac-address', + 'no interface Vlan99', 'no interface Vlan100', + 'ip virtual-router mac-address aa:bb:cc:dd:ee:ff', + 'interface Vlan99', 'interface Vlan100']) + end + + it 'returns a varp resource instance' do + expect(subject.get).to be_a_kind_of(Hash) + end + + it 'has a key for mac_address' do + expect(subject.get).to include(:mac_address) + end + + it 'has a key for interfaces' do + expect(subject.get).to include(:interfaces) + end + end + + describe '#interfaces' do + it 'is a kind of VarpInterfaces' do + expect(subject.interfaces).to be_a_kind_of(Rbeapi::Api::VarpInterfaces) + end + end + + describe '#set_mac_address' do + before { node.config('no ip virtual-router mac-address') } + + it 'set mac-address to aa:bb:cc:dd:ee:ff' do + expect(subject.get[:mac_address]).to be_empty + expect(subject.set_mac_address(value: 'aa:bb:cc:dd:ee:ff')).to be_truthy + expect(subject.get[:mac_address]).to eq('aa:bb:cc:dd:ee:ff') + end + + it 'set mac-address to ff-ff-ff-ff-ff-ff' do + expect(subject.get[:mac_address]).to be_empty + expect(subject.set_mac_address(value: 'ff-ff-ff-ff-ff-ff')).to be_truthy + expect(subject.get[:mac_address]).to eq('ff:ff:ff:ff:ff:ff') + end + + it 'set mac-address to ffff.ffff.ffff' do + expect(subject.get[:mac_address]).to be_empty + expect(subject.set_mac_address(value: 'ffff.ffff.ffff')).to be_truthy + expect(subject.get[:mac_address]).to eq('ff:ff:ff:ff:ff:ff') + end + + it 'set mac-address to ffff:ffff:ffff fails' do + expect(subject.get[:mac_address]).to be_empty + expect(subject.set_mac_address(value: 'ffff:ffff:ffff')).to be_falsey + expect(subject.get[:mac_address]).to eq('') + end + + it 'set mac-address to ff.ff.ff.ff.ff.ff fails' do + expect(subject.get[:mac_address]).to be_empty + expect(subject.set_mac_address(value: 'ff.ff.ff.ff.ff.ff')).to be_falsey + expect(subject.get[:mac_address]).to eq('') + end + end +end diff --git a/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb b/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb index 822d946..11ae02e 100644 --- a/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb +++ b/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb @@ -44,6 +44,8 @@ { bgp_as: '64600', router_id: '192.168.254.1', shutdown: false, + maximum_paths: 32, + maximum_ecmp_paths: 32, networks: [ { prefix: '192.168.254.1', masklen: 32, route_map: nil }, { prefix: '192.168.254.2', masklen: 32, route_map: 'rmap' }, diff --git a/spec/unit/rbeapi/api/bgp/bgp_spec.rb b/spec/unit/rbeapi/api/bgp/bgp_spec.rb index c9f84a1..3b73ca4 100644 --- a/spec/unit/rbeapi/api/bgp/bgp_spec.rb +++ b/spec/unit/rbeapi/api/bgp/bgp_spec.rb @@ -44,6 +44,8 @@ { bgp_as: '64600', router_id: '192.168.254.1', shutdown: false, + maximum_paths: 32, + maximum_ecmp_paths: 32, networks: [ { prefix: '192.168.254.1', masklen: 32, route_map: nil }, { prefix: '192.168.254.2', masklen: 32, route_map: 'rmap' }, @@ -88,9 +90,40 @@ def bgp describe '#create' do it 'create a new BGP resource' do - expect(node).to receive(:config).with('router bgp 1000') + expect(node).to receive(:config).with(['router bgp 1000']) expect(subject.create('1000')).to be_truthy end + it 'create with enable' do + expect(node).to receive(:config).with(['router bgp 1000', 'no shutdown']) + expect(subject.create('1000', enable: true)).to be_truthy + end + it 'create with router_id' do + expect(node).to receive(:config).with(['router bgp 1000', 'router-id 1']) + expect(subject.create('1000', router_id: 1)).to be_truthy + end + it 'create with maximum paths' do + expect(node).to receive(:config).with(['router bgp 1000', + 'maximum-paths 1']) + expect(subject.create('1000', maximum_paths: 1)).to be_truthy + end + it 'create with maximum paths and ecmp paths' do + expect(node).to receive(:config).with(['router bgp 1000', + 'maximum-paths 13 ecmp 13']) + expect(subject.create('1000', maximum_paths: 13, + maximum_ecmp_paths: 13)).to be_truthy + end + it 'raises ArgumentError for create with ecmp paths only' do + expect { subject.create('1000', maximum_ecmp_paths: 13) }.to \ + raise_error ArgumentError + end + it 'create with all options set' do + expect(node).to receive(:config).with(['router bgp 1000', 'no shutdown', + 'router-id 1', + 'maximum-paths 13 ecmp 13']) + expect(subject.create('1000', enable: true, router_id: 1, + maximum_paths: 13, + maximum_ecmp_paths: 13)).to be_truthy + end end describe '#delete' do @@ -162,6 +195,26 @@ def bgp end end + describe '#set_maximum_paths' do + it 'set the maximum paths and ecmp paths' do + expect(node).to receive(:config).with(['router bgp 64600', + 'maximum-paths 13 ecmp 200']) + expect(subject.set_maximum_paths(13, 200)).to be_truthy + end + + it 'remove the maximum paths' do + expect(node).to receive(:config).with(['router bgp 64600', + 'no maximum-paths']) + expect(subject.set_maximum_paths(0, 0, enable: false)).to be_truthy + end + + it 'defaults the maximum paths' do + expect(node).to receive(:config).with(['router bgp 64600', + 'default maximum-paths']) + expect(subject.set_maximum_paths(0, 0, default: true)).to be_truthy + end + end + describe '#add_network' do it 'add a BGP network with a route map' do expect(node).to receive(:config) diff --git a/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb b/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb index b476229..2c04d88 100644 --- a/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb +++ b/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb @@ -22,7 +22,7 @@ def interfaces describe '#get' do before :each do allow(subject.node).to receive(:enable) - .with(include('show port-channel'), format: 'text') + .with(include('show port-channel'), encoding: 'text') .and_return([{ result: { 'output' => "Port Channel Port-Channel1:\n Active " \ 'Ports: Ethernet1 PeerEthernet1 ' \ diff --git a/spec/unit/rbeapi/api/routemaps/default_spec.rb b/spec/unit/rbeapi/api/routemaps/default_spec.rb new file mode 100644 index 0000000..a80c886 --- /dev/null +++ b/spec/unit/rbeapi/api/routemaps/default_spec.rb @@ -0,0 +1,370 @@ +# +# Copyright (c) 2015, Arista Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of Arista Networks nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +require 'spec_helper' + +require 'rbeapi/api/routemaps' + +include FixtureHelpers + +describe Rbeapi::Api::Routemaps do + subject { described_class.new(node) } + + let(:node) { double('node') } + + let(:test) do + { + 'permit' => { + 10 => { + match: ['interface Loopback0', + 'ip address prefix-list MYLOOPBACK'], + set: ['community internet 5555:5555'], + description: 'description', + continue: 99 + } + } + } + end + let(:name) { 'test1' } + + def routemaps + routemaps = Fixtures[:routemaps] + return routemaps if routemaps + fixture('routemaps', format: :text, dir: File.dirname(__FILE__)) + end + + before :each do + allow(subject.node).to receive(:running_config).and_return(routemaps) + end + + describe '#getall' do + let(:test1_entries) do + { + 'test1' => { + 'permit' => { + 10 => { + match: ['interface Loopback0', + 'ip address prefix-list MYLOOPBACK'], + set: ['community internet 5555:5555'], + description: 'description', + continue: 99 + } + } + }, + 'test' => { + 'permit' => { + 10 => { + match: ['interface Vlan100'], + description: 'description', + continue: 99 + }, + 20 => { + continue: 99, + description: 'description', + set: ['community internet 5555:5555'] + } + }, + 'deny' => { + 10 => { + match: ['interface Vlan100'], + description: 'description', + continue: 99 + }, + 20 => { + continue: 99, + description: 'description', + set: ['community internet 5555:5555'] + } + } + } + } + end + + it 'returns the routemap collection' do + expect(subject.getall).to include(test1_entries) + end + + it 'returns a hash collection' do + expect(subject.getall).to be_a_kind_of(Hash) + end + + it 'has two entries' do + expect(subject.getall.size).to eq(2) + end + end + + describe '#get' do + it 'returns the routemap resource for given name' do + expect(subject.get(name)).to eq(test) + end + + it 'returns a hash' do + expect(subject.get(name)).to be_a_kind_of(Hash) + end + + it 'has two entries' do + expect(subject.get(name).size).to eq(1) + end + end + + describe '#create' do + it 'create an existing routemap test1 permit 10' do + expect(node).to receive(:config).with(['route-map test1 permit 10']) + expect(subject.create('test1', 'permit', 10)).to be_truthy + end + + it 'create an existing routemap test deny 20' do + expect(node).to receive(:config).with(['route-map test deny 20']) + expect(subject.create('test', 'deny', 20)).to be_truthy + end + + it 'create a new routemap test4 permit 20' do + expect(node).to receive(:config).with(['route-map test4 permit 20']) + expect(subject.create('test4', 'permit', 20)).to be_truthy + end + + it 'create a new routemap test4 permit 20 with enable false' do + expect(node).to receive(:config).with(['no route-map test4 permit 20']) + expect(subject.create('test4', 'permit', 20, enable: false)).to be_truthy + end + + it 'create a new routemap test4 permit 20 with enable true' do + expect(node).to receive(:config).with(['route-map test4 permit 20']) + expect(subject.create('test4', 'permit', 20, enable: true)).to be_truthy + end + + it 'add description to routemap test1 permit 10 with create' do + expect(node).to receive(:config) + .with(['route-map test1 permit 10', 'no description', + 'description description']) + expect(subject.create('test1', 'permit', 10, + description: 'description')).to be_truthy + end + + it 'add description to routemap test deny 20 with create' do + expect(node).to receive(:config) + .with(['route-map test deny 20', 'no description', + 'description description']) + expect(subject.create('test', 'deny', 20, + description: 'description')).to be_truthy + end + + it 'add match statements to routemap test1 permit 10 with create' do + expect(node).to receive(:config) + .with(['route-map test1 permit 10', + 'no match interface Loopback0', + 'no match ip address prefix-list MYLOOPBACK', + 'match ip address prefix-list MYLOOPBACK', + 'match interface Loopback0']) + expect(subject.create('test1', 'permit', 10, + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'])).to be_truthy + end + + it 'add match statements to routemap test deny 20 with create' do + expect(node).to receive(:config) + .with(['route-map test deny 20', + 'match ip address prefix-list MYLOOPBACK', + 'match interface Loopback0']) + expect(subject.create('test', 'deny', 20, + match: ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0'])).to be_truthy + end + + it 'add set statements to routemap test1 permit 10 with create' do + expect(node).to receive(:config) + .with(['route-map test1 permit 10', + 'no set community internet 5555:5555', + 'set community internet 5555:5555']) + expect(subject.create('test1', 'permit', 10, + set: ['community internet 5555:5555'])).to be_truthy + end + + it 'add set statements to routemap test deny 20 with create' do + expect(node).to receive(:config) + .with(['route-map test deny 20', + 'no set community internet 5555:5555', + 'set community internet 5555:5555']) + expect(subject.create('test', 'deny', 20, + set: ['community internet 5555:5555'])).to be_truthy + end + + it 'add continue to routemap test1 permit 10 with create' do + expect(node).to receive(:config) + .with(['route-map test1 permit 10', 'no continue', 'continue 99']) + expect(subject.create('test1', 'permit', 10, + continue: 99)).to be_truthy + end + + it 'add continue to routemap test deny 20 with create' do + expect(node).to receive(:config) + .with(['route-map test deny 20', 'no continue', 'continue 99']) + expect(subject.create('test', 'deny', 20, + continue: 99)).to be_truthy + end + + it 'default routemap test permit 10 with create' do + expect(node).to receive(:config) + .with(['default route-map test1 permit 10']) + expect(subject.create('test1', 'permit', 10, + default: true)).to be_truthy + end + + it 'default routemap test deny 20 with create' do + expect(node).to receive(:config) + .with(['default route-map test deny 20']) + expect(subject.create('test', 'deny', 20, + default: true)).to be_truthy + end + end + + describe '#delete' do + it 'delete test1 permit 10 routemap resource' do + expect(node).to receive(:config).with(['no route-map test1 permit 10']) + expect(subject.delete('test1', 'permit', 10)).to be_truthy + end + + it 'delete test deny 20 routemap resource' do + expect(node).to receive(:config).with(['no route-map test deny 20']) + expect(subject.delete('test', 'deny', 20)).to be_truthy + end + + it 'delete non existent routemap' do + expect(node).to receive(:config).with(['no route-map blah deny 30']) + expect(subject.delete('blah', 'deny', 30)).to be_truthy + end + end + + describe '#default' do + it 'default test1 permit 10 routemap resource' do + expect(node).to receive(:config) + .with(['default route-map test1 permit 10']) + expect(subject.default('test1', 'permit', 10)).to be_truthy + end + end + + describe '#set_match_statements' do + it 'set the match statements on exsiting routemap' do + expect(node).to receive(:config) + .with(['route-map test1 permit 10', + 'no match interface Loopback0', + 'no match ip address prefix-list MYLOOPBACK', + 'match ip address prefix-list MYLOOPBACK', + 'match interface Loopback0']) + expect( + subject + .set_match_statements('test1', 'permit', 10, + ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0']) + ).to be_truthy + expect(subject.get('test1').assoc('permit')[1].assoc(10)[1][:match]) + .to include('ip address prefix-list MYLOOPBACK', + 'interface Loopback0') + end + + it 'set the match statements on a new routemap' do + expect(node).to receive(:config) + .with(['route-map test4 permit 20', + 'match ip address prefix-list MYLOOPBACK', + 'match interface Loopback0']) + expect( + subject + .set_match_statements('test4', 'permit', 20, + ['ip address prefix-list MYLOOPBACK', + 'interface Loopback0']) + ).to be_truthy + end + end + + describe '#set_set_statements' do + it 'set the set statements on existing routemap' do + expect(node).to receive(:config) + .with(['route-map test1 permit 10', + 'no set community internet 5555:5555', + 'set community internet 5555:5555']) + expect( + subject.set_set_statements('test1', 'permit', 10, + ['community internet 5555:5555']) + ).to be_truthy + expect(subject.get('test1').assoc('permit')[1].assoc(10)[1][:set]) + .to include('community internet 5555:5555') + end + + it 'set the set statements on new routemap' do + expect(node).to receive(:config) + .with(['route-map test4 permit 20', + 'set community internet 5555:5555']) + expect( + subject.set_set_statements('test4', 'permit', 20, + ['community internet 5555:5555']) + ).to be_truthy + end + end + + describe '#set_continue' do + it 'set the continue statement on existing routemap' do + expect(node).to receive(:config).with(['route-map test1 permit 10', + 'no continue', + 'continue 99']) + expect(subject.set_continue('test1', 'permit', 10, 99)).to be_truthy + expect(subject.get('test1').assoc('permit')[1] + .assoc(10)[1][:continue]).to eq(99) + end + + it 'set the continue statement on new routemap' do + expect(node).to receive(:config).with(['route-map test4 permit 10', + 'no continue', + 'continue 99']) + expect(subject.set_continue('test4', 'permit', 10, 99)).to be_truthy + end + end + + describe '#set_description' do + it 'set the description statement on existing routemap' do + expect(node).to receive(:config).with(['route-map test1 permit 10', + 'no description', + 'description description']) + expect(subject.set_description('test1', 'permit', 10, + 'description')).to be_truthy + expect(subject.get('test1') + .assoc('permit')[1].assoc(10)[1][:description]) + .to eq('description') + end + + it 'set the description statement on new routemap' do + expect(node).to receive(:config).with(['route-map test4 permit 20', + 'no description', + 'description description']) + expect(subject.set_description('test4', 'permit', 20, + 'description')).to be_truthy + end + end +end diff --git a/spec/unit/rbeapi/api/routemaps/fixture_routemaps.text b/spec/unit/rbeapi/api/routemaps/fixture_routemaps.text new file mode 100644 index 0000000..d6910fd --- /dev/null +++ b/spec/unit/rbeapi/api/routemaps/fixture_routemaps.text @@ -0,0 +1,27 @@ +route-map test1 permit 10 + description description + match interface Loopback0 + match ip address prefix-list MYLOOPBACK + continue 99 + set community internet 5555:5555 +! +route-map test permit 10 + description description + match interface Vlan100 + continue 99 +! +route-map test permit 20 + description description + continue 99 + set community internet 5555:5555 +! +route-map test deny 10 + description description + match interface Vlan100 + continue 99 +! +route-map test deny 20 + description description + continue 99 + set community internet 5555:5555 +! \ No newline at end of file diff --git a/spec/unit/rbeapi/api/system/default_spec.rb b/spec/unit/rbeapi/api/system/default_spec.rb new file mode 100644 index 0000000..cb4ea8c --- /dev/null +++ b/spec/unit/rbeapi/api/system/default_spec.rb @@ -0,0 +1,102 @@ +# +# Copyright (c) 2015, Arista Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of Arista Networks nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +require 'spec_helper' + +require 'rbeapi/api/system' + +include FixtureHelpers + +describe Rbeapi::Api::System do + subject { described_class.new(node) } + + let(:node) { double('node') } + + let(:test) do + { hostname: 'localhost', iprouting: true } + end + + def system + system = Fixtures[:system] + return system if system + fixture('system', format: :text, dir: File.dirname(__FILE__)) + end + + before :each do + allow(subject.node).to receive(:running_config).and_return(system) + end + + describe '#get' do + it 'returns the username collection' do + expect(subject.get).to include(test) + end + + it 'returns a hash collection' do + expect(subject.get).to be_a_kind_of(Hash) + end + + it 'has two entries' do + expect(subject.get.size).to eq(2) + end + end + + describe '#set_hostname' do + it 'sets the hostname' do + expect(node).to receive(:config).with('hostname localhost') + expect(subject.set_hostname(value: 'localhost')).to be_truthy + expect(subject.get[:hostname]).to eq('localhost') + end + end + + describe '#set_iprouting' do + it 'sets ip routing default true' do + expect(node).to receive(:config).with('default ip routing') + expect(subject.set_iprouting(default: true)).to be_truthy + end + + it 'sets ip routing default false' do + expect(node).to receive(:config).with('ip routing') + expect(subject.set_iprouting(default: false)).to be_truthy + expect(subject.get[:iprouting]).to eq(true) + end + + it 'sets ip routing enable true' do + expect(node).to receive(:config).with('ip routing') + expect(subject.set_iprouting(enable: true)).to be_truthy + expect(subject.get[:iprouting]).to eq(true) + end + + it 'sets ip routing enable false' do + expect(node).to receive(:config).with('no ip routing') + expect(subject.set_iprouting(enable: false)).to be_truthy + end + end +end diff --git a/spec/unit/rbeapi/api/system/fixture_system.text b/spec/unit/rbeapi/api/system/fixture_system.text new file mode 100644 index 0000000..f84a0fc --- /dev/null +++ b/spec/unit/rbeapi/api/system/fixture_system.text @@ -0,0 +1,2 @@ +hostname localhost +ip routing diff --git a/spec/unit/rbeapi/api/users/default_spec.rb b/spec/unit/rbeapi/api/users/default_spec.rb new file mode 100644 index 0000000..c395f67 --- /dev/null +++ b/spec/unit/rbeapi/api/users/default_spec.rb @@ -0,0 +1,280 @@ +# +# Copyright (c) 2015, Arista Networks, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of Arista Networks nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +require 'spec_helper' + +require 'rbeapi/api/users' + +include FixtureHelpers + +describe Rbeapi::Api::Users do + subject { described_class.new(node) } + + let(:node) { double('node') } + + let(:sshkey) do + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKL1UtBALa4CvFUsHUipN' \ + 'ymA04qCXuAtTwNcMj84bTUzUI+q7mdzRCTLkllXeVxKuBnaTm2PW7W67K5C' \ + 'Vpl0EVCm6IY7FS7kc4nlnD/tFvTvShy/fzYQRAdM7ZfVtegW8sMSFJzBR/T' \ + '/Y/sxI16Y/dQb8fC3la9T25XOrzsFrQiKRZmJGwg8d+0RLxpfMg0s/9ATwQ' \ + 'Kp6tPoLE4f3dKlAgSk5eENyVLA3RsypWADHpenHPcB7sa8D38e1TS+n+EUy' \ + 'Adb3Yov+5ESAbgLIJLd52Xv+FyYi0c2L49ByBjcRrupp4zfXn4DNRnEG4K6' \ + 'GcmswHuMEGZv5vjJ9OYaaaaaaa' + end + + let(:test) do + { name: 'rbeapi', + privilege: 1, + role: nil, + nopassword: false, + encryption: 'md5', + secret: '$1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1', + sshkey: sshkey + } + end + let(:name) { test[:name] } + + def users + users = Fixtures[:users] + return users if users + fixture('users', format: :text, dir: File.dirname(__FILE__)) + end + + before :each do + allow(subject.node).to receive(:running_config).and_return(users) + end + + describe '#getall' do + let(:test1_entries) do + { 'admin' => { name: 'admin', privilege: 1, + role: 'network-admin', nopassword: true, + encryption: nil, secret: nil, sshkey: nil }, + 'rbeapi' => { name: 'rbeapi', privilege: 1, role: nil, + nopassword: false, encryption: 'md5', + secret: '$1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1', + sshkey: sshkey }, + 'rbeapi1' => { name: 'rbeapi1', privilege: 2, + role: 'network-minon', nopassword: false, + encryption: 'cleartext', secret: 'icanttellyou', + sshkey: nil } + } + end + + it 'returns the username collection' do + expect(subject.getall).to include(test1_entries) + end + + it 'returns a hash collection' do + expect(subject.getall).to be_a_kind_of(Hash) + end + + it 'has two entries' do + expect(subject.getall.size).to eq(3) + end + end + + describe '#get' do + it 'returns the user resource for given name' do + expect(subject.get(name)).to eq(test) + end + + it 'returns a hash' do + expect(subject.get(name)).to be_a_kind_of(Hash) + end + + it 'has two entries' do + expect(subject.get(name).size).to eq(7) + end + end + + describe '#create' do + it 'create a new user name with no password' do + expect(node).to receive(:config).with(['username rbeapi nopassword']) + expect(subject.create('rbeapi', nopassword: :true)).to be_truthy + end + it 'create a new user name with no password and privilege' do + expect(node).to receive(:config) + .with(['username rbeapi privilege 4 nopassword']) + expect(subject.create('rbeapi', + privilege: 4, + nopassword: :true)).to be_truthy + end + it 'create a new user name with no password, privilege, and role' do + expect(node).to receive(:config) + .with(['username rbeapi privilege 4 role net-minion nopassword']) + expect(subject.create('rbeapi', + privilege: 4, + role: 'net-minion', + nopassword: :true)).to be_truthy + end + it 'create a new user name with a password' do + expect(node).to receive(:config) + .with(['username rbeapi secret 0 icanttellyou']) + expect(subject.create('rbeapi', secret: 'icanttellyou')).to be_truthy + end + it 'create a new user name with a password and privilege' do + expect(node).to receive(:config) + .with(['username rbeapi privilege 5 secret 0 icanttellyou']) + expect(subject.create('rbeapi', + secret: 'icanttellyou', + privilege: 5)).to be_truthy + end + it 'create a new user name with a password, privilege, and role' do + expect(node).to receive(:config) + .with(['username rbeapi privilege 5 role net secret 0 icanttellyou']) + expect(subject.create('rbeapi', + secret: 'icanttellyou', + privilege: 5, role: 'net')).to be_truthy + end + it 'create a new user name with a password and md5 encryption' do + expect(node).to receive(:config) + .with(['username rbeapi secret 5 icanttellyou']) + expect(subject.create('rbeapi', + secret: 'icanttellyou', + encryption: 'md5')).to be_truthy + end + it 'create a new user name with a password and sha512 encryption' do + expect(node).to receive(:config) + .with(['username rbeapi secret sha512 icanttellyou']) + expect(subject.create('rbeapi', + secret: 'icanttellyou', + encryption: 'sha512')).to be_truthy + end + it 'create a new user name with a password, sha512 encryption, and key' do + expect(node).to receive(:config) + .with(['username rbeapi secret sha512 icanttellyou', + "username rbeapi sshkey #{sshkey}"]) + expect(subject.create('rbeapi', + secret: 'icanttellyou', + encryption: 'sha512', + sshkey: sshkey)).to be_truthy + end + it 'raises ArgumentError for create without required args ' do + expect { subject.create('rbeapi') }.to \ + raise_error ArgumentError + end + it 'raises ArgumentError for invalid encryption value' do + expect { subject.create('name', encryption: 'bogus') }.to \ + raise_error ArgumentError + end + end + + describe '#delete' do + it 'delete a username resource' do + expect(node).to receive(:config).with('no username user1') + expect(subject.delete('user1')).to be_truthy + end + end + + describe '#default' do + it 'sets username resource to default value' do + expect(node).to receive(:config) + .with('default username user1') + expect(subject.default('user1')).to be_truthy + end + end + + describe '#set_privilege' do + it 'set the privilege' do + expect(node).to receive(:config).with('username rbeapi privilege 13') + expect(subject.set_privilege('rbeapi', value: '13')).to be_truthy + end + + it 'remove the privilege without a value' do + expect(node).to receive(:config).with('no username rbeapi privilege') + expect(subject.set_privilege('rbeapi', enable: false)).to be_truthy + end + + it 'remove the privilege with a value' do + expect(node).to receive(:config).with('no username rbeapi privilege 13') + expect(subject.set_privilege('rbeapi', value: '13', enable: false)) + .to be_truthy + end + + it 'defaults the privilege without a value' do + expect(node).to receive(:config).with('default username rbeapi privilege') + expect(subject.set_privilege('rbeapi', default: true)).to be_truthy + end + + it 'defaults the privilege with a value' do + expect(node).to receive(:config).with('default username rb privilege 3') + expect(subject.set_privilege('rb', value: '3', default: true)) + .to be_truthy + end + end + + describe '#set_role' do + it 'set the role' do + expect(node).to receive(:config).with('username rbeapi role net-minion') + expect(subject.set_role('rbeapi', value: 'net-minion')).to be_truthy + end + + it 'remove the role without a value' do + expect(node).to receive(:config).with('no username rbeapi role') + expect(subject.set_role('rbeapi', enable: false)).to be_truthy + end + + it 'remove the role with a value' do + expect(node).to receive(:config).with('no username rbeapi role net') + expect(subject.set_role('rbeapi', value: 'net', enable: false)) + .to be_truthy + end + + it 'defaults the role without a value' do + expect(node).to receive(:config).with('default username rbeapi role') + expect(subject.set_role('rbeapi', default: true)).to be_truthy + end + + it 'defaults the role with a value' do + expect(node).to receive(:config).with('default username rbeapi role net') + expect(subject.set_role('rbeapi', value: 'net', default: true)) + .to be_truthy + end + end + + describe '#set_sshkey' do + it 'set the sshkey' do + expect(node).to receive(:config).with("username rbeapi sshkey #{sshkey}") + expect(subject.set_sshkey('rbeapi', value: sshkey)).to be_truthy + end + + it 'remove the sshkey with a value' do + expect(node).to receive(:config).with("no username rb sshkey #{sshkey}") + expect(subject.set_sshkey('rb', value: sshkey, enable: false)) + .to be_truthy + end + + it 'defaults the sshkey without a value' do + expect(node).to receive(:config).with('default username rbeapi sshkey') + expect(subject.set_sshkey('rbeapi', default: true)).to be_truthy + end + end +end diff --git a/spec/unit/rbeapi/api/users/fixture_users.text b/spec/unit/rbeapi/api/users/fixture_users.text new file mode 100644 index 0000000..92aab03 --- /dev/null +++ b/spec/unit/rbeapi/api/users/fixture_users.text @@ -0,0 +1,4 @@ +username admin privilege 1 role network-admin nopassword +username rbeapi1 privilege 2 role network-minon secret 0 icanttellyou +username rbeapi privilege 1 secret 5 $1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1 +username rbeapi sshkey ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKL1UtBALa4CvFUsHUipNymA04qCXuAtTwNcMj84bTUzUI+q7mdzRCTLkllXeVxKuBnaTm2PW7W67K5CVpl0EVCm6IY7FS7kc4nlnD/tFvTvShy/fzYQRAdM7ZfVtegW8sMSFJzBR/T/Y/sxI16Y/dQb8fC3la9T25XOrzsFrQiKRZmJGwg8d+0RLxpfMg0s/9ATwQKp6tPoLE4f3dKlAgSk5eENyVLA3RsypWADHpenHPcB7sa8D38e1TS+n+EUyAdb3Yov+5ESAbgLIJLd52Xv+FyYi0c2L49ByBjcRrupp4zfXn4DNRnEG4K6GcmswHuMEGZv5vjJ9OYaaaaaaa diff --git a/spec/unit/rbeapi/api/vrrp/default_spec.rb b/spec/unit/rbeapi/api/vrrp/default_spec.rb new file mode 100644 index 0000000..60031ea --- /dev/null +++ b/spec/unit/rbeapi/api/vrrp/default_spec.rb @@ -0,0 +1,582 @@ +require 'spec_helper' + +require 'rbeapi/api/vrrp' + +include FixtureHelpers + +describe Rbeapi::Api::Vrrp do + subject { described_class.new(node) } + + let(:node) { double('node') } + + def vrrp + vrrp = Fixtures[:vrrp] + return vrrp if vrrp + fixture('vrrp', format: :text, dir: File.dirname(__FILE__)) + end + + before :all do + @sec_ips = ['1.2.3.1', '1.2.3.2', '1.2.3.3', '1.2.3.4'] + @tracks = [{ name: 'Ethernet3', action: 'decrement', amount: 33 }, + { name: 'Ethernet2', action: 'decrement', amount: 22 }, + { name: 'Ethernet2', action: 'shutdown' }] + + # Create the secondary IP commands array + @sec_ips_cmds = [] + @sec_ips.each do |addr| + @sec_ips_cmds << "vrrp 9 ip #{addr} secondary" + end + + # Create the track commands array + @track_cmds = [] + @tracks.each do |tk| + cmd = "vrrp 9 track #{tk[:name]} #{tk[:action]}" + cmd << " #{tk[:amount]}" if tk.key?(:amount) + @track_cmds << cmd + end + end + + before :each do + allow(subject.node).to receive(:running_config).and_return(vrrp) + end + + describe '#get' do + let(:entity) do + { 30 => { primary_ip: '40.10.5.31', delay_reload: 0, + description: 'The description', enable: false, ip_version: 2, + mac_addr_adv_interval: 30, preempt: false, preempt_delay_min: 0, + preempt_delay_reload: 0, priority: 100, secondary_ip: [], + timers_advertise: 1, + track: [ + { name: 'Ethernet1', action: 'decrement', amount: 5 } + ] + }, + 40 => { primary_ip: '40.10.5.32', delay_reload: 0, description: nil, + enable: true, ip_version: 2, mac_addr_adv_interval: 30, + preempt: true, preempt_delay_min: 0, preempt_delay_reload: 0, + priority: 200, secondary_ip: [], timers_advertise: 1, + track: @tracks + } + } + end + + it 'returns the virtual router resource' do + expect(subject.get('Vlan150')).to eq(entity) + end + end + + describe '#getall' do + it 'returns the virtual router collection' do + expect(subject.getall).to include('Vlan100') + expect(subject.getall).to include('Vlan150') + end + + it 'returns a hash collection' do + expect(subject.getall).to be_a_kind_of(Hash) + end + + it 'has only one entry' do + expect(subject.getall.size).to eq(2) + end + end + + describe '#create' do + before :all do + @values = [ + { option: :enable, value: true, cmd: ['no vrrp 9 shutdown'] }, + { option: :enable, value: false, cmd: ['vrrp 9 shutdown'] }, + { option: :primary_ip, value: '1.2.3.4', cmd: ['vrrp 9 ip 1.2.3.4'] }, + { option: :priority, value: 100, cmd: ['vrrp 9 priority 100'] }, + { option: :description, value: 'Desc', + cmd: ['vrrp 9 description Desc'] }, + { option: :secondary_ip, value: @sec_ips, cmd: @sec_ips_cmds }, + { option: :ip_version, value: 2, cmd: ['vrrp 9 ip version 2'] }, + { option: :timers_advertise, value: 77, + cmd: ['vrrp 9 timers advertise 77'] }, + { option: :mac_addr_adv_interval, value: 77, + cmd: ['vrrp 9 mac-address advertisement-interval 77'] }, + { option: :preempt, value: true, cmd: ['vrrp 9 preempt'] }, + { option: :preempt, value: false, cmd: ['no vrrp 9 preempt'] }, + { option: :preempt_delay_min, value: 100, + cmd: ['vrrp 9 preempt delay minimum 100'] }, + { option: :preempt_delay_reload, value: 100, + cmd: ['vrrp 9 preempt delay reload 100'] }, + { option: :delay_reload, value: 100, cmd: ['vrrp 9 delay reload 100'] }, + { option: :track, value: @tracks, cmd: @track_cmds } + ] + + # Build the testcases specifying one option per test + @test_opts1 = [] + @values.each do |entry| + opts = Hash[entry[:option], entry[:value]] + @test_opts1.push(opts: opts, cmds: entry[:cmd]) + end + + # Build the testcases specifying two options per test + @test_opts2 = [] + @values.each_with_index do |entry1, idx1| + @values.each_with_index do |entry2, idx2| + # Skip if both options are the same + next if entry1[:option] == entry2[:option] + # Skip if already generated a testcase for this pair + next if idx2 <= idx1 + opts = Hash[entry1[:option], entry1[:value], + entry2[:option], entry2[:value]] + @test_opts2.push(opts: opts, cmds: entry1[:cmd] + entry2[:cmd]) + end + end + end + + it 'creates a new virtual router resource with one option set' do + @test_opts1.each do |test| + cmds = ['interface Vlan100'] + cmds += test[:cmds] + + expect(node).to receive(:config).with(cmds) + expect(subject.create('Vlan100', 9, test[:opts])).to be_truthy + end + end + + it 'creates a new virtual router resource with two options set' do + @test_opts2.each do |test| + cmds = ['interface Vlan100'] + cmds += test[:cmds] + + expect(node).to receive(:config).with(cmds) + expect(subject.create('Vlan100', 9, test[:opts])).to be_truthy + end + end + + it 'creates a new virtual router resource with all options set' do + @test_opts3 = [] + opts = {} + cmds = ['interface Vlan100'] + @values.each do |entry| + # Skip boolean pairs in the options that are false because + # the option can only be set once. + next unless entry[:value] + opts[entry[:option]] = entry[:value] + entry[:cmd].each do |cmd| + cmds << cmd + end + end + + expect(node).to receive(:config).with(cmds) + expect(subject.create('Vlan100', 9, opts)).to be_truthy + end + + it 'raises ArgumentError for create without options' do + expect { subject.create('Vlan100', 9) }.to \ + raise_error ArgumentError + end + end + + describe '#delete' do + it 'deletes a virtual router resource' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9']) + expect(subject.delete('Vlan100', 9)).to be_truthy + end + end + + describe '#default' do + it 'sets virtual router resource to default' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9']) + expect(subject.default('Vlan100', 9)).to be_truthy + end + end + + describe '#set_shutdown' do + it 'enable Vlan100 vrid 9' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 shutdown']) + expect(subject.set_shutdown('Vlan100', 9)).to be_truthy + end + + it 'disable Vlan100 vrid 9' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 shutdown']) + expect(subject.set_shutdown('Vlan100', 9, enable: false)).to be_truthy + end + + it 'defaults Vlan100 vrid 9' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 shutdown']) + expect(subject.set_shutdown('Vlan100', 9, default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 shutdown']) + expect(subject.set_shutdown('Vlan100', 9, enable: false, + default: true)).to be_truthy + end + end + + describe '#set_primary_ip' do + it 'set primary IP address' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 ip 1.2.3.4']) + expect(subject.set_primary_ip('Vlan100', 9, + value: '1.2.3.4')).to be_truthy + end + + it 'disable primary IP address' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 ip 1.2.3.4']) + expect(subject.set_primary_ip('Vlan100', 9, value: '1.2.3.4', + enable: false)).to be_truthy + end + + it 'defaults primary IP address' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 ip 1.2.3.4']) + expect(subject.set_primary_ip('Vlan100', 9, value: '1.2.3.4', + default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 ip 1.2.3.4']) + expect(subject.set_primary_ip('Vlan100', 9, enable: false, + value: '1.2.3.4', + default: true)).to be_truthy + end + end + + describe '#set_priority' do + it 'set priority' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 priority 13']) + expect(subject.set_priority('Vlan100', 9, value: 13)).to be_truthy + end + + it 'disable priority' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 priority']) + expect(subject.set_priority('Vlan100', 9, enable: false)).to be_truthy + end + + it 'defaults priority' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 priority']) + expect(subject.set_priority('Vlan100', 9, default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 priority']) + expect(subject.set_priority('Vlan100', 9, enable: false, + default: true)).to be_truthy + end + end + + describe '#set_description' do + it 'set description' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 description Howdy']) + expect(subject.set_description('Vlan100', 9, + value: 'Howdy')).to be_truthy + end + + it 'disable description' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 description']) + expect(subject.set_description('Vlan100', 9, enable: false)).to be_truthy + end + + it 'defaults description' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 description']) + expect(subject.set_description('Vlan100', 9, default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 description']) + expect(subject.set_description('Vlan100', 9, enable: false, + default: true)).to be_truthy + end + end + + describe '#set_secondary_ip' do + before :all do + @cmds = ['interface Vlan100'] + @cmds += @sec_ips_cmds + end + it 'set secondary IP addresses' do + # Set current IP addresses + expect(node).to receive(:config).with(@cmds) + expect(subject.set_secondary_ip('Vlan100', 9, @sec_ips)).to be_truthy + end + + it 'remove all secondary IP addresses' do + # Set current IP addresses + expect(node).to receive(:config).with(@cmds) + expect(subject.set_secondary_ip('Vlan100', 9, @sec_ips)).to be_truthy + # Delete all IP addresses + expect(subject.set_secondary_ip('Vlan100', 9, [])).to be_truthy + end + end + + describe '#set_ip_version' do + it 'set VRRP version' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 ip version 3']) + expect(subject.set_ip_version('Vlan100', 9, value: 3)).to be_truthy + end + + it 'disable VRRP version' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 ip version']) + expect(subject.set_ip_version('Vlan100', 9, enable: false)).to be_truthy + end + + it 'defaults VRRP version' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 ip version']) + expect(subject.set_ip_version('Vlan100', 9, default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 ip version']) + expect(subject.set_ip_version('Vlan100', 9, enable: false, + default: true)).to be_truthy + end + end + + describe '#set_timers_advertise' do + it 'set advertise timer' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 timers advertise 7']) + expect(subject.set_timers_advertise('Vlan100', 9, value: 7)).to be_truthy + end + + it 'disable advertise timer' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 timers advertise']) + expect(subject.set_timers_advertise('Vlan100', 9, + enable: false)).to be_truthy + end + + it 'defaults advertise timer' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 timers advertise']) + expect(subject.set_timers_advertise('Vlan100', 9, + default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 timers advertise']) + expect(subject.set_timers_advertise('Vlan100', 9, + enable: false, + default: true)).to be_truthy + end + end + + describe '#set_mac_addr_adv_interval' do + it 'set mac address advertisement interval' do + expect(node).to receive(:config) + .with(['interface Vlan100', + 'vrrp 9 mac-address advertisement-interval 12']) + expect(subject.set_mac_addr_adv_interval('Vlan100', 9, + value: 12)).to be_truthy + end + + it 'disable mac address advertisement interval' do + expect(node).to receive(:config) + .with(['interface Vlan100', + 'no vrrp 9 mac-address advertisement-interval']) + expect(subject.set_mac_addr_adv_interval('Vlan100', 9, + enable: false)).to be_truthy + end + + it 'defaults mac address advertisement interval' do + expect(node).to receive(:config) + .with(['interface Vlan100', + 'default vrrp 9 mac-address advertisement-interval']) + expect(subject.set_mac_addr_adv_interval('Vlan100', 9, + default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config) + .with(['interface Vlan100', + 'default vrrp 9 mac-address advertisement-interval']) + expect(subject.set_mac_addr_adv_interval('Vlan100', 9, + enable: false, + default: true)).to be_truthy + end + end + + describe '#set_preempt' do + it 'enable preempt mode' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 preempt']) + expect(subject.set_preempt('Vlan100', 9)).to be_truthy + end + + it 'disable preempt mode' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 preempt']) + expect(subject.set_preempt('Vlan100', 9, enable: false)).to be_truthy + end + + it 'defaults preempt mode' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 preempt']) + expect(subject.set_preempt('Vlan100', 9, default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config).with(['interface Vlan100', + 'default vrrp 9 preempt']) + expect(subject.set_preempt('Vlan100', 9, enable: false, + default: true)).to be_truthy + end + end + + describe '#set_preempt_delay_min' do + it 'enable preempt mode' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 preempt delay minimum 8']) + expect(subject.set_preempt_delay_min('Vlan100', 9, value: 8)).to be_truthy + end + + it 'disable preempt mode' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 preempt delay minimum']) + expect(subject.set_preempt_delay_min('Vlan100', 9, + enable: false)).to be_truthy + end + + it 'defaults preempt mode' do + expect(node).to receive(:config) + .with(['interface Vlan100', 'default vrrp 9 preempt delay minimum']) + expect(subject.set_preempt_delay_min('Vlan100', 9, + default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config) + .with(['interface Vlan100', 'default vrrp 9 preempt delay minimum']) + expect(subject.set_preempt_delay_min('Vlan100', 9, + enable: false, + default: true)).to be_truthy + end + end + + describe '#set_preempt_delay_reload' do + it 'enable preempt delay reload' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 preempt delay reload 8']) + expect(subject.set_preempt_delay_reload('Vlan100', 9, + value: 8)).to be_truthy + end + + it 'disable preempt delay reload' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 preempt delay reload']) + expect(subject.set_preempt_delay_reload('Vlan100', 9, + enable: false)).to be_truthy + end + + it 'defaults preempt delay reload' do + expect(node).to receive(:config) + .with(['interface Vlan100', 'default vrrp 9 preempt delay reload']) + expect(subject.set_preempt_delay_reload('Vlan100', 9, + default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config) + .with(['interface Vlan100', 'default vrrp 9 preempt delay reload']) + expect(subject.set_preempt_delay_reload('Vlan100', 9, + enable: false, + default: true)).to be_truthy + end + end + + describe '#set_delay_reload' do + it 'enable delay reload' do + expect(node).to receive(:config).with(['interface Vlan100', + 'vrrp 9 delay reload 8']) + expect(subject.set_delay_reload('Vlan100', 9, value: 8)).to be_truthy + end + + it 'disable delay reload' do + expect(node).to receive(:config).with(['interface Vlan100', + 'no vrrp 9 delay reload']) + expect(subject.set_delay_reload('Vlan100', 9, enable: false)).to be_truthy + end + + it 'defaults delay reload' do + expect(node).to receive(:config) + .with(['interface Vlan100', 'default vrrp 9 delay reload']) + expect(subject.set_delay_reload('Vlan100', 9, default: true)).to be_truthy + end + + it 'default option takes precedence' do + expect(node).to receive(:config) + .with(['interface Vlan100', 'default vrrp 9 delay reload']) + expect(subject.set_delay_reload('Vlan100', 9, + enable: false, + default: true)).to be_truthy + end + end + + describe '#set_tracks' do + before :all do + @cmds = ['interface Vlan100'] + @cmds += @track_cmds + + @bad_key = [{ nombre: 'Ethernet3', action: 'decrement', amount: 33 }] + @miss_key = [{ action: 'decrement', amount: 33 }] + @bad_action = [{ name: 'Ethernet3', action: 'dec', amount: 33 }] + @sem_key = [{ name: 'Ethernet3', action: 'shutdown', amount: 33 }] + @bad_amount = [{ name: 'Ethernet3', action: 'decrement', amount: -1 }] + end + + it 'set tracks' do + # Set current IP addresses + expect(node).to receive(:config).with(@cmds) + expect(subject.set_tracks('Vlan100', 9, @tracks)).to be_truthy + end + + it 'remove all tracks' do + # Set current IP addresses + expect(node).to receive(:config).with(@cmds) + expect(subject.set_tracks('Vlan100', 9, @tracks)).to be_truthy + # Delete all IP addresses + expect(subject.set_tracks('Vlan100', 9, [])).to be_truthy + end + + it 'raises ArgumentError for track hash with a bad key' do + expect { subject.set_tracks('Vlan100', 9, @bad_key) }.to \ + raise_error ArgumentError + end + + it 'raises ArgumentError for track hash with missing required key' do + expect { subject.set_tracks('Vlan100', 9, @miss_key) }.to \ + raise_error ArgumentError + end + + it 'raises ArgumentError for track hash with invalid action' do + expect { subject.set_tracks('Vlan100', 9, @bad_action) }.to \ + raise_error ArgumentError + end + + it 'raises ArgumentError for track hash with shutdown and amount' do + expect { subject.set_tracks('Vlan100', 9, @sem_key) }.to \ + raise_error ArgumentError + end + + it 'raises ArgumentError for track hash with negative amount' do + expect { subject.set_tracks('Vlan100', 9, @bad_amount) }.to \ + raise_error ArgumentError + end + end +end diff --git a/spec/unit/rbeapi/api/vrrp/fixture_vrrp.text b/spec/unit/rbeapi/api/vrrp/fixture_vrrp.text new file mode 100644 index 0000000..2026e9d --- /dev/null +++ b/spec/unit/rbeapi/api/vrrp/fixture_vrrp.text @@ -0,0 +1,186 @@ +interface Vlan100 + no description + no shutdown + default load-interval + mtu 1500 + logging event link-status use-global + autostate + no private-vlan mapping + snmp trap link-status + no ip proxy-arp + no ip local-proxy-arp + ip address 10.10.4.2/24 + no ip verify unicast + default arp timeout 14400 + default ipv6 nd cache expire 14400 + bfd interval 300 min_rx 300 multiplier 3 + no bfd echo + default ip dhcp smart-relay + no ip helper-address + no ipv6 dhcp relay destination + ip dhcp relay information option circuit-id Vlan100 + no ip igmp + ip igmp version 3 + ip igmp last-member-query-count 2 + ip igmp last-member-query-interval 10 + ip igmp query-max-response-time 100 + ip igmp query-interval 125 + ip igmp startup-query-count 2 + ip igmp startup-query-interval 310 + ip igmp router-alert optional connected + ip igmp host-proxy + no ip igmp host-proxy report-interval + ip igmp host-proxy version 3 + no ip igmp host-proxy + no ipv6 enable + no ipv6 address + no ipv6 verify unicast + no ipv6 nd ra suppress + ipv6 nd ra interval msec 200000 + ipv6 nd ra lifetime 1800 + no ipv6 nd ra mtu suppress + no ipv6 nd managed-config-flag + no ipv6 nd other-config-flag + ipv6 nd reachable-time 0 + ipv6 nd router-preference medium + ipv6 nd ra dns-servers lifetime 300 + ipv6 nd ra dns-suffixes lifetime 300 + ipv6 nd ra hop-limit 64 + ip mfib fastdrop + default ntp serve + no ip pim sparse-mode + no ip pim border-router + ip pim query-interval 30 + ip pim join-prune-interval 60 + ip pim dr-priority 1 + no ip pim neighbor-filter + default ip pim bfd-instance + no ip pim bsr-border + no ip virtual address + vrrp 10 priority 100 + vrrp 10 timers advertise 1 + vrrp 10 mac-address advertisement-interval 30 + no vrrp 10 preempt + vrrp 10 preempt delay minimum 0 + vrrp 10 preempt delay reload 0 + vrrp 10 delay reload 0 + no vrrp 10 authentication + vrrp 10 ip 10.10.4.10 + vrrp 10 ip 1.2.3.4 secondary + vrrp 10 ip 10.10.4.4 secondary + vrrp 10 ipv6 :: + no vrrp 10 description + no vrrp 10 shutdown + no vrrp 10 bfd ip + no vrrp 10 bfd ipv6 + vrrp 10 ip version 2 + vrrp 20 priority 200 + vrrp 20 timers advertise 1 + vrrp 20 mac-address advertisement-interval 30 + no vrrp 20 preempt + vrrp 20 preempt delay minimum 0 + vrrp 20 preempt delay reload 0 + vrrp 20 delay reload 0 + no vrrp 20 authentication + vrrp 20 ip 10.10.4.20 + vrrp 20 ipv6 :: + no vrrp 20 description + no vrrp 20 shutdown + no vrrp 20 bfd ip + no vrrp 20 bfd ipv6 + vrrp 20 ip version 2 +! +interface Vlan150 + no description + no shutdown + default load-interval + mtu 1500 + logging event link-status use-global + autostate + no private-vlan mapping + snmp trap link-status + no ip proxy-arp + no ip local-proxy-arp + ip address 40.10.5.8/24 + no ip verify unicast + default arp timeout 14400 + default ipv6 nd cache expire 14400 + bfd interval 300 min_rx 300 multiplier 3 + no bfd echo + default ip dhcp smart-relay + no ip helper-address + no ipv6 dhcp relay destination + ip dhcp relay information option circuit-id Vlan150 + no ip igmp + ip igmp version 3 + ip igmp last-member-query-count 2 + ip igmp last-member-query-interval 10 + ip igmp query-max-response-time 100 + ip igmp query-interval 125 + ip igmp startup-query-count 2 + ip igmp startup-query-interval 310 + ip igmp router-alert optional connected + ip igmp host-proxy + no ip igmp host-proxy report-interval + ip igmp host-proxy version 3 + no ip igmp host-proxy + no ipv6 enable + no ipv6 address + no ipv6 verify unicast + no ipv6 nd ra suppress + ipv6 nd ra interval msec 200000 + ipv6 nd ra lifetime 1800 + no ipv6 nd ra mtu suppress + no ipv6 nd managed-config-flag + no ipv6 nd other-config-flag + ipv6 nd reachable-time 0 + ipv6 nd router-preference medium + ipv6 nd ra dns-servers lifetime 300 + ipv6 nd ra dns-suffixes lifetime 300 + ipv6 nd ra hop-limit 64 + ip mfib fastdrop + default ntp serve + no ip pim sparse-mode + no ip pim border-router + ip pim query-interval 30 + ip pim join-prune-interval 60 + ip pim dr-priority 1 + no ip pim neighbor-filter + default ip pim bfd-instance + no ip pim bsr-border + no ip virtual address + vrrp 30 priority 100 + vrrp 30 timers advertise 1 + vrrp 30 mac-address advertisement-interval 30 + no vrrp 30 preempt + vrrp 30 preempt delay minimum 0 + vrrp 30 preempt delay reload 0 + vrrp 30 delay reload 0 + no vrrp 30 authentication + vrrp 30 ip 40.10.5.31 + vrrp 30 ipv6 :: + vrrp 30 description The description + vrrp 30 shutdown + vrrp 30 track Ethernet1 decrement 5 + no vrrp 30 bfd ip + no vrrp 30 bfd ipv6 + vrrp 30 ip version 2 + vrrp 40 priority 200 + vrrp 40 timers advertise 1 + vrrp 40 mac-address advertisement-interval 30 + vrrp 40 preempt + vrrp 40 preempt delay minimum 0 + vrrp 40 preempt delay reload 0 + vrrp 40 delay reload 0 + no vrrp 40 authentication + vrrp 40 ip 40.10.5.32 + vrrp 40 ipv6 :: + no vrrp 40 description + no vrrp 40 shutdown + vrrp 40 track Ethernet3 decrement 33 + vrrp 40 track Ethernet2 decrement 22 + vrrp 40 track Ethernet2 shutdown + no vrrp 40 bfd ip + no vrrp 40 bfd ipv6 + vrrp 40 ip version 2 +!