- 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). In addition to this guide, please reference the cisco_node_utils development 'best practices' guide.
There are multiple components involved when creating new resources. This document focuses on the cisco_node_utils API, command reference YAML files, and minitests.
Any code commits must be associated with your github account and email address. If you intend to commit code to this repository then you must configure git
with your identity. If you have not already done so, use the following commands to configure the default identity that git
will use for all projects on your system:
git config --global user.name "John Doe"
git config --global user.email [email protected]
If you do not wish to change the global configuration on your system, you can set your identity for a single local repository by using the above commands without the --global
flag.
This project requires Ruby 2.0 or later.
This development guide uses tools that are packaged as gems that need to be installed in your development environment. You can install the gems manually but it's recommended to use Bundler to guarantee the dependencies are all present.
gem install bundler
At present the following gems are needed to work on this code base - if you wish to install them all manually instead of through Bundler, you can run:
gem install grpc kwalify minitest net_http_unix rake rspec simplecov
gem install rubocop --version 0.35.1
NOTE: If you are working from a server where you don't have admin/root privileges, use the following commands to install the gems:
gem install --user-install bundler
gem install --user-install grpc kwalify minitest net_http_unix rake rspec simplecov
gem install --user-install rubocop --version 0.35.1
or add --user-install
to your .gemrc
to make this the default behavior:
echo 'gem: --user-install' >> ~/.gemrc
and then update the PATH
to include ~/.gem/ruby/x.x.x/bin
. For example, you could add this to your .bashrc
or .profile
:
if which ruby >/dev/null && which gem >/dev/null; then
PATH="$(ruby -rubygems -e 'puts Gem.user_dir')/bin:$PATH"
fi
A virtual Nexus N9000 may be helpful for development and testing. Users with a valid cisco.com user ID can download the software on CCO. If you do not have a cisco.com user ID please register for one at https://tools.cisco.com/IDREG/guestRegistration
First fork the cisco-network-node-utils repository on GitHub.
Next create your local repository. 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/
If you're using Bundler, invoke it to ensure all of the development dependencies are installed on your system:
bundle install
or (if you don't have admin/root privileges and have used --user-install
to install Bundler):
~/.gem/ruby/$RUBY_VERSION/bin/bundle install --path ~/.gem
Optionally create the file ~/cisco_node_utils.yaml
to specify how the test scripts will connect to your Nexus VM or other node under test. Refer to the project README for details.
If you intend to contribute your code back to the project then you should install the git hooks that are checked in with the project source code. These hooks check your commits for conformance with various style guidelines. To install them in your local repository (or to update them to match the files currently in the repository, in case they are out of sync), simply run the update-hooks
script:
bin/git/update-hooks
As a best practice, create a topic branch (also sometimes called a 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 its topic branch, feature/eigrp
.
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. By convention we create a new YAML file to handle a new feature set, so we will create
the following file:
lib/cisco_node_utils/cmd_ref/eigrp.yaml
YAML files in the /cmd_ref/
subdirectory are automatically discovered at runtime, so we don't need to do anything special once we have created this file
The following basic command_reference parameters will be defined for each resource property:
get_command:
This defines the CLI command (usually a 'show...' command) or similar request string used to retrieve the property's current configuration state. Note that some commands may not be present on NX-OS until a pre-requisite feature is enabled.get_value:
A regexp pattern for extracting state values from the get_command output.set_value:
The 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)kind:
The data type of this property. If omitted, the property will be a string by default. Commonly used values for this property areint
andboolean
.multiple:
By default a property is assumed to be found once or not at all by theget_command
/get_value
lookup, and an error will be raised if multiple matches are found. If multiple matches are valid and expected, you must setmultiple: true
for this property.os_version:
This command_reference parameter is used to specify the product family and minimum os version within that product family required to support the property. Any version that is greater then or equal to the specified os version within the product family will be supported. If nothing is specified, the property is assumed to be supported on all version of software. This is a test_only feature and is ignored outside the context of the minitests and must be used in conjunction with the skip_incompat_version? API defined in the ciscotest.rb file.
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 get_value
and set_value
because they need to differentiate between different eigrp instances. This is done with the context
parameter. (For more complex properties, you can define get_context
and set_context
separately if needed.) 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.
Note: Property definitions in the YAML must be given in alphabetical order. Parameters under a property can be given in any order.
# eigrp.yaml
---
feature:
# feature eigrp must be enabled before configuring router eigrp
kind: boolean
os_version: 'N3k, N9k:7.0.3.I2.1; N7k:8.1.1'
get_command: 'show running eigrp all'
get_value: 'feature eigrp'
set_value: '<state> feature eigrp'
maximum_paths:
# This is an integer property
kind: int
context:
- 'router eigrp <name>'
get_command: 'show running eigrp all'
get_value: 'maximum-paths (\d+)'
set_value: 'maximum-paths <val>'
default_value: 8
router:
# There can be multiple eigrp instances
multiple: true
get_command: 'show running eigrp all' # all eigrp-related configs
get_value: 'router eigrp (\S+)' # Match instance name
set_value: '<state> router eigrp <name>' # config to add or remove
shutdown:
# This is a boolean property
kind: boolean
context:
- 'router eigrp <name>'
get_command: 'show running eigrp all'
get_value: 'shutdown'
set_value: '<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
def to_s
"RouterEigrp '#{name}'"
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
config_get('eigrp', 'feature')
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
config_get('eigrp', 'shutdown', name: @name)
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
config_get('eigrp', 'maximum_paths', name: @name)
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'"
assert_show_match(pattern: /^router eigrp #{id1}$/,
msg: "failed to create router eigrp #{id1}")
assert_show_match(pattern: /^router eigrp #{id2}$/,
msg: "failed to create 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 (see README-test-execution for detailed instructions):
% ruby test_router_eigrp.rb -v --environment my_nexus_vm
Run options: -v -- --seed 56593
# Running:
Node under test:
- name - my_n3k
- type - N3K-C3132Q-40GX
- image -
2.90 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.
5 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 files router_eigrp.rb
and eigrp.yaml
will be ignored by the build until they are added to the repo with git add
:
git add lib/cisco_node_utils/router_eigrp.rb \
lib/cisco_node_utils/cmd_ref/eigrp.yaml
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.