- Overview
- Before You Begin
- Start here: Fork and Clone the Repo
- Example: router eigrp
- Step 1. YAML Definitions: router eigrp
- Step 2. Create the node_utils API: router eigrp
- Step 3. Create the Minitest: router eigrp
- Step 4. rubocop / lint: router eigrp
- Step 5. Build and Install the gem
This document is a HowTo guide for writing new cisco_node_utils APIs. The APIs act as an interface between the NX-OS CLI and an agent's resource/provider. If written properly the new API will work as a common framework for multiple providers (Puppet, Chef, etc).
There are multiple components involved when creating new resources. This document focuses on the cisco_node_utils API, command reference YAML files, and minitests.
Please note: A virtual Nexus N9000/N3000 may be helpful for development and testing. Users with a valid cisco.com user ID can obtain a copy of a virtual Nexus N9000/N3000 by sending their cisco.com user ID in an email to [email protected]. If you do not have a cisco.com user ID please register for one at https://tools.cisco.com/IDREG/guestRegistration
This development guide uses tools that are packaged as gems that need to be installed on your server.
gem install cisco_nxapi
gem install rake
gem install rubocop
gem install simplecov
gem install minitest --version 4.3.2
NOTE: If you are working from a server where you don't have admin/root privilages, use the following commands to install the gems and then update the PATH
to include ~/.gem/ruby/x.x.x/bin
gem install --user-install cisco_nxapi
gem install --user-install rake
gem install --user-install rubocop
gem install --user-install simplecov
gem install --user-install minitest --version 4.3.2
First fork the cisco-network-node-utils git repository
Next install the code base. Clone the cisco-network-node-utils repo from your fork into a workspace:
git clone https://github.com/YOUR-USERNAME/cisco-network-node-utils.git
cd cisco-network-node-utils/
Please note that any code commits must be associated with your github account and email address. If you intend to commit code to this repository then use the following commands to update your workspace with your credentials:
git config --global user.name "John Doe"
git config --global user.email [email protected]
As a best practice create a topic/feature branch for your feature work using the git branch feature/<feature_name>
command.
git branch feature/eigrp
git branch
* develop
feature/eigrp
Before you start working on the eigrp feature, checkout the feature branch you created earlier.
git checkout feature/eigrp
git branch
develop
* feature/eigrp
router eigrp
requires feature enablement and supports multiple eigrp instances. It also has multiple configuration levels for vrf and address-family.
For the purposes of this example we will only implement the following properties:
[no] feature eigrp (boolean)
[no] router eigrp [name] (string)
maximum-paths [n] (integer)
[no] shutdown (boolean)
Example:
feature eigrp
router eigrp Blue
maximum-paths 5
shutdown
The new API for router eigrp
will need some basic YAML definitions.
command_reference_common.yaml
is used for settings that are common across all platforms while other files are used for settings that are unique to a given platform. Our router eigrp
example uses the same cli syntax on all platforms, thus we only need to edit the common file:
lib/cisco_node_utils/command_reference_common.yaml
Four basic command_reference parameters will be defined for each resource property:
config_get:
This defines the NX-OS CLI command (usually a 'show...' command) used to retrieve the property's current configuration state. Note that some commands may not be present until a feature is enabled.config_get_token:
A regexp pattern for extracting state values from the config_get output.config_set:
The NX-OS CLI configuration command(s) used to set the property configuration. May contain wildcards for variable parameters.default_value:
This is typically the "factory" default state of the property, expressed as an actual value (true, 12, "off", etc)
There are additional YAML command parameters available which are not covered by this document. Please see the README_YAML.md document for more information on the structure and semantics of these files. The properties in this example require additional context for their config_get_token values because they need to differentiate between different eigrp instances. Most properties will also have a default value.
Note: Eigrp also has vrf and address-family contexts. These contexts require additional coding and are beyond the scope of this document.
Note: The basic token definitions for multi-level commands can become long and complicated. A better solution for these commands is to use a command_reference _template: definition to simplify the configuration. The example below will use the basic syntax; see the ospf definitions in the YAML file for an example of _template: usage.
eigrp:
feature:
# feature eigrp must be enabled before configuring router eigrp
config_get: 'show running eigrp all'
config_get_token: '/^feature eigrp$/'
config_set: '<state> feature eigrp'
router:
# There can be multiple eigrp instances
config_get: 'show running eigrp all' # all eigrp-related configs
config_get_token: '/^router eigrp (\S+)$/' # Match instance name
config_set: '<state> router eigrp <name>' # config to add or remove
maximum_paths:
# This is an integer property
config_get: 'show running eigrp all'
config_get_token: ['/^router eigrp <name>$/', '/^maximum-paths (\d+)/']
config_set: ['router eigrp <name>', 'maximum-paths <val>']
default_value: 8
shutdown:
# This is a boolean property
config_get: 'show running eigrp all'
config_get_token: ['/^router eigrp <name>$/', '/^shutdown$/']
config_set: ['router eigrp <name>', '<state> shutdown']
default_value: false
- The
template-router.rb
file provides a basic router API that we will use as the basis forrouter_eigrp.rb
:
cp docs/template-router.rb lib/cisco_node_utils/router_eigrp.rb
- Our new
router_eigrp.rb
requires changes from the original template. Editrouter_eigrp.rb
and change the placeholder names as shown.
/X__CLASS_NAME__X/RouterEigrp/
/X__RESOURCE_NAME__X/eigrp/
/X__PROPERTY_BOOL__X/shutdown/
/X__PROPERTY_INT__X/maximum_paths/
Note that this template only provides example property methods for a few properties. Copy the example methods for additional properties as needed.
This is the completed router_eigrp
API based on template-router.rb
:
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative 'node_util'
module Cisco
# RouterEigrp - node utility class for EIGRP config management.
class RouterEigrp < NodeUtil
attr_reader :name
# name: name of the router instance
# instantiate: true = create router instance
def initialize(name, instantiate=true)
fail ArgumentError unless name.length > 0
@name = name
create if instantiate
end
# Create a hash of all current router instances.
def self.routers
instances = config_get('eigrp', 'router')
return {} if instances.nil?
hash = {}
instances.each do |name|
hash[name] = RouterEigrp.new(name, false)
end
return hash
rescue Cisco::CliError => e
# CLI will syntax reject when feature is not enabled
raise unless e.clierror =~ /Syntax error/
return {}
end
def feature_enabled
feat = config_get('eigrp', 'feature')
return !(feat.nil? || feat.empty?)
rescue Cisco::CliError => e
# This cmd will syntax reject if feature is not
# enabled. Just catch the reject and return false.
return false if e.clierror =~ /Syntax error/
raise
end
def feature_enable
config_set('eigrp', 'feature', state: '')
end
def feature_disable
config_set('eigrp', 'feature', state: 'no')
end
# Enable feature and create router instance
def create
feature_enable unless feature_enabled
eigrp_router
end
# Destroy a router instance; disable feature on last instance
def destroy
ids = config_get('eigrp', 'router')
return if ids.nil?
if ids.size == 1
feature_disable
else
eigrp_router('no')
end
rescue Cisco::CliError => e
# CLI will syntax reject when feature is not enabled
raise unless e.clierror =~ /Syntax error/
end
def eigrp_router(state='')
config_set('eigrp', 'router', name: @name, state: state)
end
# ----------
# PROPERTIES
# ----------
# Property methods for boolean property
def default_shutdown
config_get_default('eigrp', 'shutdown')
end
def shutdown
state = config_get('eigrp', 'shutdown', name: @name)
state ? true : false
end
def shutdown=(state)
state = (state ? '' : 'no')
config_set('eigrp', 'shutdown', name: @name, state: state)
end
# Property methods for integer property
def default_maximum_paths
config_get_default('eigrp', 'maximum_paths')
end
def maximum_paths
val = config_get('eigrp', 'maximum_paths', name: @name)
val.nil? ? default_maximum_paths : val.first.to_i
end
def maximum_paths=(val)
config_set('eigrp', 'maximum_paths', name: @name, val: val)
end
end
end
- Use
template-test_router.rb
to build the minitest forrouter_eigrp.rb
:
cp docs/template-test_router.rb tests/test_router_eigrp.rb
- As with the API code, edit
test_router_eigrp.rb
and change the placeholder names as shown:
/X__CLASS_NAME__X/RouterEigrp/
/X__RESOURCE_NAME__X/eigrp/
/X__PROPERTY_BOOL__X/shutdown/
/X__PROPERTY_INT__X/maximum_paths/
- At a minimum, the tests should include coverage for:
- creating & destroying a single
router eigrp
instance - creating & destroying multiple
router eigrp
instances - feature disablement when removing last
router eigrp
- testing each property state
- creating & destroying a single
This is the completed test_router_eigrp
minitest based on template-test_router.rb
:
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative 'ciscotest'
require_relative '../lib/cisco_node_utils/router_eigrp'
# TestRouterEigrp - Minitest for RouterEigrp node utility class
class TestRouterEigrp < CiscoTestCase
def setup
# setup runs at the beginning of each test
super
no_feature_eigrp
end
def teardown
# teardown runs at the end of each test
no_feature_eigrp
super
end
def no_feature_eigrp
# Turn the feature off for a clean test.
config('no feature eigrp')
end
# TESTS
def test_router_create_destroy_one
id = 'blue'
rtr = RouterEigrp.new(id)
@default_show_command = "show runn | i 'router eigrp #{id}'")
assert_show_match(pattern: /^router eigrp #{id}$/,
msg: "failed to create router eigrp #{id}")
rtr.destroy
refute_show_match(pattern: /^router eigrp #{id}$/,
msg: "failed to destroy router eigrp #{id}")
refute_show_match(command: "show runn | i 'feature eigrp'",
pattern: /^feature eigrp$/,
msg: "failed to disable feature eigrp")
end
def test_router_create_destroy_multiple
id1 = 'blue'
rtr1 = RouterEigrp.new(id1)
id2 = 'red'
rtr2 = RouterEigrp.new(id2)
@default_show_command = "show runn | i 'router eigrp'"
s = @device.cmd("show runn | i 'router eigrp'")
assert_match(s, /^router eigrp #{id1}$/)
assert_match(s, /^router eigrp #{id2}$/)
rtr1.destroy
refute_show_match(pattern: /^router eigrp #{id1}$/,
msg: "failed to destroy router eigrp #{id1}")
rtr2.destroy
refute_show_match(pattern: /^router eigrp #{id2}$/,
msg: "failed to destroy router eigrp #{id2}")
refute_show_match(command: "show runn | i 'feature eigrp'",
pattern: /^feature eigrp$/,
msg: "failed to disable feature eigrp")
end
def test_router_maximum_paths
id = 'blue'
rtr = RouterEigrp.new(id)
val = 5 # This value depends on property bounds
rtr.maximum_paths = val
assert_equal(rtr.maximum_paths, val, "maximum_paths is not #{val}")
# Get default value from yaml
val = node.config_get_default('eigrp', 'maximum_paths')
rtr.maximum_paths = val
assert_equal(rtr.maximum_paths, val, "maximum_paths is not #{val}")
end
def test_router_shutdown
id = 'blue'
rtr = RouterEigrp.new(id)
rtr.shutdown = true
assert(rtr.shutdown, 'shutdown state is not true')
rtr.shutdown = false
refute(rtr.shutdown, 'shutdown state is not false')
end
end
Now run the test:
% ruby-1.9.3-p0 test_router_eigrp.rb -v -- 192.168.0.1 admin admin
Run options: -v -- --seed 56593
# Running tests:
CiscoTestCase#test_placeholder =
Ruby Version - 1.9.3
Node in CiscoTestCase Class: 192.168.0.1
Platform:
- name - my_n3k
- type - N3K-C3132Q-40GX
- image -
2.90 s = .
TestCase#test_placeholder = 0.92 s = .
TestRouterEigrp#test_placeholder = 0.97 s = .
TestRouterEigrp#test_router_create_destroy_multiple = 10.77 s = .
TestRouterEigrp#test_router_create_destroy_one = 6.14 s = .
TestRouterEigrp#test_router_maximum_paths = 9.41 s = .
TestRouterEigrp#test_router_shutdown = 6.40 s = .
Finished tests in 37.512356s, 0.1866 tests/s, 0.3199 assertions/s.
7 tests, 12 assertions, 0 failures, 0 errors, 0 skips
rubocop is a Ruby static analysis tool. Run rubocop to validate the new code:
% rubocop lib/cisco_node_utils/router_eigrp.rb tests/test_router_eigrp.rb
Inspecting 2 file
..
2 file inspected, no offenses detected
The final step is to build and install the gem that contains the new APIs.
Please note: gem build
will only include files that are part of the repository. This means that new file router_eigrp.rb
will be ignored by the build until it is added to the repo with git add
:
git add lib/cisco_node_utils/router_eigrp.rb
From the root of the cisco-network-node-utils repository issue the following command.
% gem build cisco_node_utils.gemspec
Successfully built RubyGem
Name: cisco_node_utils
Version: 1.0.1
File: cisco_node_utils-1.0.1.gem
Copy the new gem to your NX-OS device and then install it.
n9k#gem install --local /bootflash/cisco_node_utils-1.0.1.gem
Successfully installed cisco_node_utils-1.0.1
Parsing documentation for cisco_node_utils-1.0.1
Installing ri documentation for cisco_node_utils-1.0.1
Done installing documentation for cisco_node_utils after 2 seconds
1 gem installed
This was hopefully a good introduction to writing a Cisco node_utils API. At this point you could continue adding properties or try your hand at writing Puppet or Chef provider code to utilize your new API.