diff --git a/.rubocop.yml b/.rubocop.yml index df8f4c7..0df8557 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,3 +19,6 @@ Metrics/AbcSize: Metrics/ClassLength: Max: 130 + +Metrics/BlockLength: + Enabled: False diff --git a/CHANGELOG.md b/CHANGELOG.md index 96bb38b..9477332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,28 @@ # Change Log -## [1.0](https://github.com/arista-eosplus/rbeapi/tree/1.0) (2016-09-22) -[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.5.1...1.0) +## [1.1](https://github.com/arista-eosplus/rbeapi/tree/1.1) (2016-12-06) +[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v1.0...1.1) + +**Implemented enhancements:** + +- add subinterface functionality [\#161](https://github.com/arista-eosplus/rbeapi/pull/161) ([mmailand](https://github.com/mmailand)) +- add support to set aliases [\#160](https://github.com/arista-eosplus/rbeapi/pull/160) ([mmailand](https://github.com/mmailand)) +- added support for setting the crypto in managementdefaults [\#159](https://github.com/arista-eosplus/rbeapi/pull/159) ([mmailand](https://github.com/mmailand)) +- added support for autostate [\#158](https://github.com/arista-eosplus/rbeapi/pull/158) ([mmailand](https://github.com/mmailand)) +- add support for multi/single-line prefix list output [\#155](https://github.com/arista-eosplus/rbeapi/pull/155) ([mrvinti](https://github.com/mrvinti)) + +**Fixed bugs:** + +- Fix multiline alias support [\#165](https://github.com/arista-eosplus/rbeapi/pull/165) ([jerearista](https://github.com/jerearista)) +- extend and fix ospf features [\#156](https://github.com/arista-eosplus/rbeapi/pull/156) ([rknaus](https://github.com/rknaus)) + +**Merged pull requests:** + +- Style updates for Rubocop 0.45 [\#163](https://github.com/arista-eosplus/rbeapi/pull/163) ([jerearista](https://github.com/jerearista)) +- fix for rspec failure on current develop [\#162](https://github.com/arista-eosplus/rbeapi/pull/162) ([mmailand](https://github.com/mmailand)) + +## [v1.0](https://github.com/arista-eosplus/rbeapi/tree/v1.0) (2016-09-26) +[Full Changelog](https://github.com/arista-eosplus/rbeapi/compare/v0.5.1...v1.0) **Implemented enhancements:** @@ -24,6 +45,8 @@ **Merged pull requests:** +- Release 1.0 [\#153](https://github.com/arista-eosplus/rbeapi/pull/153) ([jerearista](https://github.com/jerearista)) +- Release 1.0 [\#152](https://github.com/arista-eosplus/rbeapi/pull/152) ([jerearista](https://github.com/jerearista)) - Add json option to get\_config [\#151](https://github.com/arista-eosplus/rbeapi/pull/151) ([jerearista](https://github.com/jerearista)) - Handle more multiline config commands [\#150](https://github.com/arista-eosplus/rbeapi/pull/150) ([jerearista](https://github.com/jerearista)) - Ensure get\_config, running\_config, and startup\_config return sane output [\#149](https://github.com/arista-eosplus/rbeapi/pull/149) ([jerearista](https://github.com/jerearista)) diff --git a/Gemfile b/Gemfile index 1ccdebf..9c1664b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,9 @@ source ENV['GEM_SOURCE'] || 'https://rubygems.org' +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'rbeapi/version' + gem 'inifile' gem 'net_http_unix' gem 'netaddr' @@ -12,20 +16,20 @@ group :development do end group :development, :test do + gem 'ci_reporter_rspec', require: false gem 'listen', '<=3.0.3' + gem 'pry', require: false + gem 'pry-doc', require: false + gem 'pry-stack_explorer', require: false gem 'rake', '~> 10.1.0' + gem 'rbeapi', Rbeapi::VERSION, path: '.' + gem 'redcarpet', '~> 3.1.2' gem 'rspec', '~> 3.0.0' gem 'rspec-mocks', '~> 3.0.0' gem 'simplecov' - gem 'yard' - gem 'redcarpet', '~> 3.1.2' - gem 'pry', require: false - gem 'pry-doc', require: false - gem 'pry-stack_explorer', require: false - gem 'rbeapi', '1.0', path: '.' - gem 'ci_reporter_rspec', require: false gem 'simplecov-json', require: false gem 'simplecov-rcov', require: false + gem 'yard' end # Rubocop > 0.37 requires a gem that only works with ruby 2.x @@ -35,6 +39,8 @@ if RUBY_VERSION.to_f < 2.0 gem 'rubocop', '>=0.35.1', '< 0.38' end else + # Rubocop thinks these are duplicates. + # rubocop:disable Bundler/DuplicatedGem gem 'json' group :development, :test do gem 'rubocop', '>=0.35.1' diff --git a/Rakefile b/Rakefile index 7b4cb2e..595f686 100644 --- a/Rakefile +++ b/Rakefile @@ -9,13 +9,14 @@ end RPM_OPTS = '--define "_topdir %(pwd)/rpmbuild" --define "_builddir ' \ '%{_topdir}" --define "_rpmdir %(pwd)/rpms" --define "_srcrpmdir ' \ - '%{_rpmdir}" --define "_sourcedir %(pwd)" --define "_specdir %(pwd)" -bb' + '%{_rpmdir}" --define "_sourcedir %(pwd)" --define "_specdir %(pwd)" '\ + '-bb'.freeze desc 'Generate regular and puppet-enterprise rbeapi RPMs for EOS' task rpm: :build do system "sed -e 's/^Version:.*/Version: #{Rbeapi::VERSION}/g' " \ 'rbeapi.spec.tmpl > rbeapi.spec' system "rpmbuild #{RPM_OPTS} rbeapi.spec" - RPMS = `find rpms/noarch -name "*rbeapi*rpm"` + RPMS = `find rpms/noarch -name "*rbeapi*rpm"`.freeze puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" puts "#\n################################################\n\n" @@ -32,7 +33,7 @@ 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" - RPMS = `find rpms/noarch -name "*inifile*rpm"` + RPMS = `find rpms/noarch -name "*inifile*rpm"`.freeze puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" puts "#\n################################################\n\n" @@ -50,7 +51,7 @@ task :net_http_unix do 'gems/net_http_unix/net_http_unix.spec.tmpl > ' \ 'gems/net_http_unix/net_http_unix.spec' system "rpmbuild #{RPM_OPTS} gems/net_http_unix/net_http_unix.spec" - RPMS = `find rpms/noarch -name "*net_http_unix*rpm"` + RPMS = `find rpms/noarch -name "*net_http_unix*rpm"`.freeze puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" puts "#\n################################################\n\n" @@ -67,7 +68,7 @@ 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" - RPMS = `find rpms/noarch -name "*netaddr*rpm"` + RPMS = `find rpms/noarch -name "*netaddr*rpm"`.freeze puts "\n################################################\n#" puts "Created the following in rpms/noarch/\n#{RPMS}" puts "#\n################################################\n\n" @@ -114,7 +115,7 @@ end desc 'Generate SWIX files from RPMs' task swix: :all_rpms do SWIX = 'PYTHONPATH=${PYTHONPATH}:/nfs/misc/tools/swix \ - /nfs/misc/tools/swix/swix' + /nfs/misc/tools/swix/swix'.freeze system "(cd rpms/noarch; rm -f rbeapi-#{Rbeapi::VERSION}-1.swix; #{SWIX} create rbeapi-#{Rbeapi::VERSION}-1.swix \ @@ -141,7 +142,7 @@ task swix: :all_rpms do rubygem-inifile-puppet-aio-3.0.0-5.eos4.noarch.rpm \ rubygem-netaddr-puppet-aio-1.5.1-4.eos4.noarch.rpm \ rubygem-net_http_unix-puppet-aio-0.2.2-5.eos4.noarch.rpm)" - SWIXS = `find rpms/noarch -name "rbeapi*swix" -ls` + SWIXS = `find rpms/noarch -name "rbeapi*swix" -ls`.freeze puts "\n################################################\n#" puts "The following artifacts are in rpms/noarch/\n#{SWIXS}" puts "#\n################################################\n\n" diff --git a/lib/rbeapi/api/alias.rb b/lib/rbeapi/api/alias.rb new file mode 100644 index 0000000..0ec02b9 --- /dev/null +++ b/lib/rbeapi/api/alias.rb @@ -0,0 +1,160 @@ +# +## Copyright (c) 2016, 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 namespace for working with the EOS command API. + module Api + ## + # The Alias class manages aliass entries on an EOS node. + class Alias < Entity + ## + # get returns the current alias configuration hash extracted from the + # nodes running configuration. + # + # @example + # { + # alias: array + # } + # + # @return [Hash] Returns the alias resource as a hash + # object from the nodes current configuration. + def get(name) + # Complex regex handles the following cases: + # All aliases start with 'alian ' followed by + # + # + pattern = /^alias #{name}((?:(?= )(?:.+?)(?=\n)|\n(?:.+?)(?=\n\!)))/m + aliases = config.scan(pattern) + return nil unless aliases[0] + parse_alias_entry(name, aliases[0]) + end + + ## + # getall returns a collection of alias resource hashes from the nodes + # running configuration. The alias resource collection hash is keyed + # by the unique alias name. + # + # @example + # [ + # : { + # command: + # }, + # : { + # command: + # }, + # ... + # ] + # + # @return [Hash] Returns a hash that represents the + # entire alias collection from the nodes running configuration. If + # there are no aliass configured, this method will return an empty + # hash. + def getall + entries = config.scan(/^alias (\w+)(.+)?/) + entries.inspect + response = {} + entries.each do |aliases| + response[aliases[0]] = get aliases[0] + end + response + end + + ## + # parse_alias_entry maps the tokens found to the hash entries. + # + # @api private + # + # @param alias [Array] An array of values returned from the regular + # expression scan of the aliass configuration. + # + # @return [Hash] Returns the resource hash attribute. + def parse_alias_entry(name, command) + hsh = {} + hsh[:name] = name + com = command[0] + hsh[:command] = com.strip + hsh + end + private :parse_alias_entry + + ## + # create will create a alias entry in the nodes current + # configuration with the specified address. + # + # @since eos_version 4.13.7M + # + # ===Commands + # alias
+ # + # @param name [String] The name of the alias. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts command [String] Configures the alias ip address + # + # @return [Boolean] Returns true if the command completed successfully. + def create(name, opts = {}) + raise ArgumentError, 'a command must be provided' unless \ + opts[:command] =~ /.+/ + command = opts.fetch(:command) + cmd = ["alias #{name} "] + if command =~ /\\n/ + command.split('\\n').each { |a| cmd << a } + else + cmd[0] << command + end + configure(cmd) + end + + ## + # delete will delete an existing alias entry from the nodes current + # running configuration. If the delete method is called and the alias + # entry does not exist, this method will succeed. + # + # @since eos_version 4.13.7M + # + # ===Commands + # no alias + # + # @param name [String] The alias name entry to delete from the node. + # + # @return [Boolean] Returns true if the command completed successfully. + def delete(name) + configure("no alias #{name}") + end + end + end +end diff --git a/lib/rbeapi/api/bgp.rb b/lib/rbeapi/api/bgp.rb index efdba9d..09208be 100644 --- a/lib/rbeapi/api/bgp.rb +++ b/lib/rbeapi/api/bgp.rb @@ -227,7 +227,7 @@ def parse_networks(config) 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 + raise ArgumentError, message end cmds = ["router bgp #{bgp_as}"] if opts.key?(:enable) @@ -284,7 +284,7 @@ def default # @return [Boolean] Returns true if the command complete successfully. def configure_bgp(cmd) config = get_block('^router bgp .*') - fail 'BGP router is not configured' unless config + raise 'BGP router is not configured' unless config bgp_as = Bgp.parse_bgp_as(config) cmds = ["router bgp #{bgp_as[:bgp_as]}", cmd] configure(cmds) @@ -341,10 +341,10 @@ def set_router_id(opts = {}) # # @return [Boolean] Returns true if the command complete successfully. def set_shutdown(opts = {}) - fail 'set_shutdown has the value option set' if opts[:value] + raise 'set_shutdown has the value option set' if opts[:value] # Shutdown semantics are opposite of enable semantics so invert enable value = !opts[:enable] - opts.merge!(enable: value) + opts[:enable] = value configure_bgp(command_builder('shutdown', opts)) end @@ -697,7 +697,7 @@ def parse_route_map_out(config, name) # @return [Boolean] Returns true if the command complete successfully. def configure_bgp(cmd) config = get_block('^router bgp .*') - fail 'BGP router is not configured' unless config + raise 'BGP router is not configured' unless config bgp_as = Bgp.parse_bgp_as(config) cmds = ["router bgp #{bgp_as[:bgp_as]}", cmd] configure(cmds) @@ -831,10 +831,10 @@ def set_remote_as(name, opts = {}) # # @return [Boolean] Returns true if the command complete successfully. def set_shutdown(name, opts = {}) - fail 'set_shutdown has value option set' if opts[:value] + raise 'set_shutdown has value option set' if opts[:value] # Shutdown semantics are opposite of enable semantics so invert enable. value = !opts[:enable] - opts.merge!(enable: value) + opts[:enable] = value configure_bgp(neigh_command_builder(name, 'shutdown', opts)) end @@ -859,7 +859,7 @@ def set_shutdown(name, opts = {}) # # @return [Boolean] Returns true if the command complete successfully. def set_send_community(name, opts = {}) - fail 'send_community has the value option set' if opts[:value] + raise 'send_community has the value option set' if opts[:value] configure_bgp(neigh_command_builder(name, 'send-community', opts)) end @@ -885,7 +885,7 @@ def set_send_community(name, opts = {}) # # @return [Boolean] Returns true if the command complete successfully. def set_next_hop_self(name, opts = {}) - fail 'set_next_hop_self has the value option set' if opts[:value] + raise 'set_next_hop_self has the value option set' if opts[:value] configure_bgp(neigh_command_builder(name, 'next-hop-self', opts)) end diff --git a/lib/rbeapi/api/dns.rb b/lib/rbeapi/api/dns.rb index adfe9a5..57b9a49 100644 --- a/lib/rbeapi/api/dns.rb +++ b/lib/rbeapi/api/dns.rb @@ -213,7 +213,9 @@ def set_domain_list(opts = {}) default = opts[:default] || false if value - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' + end end cmds = [] diff --git a/lib/rbeapi/api/interfaces.rb b/lib/rbeapi/api/interfaces.rb index 31af818..3b5feb5 100644 --- a/lib/rbeapi/api/interfaces.rb +++ b/lib/rbeapi/api/interfaces.rb @@ -55,6 +55,7 @@ def initialize(node) # name: , # type: , # description: , + # encapsulation: , # shutdown: # } # @@ -77,6 +78,7 @@ def get(name) # name: , # type: , # description: , + # encapsulation: , # shutdown: , # ... # }, @@ -84,6 +86,7 @@ def get(name) # name: , # type: , # description: , + # encapsulation: , # shutdown: , # ... # }, @@ -110,16 +113,18 @@ def getall # @return [Object] Returns the interface instance as an Object. def get_instance(name) name = name[0, 2].upcase - case name - when 'ET' - cls = 'Rbeapi::Api::EthernetInterface' - when 'PO' - cls = 'Rbeapi::Api::PortchannelInterface' - when 'VX' - cls = 'Rbeapi::Api::VxlanInterface' - else - cls = 'Rbeapi::Api::BaseInterface' - end + cls = case name + when 'ET' + 'Rbeapi::Api::EthernetInterface' + when 'PO' + 'Rbeapi::Api::PortchannelInterface' + when 'VX' + 'Rbeapi::Api::VxlanInterface' + when 'VL' + 'Rbeapi::Api::VlanInterface' + else + 'Rbeapi::Api::BaseInterface' + end return @instances[name] if @instances.include?(cls) instance = Rbeapi::Utils.class_from_string(cls).new(@node) @@ -127,10 +132,12 @@ def get_instance(name) instance end + # rubocop:disable Style/MethodMissing def method_missing(method_name, *args, &block) instance = get_instance(args[0]) instance.send(method_name.to_sym, *args, &block) end + # rubocop:enable Style/MethodMissing def respond_to?(method_name, name = nil) return super unless name @@ -143,8 +150,9 @@ def respond_to?(method_name, name = nil) # The BaseInterface class extends Entity and provides an implementation # that is common to all interfaces configured in EOS. class BaseInterface < Entity - DEFAULT_INTF_DESCRIPTION = '' - DEFAULT_LOAD_INTERVAL = '' + DEFAULT_INTF_DESCRIPTION = ''.freeze + DEFAULT_INTF_ENCAPSULATION = ''.freeze + DEFAULT_LOAD_INTERVAL = ''.freeze ## # get returns the specified interface resource hash that represents the @@ -157,6 +165,7 @@ class BaseInterface < Entity # name: # type: 'generic' # description: + # encapsulation: # shutdown: [true, false] # load_interval: # } @@ -173,6 +182,7 @@ def get(name) response = { name: name, type: 'generic' } response.merge!(parse_description(config)) + response.merge!(parse_encapsulation(config)) response.merge!(parse_shutdown(config)) response.merge!(parse_load_interval(config)) response @@ -197,6 +207,26 @@ def parse_description(config) end private :parse_description + ## + # parse_encapsulation scans the provided configuration block and parses + # the encapsulation value if it exists in the configuration. If the + # encapsulation value is not configured, then the + # DEFALT_INTF_ENCAPSULATION value is returned. The hash returned by this + # method is intended to be merged into the interface resource hash + # returned by the get method. + # + # @api private + # + # @param config [String] The configuration block retrieved from the + # nodes current running configuration. + # + # @return [Hash] Returns the resource hash attribute. + def parse_encapsulation(config) + mdata = /^\s{3}encapsulation dot1q vlan\s(.+)$/.match(config) + { encapsulation: mdata.nil? ? DEFAULT_INTF_ENCAPSULATION : mdata[1] } + end + private :parse_encapsulation + ## # parse_shutdown scans the provided configuration block and parses # the shutdown value. If the shutdown value is configured then true @@ -315,6 +345,44 @@ def set_description(name, opts = {}) configure_interface(name, commands) end + ## + # set_encapsulation configures the VLAN ID value for the specified + # interface name in the nodes running configuration. If the enable + # keyword is false then the encapsulation value is negated using the no + # keyword. If the default keyword is set to true, then the encapsulation + # value is defaulted using the default keyword. The default keyword takes + # precedence over the enable keyword if both are provided. + # + # @since eos_version X.XX.XM + # + # @param name [String] The interface name to apply the configuration + # to. The name value must be the full interface identifier. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts value [String] The value to configure the VLAN ID to be + # used in the encapsulation dot1q vlan setting for a subinterface. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the interface encapsulation + # using the default keyword. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_encapsulation(name, opts = {}) + unless name =~ /\./ + raise ArgumentError, 'Parameter encapsulation can be set only on '\ + 'subinterfaces' + end + unless name.downcase =~ /et|po/ + raise ArgumentError, 'Parameter encapsulation can be set only on '\ + 'Ethernet and PostChannel interfaces' + end + commands = command_builder('encapsulation dot1q vlan', opts) + configure_interface(name, commands) + end + ## # set_shutdown configures the administrative state of the specified # interface in the node. If the enable keyword is false, then the @@ -340,10 +408,10 @@ def set_description(name, opts = {}) # # @return [Boolean] Returns true if the command completed successfully. def set_shutdown(name, opts = {}) - fail 'set_shutdown has the value option set' if opts[:value] + raise 'set_shutdown has the value option set' if opts[:value] # Shutdown semantics are opposite of enable semantics so invert enable. value = !opts[:enable] - opts.merge!(enable: value) + opts[:enable] = value commands = command_builder('shutdown', opts) configure_interface(name, commands) end @@ -374,9 +442,9 @@ def set_load_interval(name, opts = {}) # The EthernetInterface class manages all Ethernet interfaces on an # EOS node. class EthernetInterface < BaseInterface - DEFAULT_ETH_FLOWC_TX = 'off' - DEFAULT_ETH_FLOWC_RX = 'off' - DEFAULT_SPEED = 'default' + DEFAULT_ETH_FLOWC_TX = 'off'.freeze + DEFAULT_ETH_FLOWC_RX = 'off'.freeze + DEFAULT_SPEED = 'default'.freeze DEFAULT_LACP_PRIORITY = 32_768 ## @@ -388,6 +456,7 @@ class EthernetInterface < BaseInterface # name: , # type: , # description: , + # encapsulation: , # shutdown: , # load_interval: # speed: , @@ -509,14 +578,19 @@ def parse_lacp_priority(config) ## # create overrides the create method from the BaseInterface and raises # an exception because Ethernet interface creation is not supported. + # This doesn't happen for Ethernet subinterfaces # - # @param _name [String] The name of the interface. + # @param name [String] The name of the interface. # # @raise [NotImplementedError] Creation of physical Ethernet interfaces - # is not supported. - def create(_name) - fail NotImplementedError, 'creating Ethernet interfaces is '\ - 'not supported' + # is not supported. Only subinterfaces are allowed. + def create(name) + if name !~ /\./ + raise NotImplementedError, 'creating Ethernet interfaces is '\ + 'not supported' + else + configure("interface #{name}") + end end ## @@ -524,13 +598,17 @@ def create(_name) # raises an exception because Ethernet interface deletion is not # supported. # - # @param _name [String] The name of the interface. + # @param name [String] The name of the interface. # # @raise [NotImplementedError] Deletion of physical Ethernet interfaces # is not supported. - def delete(_name) - fail NotImplementedError, 'deleting Ethernet interfaces is '\ - 'not supported' + def delete(name) + if name !~ /\./ + raise NotImplementedError, 'deleting Ethernet interfaces is '\ + 'not supported' + else + configure("no interface #{name}") + end end ## @@ -720,9 +798,9 @@ def set_lacp_priority(name, opts = {}) # The PortchannelInterface class manages all port channel interfaces on an # EOS node. class PortchannelInterface < BaseInterface - DEFAULT_LACP_FALLBACK = 'disabled' - DEFAULT_LACP_MODE = 'on' - DEFAULT_MIN_LINKS = '0' + DEFAULT_LACP_FALLBACK = 'disabled'.freeze + DEFAULT_LACP_MODE = 'on'.freeze + DEFAULT_MIN_LINKS = '0'.freeze ## # get returns the specified port-channel interface configuration from @@ -734,6 +812,7 @@ class PortchannelInterface < BaseInterface # { # type: 'portchannel' # description: + # encapsulation: # shutdown: [true, false] # load_interval: # members: array[] @@ -861,6 +940,7 @@ def parse_lacp_fallback(config) # @return [Hash] Returns the resource hash attribute. def parse_lacp_timeout(config) mdata = /lacp fallback timeout (\d+)$/.match(config) + return { lacp_timeout: [] } unless defined? mdata[1] { lacp_timeout: mdata[1] } end private :parse_lacp_timeout @@ -925,7 +1005,7 @@ def set_minimum_links(name, opts = {}) # # @return [Boolean] Returns true if the command completed successfully. def set_members(name, members, mode = nil) - fail ArgumentError, 'members must be an Array' unless + raise ArgumentError, 'members must be an Array' unless members.is_a?(Array) current_members = Set.new parse_members(name)[:members] @@ -1091,8 +1171,8 @@ def set_lacp_timeout(name, opts = {}) ## # The VxlanInterface class manages all Vxlan interfaces on an EOS node. class VxlanInterface < BaseInterface - DEFAULT_SRC_INTF = '' - DEFAULT_MCAST_GRP = '' + DEFAULT_SRC_INTF = ''.freeze + DEFAULT_MCAST_GRP = ''.freeze ## # Returns the Vxlan interface configuration as a Ruby hash of key/value @@ -1105,6 +1185,7 @@ class VxlanInterface < BaseInterface # name: , # type: , # description: , + # encapsulation: , # shutdown: , # load_interval: # source_interface: , @@ -1374,5 +1455,86 @@ def remove_vlan(name, vlan) configure_interface(name, "no vxlan vlan #{vlan} vni") end end + + ## + # The VlanInterface class manages all Vlan interfaces on an EOS node. + class VlanInterface < BaseInterface + ## + # Returns the Vlan interface configuration as a Ruby hash of key/value + # pairs from the nodes running configuration. This method extends the + # BaseInterface get method and adds the Vlan specific attributes to + # the hash. + # + # @example + # { + # name: , + # type: , + # description: , + # shutdown: , + # autostate: + # } + # + # @param name [String] The interface name to return from the nodes + # configuration. + # + # @return [nil, Hash] Returns the interface configuration + # as a Ruby hash object. If the provided interface name is not found + # then this method will return nil. + def get(name) + config = get_block("interface #{name}") + return nil unless config + + response = super(name) + response[:type] = 'vlan' + response.merge!(parse_autostate(config)) + response + end + + ## + # parse_autostate scans the interface config block and returns the + # value of the vlan autostate. If the autostate is not + # configured then it's enabled, as by default. The hash + # returned is intended to be merged into the interface resource hash + # + # @api private + # + # @param config [String] The interface configuration block to extract + # the vlan autostate value from. + # + # @return [Hash] + def parse_autostate(config) + mdata = /^\s{3}no\sautostate$/.match(config) + { autostate: mdata.nil? ? :true : :false } + end + private :parse_autostate + + ## + # set_autostate configures the autostate on a vlan interface. + # Default value is true, if set to false 'no autostate' is + # configured on the interface. + # + # @since eos_version 4.13.7M + # + # @param name [String] The interface name to apply the configuration + # values to. The name must be the full interface identifier. + # + # @param opts [Hash] Optional keyword arguments. + # + # @option opts value [Boolean] Enables or disables autotate for vlan + # interfaces. Default is true. + # + # @option opts default [Boolean] Configures the autostate value + # on the interface using the default value (true). + # + # @return [Boolean] Returns true if the command completed successfully. + def set_autostate(name, opts = {}) + commands = if opts[:value] == :false + command_builder('no autostate') + else + command_builder('autostate') + end + configure_interface(name, commands) + end + end end end diff --git a/lib/rbeapi/api/ipinterfaces.rb b/lib/rbeapi/api/ipinterfaces.rb index 163b421..a25990c 100644 --- a/lib/rbeapi/api/ipinterfaces.rb +++ b/lib/rbeapi/api/ipinterfaces.rb @@ -41,8 +41,8 @@ module Api # The Ipinterface class provides an instance for managing logical # IP interfaces configured using eAPI. class Ipinterfaces < Entity - DEFAULT_ADDRESS = '' - DEFAULT_LOAD_INTERVAL = '' + DEFAULT_ADDRESS = ''.freeze + DEFAULT_LOAD_INTERVAL = ''.freeze ## # get returns a resource hash that represents the configuration of the IP @@ -336,7 +336,9 @@ def set_helper_addresses(name, opts = {}) default = opts[:default] || false if value - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' + end end case default diff --git a/lib/rbeapi/api/managementdefaults.rb b/lib/rbeapi/api/managementdefaults.rb new file mode 100644 index 0000000..b40899d --- /dev/null +++ b/lib/rbeapi/api/managementdefaults.rb @@ -0,0 +1,119 @@ +# +# Copyright (c) 2014,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 namespace for working with the EOS command API. + module Api + ## + # The Managementdefaults class provides a configuration instance for + # configuring management defaults of the node. + class Managementdefaults < Entity + DEFAULT_SECRET_HASH = 'md5'.freeze + + ## + # get scans the current nodes configuration and returns the values as + # a Hash describing the current state. + # + # @example + # { + # secret_hash: + # } + # + # @return [nil, Hash] Returns the resource hash attribute. + def parse_secret_hash(config) + mdata = /(?<=\s{3}secret hash\s)(md5|sha512)$/.match(config) + { secret_hash: mdata.nil? ? DEFAULT_SECRET_HASH : mdata[1] } + end + private :parse_secret_hash + + ## + # set_secret_hash configures the management defaults secret hash value + # in the current nodes running configuration. + # If the default keyword is provided, the configuration is defaulted + # using the default keyword. + # + # @since eos_version 4.13.7M + # + # ===Commands + # management defaults + # secret hash + # no secret hash + # default secret hash + # + # @param opts [Hash] Optional keyword arguments + # + # @option opts value [String] The value to configure the secret hash to. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the secret hash value using + # the default keyword. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_secret_hash(opts = {}) + unless ['md5', 'sha512', nil].include?(opts[:value]) + raise ArgumentError, 'secret hash must be md5 or sha512' + end + cmd = command_builder("secret hash #{opts[:value]}") + cmds = ['management defaults', cmd] + configure(cmds) + end + end + end +end diff --git a/lib/rbeapi/api/mlag.rb b/lib/rbeapi/api/mlag.rb index eb5d57b..2b05bfe 100644 --- a/lib/rbeapi/api/mlag.rb +++ b/lib/rbeapi/api/mlag.rb @@ -41,10 +41,10 @@ module Api # The Mlag class provides a configuration instance for working with # the global MLAG configuration of the node. class Mlag < Entity - DEFAULT_DOMAIN_ID = '' - DEFAULT_LOCAL_INTF = '' - DEFAULT_PEER_ADDR = '' - DEFAULT_PEER_LINK = '' + DEFAULT_DOMAIN_ID = ''.freeze + DEFAULT_LOCAL_INTF = ''.freeze + DEFAULT_PEER_ADDR = ''.freeze + DEFAULT_PEER_LINK = ''.freeze ## # get scans the current nodes configuration and returns the values as @@ -367,10 +367,10 @@ def set_peer_address(opts = {}) # # @return [Boolean] Returns true if the command completed successfully. def set_shutdown(opts = {}) - fail 'set_shutdown has the value option set' if opts[:value] + raise 'set_shutdown has the value option set' if opts[:value] # Shutdown semantics are opposite of enable semantics so invert enable value = !opts[:enable] - opts.merge!(enable: value) + opts[:enable] = value cmd = command_builder('shutdown', opts) cmds = ['mlag configuration', cmd] configure(cmds) diff --git a/lib/rbeapi/api/ntp.rb b/lib/rbeapi/api/ntp.rb index 27f91de..a733d3d 100644 --- a/lib/rbeapi/api/ntp.rb +++ b/lib/rbeapi/api/ntp.rb @@ -41,7 +41,7 @@ module Api # The Ntp class provides an instance for working with the nodes # NTP configuration. class Ntp < Entity - DEFAULT_SRC_INTF = '' + DEFAULT_SRC_INTF = ''.freeze ## # get returns the nodes current ntp configure as a resource hash. diff --git a/lib/rbeapi/api/ospf.rb b/lib/rbeapi/api/ospf.rb index 9e21c70..745d347 100644 --- a/lib/rbeapi/api/ospf.rb +++ b/lib/rbeapi/api/ospf.rb @@ -48,11 +48,18 @@ class Ospf < Entity # # @example # { - # router_id: + # router_id: , + # max_lsa: , + # maximum_paths: , + # passive_interface_default , + # passive_interfaces: array, + # active_interfaces: array, # areas: { # : array # }, - # redistribute: {} + # redistribute: { + # : {route_map: } + # } # } # # @param inst [String] The ospf instance name. @@ -65,7 +72,23 @@ def get(inst) response = {} mdata = /(?<=^\s{3}router-id\s)(.+)$/.match(config) - response['router_id'] = mdata.nil? ? '' : mdata[0] + response[:router_id] = mdata.nil? ? '' : mdata[0] + + mdata = /(?<=^\s{3}max-lsa\s)(\d+)(?=.*$)/.match(config) + response[:max_lsa] = mdata.nil? ? '' : mdata[0].to_i + + mdata = /(?<=^\s{3}maximum-paths\s)(\d+)$/.match(config) + response[:maximum_paths] = mdata.nil? ? '' : mdata[0].to_i + + mdata = /^\s{3}passive-interface default$/ =~ config + response[:passive_interface_default] = !mdata.nil? + + response[:passive_interfaces] = + config.scan(/(?<=^\s{3}passive-interface\s)(?!default)(.*)$/) + .flatten!.to_a + + response[:active_interfaces] = + config.scan(/(?<=^\s{3}no passive-interface\s)(.*)$/).flatten!.to_a networks = config.scan(/^\s{3}network\s(.+)\sarea\s(.+)$/) areas = networks.each_with_object({}) do |cfg, hsh| @@ -76,13 +99,13 @@ def get(inst) hsh[area] = [net] end end - response['areas'] = areas + response[:areas] = areas values = \ config.scan(/(?<=^\s{3}redistribute\s)(\w+)[\s|$]*(route-map\s(.+))?/) - response['redistribute'] = values.each_with_object({}) do |value, hsh| - hsh[value[0]] = { 'route_map' => value[2] } + response[:redistribute] = values.each_with_object({}) do |value, hsh| + hsh[value[0]] = { route_map: value[2] } end response end @@ -94,6 +117,11 @@ def get(inst) # { # : { # router_id: , + # max_lsa: , + # maximum_paths: , + # passive_interface_default , + # passive_interfaces: array, + # active_interfaces: array, # areas: {}, # redistribute: {} # }, @@ -107,7 +135,7 @@ def getall response = instances.each_with_object({}) do |inst, hsh| hsh[inst] = get inst end - response['interfaces'] = interfaces.getall + response[:interfaces] = interfaces.getall response end @@ -156,6 +184,133 @@ def set_router_id(pid, opts = {}) configure cmds end + ## + # set_max_lsa sets router ospf max-lsa with pid and options. + # + # @param pid [String] The router ospf name. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the max-lsa to default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_max_lsa(pid, opts = {}) + cmd = command_builder('max-lsa', opts) + cmds = ["router ospf #{pid}", cmd] + configure cmds + end + + ## + # set_maximum_paths sets router ospf maximum-paths with pid and options. + # + # @param pid [String] The router ospf name. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the maximum-paths to default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_maximum_paths(pid, opts = {}) + cmd = command_builder('maximum-paths', opts) + cmds = ["router ospf #{pid}", cmd] + configure cmds + end + + ## + # set_passive_interface_default sets router ospf passive-interface + # default with pid and options. If the passive-interface default keyword + # is false, then the + # passive-interface default is disabled. If the enable keyword is true, + # then the passive-interface default is enabled. If the default keyword + # is set to true, then the passive-interface default is configured using + # the default keyword. The default keyword takes precedence ver the + # enable keyword if both are provided. + # + # @param pid [String] The router ospf name. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the passive-interface default + # to default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_passive_interface_default(pid, opts = {}) + opts[:enable] = opts[:value] | false + opts[:value] = nil + cmd = command_builder('passive-interface default', opts) + cmds = ["router ospf #{pid}", cmd] + configure cmds + end + + ## + # set_active_interfaces sets router ospf no passive interface with pid + # and options, when passive interfaces default is configured. + # + # @param pid [String] The router ospf name. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the active interface to + # default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_active_interfaces(pid, opts = {}) + values = opts[:value] + current = get(pid)[:active_interfaces] + cmds = ["router ospf #{pid}"] + current.each do |name| + unless Array(values).include?(name) + cmds << "passive-interface #{name}" + end + end + Array(values).each do |name| + cmds << "no passive-interface #{name}" + end + configure cmds + end + + ## + # set_passive_interfaces sets router ospf passive interface with pid + # and options. + # + # @param pid [String] The router ospf name. + # + # @param opts [hash] Optional keyword arguments. + # + # @option opts enable [Boolean] If false then the command is + # negated. Default is true. + # + # @option opts default [Boolean] Configure the passive interface to + # default. + # + # @return [Boolean] Returns true if the command completed successfully. + def set_passive_interfaces(pid, opts = {}) + values = opts[:value] + current = get(pid)[:passive_interfaces] + cmds = ["router ospf #{pid}"] + current.each do |name| + unless Array(values).include?(name) + cmds << "no passive-interface #{name}" + end + end + Array(values).each do |name| + cmds << "passive-interface #{name}" + end + configure cmds + end + ## # add_network adds network settings for router ospf and network area. # @@ -203,9 +358,11 @@ def remove_network(pid, net, area) # # @return [Boolean] Returns true if the command completed successfully. def set_redistribute(pid, proto, opts = {}) - routemap = opts[:routemap] - cmds = ["router ospf #{pid}", "redistribute #{proto}"] - cmds[1] << " route-map #{routemap}" if routemap + routemap = opts[:route_map] + redistribute = "redistribute #{proto}" + redistribute << " route-map #{routemap}" if routemap + cmd = command_builder(redistribute, opts) + cmds = ["router ospf #{pid}", cmd] configure cmds end end @@ -231,11 +388,13 @@ class OspfInterfaces < Entity def get(name) config = get_block("interface #{name}") return nil unless config - return nil unless /no switchport$/ =~ config + unless /no switchport$/ =~ config || /^interface (Lo|Vl)/ =~ config + return nil + end response = {} nettype = /ip ospf network point-to-point/ =~ config - response['network_type'] = nettype.nil? ? 'broadcast' : 'point-to-point' + response[:network_type] = nettype.nil? ? 'broadcast' : 'point-to-point' response end diff --git a/lib/rbeapi/api/prefixlists.rb b/lib/rbeapi/api/prefixlists.rb index caa8924..ca67bcc 100644 --- a/lib/rbeapi/api/prefixlists.rb +++ b/lib/rbeapi/api/prefixlists.rb @@ -67,12 +67,22 @@ class Prefixlists < Entity # array of hashes, where each prefix is a hash object. # If the prefix list is not found, a nil object is returned. def get(name) - config = get_block("ip prefix-list #{name}") - return nil unless config + return nil unless config =~ /ip prefix-list #{name}/ + + # single-line prefix list + if config =~ /ip prefix-list #{name}\sseq/ + entries = config.scan(/^(?:ip\sprefix-list\s#{name}\sseq\s)(\d+)\s + (permit|deny)\s(.+)$/x) + # or multi-line + else + prefix_list = get_block("ip prefix-list #{name}") + entries = prefix_list.scan(/^\s+(?:seq\s)(\d+)\s + (permit|deny)\s(.+)$/x) + end - entries = config.scan(/^\s{3}(?:seq\s)(\d+)\s(permit|deny)\s(.+)$/) entries.each_with_object([]) do |entry, arry| - arry << { 'seq' => entry[0], 'action' => entry[1], + arry << { 'seq' => entry[0], + 'action' => entry[1], 'prefix' => entry[2] } end end @@ -116,15 +126,15 @@ def get(name) # If there are no prefix lists configured, an empty hash will # be returned. def getall - lists = config.scan(/(?<=^ip\sprefix-list\s).+/) - lists.each_with_object({}) do |name, hsh| + lists = config.scan(/(?<=^ip\sprefix-list\s)[^\s]+(?=\sseq.+)?/) + lists.uniq.each_with_object({}) do |name, hsh| values = get name hsh[name] = values if values end end ## - # create will create a new ip prefix-list with designated name. + # Creates a new ip prefix-list with the designated name. # # @param name [String] The name of the ip prefix-list. # @@ -134,7 +144,7 @@ def create(name) end ## - # add_rule will create an ip prefix-list with the designated name, + # Creates an ip prefix-list with the designated name, # seqno, action and prefix. # # @param name [String] The name of the ip prefix-list. @@ -154,7 +164,7 @@ def add_rule(name, action, prefix, seq = nil) end ## - # delete will remove the designated prefix-list. + # Removes the designated prefix-list. # # @param name [String] The name of the ip prefix-list. # diff --git a/lib/rbeapi/api/radius.rb b/lib/rbeapi/api/radius.rb index e918658..83f6008 100644 --- a/lib/rbeapi/api/radius.rb +++ b/lib/rbeapi/api/radius.rb @@ -201,11 +201,11 @@ def set_global_key(opts = {}) when true cmds = 'default radius-server key' when false - if enable - cmds = "radius-server key #{key_format} #{value}" - else - cmds = 'no radius-server key' - end + cmds = if enable + "radius-server key #{key_format} #{value}" + else + 'no radius-server key' + end end configure cmds end diff --git a/lib/rbeapi/api/routemaps.rb b/lib/rbeapi/api/routemaps.rb index e3591f7..b534eae 100644 --- a/lib/rbeapi/api/routemaps.rb +++ b/lib/rbeapi/api/routemaps.rb @@ -253,13 +253,13 @@ def parse_rules(rules) # # @return [Array] Returns the prepared eos command. 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 = if opts[:default] == true + "default route-map #{name}" + elsif opts[:enable] == false + "no route-map #{name}" + else + "route-map #{name}" + end cmd << " #{action}" cmd << " #{seqno}" [cmd] @@ -305,7 +305,7 @@ def create(name, action, seqno, opts = {}) cmds = name_commands(name, action, seqno) else if opts[:match] && !opts[:match].is_a?(Array) - fail ArgumentError, 'opts match must be an Array' + raise ArgumentError, 'opts match must be an Array' end cmds = name_commands(name, action, seqno, opts) if opts[:description] @@ -346,7 +346,7 @@ def create(name, action, seqno, opts = {}) # # @return [Boolean] Returns true if the command completed successfully. def remove_match_statements(name, action, seqno, cmds) - fail ArgumentError, 'cmds must be an Array' unless cmds.is_a?(Array) + raise ArgumentError, 'cmds must be an Array' unless cmds.is_a?(Array) entries = parse_entries(name) return nil unless entries @@ -374,7 +374,7 @@ def remove_match_statements(name, action, seqno, cmds) # # @return [Boolean] Returns true if the command completed successfully. def remove_set_statements(name, action, seqno, cmds) - fail ArgumentError, 'cmds must be an Array' unless cmds.is_a?(Array) + raise ArgumentError, 'cmds must be an Array' unless cmds.is_a?(Array) entries = parse_entries(name) return nil unless entries @@ -446,7 +446,7 @@ def default(name, action, seqno) # # @return [Boolean] Returns true if the command completed successfully. def set_match_statements(name, action, seqno, value) - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' unless value.is_a?(Array) cmds = ["route-map #{name} #{action} #{seqno}"] remove_match_statements(name, action, seqno, cmds) @@ -473,7 +473,7 @@ def set_match_statements(name, action, seqno, value) # # @return [Boolean] Returns true if the command completed successfully. def set_set_statements(name, action, seqno, value) - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' unless value.is_a?(Array) cmds = ["route-map #{name} #{action} #{seqno}"] remove_set_statements(name, action, seqno, cmds) diff --git a/lib/rbeapi/api/snmp.rb b/lib/rbeapi/api/snmp.rb index db1bc56..e981407 100644 --- a/lib/rbeapi/api/snmp.rb +++ b/lib/rbeapi/api/snmp.rb @@ -44,12 +44,14 @@ module Api # # @since eos_version 4.13.7M class Snmp < Entity - DEFAULT_SNMP_LOCATION = '' - DEFAULT_SNMP_CONTACT = '' - DEFAULT_SNMP_CHASSIS_ID = '' - DEFAULT_SNMP_SOURCE_INTERFACE = '' + DEFAULT_SNMP_LOCATION = ''.freeze + DEFAULT_SNMP_CONTACT = ''.freeze + DEFAULT_SNMP_CHASSIS_ID = ''.freeze + DEFAULT_SNMP_SOURCE_INTERFACE = ''.freeze CFG_TO_STATE = { 'default' => 'default', 'no' => 'off', nil => 'on' } + .freeze STATE_TO_CFG = { 'default' => 'default', 'on' => nil, 'off' => 'no' } + .freeze ## # get returns the snmp resource Hash that represents the nodes snmp diff --git a/lib/rbeapi/api/stp.rb b/lib/rbeapi/api/stp.rb index edd6189..8d079b5 100644 --- a/lib/rbeapi/api/stp.rb +++ b/lib/rbeapi/api/stp.rb @@ -146,7 +146,7 @@ def set_mode(opts = {}) # spanning-tree instances in EOS # class StpInstances < Entity - DEFAULT_STP_PRIORITY = '32768' + DEFAULT_STP_PRIORITY = '32768'.freeze ## # get returns the specified stp instance config parsed from the nodes @@ -263,11 +263,11 @@ def set_priority(inst, opts = {}) when true cmd = "default spanning-tree mst #{inst} priority" when false - if enable - cmd = "spanning-tree mst #{inst} priority #{value}" - else - cmd = "no spanning-tree mst #{inst} priority" - end + cmd = if enable + "spanning-tree mst #{inst} priority #{value}" + else + "no spanning-tree mst #{inst} priority" + end end configure cmd end @@ -360,13 +360,13 @@ def parse_portfast(config) # # @return [Hash] Resource hash attribute. def parse_portfast_type(config) - if /spanning-tree portfast network/ =~ config - value = 'network' - elsif /no spanning-tree portfast/ =~ config - value = 'normal' - else - value = 'edge' - end + value = if /spanning-tree portfast network/ =~ config + 'network' + elsif /no spanning-tree portfast/ =~ config + 'normal' + else + 'edge' + end { portfast_type: value } end private :parse_portfast_type @@ -423,7 +423,7 @@ def set_portfast(name, opts = {}) # @return [Boolean] True if the commands succeed otherwise False. def set_portfast_type(name, opts = {}) value = opts[:value] - fail ArgumentError, 'value must be set' unless value + raise ArgumentError, 'value must be set' unless value enable = opts.fetch(:enable, true) default = opts[:default] || false @@ -431,11 +431,11 @@ def set_portfast_type(name, opts = {}) when true cmds = "default spanning-tree portfast #{value}" when false - if enable - cmds = "spanning-tree portfast #{value}" - else - cmds = "no spanning-tree portfast #{value}" - end + cmds = if enable + "spanning-tree portfast #{value}" + else + "no spanning-tree portfast #{value}" + end end configure_interface(name, cmds) end @@ -463,11 +463,11 @@ def set_bpduguard(name, opts = {}) when true cmds = 'default spanning-tree bpduguard' when false - if enable - cmds = 'spanning-tree bpduguard enable' - else - cmds = 'spanning-tree bpduguard disable' - end + cmds = if enable + 'spanning-tree bpduguard enable' + else + 'spanning-tree bpduguard disable' + end end configure_interface(name, cmds) end diff --git a/lib/rbeapi/api/switchports.rb b/lib/rbeapi/api/switchports.rb index 1f42006..b50151e 100644 --- a/lib/rbeapi/api/switchports.rb +++ b/lib/rbeapi/api/switchports.rb @@ -85,6 +85,7 @@ def get(name) # @return [Hash] Returns the resource hash attribute. def parse_mode(config) mdata = /(?<=\s{3}switchport\smode\s)(.+)$/.match(config) + return { mode: [] } unless defined? mdata[1] { mode: mdata[1] } end private :parse_mode @@ -101,6 +102,7 @@ def parse_mode(config) # @return [Hash] Returns the resource hash attribute. def parse_access_vlan(config) mdata = /(?<=access\svlan\s)(.+)$/.match(config) + return { access_vlan: [] } unless defined? mdata[1] { access_vlan: mdata[1] } end private :parse_access_vlan @@ -117,6 +119,7 @@ def parse_access_vlan(config) # @return [Hash] Returns the resource hash attribute. def parse_trunk_native_vlan(config) mdata = /(?<=trunk\snative\svlan\s)(.+)$/.match(config) + return { trunk_native_vlan: [] } unless defined? mdata[1] { trunk_native_vlan: mdata[1] } end private :parse_trunk_native_vlan @@ -154,7 +157,8 @@ def parse_trunk_allowed_vlans(config) # @return [Hash] Returns the resource hash attribute. def parse_trunk_groups(config) mdata = config.scan(/(?<=trunk\sgroup\s)(.+)$/) - mdata = mdata.flatten if mdata.length > 0 + return { trunk_group: [] } unless defined? mdata[1] + mdata = mdata.flatten unless mdata.empty? { trunk_groups: mdata } end private :parse_trunk_groups @@ -277,7 +281,9 @@ def set_trunk_allowed_vlans(name, opts = {}) default = opts[:default] || false if value - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' + end value = value.map(&:inspect).join(',').tr('"', '') end @@ -285,11 +291,11 @@ def set_trunk_allowed_vlans(name, opts = {}) when true cmds = 'default switchport trunk allowed vlan' when false - if !enable - cmds = 'no switchport trunk allowed vlan' - else - cmds = ["switchport trunk allowed vlan #{value}"] - end + cmds = if !enable + 'no switchport trunk allowed vlan' + else + ["switchport trunk allowed vlan #{value}"] + end end configure_interface(name, cmds) end @@ -370,7 +376,7 @@ def set_trunk_groups(name, opts = {}) end value = opts.fetch(:value, []) - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' unless value.is_a?(Array) value = Set.new value current_value = Set.new get(name)[:trunk_groups] @@ -385,7 +391,7 @@ def set_trunk_groups(name, opts = {}) current_value.difference(value).each do |group| cmds << "no switchport trunk group #{group}" end - configure_interface(name, cmds) if cmds.length > 0 + configure_interface(name, cmds) unless cmds.empty? end end end diff --git a/lib/rbeapi/api/tacacs.rb b/lib/rbeapi/api/tacacs.rb index 308376b..eed5843 100644 --- a/lib/rbeapi/api/tacacs.rb +++ b/lib/rbeapi/api/tacacs.rb @@ -166,7 +166,7 @@ def servers def set_global_key(opts = {}) format = opts[:key_format] key = opts[:key] - fail ArgumentError, 'key option is required' unless key + raise ArgumentError, 'key option is required' unless key result = api.config("tacacs-server key #{format} #{key}") result == [{}] end diff --git a/lib/rbeapi/api/users.rb b/lib/rbeapi/api/users.rb index f7ee470..dea4521 100644 --- a/lib/rbeapi/api/users.rb +++ b/lib/rbeapi/api/users.rb @@ -149,7 +149,7 @@ def getall # # @return [Hash] Returns the resource hash attribute. def parse_user_entry(user) - fail ArgumentError, 'user must be an Array' unless user.is_a?(Array) + raise ArgumentError, 'user must be an Array' unless user.is_a?(Array) hsh = {} hsh[:name] = user[0] @@ -219,13 +219,13 @@ def create(name, opts = {}) # just return the value. enc = opts.fetch(:encryption, 'cleartext') unless @encryption_map[enc] - fail ArgumentError, "invalid encryption value: #{enc}" + raise ArgumentError, "invalid encryption value: #{enc}" end enc = @encryption_map[enc] unless opts[:secret] - fail ArgumentError, - 'secret must be specified if nopassword is false' + raise ArgumentError, + 'secret must be specified if nopassword is false' end cmd << " secret #{enc} #{opts[:secret]}" end diff --git a/lib/rbeapi/api/varp.rb b/lib/rbeapi/api/varp.rb index 9d52480..981076c 100644 --- a/lib/rbeapi/api/varp.rb +++ b/lib/rbeapi/api/varp.rb @@ -207,7 +207,9 @@ def set_addresses(name, opts = {}) cmds = ["interface #{name}"] if value - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' + end end case default @@ -216,8 +218,10 @@ def set_addresses(name, opts = {}) when false cmds << 'no ip virtual-router address' if enable - fail ArgumentError, - 'no values for addresses provided' unless value + unless value + raise ArgumentError, + 'no values for addresses provided' + end value.each do |addr| cmds << "ip virtual-router address #{addr}" end diff --git a/lib/rbeapi/api/vlans.rb b/lib/rbeapi/api/vlans.rb index 3760f8a..96d8fa3 100644 --- a/lib/rbeapi/api/vlans.rb +++ b/lib/rbeapi/api/vlans.rb @@ -283,7 +283,7 @@ def set_name(id, opts = {}) def set_state(id, opts = {}) value = opts[:value] unless ['active', 'suspend', nil].include?(value) - fail ArgumentError, 'state must be active, suspend or nil' + raise ArgumentError, 'state must be active, suspend or nil' end cmd = command_builder('state', opts) @@ -360,7 +360,7 @@ def set_trunk_groups(name, opts = {}) return configure(["vlan #{name}", 'no trunk group']) unless enable value = opts.fetch(:value, []) - fail ArgumentError, 'value must be an Array' unless value.is_a?(Array) + raise ArgumentError, 'value must be an Array' unless value.is_a?(Array) value = Set.new value current_value = Set.new get(name)[:trunk_groups] diff --git a/lib/rbeapi/api/vrrp.rb b/lib/rbeapi/api/vrrp.rb index 463ed6e..b25683f 100644 --- a/lib/rbeapi/api/vrrp.rb +++ b/lib/rbeapi/api/vrrp.rb @@ -152,8 +152,8 @@ def getall # 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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for primary_ip' else value = match[0][0] end @@ -175,8 +175,8 @@ def parse_primary_ip(config, vrid) # <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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for priority' else value = match[0][0].to_i end @@ -198,8 +198,8 @@ def parse_priority(config, vrid) # 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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for timers advertise' else value = match[0][0].to_i end @@ -221,11 +221,11 @@ def parse_timers_advertise(config, vrid) # 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 + value = if match.empty? + false + else + true + end { preempt: value } end private :parse_preempt @@ -243,11 +243,11 @@ def parse_preempt(config, vrid) # @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 + value = if match.empty? + true + else + false + end { enable: value } end private :parse_enable @@ -289,11 +289,11 @@ def parse_secondary_ip(config, vrid) # 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 + value = if match.empty? + nil + else + match[0][0] + end { description: value } end private :parse_description @@ -341,8 +341,8 @@ def parse_track(config, vrid) # 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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for ip version' else value = match[0][0].to_i end @@ -367,7 +367,7 @@ 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 ' \ + raise 'Did not get a default value for mac address ' \ 'advertisement interval' else value = match[0][0].to_i @@ -390,8 +390,8 @@ def parse_mac_addr_adv_interval(config, vrid) # 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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for preempt delay minimum' else value = match[0][0].to_i end @@ -413,8 +413,8 @@ def parse_preempt_delay_min(config, vrid) # 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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for preempt delay reload' else value = match[0][0].to_i end @@ -436,8 +436,8 @@ def parse_preempt_delay_reload(config, vrid) # 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' + if match.empty? # rubocop:disable Style/GuardClause + raise 'Did not get a default value for delay reload' else value = match[0][0].to_i end @@ -513,23 +513,23 @@ def parse_delay_reload(config, vrid) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, # rubocop:disable Metrics/PerceivedComplexity def create(name, vrid, opts = {}) - fail ArgumentError, 'create has no options set' if opts.empty? + raise ArgumentError, 'create has no options set' if opts.empty? if opts[:secondary_ip] && !opts[:secondary_ip].is_a?(Array) - fail ArgumentError, 'opts secondary_ip must be an Array' + raise ArgumentError, 'opts secondary_ip must be an Array' end if opts[:track] && !opts[:track].is_a?(Array) - fail ArgumentError, 'opts track must be an Array' + raise ArgumentError, 'opts track must be an Array' end cmds = [] if opts.key?(:enable) - if opts[:enable] - cmds << "no vrrp #{vrid} shutdown" - else - cmds << "vrrp #{vrid} shutdown" - end + cmds << if opts[:enable] + "no vrrp #{vrid} shutdown" + else + "vrrp #{vrid} shutdown" + end end cmds << "vrrp #{vrid} ip #{opts[:primary_ip]}" if opts.key?(:primary_ip) if opts.key?(:priority) @@ -552,11 +552,11 @@ def create(name, vrid, opts = {}) 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 + cmds << if opts[:preempt] + "vrrp #{vrid} preempt" + else + "no vrrp #{vrid} preempt" + end end if opts.key?(:preempt_delay_min) val = opts[:preempt_delay_min] @@ -642,10 +642,10 @@ def default(name, vrid) # # @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] + raise '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) + opts[:enable] = !enable cmd = "vrrp #{vrid} shutdown" configure_interface(name, command_builder(cmd, opts)) end @@ -757,11 +757,11 @@ def build_secondary_ip_cmd(name, vrid, ip_addrs) 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 + current_addrs = if vrrp.key?(vrid) + Set.new vrrp[vrid][:secondary_ip] + else + Set.new [] + end cmds = [] # Add commands to delete any secondary IP addresses that are @@ -921,7 +921,7 @@ def set_mac_addr_adv_interval(name, vrid, opts = {}) # # @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] + raise 'set_preempt has the value option set' if opts[:value] cmd = "vrrp #{vrid} preempt" configure_interface(name, command_builder(cmd, opts)) end @@ -1038,22 +1038,22 @@ def build_tracks_cmd(name, vrid, tracks) tracks.each do |track| track.keys do |key| unless valid_keys.include?(key) - fail ArgumentError, 'Key: #{key} invalid in track hash' + raise 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' + raise 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'" + raise ArgumentError, "Action must be 'decrement' or 'shutdown'" end if track.key?(:amount) && track[:action] != 'decrement' - fail ArgumentError, "Action must be 'decrement' to set amount" + raise 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' + raise ArgumentError, 'Amount must be greater than zero' end end end @@ -1066,11 +1066,11 @@ def build_tracks_cmd(name, vrid, tracks) vrrp = get(name) vrrp = [] if vrrp.nil? - if vrrp.key?(vrid) - current_tracks = Set.new vrrp[vrid][:track] - else - current_tracks = Set.new [] - end + current_tracks = if vrrp.key?(vrid) + Set.new vrrp[vrid][:track] + else + Set.new [] + end cmds = [] # Add commands to delete any tracks that are diff --git a/lib/rbeapi/client.rb b/lib/rbeapi/client.rb index 1499a58..e6612cc 100644 --- a/lib/rbeapi/client.rb +++ b/lib/rbeapi/client.rb @@ -42,12 +42,13 @@ module Rbeapi # Rbeapi::Client module Client class << self - DEFAULT_TRANSPORT = 'https' + DEFAULT_TRANSPORT = 'https'.freeze TRANSPORTS = { 'http' => 'Rbeapi::Eapilib::HttpEapiConnection', 'https' => 'Rbeapi::Eapilib::HttpsEapiConnection', 'http_local' => 'Rbeapi::Eapilib::HttpLocalEapiConenction', 'socket' => 'Rbeapi::Eapilib::SocketEapiConnection' } + .freeze ## # Returns the currently loaded config object. This function will @@ -165,7 +166,7 @@ def make_connection(transport, opts = {}) # The Config class holds the loaded configuration file data. It is a # subclass of IniFile. class Config < IniFile - CONFIG_SEARCH_PATH = ['/mnt/flash/eapi.conf'] + CONFIG_SEARCH_PATH = ['/mnt/flash/eapi.conf'].freeze ## # The Config class will automatically search for a filename to load @@ -226,10 +227,10 @@ def read(filename) # For each section, if the host parameter is omitted then the # connection name is used. - has_default = self.has_section?('DEFAULT') + has_default = has_section?('DEFAULT') sections.each do |name| next unless name.start_with?('connection:') - conn = self["#{name}"] + conn = self[name.to_s] conn['host'] = name.split(':')[1] unless conn['host'] # Merge in the default values into the connections @@ -517,13 +518,15 @@ def get_config(opts = {}) begin result = run_commands("show #{config} #{params}", encoding: encoding) rescue Rbeapi::Eapilib::CommandError => error - if ( error.to_s =~ /'show (running|startup)-config'/ ) + # rubocop:disable Style/GuardClause + if error.to_s =~ /'show (running|startup)-config'/ return nil else raise error end + # rubocop:enable Style/GuardClause end - if encoding == 'json' + if encoding == 'json' # rubocop:disable Style/GuardClause return result.first else return result.first['output'].strip.split("\n") unless as_string diff --git a/lib/rbeapi/eapilib.rb b/lib/rbeapi/eapilib.rb index e548887..455bff2 100644 --- a/lib/rbeapi/eapilib.rb +++ b/lib/rbeapi/eapilib.rb @@ -46,8 +46,8 @@ module Eapilib DEFAULT_HTTP_LOCAL_PORT = 8080 DEFAULT_HTTP_OPEN_TIMEOUT = 10 DEFAULT_HTTP_READ_TIMEOUT = 10 - DEFAULT_HTTP_PATH = '/command-api' - DEFAULT_UNIX_SOCKET = '/var/run/command-api.sock' + DEFAULT_HTTP_PATH = '/command-api'.freeze + DEFAULT_UNIX_SOCKET = '/var/run/command-api.sock'.freeze ## # Base error class for generating exceptions. The EapiError class @@ -283,7 +283,7 @@ def send(data, opts) if decoded.include?('error') code = decoded['error']['code'] msg = decoded['error']['message'] - fail CommandError.new(msg, code) + raise CommandError.new(msg, code) end rescue Timeout::Error raise ConnectionError, 'unable to connect to eAPI' diff --git a/lib/rbeapi/netdev/snmp.rb b/lib/rbeapi/netdev/snmp.rb index abf513d..e219c56 100644 --- a/lib/rbeapi/netdev/snmp.rb +++ b/lib/rbeapi/netdev/snmp.rb @@ -104,7 +104,7 @@ def parse_snmp_hosts(text) resource_hash[:security] = sec_match[1] if sec_match ver_match = /^(v\d)/.match(auth) # first 2 characters resource_hash[:version] = ver_match[1] if ver_match - resource_hash[:type] = /trap/.match(type) ? :traps : :informs + resource_hash[:type] = type =~ /trap/ ? :traps : :informs resource_hash[:username] = username resource_hash end @@ -243,7 +243,7 @@ def parse_snmp_users(text) user_s.scan(/^(\w+).*?: (.*)/).each_with_object({}) do |(h, v), m| key = SNMP_USER_PARAM[h.downcase.intern] || h.downcase.intern m[key] = case key - when :privacy then /AES/.match(v) ? :aes128 : :des + when :privacy then v =~ /AES/ ? :aes128 : :des when :version then v.sub('v2c', 'v2').intern when :auth then v.downcase.intern when :roles then v.sub(/ \(.*?\)/, '') @@ -262,7 +262,7 @@ def parse_snmp_users(text) authentication: :auth, privacy: :privacy, group: :roles - } + }.freeze ## # snmp_user_set creates or updates an SNMP user account on the target @@ -290,13 +290,15 @@ def parse_snmp_users(text) # hash which is idempotent. def snmp_user_set(opts = {}) group = [*opts[:roles]].first - fail ArgumentError, 'at least one role is required' unless group + raise ArgumentError, 'at least one role is required' unless group version = opts[:version].to_s.sub('v2', 'v2c') cmd = user_cmd = "snmp-server user #{opts[:name]} #{group} #{version}" if opts[:password] && version == 'v3' privacy = opts[:privacy].to_s.scan(/aes|des/).first - fail ArgumentError, - 'privacy is required when managing passwords' unless privacy + unless privacy + raise ArgumentError, + 'privacy is required when managing passwords' + end cmd += " auth #{opts[:auth] || 'sha'} #{opts[:password]} "\ "priv #{privacy} #{opts[:password]}" end diff --git a/lib/rbeapi/switchconfig.rb b/lib/rbeapi/switchconfig.rb index 17fc2f6..dbef4a6 100644 --- a/lib/rbeapi/switchconfig.rb +++ b/lib/rbeapi/switchconfig.rb @@ -127,7 +127,7 @@ def _compare_cmds(cmds2) # @return [Section] The Section object contains the portion of self # that is not in section2. def compare_r(section2) - fail '@line must equal section2.line' if @line != section2.line + raise '@line must equal section2.line' if @line != section2.line # XXX Need to have a list of exceptions of mode commands that # support default. If all the commands have been removed from @@ -180,9 +180,7 @@ def compare_r(section2) # in section2. The second Section object returned is the portion of # section2 that is not in self. def compare(section2) - if @line != section2.line - fail 'XXX What if @line does not equal section2.line' - end + raise '@line does not equal section2.line' if @line != section2.line results = [] # Compare self with section2 @@ -234,16 +232,17 @@ def chk_format(config) config.each_line do |line| skip = true if @multiline_cmds.any? { |cmd| line =~ /#{cmd}/ } if skip - if line =~ /^\s*EOF$/ + if line =~ /^\s*EOF$/ # rubocop:disable Style/GuardClause skip = false else next end end ind = line[/\A */].size - if ind % @indent != 0 - fail ArgumentError, 'SwitchConfig indentation must be multiple of '\ - "#{@indent} improper indent #{ind}: #{line}" + if (ind % @indent).nonzero? # rubocop:disable Style/Next + raise ArgumentError, 'SwitchConfig indentation must be multiple '\ + "of #{@indent} improper indent #{ind}: "\ + "#{line}" end end true @@ -277,7 +276,7 @@ def parse(config) end if combine longline << line - if line =~ /^\s*EOF$/ + if line =~ /^\s*EOF$/ # rubocop:disable Style/GuardClause line = longline.join combine = false else @@ -287,7 +286,7 @@ def parse(config) # Ignore comment lines and the end statement if there # XXX Fix parsing end - next if line.start_with?('!') || line.start_with?('end') + next if line.start_with?('!', 'end') line.chomp! next if line.empty? indent_level = line[/\A */].size / @indent diff --git a/lib/rbeapi/version.rb b/lib/rbeapi/version.rb index 9640a4c..c97afe7 100644 --- a/lib/rbeapi/version.rb +++ b/lib/rbeapi/version.rb @@ -33,5 +33,5 @@ # # # Rbeapi toplevel namespace. module Rbeapi - VERSION = '1.0' + VERSION = '1.1'.freeze end diff --git a/spec/support/fixtures.rb b/spec/support/fixtures.rb index 73501ab..7ea082b 100644 --- a/spec/support/fixtures.rb +++ b/spec/support/fixtures.rb @@ -37,7 +37,7 @@ def self.clear def self.save(key, obj, opts = {}) dir = opts[:dir] || File.expand_path('../../fixtures', __FILE__) file = Pathname.new(File.join(dir, "fixture_#{key}.yaml")) - fail ArgumentError, "Error, file #{file} exists" if file.exist? + raise ArgumentError, "Error, file #{file} exists" if file.exist? File.open(file, 'w+') { |f| f.puts YAML.dump(obj) } end end @@ -75,9 +75,9 @@ def fixture(key, opts = { format: :ruby }) text = Pathname.new(File.join(dir, "fixture_#{key}.text")) data = if yaml.exist?; then YAML.load(File.read(yaml)) - elsif json.exist?; then JSON.load(File.read(json)) + elsif json.exist?; then JSON.parse(File.read(json)) elsif text.exist?; then File.read(text) - else fail "could not load YAML, JSON or TEXT fixture #{key} "\ + else raise "could not load YAML, JSON or TEXT fixture #{key} "\ "tried:\n #{yaml}\n #{json} #{text}" end @@ -88,7 +88,7 @@ def fixture(key, opts = { format: :ruby }) when :json then JSON.pretty_generate(data) when :yaml then YAML.dump(data) when :text then data - else fail ArgumentError, "unknown format #{opts[:format].inspect}" + else raise ArgumentError, "unknown format #{opts[:format].inspect}" end end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength diff --git a/spec/support/matchers/switch_config_sections.rb b/spec/support/matchers/switch_config_sections.rb index c940ebc..4cde0b9 100644 --- a/spec/support/matchers/switch_config_sections.rb +++ b/spec/support/matchers/switch_config_sections.rb @@ -61,12 +61,12 @@ def section_compare(section1, section2) RSpec::Matchers.define :section_equal do |expected| if expected.class != Rbeapi::SwitchConfig::Section - fail 'expected is not a Rbeapi::SwitchConfig::Section class' + raise 'expected is not a Rbeapi::SwitchConfig::Section class' end match do |actual| if actual.class != Rbeapi::SwitchConfig::Section - fail 'actual is not a Rbeapi::SwitchConfig::Section class' + raise 'actual is not a Rbeapi::SwitchConfig::Section class' end section_compare(actual, expected) diff --git a/spec/system/rbeapi/api/acl_spec.rb b/spec/system/rbeapi/api/acl_spec.rb index 150337b..c7f4599 100644 --- a/spec/system/rbeapi/api/acl_spec.rb +++ b/spec/system/rbeapi/api/acl_spec.rb @@ -34,14 +34,12 @@ '40' => { seqno: '40', action: 'permit', srcaddr: '5.6.7.0', srcprefixlen: '24', log: nil }, '50' => { seqno: '50', action: 'permit', srcaddr: '9.10.11.0', - srcprefixlen: '255.255.255.0', log: 'log' } - } + srcprefixlen: '255.255.255.0', log: 'log' } } end let(:test2_entries) do { '10' => { seqno: '10', action: 'deny', srcaddr: '16.0.0.0', - srcprefixlen: '8', log: nil } - } + srcprefixlen: '8', log: nil } } end describe '#get' do diff --git a/spec/system/rbeapi/api/alias_spec.rb b/spec/system/rbeapi/api/alias_spec.rb new file mode 100644 index 0000000..4b13518 --- /dev/null +++ b/spec/system/rbeapi/api/alias_spec.rb @@ -0,0 +1,168 @@ +# +# Copyright (c) 2016, 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/client' +require 'rbeapi/api/alias' + +describe Rbeapi::Api::Alias do + subject { described_class.new(node) } + + let(:node) do + Rbeapi::Client.config.read(fixture_file('dut.conf')) + Rbeapi::Client.connect_to('dut') + end + + let(:command) do + 'my command' + end + + let(:test) do + { name: 'test1', + command: 'my command' } + end + + let(:test_multi) do + { name: 'desc', + command: "1 conf\n 2 int %1\n 3 descr %2\n 4 end" } + end + + describe '#getall' do + let(:resource) { subject.getall } + + let(:test1_entries) do + { 'test1' => { name: 'test1', command: 'my command' }, + 'test2' => { name: 'test2', command: 'my command 2' }, + 'test3' => { name: 'test3', command: 'my command 3' }, + 'desc' => { name: 'desc', + command: "1 conf\n 2 int %1\n 3 descr %2\n 4 end" } } + end + + before do + node.config(['no alias test1', + 'no alias test2', + 'no alias test3.domain', + 'no alias desc', + 'alias test1 my command', + 'alias test2 my command 2', + 'alias test3 my command 3', + 'alias desc', + '1 conf', + '2 int %1', + '3 descr %2', + '4 end', + 'end']) + end + + it 'returns the alias 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 + end + + describe '#get' do + before do + node.config(['no alias test1', + 'no alias test2', + 'no alias test3.domain', + 'no alias desc', + 'alias test1 my command', + 'alias test2 my command 2', + 'alias test3 my command 3', + 'alias desc', + '1 conf', + '2 int %1', + '3 descr %2', + '4 end', + 'end']) + end + it 'returns the alias name and body' do + expect(subject.get('test1')).to eq(test) + end + + it 'returns the alias name and body for multiline entry' do + expect(subject.get('desc')).to eq(test_multi) + end + + it 'returns a hash' do + expect(subject.get('test1')).to be_a_kind_of(Hash) + end + + it 'has 2 entries' do + expect(subject.get('test1').size).to eq(2) + end + end + + describe '#create' do + before do + node.config(['no alias test1']) + end + + it 'create a new alias name' do + expect(subject.get('test1')).to eq(nil) + expect(subject.create('test1', command: 'my command')).to be_truthy + expect(subject.get('test1')[:command]).to eq('my command') + end + + it 'raises ArgumentError for create without required args ' do + expect { subject.create('test1') }.to \ + raise_error ArgumentError + end + end + + describe '#delete' do + before do + node.config(['alias test12 a command']) + end + + it 'delete an alias resource' do + expect(subject.get('test12')[:name]).to eq('test12') + expect(subject.delete('test12')).to be_truthy + expect(subject.get('test12')).to eq(nil) + end + end + + describe '#set_command' do + before do + node.config(['no alias test13']) + end + + it 'change the command alias' do + expect(subject.create('test13', command: 'my command')).to be_truthy + expect(subject.create('test13', command: 'my command 2')).to be_truthy + expect(subject.get('test13')[:command]).to eq('my command 2') + end + end +end diff --git a/spec/system/rbeapi/api/bgp_spec.rb b/spec/system/rbeapi/api/bgp_spec.rb index d04078a..ab2f192 100644 --- a/spec/system/rbeapi/api/bgp_spec.rb +++ b/spec/system/rbeapi/api/bgp_spec.rb @@ -68,8 +68,7 @@ shutdown: true, description: nil, next_hop_self: true, route_map_in: nil, route_map_out: nil } - } - } + } } end let(:response) do diff --git a/spec/system/rbeapi/api/interfaces_base_spec.rb b/spec/system/rbeapi/api/interfaces_base_spec.rb index 4c26cd5..fc76d5a 100644 --- a/spec/system/rbeapi/api/interfaces_base_spec.rb +++ b/spec/system/rbeapi/api/interfaces_base_spec.rb @@ -20,8 +20,8 @@ describe '#get' do context 'with interface Loopback' do let(:entity) do - { name: 'Loopback0', type: 'generic', description: '', shutdown: false, - load_interval: '' } + { name: 'Loopback0', type: 'generic', description: '', + encapsulation: '', shutdown: false, load_interval: '' } end before { node.config(['no interface Loopback0', 'interface Loopback0']) } @@ -34,9 +34,9 @@ context 'with interface Port-Channel' do let(:entity) do { name: 'Port-Channel1', type: 'portchannel', description: '', - shutdown: false, load_interval: '', members: [], lacp_mode: 'on', - minimum_links: '0', - lacp_fallback: 'disabled', lacp_timeout: '90' } + encapsulation: '', shutdown: false, load_interval: '', members: [], + lacp_mode: 'on', minimum_links: '0', lacp_fallback: 'disabled', + lacp_timeout: '90' } end before do @@ -51,10 +51,9 @@ context 'with interface Vxlan' do let(:entity) do - { name: 'Vxlan1', type: 'vxlan', description: '', + { name: 'Vxlan1', type: 'vxlan', description: '', encapsulation: '', shutdown: false, load_interval: '', source_interface: '', - multicast_group: '', - udp_port: 4789, flood_list: [], vlans: {} } + multicast_group: '', udp_port: 4789, flood_list: [], vlans: {} } end before do diff --git a/spec/system/rbeapi/api/interfaces_ethernet_spec.rb b/spec/system/rbeapi/api/interfaces_ethernet_spec.rb index 4b30aa2..c492ad1 100644 --- a/spec/system/rbeapi/api/interfaces_ethernet_spec.rb +++ b/spec/system/rbeapi/api/interfaces_ethernet_spec.rb @@ -14,8 +14,8 @@ describe '#get' do let(:entity) do - { name: 'Ethernet1', type: 'ethernet', description: '', shutdown: false, - load_interval: '', speed: 'default', sflow: true, + { name: 'Ethernet1', type: 'ethernet', description: '', encapsulation: '', + shutdown: false, load_interval: '', speed: 'default', sflow: true, flowcontrol_send: 'off', flowcontrol_receive: 'off', lacp_priority: '32768' } end @@ -45,12 +45,33 @@ end end + describe '#create' do + before { node.config('no interface Ethernet1.1') } + + it 'creates a new ethernet subinterface' do + expect(subject.get('Ethernet1.1')).to be_nil + expect(subject.create('Ethernet1.1')).to be_truthy + expect(subject.get('Ethernet1.1')).not_to be_nil + node.config(['no interface Ethernet1.1']) + end + end + describe '#delete' do it 'raises an error on create' do expect { subject.create('Ethernet1') }.to raise_error(NotImplementedError) end end + describe '#delete' do + before { node.config(['interface Ethernet1.1']) } + + it 'deletes an ethernet subinterface resource' do + expect(subject.get('Ethernet1.1')).not_to be_nil + expect(subject.delete('Ethernet1.1')).to be_truthy + expect(subject.get('Ethernet1.1')).to be_nil + end + end + describe '#default' do before { node.config(['interface Ethernet1', :shutdown]) } @@ -71,6 +92,17 @@ end end + describe '#set_encapsulation' do + it 'sets the encapsulation value on the interface' do + node.config(['interface Ethernet1.1', 'no encapsulation dot1q vlan']) + expect(subject.get('Ethernet1.1')[:encapsulation]).to be_empty + expect(subject.set_encapsulation('Ethernet1.1', value: '10')) + .to be_truthy + expect(subject.get('Ethernet1.1')[:encapsulation]).to eq('10') + node.config(['no interface Ethernet1.1']) + end + end + describe '#set_shutdown' do it 'shutdown the interface' do node.config(['interface Ethernet1', 'no shutdown']) @@ -91,7 +123,8 @@ before { node.config(['default interface Ethernet1']) } it 'sets enable true' do - expect(subject.set_speed('Ethernet1', enable: true)).to be_falsy + expect(subject.set_speed('Ethernet1', default: false, + enable: true)).to be_falsy end end diff --git a/spec/system/rbeapi/api/interfaces_portchannel_spec.rb b/spec/system/rbeapi/api/interfaces_portchannel_spec.rb index c0e6f64..eb17323 100644 --- a/spec/system/rbeapi/api/interfaces_portchannel_spec.rb +++ b/spec/system/rbeapi/api/interfaces_portchannel_spec.rb @@ -14,9 +14,9 @@ describe '#get' do let(:entity) do { name: 'Port-Channel1', type: 'portchannel', description: '', - shutdown: false, load_interval: '', members: [], lacp_mode: 'on', - minimum_links: '0', - lacp_timeout: '90', lacp_fallback: 'disabled' } + encapsulation: '', shutdown: false, load_interval: '', members: [], + lacp_mode: 'on', minimum_links: '0', lacp_timeout: '90', + lacp_fallback: 'disabled' } end before do @@ -52,6 +52,17 @@ end end + describe '#create' do + before { node.config('no interface Port-Channel1.1') } + + it 'creates a new subinterface resource' do + expect(subject.get('Port-Channel1.1')).to be_nil + expect(subject.create('Port-Channel1.1')).to be_truthy + expect(subject.get('Port-Channel1.1')).not_to be_nil + node.config(['no interface Port-Channel1.1']) + end + end + describe '#delete' do before { node.config(['interface Port-Channel1']) } @@ -62,6 +73,16 @@ end end + describe '#delete' do + before { node.config(['interface Port-Channel1.1']) } + + it 'deletes a switchport subinterface resource' do + ## expect(subject.get('Port-Channel1.1')).not_to be_nil + expect(subject.delete('Port-Channel1.1')).to be_truthy + expect(subject.get('Port-Channel1.1')).to be_nil + end + end + describe '#default' do before { node.config(['interface Port-Channel1', :shutdown]) } @@ -82,6 +103,17 @@ end end + describe '#set_encapsulation' do + it 'sets the encapsulation value on the interface' do + node.config(['interface Port-Channel1.1', 'no encapsulation dot1q vlan']) + expect(subject.get('Port-Channel1.1')[:encapsulation]).to be_empty + expect(subject.set_encapsulation('Port-Channel1.1', value: '31')) + .to be_truthy + expect(subject.get('Port-Channel1.1')[:encapsulation]).to eq('31') + node.config(['no interface Port-Channel1.1']) + end + end + describe '#set_shutdown' do it 'shutdown the interface' do node.config(['interface Port-Channel1', 'no shutdown']) diff --git a/spec/system/rbeapi/api/interfaces_vlan_spec.rb b/spec/system/rbeapi/api/interfaces_vlan_spec.rb new file mode 100644 index 0000000..92a5f1d --- /dev/null +++ b/spec/system/rbeapi/api/interfaces_vlan_spec.rb @@ -0,0 +1,90 @@ +require 'spec_helper' + +require 'rbeapi/client' +require 'rbeapi/api/interfaces' + +describe Rbeapi::Api::Interfaces 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(:entity) do + { name: 'Vlan1', type: 'vlan', description: '', shutdown: false, + autostate: :true, load_interval: '', encapsulation: '' } + end + + before { node.config(['no interface Vlan1', 'interface Vlan1']) } + + it 'returns the interface resource' do + expect(subject.get('Vlan1')).to eq(entity) + end + end + + describe '#getall' do + before { node.config(['no interface Vlan1', 'interface Vlan1']) } + + it 'returns the interface collection' do + expect(subject.getall).to include('Vlan1') + end + + it 'returns a hash collection' do + expect(subject.getall).to be_a_kind_of(Hash) + end + end + + describe '#create' do + before { node.config('no interface Vlan1') } + + it 'creates a new interface resource' do + expect(subject.create('Vlan1')).to be_truthy + expect(subject.get('Vlan1')).not_to be_nil + end + end + + describe '#delete' do + before { node.config(['interface Vlan1']) } + + it 'deletes a vlan interface resource' do + expect(subject.get('Vlan1')).not_to be_nil + expect(subject.delete('Vlan1')).to be_truthy + expect(subject.get('Vlan1')).to be_nil + end + end + + describe '#default' do + before { node.config(['interface Vlan1', 'shutdown']) } + + it 'sets Vlan1 to default' do + expect(subject.get('Vlan1')[:shutdown]).to be_truthy + expect(subject.default('Vlan1')).to be_truthy + expect(subject.get('Vlan1')[:shutdown]).to be_falsy + end + end + + describe '#set_autostate' do + it 'sets the autostate value to true' do + node.config(['interface Vlan1', 'no autostate']) + expect(subject.get('Vlan1')[:autostate]).to eq(:false) + expect(subject.set_autostate('Vlan1', value: :true)).to be_truthy + expect(subject.get('Vlan1')[:autostate]).to eq(:true) + end + + it 'sets the autostate value to false' do + node.config(['interface Vlan1', 'autostate']) + expect(subject.get('Vlan1')[:autostate]).to be_truthy + expect(subject.set_autostate('Vlan1', value: :false)).to be_truthy + expect(subject.get('Vlan1')[:autostate]).to eq(:false) + end + + it 'manages autostate default' do + node.config(['interface Vlan1', 'no autostate']) + expect(subject.get('Vlan1')[:autostate]).to eq(:false) + expect(subject.set_autostate('Vlan1', default: :true)).to be_truthy + expect(subject.get('Vlan1')[:autostate]).to eq(:true) + end + end +end diff --git a/spec/system/rbeapi/api/interfaces_vxlan_spec.rb b/spec/system/rbeapi/api/interfaces_vxlan_spec.rb index 149334a..bb064e1 100644 --- a/spec/system/rbeapi/api/interfaces_vxlan_spec.rb +++ b/spec/system/rbeapi/api/interfaces_vxlan_spec.rb @@ -13,10 +13,9 @@ describe '#get' do let(:entity) do - { name: 'Vxlan1', type: 'vxlan', description: '', shutdown: false, - load_interval: '', - source_interface: '', multicast_group: '', udp_port: 4789, - flood_list: [], vlans: {} } + { name: 'Vxlan1', type: 'vxlan', description: '', encapsulation: '', + shutdown: false, load_interval: '', source_interface: '', + multicast_group: '', udp_port: 4789, flood_list: [], vlans: {} } end before { node.config(['no interface Vxlan1', 'interface Vxlan1']) } diff --git a/spec/system/rbeapi/api/managementdefaults_spec.rb b/spec/system/rbeapi/api/managementdefaults_spec.rb new file mode 100644 index 0000000..0d18374 --- /dev/null +++ b/spec/system/rbeapi/api/managementdefaults_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +require 'rbeapi/client' +require 'rbeapi/api/managementdefaults' + +describe Rbeapi::Api::Managementdefaults 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 + before { node.config(['management defaults', 'default secret hash']) } + + it 'contains all required settings' do + expect(subject.get).to include(:secret_hash) + end + end + + describe '#set_secret_hash' do + before { node.config(['management defaults', 'default secret hash']) } + + it 'configures the management defaults secret hash value' do + expect(subject.get[:secret_hash]).to eq('md5') + expect(subject.set_secret_hash(value: 'sha512')).to be_truthy + expect(subject.get[:secret_hash]).to eq('sha512') + end + end +end diff --git a/spec/system/rbeapi/api/ospf_interfaces_spec.rb b/spec/system/rbeapi/api/ospf_interfaces_spec.rb index 66f479a..2a32a17 100644 --- a/spec/system/rbeapi/api/ospf_interfaces_spec.rb +++ b/spec/system/rbeapi/api/ospf_interfaces_spec.rb @@ -14,11 +14,15 @@ node.config(['default interface Ethernet1', 'interface Ethernet1', 'no switchport', 'ip address 88.99.99.99/24', 'ip ospf network point-to-point', - 'exit', 'default interface Ethernet2']) + 'exit', 'default interface Ethernet2', + 'no interface Vlan99', 'interface Vlan99', + 'ip address 99.9.9.9/24', 'ip ospf network point-to-point', + 'exit']) end it 'returns an ospf interface resource instance' do expect(subject.get('Ethernet1')).not_to be_nil + expect(subject.get('Vlan99')).not_to be_nil end it 'returns nil for a switchport interface' do @@ -29,16 +33,21 @@ describe '#getall' do before do node.config(['default interface Ethernet1', 'interface Ethernet1', - 'no switchport', 'ip address 88.99.99.99/24', 'exit', - 'default interface Ethernet2']) + 'no switchport', 'ip address 88.99.99.99/24', + 'ip ospf network point-to-point', + 'exit', 'default interface Ethernet2', + 'no interface Vlan99', 'interface Vlan99', + 'ip address 99.9.9.9/24', 'ip ospf network point-to-point', + 'exit']) end it 'returns the ospf resource collection' do expect(subject.getall).to be_a_kind_of(Hash) end - it 'includes an instance of Ethernet1' do + it 'includes an instance of Ethernet1 and Vlan99' do expect(subject.getall).to include('Ethernet1') + expect(subject.getall).to include('Vlan99') end it 'does not include an instance of Ethernet2' do @@ -49,30 +58,46 @@ describe '#set_network_type' do before do node.config(['default interface Ethernet1', 'interface Ethernet1', - 'no switchport', 'ip address 88.99.99.99/24', 'exit']) + 'no switchport', 'ip address 88.99.99.99/24', 'exit', + 'no interface Vlan99', 'interface Vlan99', + 'ip address 99.9.9.9/24', 'exit']) end it 'configures the ospf interface type to point-to-point' do - expect(subject.get('Ethernet1')['network_type']).to eq('broadcast') + expect(subject.get('Ethernet1')[:network_type]).to eq('broadcast') expect(subject.set_network_type('Ethernet1', value: 'point-to-point')) .to be_truthy - expect(subject.get('Ethernet1')['network_type']).to eq('point-to-point') + expect(subject.get('Ethernet1')[:network_type]).to eq('point-to-point') + expect(subject.get('Vlan99')[:network_type]).to eq('broadcast') + expect(subject.set_network_type('Vlan99', value: 'point-to-point')) + .to be_truthy + expect(subject.get('Vlan99')[:network_type]).to eq('point-to-point') end it 'negates the ospf interface type' do expect(subject.set_network_type('Ethernet1', value: 'point-to-point')) .to be_truthy - expect(subject.get('Ethernet1')['network_type']).to eq('point-to-point') + expect(subject.get('Ethernet1')[:network_type]).to eq('point-to-point') expect(subject.set_network_type('Ethernet1', enable: false)).to be_truthy - expect(subject.get('Ethernet1')['network_type']).to eq('broadcast') + expect(subject.get('Ethernet1')[:network_type]).to eq('broadcast') + expect(subject.set_network_type('Vlan99', value: 'point-to-point')) + .to be_truthy + expect(subject.get('Vlan99')[:network_type]).to eq('point-to-point') + expect(subject.set_network_type('Vlan99', enable: false)).to be_truthy + expect(subject.get('Vlan99')[:network_type]).to eq('broadcast') end it 'defaults the ospf interface type' do expect(subject.set_network_type('Ethernet1', value: 'point-to-point')) .to be_truthy - expect(subject.get('Ethernet1')['network_type']).to eq('point-to-point') + expect(subject.get('Ethernet1')[:network_type]).to eq('point-to-point') expect(subject.set_network_type('Ethernet1', default: true)).to be_truthy - expect(subject.get('Ethernet1')['network_type']).to eq('broadcast') + expect(subject.get('Ethernet1')[:network_type]).to eq('broadcast') + expect(subject.set_network_type('Vlan99', value: 'point-to-point')) + .to be_truthy + expect(subject.get('Vlan99')[:network_type]).to eq('point-to-point') + expect(subject.set_network_type('Vlan99', default: true)).to be_truthy + expect(subject.get('Vlan99')[:network_type]).to eq('broadcast') end end end diff --git a/spec/system/rbeapi/api/ospf_spec.rb b/spec/system/rbeapi/api/ospf_spec.rb index 22c95d1..a849827 100644 --- a/spec/system/rbeapi/api/ospf_spec.rb +++ b/spec/system/rbeapi/api/ospf_spec.rb @@ -14,15 +14,25 @@ node.config(['no router ospf 1', 'router ospf 1', 'router-id 1.1.1.1', + 'max-lsa 12000', + 'maximum-paths 16', + 'passive-interface default', + 'no passive-interface Ethernet1', + 'no passive-interface Ethernet2', 'redistribute static route-map word', 'network 192.168.10.10/24 area 0.0.0.0', 'network 192.168.11.10/24 area 0.0.0.0']) end let(:entity) do - { 'router_id' => '1.1.1.1', - 'areas' => { '0.0.0.0' => ['192.168.10.0/24', '192.168.11.0/24'] }, - 'redistribute' => { 'static' => { 'route_map' => 'word' } } } + { router_id: '1.1.1.1', + max_lsa: 12_000, + maximum_paths: 16, + passive_interface_default: true, + active_interfaces: %w(Ethernet1 Ethernet2), + passive_interfaces: [], + areas: { '0.0.0.0' => ['192.168.10.0/24', '192.168.11.0/24'] }, + redistribute: { 'static' => { route_map: 'word' } } } end it 'returns an ospf resource instance' do @@ -39,8 +49,8 @@ expect(collection).to include('1') end - it ' includes interfaces' do - expect(collection).to include('interfaces') + it 'includes interfaces' do + expect(collection).to include(:interfaces) end it 'is a kind of hash' do @@ -58,23 +68,203 @@ before { node.config(['no router ospf 1', 'router ospf 1']) } it 'configures the ospf router id to 1.1.1.1' do - expect(subject.get('1')['router_id']).to be_empty + expect(subject.get('1')[:router_id]).to be_empty expect(subject.set_router_id('1', value: '1.1.1.1')).to be_truthy - expect(subject.get('1')['router_id']).to eq('1.1.1.1') + expect(subject.get('1')[:router_id]).to eq('1.1.1.1') end it 'negates the router id' do expect(subject.set_router_id('1', value: '1.1.1.1')).to be_truthy - expect(subject.get('1')['router_id']).to eq('1.1.1.1') + expect(subject.get('1')[:router_id]).to eq('1.1.1.1') expect(subject.set_router_id('1', enable: false)).to be_truthy - expect(subject.get('1')['router_id']).to be_empty + expect(subject.get('1')[:router_id]).to be_empty end it 'defaults the router id' do expect(subject.set_router_id('1', value: '1.1.1.1')).to be_truthy - expect(subject.get('1')['router_id']).to eq('1.1.1.1') + expect(subject.get('1')[:router_id]).to eq('1.1.1.1') expect(subject.set_router_id('1', default: true)).to be_truthy - expect(subject.get('1')['router_id']).to be_empty + expect(subject.get('1')[:router_id]).to be_empty + end + end + + describe '#set_max_lsa' do + before { node.config(['no router ospf 1', 'router ospf 1']) } + + it 'configures the ospf max-lsa to 24000' do + expect(subject.get('1')[:max_lsa]).to eq(12_000) + expect(subject.set_max_lsa('1', value: 24_000)).to be_truthy + expect(subject.get('1')[:max_lsa]).to eq(24_000) + end + + it 'negates the max-lsa' do + expect(subject.set_max_lsa('1', value: 24_000)).to be_truthy + expect(subject.get('1')[:max_lsa]).to eq(24_000) + expect(subject.set_max_lsa('1', enable: false)).to be_truthy + expect(subject.get('1')[:max_lsa]).to eq(12_000) + end + + it 'defaults the max-lsa' do + expect(subject.set_max_lsa('1', value: 24_000)).to be_truthy + expect(subject.get('1')[:max_lsa]).to eq(24_000) + expect(subject.set_max_lsa('1', default: true)).to be_truthy + expect(subject.get('1')[:max_lsa]).to eq(12_000) + end + end + + describe '#set_maximum_paths' do + before { node.config(['no router ospf 1', 'router ospf 1']) } + + it 'configures the ospf maximum-paths to 16' do + expect(subject.get('1')[:maximum_paths]).to eq(128) + expect(subject.set_maximum_paths('1', value: 16)).to be_truthy + expect(subject.get('1')[:maximum_paths]).to eq(16) + end + + it 'negates the maximum-paths' do + expect(subject.set_maximum_paths('1', value: 16)).to be_truthy + expect(subject.get('1')[:maximum_paths]).to eq(16) + expect(subject.set_maximum_paths('1', enable: false)).to be_truthy + expect(subject.get('1')[:maximum_paths]).to eq(128) + end + + it 'defaults the maximum-paths' do + expect(subject.set_maximum_paths('1', value: 16)).to be_truthy + expect(subject.get('1')[:maximum_paths]).to eq(16) + expect(subject.set_maximum_paths('1', default: true)).to be_truthy + expect(subject.get('1')[:maximum_paths]).to eq(128) + end + end + + describe '#passive_interface_default' do + before { node.config(['no router ospf 1', 'router ospf 1']) } + + it 'configures the passive-interface default' do + expect(subject.get('1')[:passive_interface_default]).to eq(false) + expect(subject.set_passive_interface_default('1', value: true)) + .to be_truthy + expect(subject.get('1')[:passive_interface_default]).to eq(true) + end + + it 'negates the passive-interface default' do + expect(subject.set_passive_interface_default('1', value: true)) + .to be_truthy + expect(subject.get('1')[:passive_interface_default]).to eq(true) + expect(subject.set_passive_interface_default('1', enable: false)) + .to be_truthy + expect(subject.get('1')[:passive_interface_default]).to eq(false) + end + + it 'defaults the passive-interface default' do + expect(subject.set_passive_interface_default('1', value: true)) + .to be_truthy + expect(subject.get('1')[:passive_interface_default]).to eq(true) + expect(subject.set_passive_interface_default('1', default: true)) + .to be_truthy + expect(subject.get('1')[:passive_interface_default]).to eq(false) + end + end + + describe '#set_active_interfaces' do + before do + node.config(['no router ospf 1', 'router ospf 1', + 'passive-interface default']) + end + + it 'configures the ospf no passive-interface Ethernet1, 2, 3' do + expect(subject.get('1')[:active_interfaces]).to be_empty + expect(subject.set_active_interfaces('1', + value: %w(Ethernet1 + Ethernet2 + Ethernet3))).to be_truthy + expect(subject.get('1')[:active_interfaces]).to eq(%w(Ethernet1 + Ethernet2 + Ethernet3)) + end + + it 'configures the ospf no passive-interface Ethernet1, 2' do + expect(subject.set_active_interfaces('1', + value: %w(Ethernet1 + Ethernet2 + Ethernet3))).to be_truthy + expect(subject.get('1')[:active_interfaces]).to eq(%w(Ethernet1 + Ethernet2 + Ethernet3)) + expect(subject.set_active_interfaces('1', + value: %w(Ethernet1 + Ethernet2))).to be_truthy + expect(subject.get('1')[:active_interfaces]).to eq(%w(Ethernet1 + Ethernet2)) + end + + it 'negates the no passive-interface' do + expect(subject.set_active_interfaces('1', + value: %w(Ethernet1 + Ethernet2))).to be_truthy + expect(subject.get('1')[:active_interfaces]).to eq(%w(Ethernet1 + Ethernet2)) + expect(subject.set_active_interfaces('1', enable: false)).to be_truthy + expect(subject.get('1')[:active_interfaces]).to be_empty + end + + it 'defaults the no passive-interface' do + expect(subject.set_active_interfaces('1', + value: %w(Ethernet1 + Ethernet2))).to be_truthy + expect(subject.get('1')[:active_interfaces]).to eq(%w(Ethernet1 + Ethernet2)) + expect(subject.set_active_interfaces('1', default: true)).to be_truthy + expect(subject.get('1')[:active_interfaces]).to be_empty + end + end + + describe '#set_passive_interfaces' do + before { node.config(['no router ospf 1', 'router ospf 1']) } + + it 'configures the ospf passive-interface Ethernet1, 2, 3' do + expect(subject.get('1')[:passive_interfaces]).to be_empty + expect(subject.set_passive_interfaces('1', + value: %w(Ethernet1 + Ethernet2 + Ethernet3))).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to eq(%w(Ethernet1 + Ethernet2 + Ethernet3)) + end + + it 'configures the ospf passive-interface Ethernet1, 2' do + expect(subject.set_passive_interfaces('1', + value: %w(Ethernet1 + Ethernet2 + Ethernet3))).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to eq(%w(Ethernet1 + Ethernet2 + Ethernet3)) + expect(subject.set_passive_interfaces('1', + value: %w(Ethernet1 + Ethernet2))).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to eq(%w(Ethernet1 + Ethernet2)) + end + + it 'negates the passive-interface' do + expect(subject.set_passive_interfaces('1', + value: %w(Ethernet1 + Ethernet2))).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to eq(%w(Ethernet1 + Ethernet2)) + expect(subject.set_passive_interfaces('1', enable: false)).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to be_empty + end + + it 'defaults the passive-interface' do + expect(subject.set_passive_interfaces('1', + value: %w(Ethernet1 + Ethernet2))).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to eq(%w(Ethernet1 + Ethernet2)) + expect(subject.set_passive_interfaces('1', default: true)).to be_truthy + expect(subject.get('1')[:passive_interfaces]).to be_empty end end @@ -102,10 +292,10 @@ before { node.config('router ospf 1') } it 'adds the network with area to the ospf process' do - expect(subject.get('1')['areas']).to be_empty + expect(subject.get('1')[:areas]).to be_empty expect(subject.add_network('1', '192.168.10.0/24', '0.0.0.0')) .to be_truthy - expect(subject.get('1')['areas']['0.0.0.0']).to include('192.168.10.0/24') + expect(subject.get('1')[:areas]['0.0.0.0']).to include('192.168.10.0/24') end end @@ -115,10 +305,10 @@ end it 'removes the network with area to the ospf process' do - expect(subject.get('1')['areas']['0.0.0.0']).to include('192.168.10.0/24') + expect(subject.get('1')[:areas]['0.0.0.0']).to include('192.168.10.0/24') expect(subject.remove_network('1', '192.168.10.0/24', '0.0.0.0')) .to be_truthy - expect(subject.get('1')['areas']).to be_empty + expect(subject.get('1')[:areas]).to be_empty end end @@ -126,9 +316,42 @@ before { node.config(['no router ospf 1', 'router ospf 1']) } it 'configures redistribution of static routes' do - expect(subject.get('1')['redistribute']).not_to include('static') + expect(subject.get('1')[:redistribute]).to be_empty + expect(subject.set_redistribute('1', 'static')).to be_truthy + expect(subject.get('1')[:redistribute]) + .to eq('static' => { route_map: nil }) + end + + it 'configures redistribution of static routes with routemap' do expect(subject.set_redistribute('1', 'static')).to be_truthy - expect(subject.get('1')['redistribute']).to include('static') + expect(subject.get('1')[:redistribute]) + .to eq('static' => { route_map: nil }) + expect(subject.set_redistribute('1', 'static', route_map: 'test')) + .to be_truthy + expect(subject.get('1')[:redistribute]) + .to eq('static' => { route_map: 'test' }) + end + + it 'negates the redistribution' do + expect(subject.set_redistribute('1', 'static', route_map: 'test')) + .to be_truthy + expect(subject.get('1')[:redistribute]) + .to eq('static' => { route_map: 'test' }) + expect(subject.set_redistribute('1', + 'static', + enable: false)).to be_truthy + expect(subject.get('1')[:redistribute]).to be_empty + end + + it 'defaults the redistribution' do + expect(subject.set_redistribute('1', 'connected', route_map: 'foo')) + .to be_truthy + expect(subject.get('1')[:redistribute]) + .to eq('connected' => { route_map: 'foo' }) + expect(subject.set_redistribute('1', + 'connected', + default: true)).to be_truthy + expect(subject.get('1')[:redistribute]).to be_empty end end end diff --git a/spec/system/rbeapi/api/prefixlists_spec.rb b/spec/system/rbeapi/api/prefixlists_spec.rb index 03f5cac..193d30f 100644 --- a/spec/system/rbeapi/api/prefixlists_spec.rb +++ b/spec/system/rbeapi/api/prefixlists_spec.rb @@ -42,94 +42,111 @@ Rbeapi::Client.connect_to('dut') end - describe '#get' do - before do - node.config(['no ip prefix-list test1', - 'ip prefix-list test1', - 'seq 10 permit 1.2.3.0/24', - 'seq 20 permit 2.3.4.0/24 le 30', - 'seq 30 deny 3.4.5.0/24 ge 26 le 30', - 'permit 5.6.7.16/28 eq 29']) + let(:delete_prefix_lists) do + config = subject.node.running_config + config.scan(/(?<=^ip\sprefix-list\s)[^\s]+(?=\sseq.+)?/).uniq.map do |name| + "no ip prefix-list #{name}" end + end + describe '#get' do let(:prefixlist) { subject.get('test1') } - it 'returns the prefix list for an existing name' do - expect(prefixlist).to be_a_kind_of(Array) - end - - it 'returns all rules as hash' do - expect(prefixlist).to all ( be_an(Hash) ) - end - - it 'has all keys for each rule' do - prefixlist.each do |rule| - expect(rule).to have_key('seq') - expect(rule).to have_key('prefix') - expect(rule).to have_key('action') - end - end - - let(:values) do + let(:expected) do [ - { - 'seq' => '10', + { 'seq' => '10', 'action' => 'permit', - 'prefix' => '1.2.3.0/24' - }, - { - 'seq' => '20', + 'prefix' => '10.10.1.0/24' }, + { 'seq' => '20', 'action' => 'permit', - 'prefix' => '2.3.4.0/24 le 30' - }, - { - 'seq' => '30', + 'prefix' => '10.20.1.0/24 le 30' }, + { 'seq' => '30', 'action' => 'deny', - 'prefix' => '3.4.5.0/24 ge 26 le 30' - }, - { - 'seq' => '40', + 'prefix' => '10.30.1.0/24 ge 26 le 30' }, + { 'seq' => '40', 'action' => 'permit', - 'prefix' => '5.6.7.16/28 eq 29' - } + 'prefix' => '10.40.1.16/28 eq 29' } ] end - it 'returns the correct values for all the keys' do - expect(prefixlist).to eq(values) + let(:keys) { %w(seq action prefix) } + + [ + { title: 'single-line', + cmds: ['ip prefix-list test1 seq 10 permit 10.10.1.0/24', + 'ip prefix-list test1 seq 20 permit 10.20.1.0/24 le 30', + 'ip prefix-list test1 seq 30 deny 10.30.1.0/24 ge 26 le 30', + 'ip prefix-list test1 permit 10.40.1.16/28 eq 29'] }, + { title: 'multi-line', + cmds: ['ip prefix-list test1', + 'seq 10 permit 10.10.1.0/24', + 'seq 20 permit 10.20.1.0/24 le 30', + 'seq 30 deny 10.30.1.0/24 ge 26 le 30', + 'permit 10.40.1.16/28 eq 29'] } + ].each do |context| + context "when prefix list is #{context[:title]}" do + before { node.config(delete_prefix_lists + context[:cmds]) } + + it 'returns all rules in an array' do + expect(prefixlist).to be_a_kind_of(Array) + end + + it 'returns each rule as hash' do + expect(prefixlist).to all be_an(Hash) + end + + it 'has all keys for each rule' do + prefixlist.each do |rule| + expect(rule.keys).to match_array(keys) + end + end + + it 'returns the correct values for all rules' do + expect(prefixlist).to eq(expected) + end + end end end describe '#getall' do - let(:del_pref_lists) { - subject.getall.keys.map { |k| "no ip prefix-list #{k}" } - } - - before do - node.config(del_pref_lists + - ['ip prefix-list test1', - 'seq 10 permit 1.2.3.0/24', - 'seq 20 permit 2.3.4.0/24 le 30', - 'seq 30 deny 3.4.5.0/24 ge 26 le 30', - 'permit 5.6.7.8/28', - 'ip prefix-list test2', - 'seq 10 permit 10.11.0.0/16', - 'seq 20 permit 10.12.0.0/16 le 24', - 'ip prefix-list test3']) - end - let(:prefixlists) { subject.getall } - it 'returns the collection as hash' do - expect(prefixlists).to be_a_kind_of(Hash) - end - - it 'returns all prefix lists as array' do - expect(prefixlists).to all ( be_an(Array) ) - end - - it 'has three prefix lists' do - expect(prefixlists.size).to eq(3) + [ + { title: 'single-line', + cmds: ['ip prefix-list test1 seq 10 permit 10.10.1.0/24', + 'ip prefix-list test1 seq 20 permit 10.20.1.0/24 le 30', + 'ip prefix-list test1 seq 30 deny 10.30.1.0/24 ge 26 le 30', + 'ip prefix-list test1 permit 10.40.1.8/28', + 'ip prefix-list test2 seq 10 permit 10.11.0.0/16', + 'ip prefix-list test2 seq 20 permit 10.12.0.0/16 le 24', + 'ip prefix-list test3 permit 10.13.0.0/16'] }, + { title: 'multi-line', + cmds: ['ip prefix-list test1', + 'seq 10 permit 10.10.1.0/24', + 'seq 20 permit 10.20.1.0/24 le 30', + 'seq 30 deny 10.30.1.0/24 ge 26 le 30', + 'permit 10.40.1.8/28', + 'ip prefix-list test2', + 'seq 10 permit 10.11.0.0/16', + 'seq 20 permit 10.12.0.0/16 le 24', + 'ip prefix-list test3', + 'permit 10.13.0.0/16'] } + ].each do |context| + context "when prefix lists are #{context[:title]}" do + before { node.config(delete_prefix_lists + context[:cmds]) } + + it 'returns the collection as hash' do + expect(prefixlists).to be_a_kind_of(Hash) + end + + it 'returns each prefix lists as an array' do + expect(prefixlists).to all be_an(Array) + end + + it 'has three prefix lists' do + expect(prefixlists.size).to eq(3) + end + end end end @@ -149,40 +166,39 @@ describe '#add_rule' do before do node.config(['no ip prefix-list test5', - 'ip prefix-list test5']) + 'no ip prefix-list test6', + 'ip prefix-list test5']) end it 'adds rule to an existing prefix list' do expect(subject.get('test5')).to eq([]) - expect(subject.add_rule('test5', 'permit', '1.1.1.0/24')).to be_truthy - expect(subject.get('test5')).to eq([{ - "seq" => "10", - "action" => "permit", - "prefix" => "1.1.1.0/24"}]) + expect(subject.add_rule('test5', 'permit', '10.50.1.0/24')).to be_truthy + expect(subject.get('test5')).to eq([{ 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.50.1.0/24' }]) end it 'adds rule to a non-existent prefix list' do expect(subject.get('test6')).to eq(nil) - expect(subject.add_rule('test6', 'deny', '2.2.2.0/24')).to be_truthy - expect(subject.get('test6')).to eq([{ - "seq" => "10", - "action" => "deny", - "prefix" => "2.2.2.0/24"}]) + expect(subject.add_rule('test6', 'deny', '10.60.1.0/24')).to be_truthy + expect(subject.get('test6')).to eq([{ 'seq' => '10', + 'action' => 'deny', + 'prefix' => '10.60.1.0/24' }]) end end describe '#delete' do before do node.config(['no ip prefix-list test7', - 'no ip prefix-list test8', - 'ip prefix-list test7', - 'seq 10 permit 7.7.0.0/16', - 'ip prefix-list test8', - 'seq 10 permit 8.8.0.0/16', - 'deny 9.9.0.0/16 le 24']) + 'no ip prefix-list test8', + 'ip prefix-list test7', + 'seq 10 permit 10.70.0.0/16', + 'ip prefix-list test8', + 'seq 10 permit 10.80.0.0/16', + 'deny 10.82.0.0/16 le 24']) end - it 'delets a prefix list' do + it 'deletes a prefix list' do expect(subject.get('test7')).to be_truthy expect(subject.delete('test7')).to be_truthy expect(subject.get('test7')).to eq(nil) @@ -195,4 +211,4 @@ expect(subject.get('test8')[1]).to eq(nil) end end -end \ No newline at end of file +end diff --git a/spec/system/rbeapi/api/routemaps_spec.rb b/spec/system/rbeapi/api/routemaps_spec.rb index ee3cff4..7f481a2 100644 --- a/spec/system/rbeapi/api/routemaps_spec.rb +++ b/spec/system/rbeapi/api/routemaps_spec.rb @@ -124,8 +124,7 @@ continue: 99, description: 'descript', match: ['ip address prefix-list MYLOOPBACK', 'interface Loopback0'], - set: ['community internet 5555:5555']) - ).to be_truthy + set: ['community internet 5555:5555'])).to be_truthy expect(subject.get('test')).to eq(test_entry) end @@ -135,7 +134,8 @@ 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) + 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) @@ -213,11 +213,13 @@ expect( subject.set_match_statements('test', 'permit', 10, ['ip address prefix-list MYLOOPBACK', - 'interface Loopback0'])).to be_truthy + 'interface Loopback0']) + ).to be_truthy expect(subject.get('test')) .to eq('permit' => { 10 => { match: ['ip address prefix-list MYLOOPBACK', - 'interface Loopback0'] } }) + 'interface Loopback0'] + } }) end it 'adds more match statements' do @@ -230,12 +232,13 @@ expect(subject .set_match_statements('test', 'permit', 10, ['interface Vlan100', - 'ip address prefix-list MYLOOPBACK']) - ).to be_truthy + 'ip address prefix-list MYLOOPBACK'])) + .to be_truthy expect(subject.get('test')) .to eq('permit' => { 10 => { match: ['ip address prefix-list MYLOOPBACK', - 'interface Vlan100'] } }) + 'interface Vlan100'] + } }) expect(subject .set_match_statements('test', 'permit', 10, ['interface Vlan100'])).to be_truthy @@ -261,7 +264,8 @@ expect(subject.get('test1')) .to eq('permit' => { 10 => { match: ['ip address prefix-list MYLOOPBACK', - 'interface Loopback0'] } }) + 'interface Loopback0'] + } }) end end @@ -290,7 +294,8 @@ 'community internet 4444:4444'])).to be_truthy expect(subject.get('test1')) .to eq('permit' => { 10 => { - set: ['community internet 4444:4444 5555:5555'] } }) + set: ['community internet 4444:4444 5555:5555'] + } }) end end diff --git a/spec/system/rbeapi/api/users_spec.rb b/spec/system/rbeapi/api/users_spec.rb index 2623cce..9071f9b 100644 --- a/spec/system/rbeapi/api/users_spec.rb +++ b/spec/system/rbeapi/api/users_spec.rb @@ -68,8 +68,7 @@ nopassword: false, encryption: 'md5', secret: md5_secret, - sshkey: sshkey - } + sshkey: sshkey } end describe '#getall' do @@ -82,8 +81,7 @@ 'rbeapi' => { name: 'rbeapi', privilege: 1, role: nil, nopassword: false, encryption: 'md5', secret: md5_secret, - sshkey: sshkey } - } + sshkey: sshkey } } end before do @@ -91,7 +89,8 @@ 'no username user1', 'username admin privilege 1 role network-admin nopassword', "username rbeapi privilege 1 secret 5 #{md5_secret}", - "username rbeapi sshkey #{sshkey}"]) + "username rbeapi sshkey #{sshkey}", + 'management defaults', 'default secret hash']) end it 'returns the username collection' do diff --git a/spec/system/rbeapi/api/vrrp_spec.rb b/spec/system/rbeapi/api/vrrp_spec.rb index 486982e..2a3cee7 100644 --- a/spec/system/rbeapi/api/vrrp_spec.rb +++ b/spec/system/rbeapi/api/vrrp_spec.rb @@ -57,15 +57,12 @@ 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 - } - } + track: @tracks } } end it 'returns the virtual router resource' do diff --git a/spec/system/rbeapi/client_spec.rb b/spec/system/rbeapi/client_spec.rb index 99c565c..0e4658a 100644 --- a/spec/system/rbeapi/client_spec.rb +++ b/spec/system/rbeapi/client_spec.rb @@ -171,8 +171,7 @@ def test_conf username: 'test2', password: 'test', transport: 'http', - host: 'test2' - )).to eq(nil) + host: 'test2')).to eq(nil) expect(subject.config.get_connection('test2')) .to eq(username: 'test2', password: 'test', diff --git a/spec/unit/rbeapi/api/acl/default_spec.rb b/spec/unit/rbeapi/api/acl/default_spec.rb index 2bc76b0..3d0e490 100644 --- a/spec/unit/rbeapi/api/acl/default_spec.rb +++ b/spec/unit/rbeapi/api/acl/default_spec.rb @@ -63,8 +63,7 @@ def acls '50' => { seqno: '50', action: 'permit', srcaddr: '16.0.0.0', srcprefixlen: '8', log: nil }, '60' => { seqno: '60', action: 'permit', srcaddr: '9.10.11.0', - srcprefixlen: '255.255.255.0', log: 'log' } - } + srcprefixlen: '255.255.255.0', log: 'log' } } end it 'returns the ACL resource' do diff --git a/spec/unit/rbeapi/api/alias/default_spec.rb b/spec/unit/rbeapi/api/alias/default_spec.rb new file mode 100644 index 0000000..2227575 --- /dev/null +++ b/spec/unit/rbeapi/api/alias/default_spec.rb @@ -0,0 +1,119 @@ +# +# 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/alias' + +include FixtureHelpers + +describe Rbeapi::Api::Alias do + subject { described_class.new(node) } + + let(:node) { double('node') } + + let(:test) do + { name: 'Alias1', + command: 'my command' } + end + let(:name) { test[:name] } + + def aliases + aliases = Fixtures[:alias] + return aliases if aliases + fixture('alias', format: :text, dir: File.dirname(__FILE__)) + end + + before :each do + allow(subject.node).to receive(:running_config).and_return(aliases) + end + + describe '#getall' do + let(:test1_entries) do + { 'Alias1' => { name: 'Alias1', command: 'my command' }, + 'Alias2' => { name: 'Alias2', command: 'my command 2' }, + 'Alias3' => { name: 'Alias3', + command: '1 conf\n2 int %1\n3 description %2\n'\ + '4 end\nend' } } + end + + it 'returns the alias 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 three entries' do + expect(subject.getall.size).to eq(3) + end + end + + describe '#get' do + it 'returns the alias 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(2) + end + end + + describe '#create' do + it 'create a new alias entry' do + expect(node).to receive(:config).with(['alias Alias1 my command']) + expect(subject.create('Alias1', command: 'my command')).to be_truthy + end + it 'raises ArgumentError for create without required args ' do + expect { subject.create('Alias') }.to \ + raise_error ArgumentError + end + end + + describe '#set_command' do + it 'set the command' do + expect(node).to receive(:config).with(['alias Alias4 my command']) + expect(subject.create('Alias4', command: 'my command')).to be_truthy + end + end + + describe '#delete' do + it 'delete a alias resource' do + expect(node).to receive(:config).with('no alias Alias1') + expect(subject.delete('Alias1')).to be_truthy + end + end +end diff --git a/spec/unit/rbeapi/api/alias/fixture_alias.text b/spec/unit/rbeapi/api/alias/fixture_alias.text new file mode 100644 index 0000000..fcddea1 --- /dev/null +++ b/spec/unit/rbeapi/api/alias/fixture_alias.text @@ -0,0 +1,3 @@ +alias Alias1 my command +alias Alias2 my command 2 +alias Alias3 1 conf\n2 int %1\n3 description %2\n4 end\nend diff --git a/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb b/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb index 11ae02e..df73537 100644 --- a/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb +++ b/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb @@ -67,8 +67,7 @@ shutdown: true, description: nil, next_hop_self: true, route_map_in: nil, route_map_out: nil } - } - } + } } end let(:bgp_as) { test[:bgp_as] } diff --git a/spec/unit/rbeapi/api/bgp/bgp_spec.rb b/spec/unit/rbeapi/api/bgp/bgp_spec.rb index 3b73ca4..5e6ad5d 100644 --- a/spec/unit/rbeapi/api/bgp/bgp_spec.rb +++ b/spec/unit/rbeapi/api/bgp/bgp_spec.rb @@ -67,8 +67,7 @@ shutdown: true, description: nil, next_hop_self: true, route_map_in: nil, route_map_out: nil } - } - } + } } end let(:bgp_as) { test[:bgp_as] } diff --git a/spec/unit/rbeapi/api/interfaces/base_spec.rb b/spec/unit/rbeapi/api/interfaces/base_spec.rb index 476a89d..156ef00 100644 --- a/spec/unit/rbeapi/api/interfaces/base_spec.rb +++ b/spec/unit/rbeapi/api/interfaces/base_spec.rb @@ -23,7 +23,7 @@ def interfaces let(:resource) { subject.get('Loopback0') } let(:keys) do - [:type, :shutdown, :load_interval, :description, :name] + [:type, :shutdown, :load_interval, :description, :encapsulation, :name] end it 'returns an ethernet resource as a hash' do diff --git a/spec/unit/rbeapi/api/interfaces/ethernet_spec.rb b/spec/unit/rbeapi/api/interfaces/ethernet_spec.rb index 527b0f8..eb3eabe 100644 --- a/spec/unit/rbeapi/api/interfaces/ethernet_spec.rb +++ b/spec/unit/rbeapi/api/interfaces/ethernet_spec.rb @@ -24,7 +24,8 @@ def interfaces let(:keys) do [:type, :speed, :sflow, :flowcontrol_send, :flowcontrol_receive, - :shutdown, :description, :name, :load_interval, :lacp_priority] + :shutdown, :description, :encapsulation, :name, :load_interval, + :lacp_priority] end it 'returns an ethernet resource as a hash' do @@ -86,4 +87,37 @@ def interfaces default: true)).to be_truthy end end + + describe '#set_encapsulation' do + it 'sets the interface encapsulation' do + expect(node).to receive(:config).with(['interface Ethernet1.1', + 'encapsulation dot1q vlan 10']) + expect(subject.set_encapsulation('Ethernet1.1', value: '10')) + .to be_truthy + end + + it 'negates the interface encapsulation' do + expect(node).to receive(:config).with(['interface Ethernet1.1', + 'no encapsulation dot1q vlan']) + expect(subject.set_encapsulation('Ethernet1.1', + enable: false)).to be_truthy + end + + it 'defaults the interface encapsulation' do + expect(node).to receive(:config) + .with(['interface Ethernet1.1', + 'default encapsulation dot1q vlan']) + expect(subject.set_encapsulation('Ethernet1.1', + default: true)).to be_truthy + end + + it 'default is preferred over enable' do + expect(node).to receive(:config) + .with(['interface Ethernet1.1', + 'default encapsulation dot1q vlan']) + expect(subject.set_encapsulation('Ethernet1.1', + enable: false, + default: true)).to be_truthy + end + end end diff --git a/spec/unit/rbeapi/api/interfaces/fixture_interfaces.text b/spec/unit/rbeapi/api/interfaces/fixture_interfaces.text index 2ae5f1e..7c97ba1 100644 --- a/spec/unit/rbeapi/api/interfaces/fixture_interfaces.text +++ b/spec/unit/rbeapi/api/interfaces/fixture_interfaces.text @@ -217,3 +217,71 @@ interface Port-Channel1 mlag 1 spanning-tree portfast ! +interface Vlan1 + no description + no shutdown + default load-interval + mtu 1500 + logging event link-status use-global + autostate + snmp trap link-status + no ip proxy-arp + no ip local-proxy-arp + no ip address + 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 Vlan1 + 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 + 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 + no ip multicast static + ip mfib fastdrop + default ntp serve + default ip ospf bfd + ip ospf cost 10 + ip ospf dead-interval 40 + ip ospf hello-interval 10 + ip ospf priority 1 + ip ospf retransmit-interval 5 + no ip ospf shutdown + ip ospf transmit-delay 1 + no ip ospf authentication + no ip ospf mtu-ignore + no ip pim sparse-mode + no ip pim bidirectional + no ip pim border-router + ip pim query-interval 30 + ip pim query-count 3.5 + 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 +! diff --git a/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb b/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb index 9fb9833..8176be0 100644 --- a/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb +++ b/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb @@ -31,8 +31,8 @@ def interfaces let(:resource) { subject.get('Port-Channel1') } let(:keys) do - [:type, :shutdown, :load_interval, :description, :name, :members, - :lacp_mode, :minimum_links, :lacp_timeout, :lacp_fallback] + [:type, :shutdown, :load_interval, :description, :encapsulation, :name, + :members, :lacp_mode, :minimum_links, :lacp_timeout, :lacp_fallback] end it 'returns an ethernet resource as a hash' do @@ -111,6 +111,40 @@ def interfaces end end + describe '#set_encapsulation' do + it 'sets the interface encapsulation' do + expect(node).to receive(:config) + .with(['interface Port-Channel1.1', 'encapsulation dot1q vlan 10']) + + expect(subject.set_encapsulation('Port-Channel1.1', value: '10')) + .to be_truthy + end + + it 'negates the interface encapsulation' do + expect(node).to receive(:config) + .with(['interface Port-Channel1.1', 'no encapsulation dot1q vlan']) + + expect(subject.set_encapsulation('Port-Channel1.1', + enable: false)).to be_truthy + end + + it 'defaults the interface encapsulation' do + expect(node).to receive(:config) + .with(['interface Port-Channel1.1', 'default encapsulation dot1q vlan']) + + expect(subject.set_encapsulation('Port-Channel1.1', default: true)) + .to be_truthy + end + + it 'default is preferred over enable' do + expect(node).to receive(:config) + .with(['interface Port-Channel1.1', 'default encapsulation dot1q vlan']) + + opts = { enable: false, default: true } + expect(subject.set_encapsulation('Port-Channel1.1', opts)).to be_truthy + end + end + describe '#set_shutdown' do it 'enables the interface' do expect(node).to receive(:config) @@ -144,8 +178,11 @@ def interfaces describe '#set_members' do it 'raises an ArgumentError if members is not an array' do - expect { subject.set_members('Port-Channel1', 'Ethernet3') }.to \ - raise_error(ArgumentError) + expect do + subject.set_members('Port-Channel1', + 'Ethernet3') + end + .to raise_error(ArgumentError) end end end diff --git a/spec/unit/rbeapi/api/interfaces/vlan_spec.rb b/spec/unit/rbeapi/api/interfaces/vlan_spec.rb new file mode 100644 index 0000000..45b05a9 --- /dev/null +++ b/spec/unit/rbeapi/api/interfaces/vlan_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +require 'rbeapi/api/interfaces' + +include FixtureHelpers + +describe Rbeapi::Api::VlanInterface do + subject { described_class.new(node) } + + let(:node) { double('node') } + + def interfaces + interfaces = Fixtures[:interfaces] + return interfaces if interfaces + fixture('interfaces', format: :text, dir: File.dirname(__FILE__)) + end + + before :each do + allow(subject.node).to receive(:running_config).and_return(interfaces) + end + + describe '#get' do + let(:resource) { subject.get('Vlan1') } + + let(:keys) do + [:type, :shutdown, :load_interval, :description, :name, :autostate, + :encapsulation] + end + + it 'returns the resource as a hash' do + expect(resource).to be_a_kind_of(Hash) + end + + it 'returns an interface type of vlan' do + expect(resource[:type]).to eq('vlan') + end + + it 'has all keys' do + expect(resource.keys).to match_array(keys) + end + end + + describe '#create' do + it 'creates the interface in the config' do + expect(node).to receive(:config).with('interface Vlan1') + expect(subject.create('Vlan1')).to be_truthy + end + end + + describe '#delete' do + it 'deletes the interface in the config' do + expect(node).to receive(:config).with('no interface Vlan1') + expect(subject.delete('Vlan1')).to be_truthy + end + end + + describe '#default' do + it 'defaults the interface config' do + expect(node).to receive(:config).with('default interface Vlan1') + expect(subject.default('Vlan1')).to be_truthy + end + end + + describe '#set_autostate' do + it 'sets the autostate' do + commands = ['interface Vlan1', 'autostate'] + opts = { value: :true } + expect(node).to receive(:config).with(commands) + expect(subject.set_autostate('Vlan1', opts)).to be_truthy + end + end +end diff --git a/spec/unit/rbeapi/api/interfaces/vxlan_spec.rb b/spec/unit/rbeapi/api/interfaces/vxlan_spec.rb index 4c6b0fb..1113243 100644 --- a/spec/unit/rbeapi/api/interfaces/vxlan_spec.rb +++ b/spec/unit/rbeapi/api/interfaces/vxlan_spec.rb @@ -23,8 +23,8 @@ def interfaces let(:resource) { subject.get('Vxlan1') } let(:keys) do - [:type, :shutdown, :load_interval, :description, :name, :source_interface, - :multicast_group, :udp_port, :flood_list, :vlans] + [:type, :shutdown, :load_interval, :description, :encapsulation, :name, + :source_interface, :multicast_group, :udp_port, :flood_list, :vlans] end it 'returns the resource as a hash' do diff --git a/spec/unit/rbeapi/api/managementdefaults/default_spec.rb b/spec/unit/rbeapi/api/managementdefaults/default_spec.rb new file mode 100644 index 0000000..da7d83b --- /dev/null +++ b/spec/unit/rbeapi/api/managementdefaults/default_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +require 'rbeapi/api/managementdefaults' + +include FixtureHelpers + +describe Rbeapi::Api::Managementdefaults do + subject { described_class.new(node) } + + let(:node) { double('node') } + + def managementdefaults + managementdefaults = Fixtures[:managementdefaults] + return managementdefaults if managementdefaults + fixture('managementdefaults', format: :text, dir: File.dirname(__FILE__)) + end + + before :each do + allow(subject.node).to receive(:running_config) + .and_return(managementdefaults) + end + + describe '#get' do + let(:keys) { [:secret_hash] } + + it 'returns the management defaults resource hash with all keys' do + expect(subject.get.keys).to match_array(keys) + end + end + + describe '#set_secret_hash' do + it 'sets the secret_hash to sha512' do + expect(node).to receive(:config).with(['management defaults', + 'secret hash sha512']) + expect(subject.set_secret_hash(value: 'sha512')).to be_truthy + end + + it 'sets the secret_hash to md5' do + expect(node).to receive(:config).with(['management defaults', + 'secret hash md5']) + expect(subject.set_secret_hash(value: 'md5')).to be_truthy + end + + it 'defaults the secret_hash' do + expect(node).to receive(:config).with(['management defaults', + 'secret hash ']) + expect(subject.set_secret_hash(default: true)).to be_truthy + end + end +end diff --git a/spec/unit/rbeapi/api/managementdefaults/fixture_managementdefaults.yaml b/spec/unit/rbeapi/api/managementdefaults/fixture_managementdefaults.yaml new file mode 100644 index 0000000..bc98f03 --- /dev/null +++ b/spec/unit/rbeapi/api/managementdefaults/fixture_managementdefaults.yaml @@ -0,0 +1 @@ +sha512 diff --git a/spec/unit/rbeapi/api/prefixlists/default_spec.rb b/spec/unit/rbeapi/api/prefixlists/default_spec.rb index 160eba5..1a26afa 100644 --- a/spec/unit/rbeapi/api/prefixlists/default_spec.rb +++ b/spec/unit/rbeapi/api/prefixlists/default_spec.rb @@ -51,43 +51,49 @@ def prefixlists end describe '#get' do - let(:resource) { subject.get('test1') } - - let(:prefixlist) do - [{ - 'seq' => '10', - 'action' => 'permit', - 'prefix' => '1.2.3.0/24' - }, - { - 'seq' => '20', - 'action' => 'permit', - 'prefix' => '2.3.4.0/24 le 30' - }, - { - 'seq' => '30', - 'action' => 'permit', - 'prefix' => '3.4.5.0/24 ge 26 le 30' - }] - end - - let(:keys) { ['seq', 'action', 'prefix'] } - - it 'returns the prefix list for an existing name' do - expect(resource).to eq(prefixlist) - end - - it 'returns an array of prefixes' do - expect(resource).to be_a_kind_of(Array) - end - - it 'has three prefixes' do - expect(resource.size).to eq(3) - end - - it 'has all keys' do - resource.each do |prefix| - expect(prefix.keys).to match_array(keys) + let(:keys) { %w(seq action prefix) } + + [ + { title: 'single-line', + prefix_list: 'test5', + rules: [{ 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.50.1.0/24' }, + { 'seq' => '20', + 'action' => 'permit', + 'prefix' => '10.50.2.0/24' }] }, + { title: 'multi-line', + prefix_list: 'test1', + rules: [{ 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.10.1.0/24' }, + { 'seq' => '20', + 'action' => 'permit', + 'prefix' => '10.20.1.0/24 le 30' }, + { 'seq' => '30', + 'action' => 'permit', + 'prefix' => '10.30.1.0/24 ge 26 le 30' }] } + ].each do |context| + context "when prefix list is #{context[:title]}" do + let(:resource) { subject.get(context[:prefix_list]) } + + it 'returns the correct rules' do + expect(resource).to eq(context[:rules]) + end + + it 'returns an array of rules' do + expect(resource).to be_a_kind_of(Array) + end + + it "has #{context[:rules].size} rules" do + expect(resource.size).to eq(context[:rules].size) + end + + it 'has all keys' do + resource.each do |rule| + expect(rule.keys).to match_array(keys) + end + end end end @@ -101,37 +107,40 @@ def prefixlists let(:resource) { subject.getall } let(:plists) do - { - "test1" => [ - { - "seq" => "10", - "action" => "permit", - "prefix" => "1.2.3.0/24" - }, - { - "seq" => "20", - "action" => "permit", - "prefix" => "2.3.4.0/24 le 30" - }, - { - "seq" => "30", - "action" => "permit", - "prefix" => "3.4.5.0/24 ge 26 le 30" - } + { + 'test1' => [ + { 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.10.1.0/24' }, + { 'seq' => '20', + 'action' => 'permit', + 'prefix' => '10.20.1.0/24 le 30' }, + { 'seq' => '30', + 'action' => 'permit', + 'prefix' => '10.30.1.0/24 ge 26 le 30' } ], - "test2" => [ - { - "seq" => "10", - "action" => "permit", - "prefix" => "10.11.0.0/16" - }, - { - "seq" => "20", - "action" => "permit", - "prefix" => "10.12.0.0/16 le 24" - } + 'test2' => [ + { 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.11.0.0/16' }, + { 'seq' => '20', + 'action' => 'permit', + 'prefix' => '10.12.0.0/16 le 24' } ], - "test3" => [] + 'test3' => [], + 'test4' => [ + { 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.14.0.0/16 le 20' } + ], + 'test5' => [ + { 'seq' => '10', + 'action' => 'permit', + 'prefix' => '10.50.1.0/24' }, + { 'seq' => '20', + 'action' => 'permit', + 'prefix' => '10.50.2.0/24' } + ] } end @@ -143,8 +152,8 @@ def prefixlists expect(resource).to be_a_kind_of(Hash) end - it 'has three prefix lists' do - expect(resource.size).to eq(3) + it 'has five prefix lists' do + expect(resource.size).to eq(5) end end @@ -162,28 +171,38 @@ def prefixlists describe '#add_rule' do it 'adds rule to existing prefix list' do - expect(node).to receive(:config).with('ip prefix-list test1 seq 25 permit 9.8.7.0/24') - expect(subject.add_rule('test1', 'permit','9.8.7.0/24', '25')).to be_truthy + expect(node).to receive(:config) + .with('ip prefix-list test1 seq 25 permit 10.25.1.0/24') + expect(subject.add_rule('test1', 'permit', '10.25.1.0/24', '25')) + .to be_truthy end it 'adds rule to existing prefix list w/o seq' do - expect(node).to receive(:config).with('ip prefix-list test1 permit 8.7.6.0/24') - expect(subject.add_rule('test1', 'permit', '8.7.6.0/24')).to be_truthy + expect(node).to receive(:config) + .with('ip prefix-list test1 permit 10.25.2.0/24') + expect(subject.add_rule('test1', 'permit', '10.25.2.0/24')) + .to be_truthy end it 'adds rule to non-existing prefix list' do - expect(node).to receive(:config).with('ip prefix-list plist2 seq 10 permit 6.5.4.128/25') - expect(subject.add_rule('plist2', 'permit', '6.5.4.128/25', '10')).to be_truthy + expect(node).to receive(:config) + .with('ip prefix-list plist2 seq 10 permit 10.25.3.128/25') + expect(subject.add_rule('plist2', 'permit', '10.25.3.128/25', '10')) + .to be_truthy end it 'adds rule to non-existing prefix list w/o seq' do - expect(node).to receive(:config).with('ip prefix-list plist2 deny 5.4.3.0/25') - expect(subject.add_rule('plist2', 'deny', '5.4.3.0/25')).to be_truthy + expect(node).to receive(:config) + .with('ip prefix-list plist2 deny 10.25.10.0/25') + expect(subject.add_rule('plist2', 'deny', '10.25.10.0/25')) + .to be_truthy end it 'overwrites existing rule' do - expect(node).to receive(:config).with('ip prefix-list test1 seq 20 permit 2.3.5.0/24 le 28') - expect(subject.add_rule('test1', 'permit', '2.3.5.0/24 le 28', '20')).to be_truthy + expect(node).to receive(:config) + .with('ip prefix-list test1 seq 20 permit 10.25.20.0/24 le 28') + expect(subject.add_rule('test1', 'permit', '10.25.20.0/24 le 28', '20')) + .to be_truthy end end @@ -198,5 +217,4 @@ def prefixlists expect(subject.delete('test2', '10')) end end - -end \ No newline at end of file +end diff --git a/spec/unit/rbeapi/api/prefixlists/fixture_prefixlists.text b/spec/unit/rbeapi/api/prefixlists/fixture_prefixlists.text index a02a489..d8ba44d 100644 --- a/spec/unit/rbeapi/api/prefixlists/fixture_prefixlists.text +++ b/spec/unit/rbeapi/api/prefixlists/fixture_prefixlists.text @@ -1,11 +1,16 @@ ip prefix-list test1 - seq 10 permit 1.2.3.0/24 - seq 20 permit 2.3.4.0/24 le 30 - seq 30 permit 3.4.5.0/24 ge 26 le 30 + seq 10 permit 10.10.1.0/24 + seq 20 permit 10.20.1.0/24 le 30 + seq 30 permit 10.30.1.0/24 ge 26 le 30 ! ip prefix-list test2 seq 10 permit 10.11.0.0/16 seq 20 permit 10.12.0.0/16 le 24 ! ip prefix-list test3 -! \ No newline at end of file +! +ip prefix-list test4 seq 10 permit 10.14.0.0/16 le 20 +! +ip prefix-list test5 seq 10 permit 10.50.1.0/24 +ip prefix-list test5 seq 20 permit 10.50.2.0/24 +! diff --git a/spec/unit/rbeapi/api/users/default_spec.rb b/spec/unit/rbeapi/api/users/default_spec.rb index af04395..fae85ea 100644 --- a/spec/unit/rbeapi/api/users/default_spec.rb +++ b/spec/unit/rbeapi/api/users/default_spec.rb @@ -57,8 +57,7 @@ nopassword: false, encryption: 'md5', secret: '$1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1', - sshkey: sshkey - } + sshkey: sshkey } end let(:name) { test[:name] } @@ -84,8 +83,7 @@ def users 'rbeapi1' => { name: 'rbeapi1', privilege: 2, role: 'network-minon', nopassword: false, encryption: 'cleartext', secret: 'icanttellyou', - sshkey: nil } - } + sshkey: nil } } end it 'returns the username collection' do diff --git a/spec/unit/rbeapi/api/vrrp/default_spec.rb b/spec/unit/rbeapi/api/vrrp/default_spec.rb index 88698dc..0ef5f36 100644 --- a/spec/unit/rbeapi/api/vrrp/default_spec.rb +++ b/spec/unit/rbeapi/api/vrrp/default_spec.rb @@ -49,15 +49,12 @@ def vrrp 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 - } - } + track: @tracks } } end it 'returns the virtual router resource' do diff --git a/spec/unit/rbeapi/client_spec.rb b/spec/unit/rbeapi/client_spec.rb index 4ae262b..92ad1dd 100644 --- a/spec/unit/rbeapi/client_spec.rb +++ b/spec/unit/rbeapi/client_spec.rb @@ -200,8 +200,7 @@ def wildcard_conf username: 'test2', password: 'test', transport: 'http', - host: 'test2' - )).to eq(nil) + host: 'test2')).to eq(nil) expect(subject.config.get_connection('test2')) .to eq(username: 'test2', password: 'test', @@ -212,11 +211,16 @@ def wildcard_conf describe '#get_config' do def startup_config - "! Command: show running-config\n! device: jere-debug-agent1 (vEOS, EOS-4.14.9.1M)\n!\n! boot system flash:/vEOS-4.14.9.1M.swi\n!\nip routing vrf MGMT\n!\nmanagement api http-commands\n no protocol https\n protocol unix-socket\n no shutdown\n vrf MGMT\n no shutdown\n!\nmanagement ssh\n vrf MGMT\n no shutdown\n!\n!\nend\n" + "! Command: show running-config\n! device: jere-debug-agent1 (vEOS, " \ + "EOS-4.14.9.1M)\n!\n! boot system flash:/vEOS-4.14.9.1M.swi\n!\n" \ + "ip routing vrf MGMT\n!\nmanagement api http-commands\n" \ + "no protocol https\n protocol unix-socket\n no shutdown\n" \ + "vrf MGMT\n no shutdown\n!\nmanagement ssh\n vrf MGMT\n" \ + "no shutdown\n!\n!\nend\n" end def startup_config_response - [{"output"=>startup_config}] + [{ 'output' => startup_config }] end let(:node) do @@ -229,26 +233,29 @@ def startup_config_response end it 'with no arguments returns the startup-config' do - expect(node.get_config()).to eq(startup_config.strip.split("\n")) + expect(node.get_config).to eq(startup_config.strip.split("\n")) end - it 'with no arguments and an empty startup-config returns the startup-config' do - allow(node).to receive(:run_commands) { [{"output"=>""}] } - expect(node.get_config()).to eq([]) + it 'with no arguments and empty startup-config returns startup-config' do + allow(node).to receive(:run_commands) { [{ 'output' => '' }] } + expect(node.get_config).to eq([]) end it 'with no arguments and no startup-config returns nil' do - msg = "CLI command 2 of 2 'show startup-config' failed: could not run command" - allow(node).to receive(:run_commands).and_raise(Rbeapi::Eapilib::CommandError.new(msg, 1000)) - expect(node.get_config()).to be_nil + msg = "CLI command 2 of 2 'show startup-config' failed: could not " \ + 'run command' + allow(node).to receive(:run_commands) + .and_raise(Rbeapi::Eapilib::CommandError.new(msg, 1000)) + expect(node.get_config).to be_nil end it 'raises invalid command error' do - msg = "CLI command 2 of 2 'show startup-configurations' failed: invalid command" - allow(node).to receive(:run_commands).and_raise(Rbeapi::Eapilib::CommandError.new(msg, 1000)) + msg = "CLI command 2 of 2 'show startup-configurations' failed:" \ + ' invalid command' + allow(node).to receive(:run_commands) + .and_raise(Rbeapi::Eapilib::CommandError.new(msg, 1000)) expect { node.get_config(config: 'running-configurations') } .to raise_error Rbeapi::Eapilib::CommandError end - end end diff --git a/spec/unit/rbeapi/switchconfig_spec.rb b/spec/unit/rbeapi/switchconfig_spec.rb index 0a88ceb..919e99b 100644 --- a/spec/unit/rbeapi/switchconfig_spec.rb +++ b/spec/unit/rbeapi/switchconfig_spec.rb @@ -90,10 +90,17 @@ 'vlan 100', 'interface Ethernet 2', "banner motd\nThis is my \n multiline\n banner\nends here\n\nEOF", - 'username fred privilege 0 role network-operator secret 5 $1$u4TDdWKN$VC7cZmeGn/sgNM0RMNwhR.', - 'username fred sshkey ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDtrjGuhFeJz76Z5f3perh/R2s8XmnPcWF1uByHOqDDp53bggS9CZ7n67/QYVjbhyP70HMxY8R5Z4AevNtZTnSUQZmEgnuHvvAGNC63qrItE1i/sXKLvB1r8v0plcK35laNvJMYqGcGpjQ7T4Ufmn54zssiq1CBx6GfEX0+zKWD/5vnVDH9MMDolawILFb2a67VngzEZ0BCeRgLTg2ZEEEQt2hEKdglx87GBf7UIBFYM5xvywZRzHWta0dO1WDXCLD67kdqP52zucFXo7U3EUK/8X9Qltg5Pjr4mxf/U+hbO/K7xZJ+neAJDYA7bSXh8LkCuz00VxI5mAwo2PRMKaSp fred@localhost', + 'username fred privilege 0 role network-operator secret 5 $1$u4TDdWKN$VC' \ + '7cZmeGn/sgNM0RMNwhR.', + 'username fred sshkey ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDtrjGuhFeJz7' \ + '6Z5f3perh/R2s8XmnPcWF1uByHOqDDp53bggS9CZ7n67/QYVjbhyP70HMxY8R5Z4AevNtZT' \ + 'nSUQZmEgnuHvvAGNC63qrItE1i/sXKLvB1r8v0plcK35laNvJMYqGcGpjQ7T4Ufmn54zssi' \ + 'q1CBx6GfEX0+zKWD/5vnVDH9MMDolawILFb2a67VngzEZ0BCeRgLTg2ZEEEQt2hEKdglx87' \ + 'GBf7UIBFYM5xvywZRzHWta0dO1WDXCLD67kdqP52zucFXo7U3EUK/8X9Qltg5Pjr4mxf/U+' \ + 'hbO/K7xZJ+neAJDYA7bSXh8LkCuz00VxI5mAwo2PRMKaSp fred@localhost', 'router ospf 1', - 'management cim-provider'] + 'management cim-provider' + ] cmds = [' switchport mode trunk', ' switchport trunk allowed vlan 100,200']