cisco_node_utils 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +81 -1
- data/.travis.yml +9 -0
- data/CHANGELOG.md +72 -6
- data/CONTRIBUTING.md +32 -7
- data/README.md +70 -7
- data/Rakefile +17 -0
- data/bin/check_metric_limits.rb +109 -0
- data/bin/git/hooks/commit-msg/enforce_style +81 -0
- data/bin/git/hooks/hook_lib +108 -0
- data/bin/git/hooks/hooks-wrapper +38 -0
- data/bin/git/hooks/post-flow-hotfix-start/update-version +24 -0
- data/bin/git/hooks/post-flow-release-finish/update-version +29 -0
- data/bin/git/hooks/post-flow-release-start/update-version +19 -0
- data/bin/git/hooks/post-merge/update-hooks +6 -0
- data/bin/git/hooks/post-rewrite/update-hooks +6 -0
- data/bin/git/hooks/pre-commit/rubocop +20 -0
- data/bin/git/hooks/pre-commit/validate-diffs +31 -0
- data/bin/git/hooks/pre-push/check-changelog +24 -0
- data/bin/git/hooks/pre-push/rubocop +7 -0
- data/bin/git/update-hooks +65 -0
- data/cisco_node_utils.gemspec +9 -3
- data/docs/README-develop-best-practices.md +404 -0
- data/docs/README-develop-node-utils-APIs.md +215 -365
- data/docs/README-maintainers.md +33 -3
- data/docs/template-router.rb +89 -91
- data/docs/template-test_router.rb +52 -55
- data/lib/.rubocop.yml +18 -0
- data/lib/cisco_node_utils.rb +2 -19
- data/lib/cisco_node_utils/README_YAML.md +1 -9
- data/lib/cisco_node_utils/bgp.rb +664 -0
- data/lib/cisco_node_utils/bgp_af.rb +530 -0
- data/lib/cisco_node_utils/bgp_neighbor.rb +425 -0
- data/lib/cisco_node_utils/bgp_neighbor_af.rb +709 -0
- data/lib/cisco_node_utils/cisco_cmn_utils.rb +59 -25
- data/lib/cisco_node_utils/command_reference.rb +72 -74
- data/lib/cisco_node_utils/command_reference_common.yaml +174 -9
- data/lib/cisco_node_utils/command_reference_common_bgp.yaml +535 -0
- data/lib/cisco_node_utils/command_reference_n7k.yaml +4 -0
- data/lib/cisco_node_utils/command_reference_n9k.yaml +0 -9
- data/lib/cisco_node_utils/configparser_lib.rb +152 -147
- data/lib/cisco_node_utils/dns_domain.rb +79 -0
- data/lib/cisco_node_utils/domain_name.rb +71 -0
- data/lib/cisco_node_utils/interface.rb +167 -161
- data/lib/cisco_node_utils/interface_ospf.rb +78 -81
- data/lib/cisco_node_utils/name_server.rb +64 -0
- data/lib/cisco_node_utils/node.rb +154 -198
- data/lib/cisco_node_utils/node_util.rb +61 -0
- data/lib/cisco_node_utils/ntp_config.rb +65 -0
- data/lib/cisco_node_utils/ntp_server.rb +76 -0
- data/lib/cisco_node_utils/platform.rb +174 -165
- data/lib/cisco_node_utils/radius_global.rb +146 -0
- data/lib/cisco_node_utils/radius_server.rb +295 -0
- data/lib/cisco_node_utils/router_ospf.rb +59 -63
- data/lib/cisco_node_utils/router_ospf_vrf.rb +226 -210
- data/lib/cisco_node_utils/snmpcommunity.rb +52 -58
- data/lib/cisco_node_utils/snmpgroup.rb +22 -23
- data/lib/cisco_node_utils/snmpserver.rb +99 -103
- data/lib/cisco_node_utils/snmpuser.rb +294 -274
- data/lib/cisco_node_utils/syslog_server.rb +92 -0
- data/lib/cisco_node_utils/syslog_settings.rb +69 -0
- data/lib/cisco_node_utils/tacacs_server.rb +137 -133
- data/lib/cisco_node_utils/tacacs_server_host.rb +84 -87
- data/lib/cisco_node_utils/version.rb +2 -1
- data/lib/cisco_node_utils/vlan.rb +28 -31
- data/lib/cisco_node_utils/vrf.rb +80 -0
- data/lib/cisco_node_utils/vtp.rb +100 -97
- data/lib/cisco_node_utils/yum.rb +15 -17
- data/tests/.rubocop.yml +15 -0
- data/tests/basetest.rb +81 -36
- data/tests/ciscotest.rb +38 -78
- data/{lib/cisco_node_utils → tests}/platform_info.rb +12 -8
- data/{lib/cisco_node_utils → tests}/platform_info.yaml +1 -1
- data/tests/test_bgp_af.rb +920 -0
- data/tests/test_bgp_neighbor.rb +403 -0
- data/tests/test_bgp_neighbor_af.rb +589 -0
- data/tests/test_command_config.rb +65 -62
- data/tests/test_command_reference.rb +31 -45
- data/tests/test_dns_domain.rb +113 -0
- data/tests/test_domain_name.rb +86 -0
- data/tests/test_interface.rb +424 -548
- data/tests/test_interface_ospf.rb +248 -432
- data/tests/test_interface_svi.rb +56 -79
- data/tests/test_interface_switchport.rb +196 -272
- data/tests/test_name_server.rb +85 -0
- data/tests/test_node.rb +7 -6
- data/tests/test_node_ext.rb +133 -186
- data/tests/test_ntp_config.rb +49 -0
- data/tests/test_ntp_server.rb +74 -0
- data/tests/test_platform.rb +58 -37
- data/tests/test_radius_global.rb +78 -0
- data/tests/test_radius_server.rb +185 -0
- data/tests/test_router_bgp.rb +838 -0
- data/tests/test_router_ospf.rb +49 -80
- data/tests/test_router_ospf_vrf.rb +274 -392
- data/tests/test_snmpcommunity.rb +128 -172
- data/tests/test_snmpgroup.rb +12 -14
- data/tests/test_snmpserver.rb +160 -189
- data/tests/test_snmpuser.rb +568 -717
- data/tests/test_syslog_server.rb +88 -0
- data/tests/test_syslog_settings.rb +54 -0
- data/tests/test_tacacs_server.rb +113 -148
- data/tests/test_tacacs_server_host.rb +108 -161
- data/tests/test_vlan.rb +63 -79
- data/tests/test_vrf.rb +92 -0
- data/tests/test_vtp.rb +108 -126
- data/tests/test_yum.rb +47 -41
- metadata +92 -56
- data/.rubocop_todo.yml +0 -293
- data/docs/.rubocop.yml +0 -13
- data/docs/template-feature.rb +0 -45
- data/docs/template-test_feature.rb +0 -51
- data/tests/test_all_cisco.rb +0 -46
data/cisco_node_utils.gemspec
CHANGED
|
@@ -16,15 +16,21 @@ Designed to work with Puppet and Chef.
|
|
|
16
16
|
Currently supports NX-OS nodes.
|
|
17
17
|
EOF
|
|
18
18
|
spec.license = 'Apache-2.0'
|
|
19
|
+
spec.homepage = 'https://github.com/cisco/cisco-network-node-utils'
|
|
19
20
|
|
|
20
21
|
spec.files = `git ls-files -z`.split("\x0")
|
|
21
|
-
|
|
22
|
+
# Files in bin/git are not executables as far as the Gem is concerned
|
|
23
|
+
spec.executables = []
|
|
22
24
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
23
25
|
spec.require_paths = ['lib']
|
|
24
26
|
|
|
25
|
-
spec.
|
|
27
|
+
spec.required_ruby_version = '>= 2.0.0'
|
|
28
|
+
spec.required_rubygems_version = '>= 2.1.0'
|
|
29
|
+
|
|
30
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
|
26
31
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
|
27
32
|
spec.add_development_dependency 'rake', '~> 10.0'
|
|
28
|
-
spec.add_development_dependency 'rubocop', '
|
|
33
|
+
spec.add_development_dependency 'rubocop', '= 0.34.2'
|
|
34
|
+
spec.add_development_dependency 'simplecov', '~> 0.9'
|
|
29
35
|
spec.add_runtime_dependency 'cisco_nxapi', '~> 1.0'
|
|
30
36
|
end
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# Develoment Best Practices for cisco_node_utils APIs.
|
|
2
|
+
|
|
3
|
+
* [Overview](#overview)
|
|
4
|
+
* [YAML Development Best Practices](#ydbp)
|
|
5
|
+
* [Common Object Development Best Practices](#odbp)
|
|
6
|
+
* [MiniTest Development Best Practices](#mdbp)
|
|
7
|
+
|
|
8
|
+
## <a name="overview">Overview</a>
|
|
9
|
+
|
|
10
|
+
This document is intended to assist in developing cisco_node_utils API's that are consistent with current best practices.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## <a name="ydbp">YAML Development Best Practices</a>
|
|
14
|
+
|
|
15
|
+
* [Y1](#yaml1): All yaml feature entries should be kept in alphabetical order.
|
|
16
|
+
* [Y2](#yaml2): Use *regexp* anchors where needed for `config_get` and `config_get_token` entries.
|
|
17
|
+
* Y3: Avoid nested optional matches.
|
|
18
|
+
* [Y4](#yaml4): Use the `_template` feature when getting/setting the same property value at multiple levels.
|
|
19
|
+
* [Y5](#yaml5): When possible include a `default_value` that represents the system default value.
|
|
20
|
+
* [Y6](#yaml6): When possible, use the same `config_get` show command for all properties and document any anomalies.
|
|
21
|
+
* [Y7](#yaml7): Use Key-value wildcards instead of Printf-style wildcards.
|
|
22
|
+
* [Y8](#yaml8): Selection of `show` commands for `config_get`.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## <a name="odbp">Common Object Development Best Practices</a>
|
|
27
|
+
|
|
28
|
+
* [CO1](#co1): Features that can be configured under the global and non-global vrfs need to account for this in the object design.
|
|
29
|
+
* [CO2](#co2): Make use of the equality operator allowing proper `instance1 == instance2` checks in the minitests.
|
|
30
|
+
* [CO3](#co3): Use `''` rather than `nil` to represent "property is absent entirely"
|
|
31
|
+
* [CO4](#co4): Make sure all new properites have a `getter`, `setter` and `default_getter` method.
|
|
32
|
+
* [CO5](#co5): Use singleton-like design for resources that cannot have multiple instances.
|
|
33
|
+
|
|
34
|
+
## <a name="mdbp">MiniTest Development Best Practices</a>
|
|
35
|
+
|
|
36
|
+
* [MT1](#mt1): Ensure that **all new API's** have minitest coverage.
|
|
37
|
+
* [MT2](#mt2): Use appropriate `assert_foo` and `refute_foo` statements rather than `assert_equal`.
|
|
38
|
+
* [MT3](#mt3): Do not hardcode interface names.
|
|
39
|
+
* [MT4](#mt4): Make use of the `config` helper method for device configuration instead of `@device.cmd`.
|
|
40
|
+
* [MT5](#mt5): Make use of the `assert_show_match` and `refute_show_match` helper methods to validate expected outcomes in the CLI instead of `@device.cmd("show...")`.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## YAML Best Practices:
|
|
45
|
+
|
|
46
|
+
### <a name="yaml1">Y1: All yaml feature entries should be kept in alphabetical order.
|
|
47
|
+
|
|
48
|
+
Please keep all feature names in alphabetical order, and all options under a feature in alphabetical order as well. As YAML permits duplicate entries (in which case the last entry overrides any earlier entries), keeping a consistent order helps to prevent accidentally introducing such duplication.
|
|
49
|
+
|
|
50
|
+
Top level features in alpabetical order:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
aaa_authentication_login:
|
|
54
|
+
...
|
|
55
|
+
dnsclient:
|
|
56
|
+
...
|
|
57
|
+
interface:
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Options under a feature:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
interface:
|
|
64
|
+
access_vlan:
|
|
65
|
+
...
|
|
66
|
+
all_interfaces:
|
|
67
|
+
...
|
|
68
|
+
create:
|
|
69
|
+
...
|
|
70
|
+
description:
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### <a name="yaml2">Y2: Use *regexp* anchors where needed for `config_get` and `config_get_token` entries.
|
|
75
|
+
|
|
76
|
+
Please use *regexp* anchors `^$` to ensure you match the correct feature information in the `show` output.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
syslog_settings:
|
|
80
|
+
timestamp:
|
|
81
|
+
config_get: "show running-config all | include '^logging timestamp'"
|
|
82
|
+
config_get_token: '/^logging timestamp (.*)$/'
|
|
83
|
+
config_set: '<state> logging timestamp <units>'
|
|
84
|
+
default_value: 'seconds'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### <a name="yaml3">Y3: Avoid nested optional matches.
|
|
88
|
+
|
|
89
|
+
### <a name="yaml4">Y4: Use the `_template` feature when getting/setting the same property value at multiple levels.
|
|
90
|
+
|
|
91
|
+
Using the template below, `auto_cost` and `default_metric` can be set under `router ospf foo` and `router ospf foo; vrf blue`.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
ospf:
|
|
95
|
+
_template:
|
|
96
|
+
config_get: "show running ospf all"
|
|
97
|
+
config_get_token: '/^router ospf <name>$/'
|
|
98
|
+
config_get_token_append:
|
|
99
|
+
- '/^vrf <vrf>$/'
|
|
100
|
+
config_set: "router ospf <name>"
|
|
101
|
+
config_set_append:
|
|
102
|
+
- "vrf <vrf>"
|
|
103
|
+
|
|
104
|
+
auto_cost:
|
|
105
|
+
config_get_token_append: '/^auto-cost reference-bandwidth (\d+)\s*(\S+)?$/'
|
|
106
|
+
config_set_append: "auto-cost reference-bandwidth <cost> <type>"
|
|
107
|
+
default_value: [40, "Gbps"]
|
|
108
|
+
|
|
109
|
+
default_metric:
|
|
110
|
+
config_get_token_append: '/^default-metric (\d+)?$/'
|
|
111
|
+
config_set_append: "<state> default-metric <metric>"
|
|
112
|
+
default_value: 0
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### <a name="yaml5">Y5: When possible include a `default_value` that represents the system default value.
|
|
116
|
+
|
|
117
|
+
Please make sure to specify a `default_value` and document properties that don't have a system default. System defaults may differ between cisco platforms making it important to define for lookup in the cisco_node_utils common object methods.
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Default value for `message_digest_alg_type` is `md5`
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
message_digest_alg_type:
|
|
124
|
+
config_get: 'show running interface all'
|
|
125
|
+
config_get_token: ['/^interface %s$/i', '/^\s*ip ospf message-digest-key \d+ (\S+)/']
|
|
126
|
+
default_value: 'md5'
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**NOTE1: Use strings rather then symbols when applicable**.
|
|
130
|
+
|
|
131
|
+
If the `default_value` differs between cisco platforms, the more specific `command_reference_[platform].yaml` file should be used.
|
|
132
|
+
|
|
133
|
+
For example, if the default value on all platforms except the n9k is `md5` then set the entry in `command_reference_common.yaml` to `md5` and set the entry in `command_reference_n9k.yaml` to it's default `sha2`.
|
|
134
|
+
|
|
135
|
+
### <a name="yaml6">Y6: When possible, use the same `config_get` show command for all properties and document any anomalies.
|
|
136
|
+
|
|
137
|
+
All properties below use the `show run tacacs all` command except `directed_request` which is documented.
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
tacacs_server:
|
|
141
|
+
deadtime:
|
|
142
|
+
config_get: "show run tacacs all"
|
|
143
|
+
config_get_token: '/^tacacs-server deadtime\s+(\d+)/'
|
|
144
|
+
config_set: "%s tacacs-server deadtime %d"
|
|
145
|
+
default_value: 0
|
|
146
|
+
|
|
147
|
+
directed_request:
|
|
148
|
+
# oddly, directed request must be retrieved from aaa output
|
|
149
|
+
config_get: "show running aaa all"
|
|
150
|
+
config_get_token: '/(?:no)?\s*tacacs-server directed-request/'
|
|
151
|
+
config_set: "%s tacacs-server directed-request"
|
|
152
|
+
default_value: false
|
|
153
|
+
|
|
154
|
+
encryption_type:
|
|
155
|
+
config_get: "show run tacacs all"
|
|
156
|
+
config_get_token: '/^tacacs-server key (\d+)\s+(\S+)/'
|
|
157
|
+
default_value: 0
|
|
158
|
+
|
|
159
|
+
encryption_password:
|
|
160
|
+
config_get: "show run tacacs all"
|
|
161
|
+
config_get_token: '/^tacacs-server key (\d+)\s+(\S+)/'
|
|
162
|
+
default_value: ""
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### <a name="yaml7">Y7: Use Key-value wildcards instead of Printf-style wildcards.
|
|
166
|
+
|
|
167
|
+
The following approach is moderately more complex to implement but is more readable in the ruby code and is flexible enough to handle significant platform differences in CLI. It is therefore the recommended approach for new development.
|
|
168
|
+
|
|
169
|
+
**Key-value wildcards**
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
config_set_append: "<state> log-adjacency-changes <type>"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This following approach is quick to implement and concise, but less flexible - in particular it cannot handle a case where different platforms take parameters in a different order - and less readable in the ruby code.
|
|
176
|
+
|
|
177
|
+
**Printf-style wildcards**
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
config_set_append: "%s log-adjacency-changes %s"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### <a name="yaml8">Y8: Selection of `show` commands for `config_get`.
|
|
184
|
+
|
|
185
|
+
The following commands should be preferred over `show [feature]` commands since not all `show [feature]` commands behave in the same manner across cisco platforms.
|
|
186
|
+
|
|
187
|
+
* `show running [feature] all` if available.
|
|
188
|
+
* `show running all` if `show running [feature] all` is *not* available.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## Common Object Best Practices:
|
|
192
|
+
|
|
193
|
+
### <a name="co1">CO1: Features that can be configured under the global and non-global vrfs need to account for this in the object design.
|
|
194
|
+
|
|
195
|
+
Many cisco features can be configured under the default or global vrf and also under *n* number of non-default vrfs.
|
|
196
|
+
|
|
197
|
+
The following `initialize` and `self.vrfs` methods account for configuration under `default` and `non-default vrfs`.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
def initialize(router, name, instantiate=true)
|
|
201
|
+
fail TypeError if router.nil?
|
|
202
|
+
fail TypeError if name.nil?
|
|
203
|
+
fail ArgumentError unless router.length > 0
|
|
204
|
+
fail ArgumentError unless name.length > 0
|
|
205
|
+
@router = router
|
|
206
|
+
@name = name
|
|
207
|
+
@parent = {}
|
|
208
|
+
if @name == 'default'
|
|
209
|
+
@get_args = @set_args = { name: @router }
|
|
210
|
+
else
|
|
211
|
+
@get_args = @set_args = { name: @router, vrf: @name }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
create if instantiate
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Create a hash of all router ospf vrf instances
|
|
218
|
+
def self.vrfs
|
|
219
|
+
hash_final = {}
|
|
220
|
+
RouterOspf.routers.each do |instance|
|
|
221
|
+
name = instance[0]
|
|
222
|
+
vrf_ids = config_get('ospf', 'vrf', name: name)
|
|
223
|
+
hash_tmp = { name =>
|
|
224
|
+
{ 'default' => RouterOspfVrf.new(name, 'default', false) } }
|
|
225
|
+
unless vrf_ids.nil?
|
|
226
|
+
vrf_ids.each do |vrf|
|
|
227
|
+
hash_tmp[name][vrf] = RouterOspfVrf.new(name, vrf, false)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
hash_final.merge!(hash_tmp)
|
|
231
|
+
end
|
|
232
|
+
hash_final
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### <a name="co2">CO2: Make use of the equality operator allowing proper `instance1 == instance2` checks in the minitests.
|
|
237
|
+
|
|
238
|
+
Having this logic defined in the common object lets the minitest easily check the specific instances.
|
|
239
|
+
|
|
240
|
+
Without this equality operator `==` only passes if they are the same instance object. With this equality operator `==` passes if they are different objects referring to the same configuration on the node.
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
def ==(other)
|
|
244
|
+
(name == other.name) && (vrf == other.vrf)
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Example Usage:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
def test_dnsdomain_create_destroy_multiple
|
|
252
|
+
id1 = 'aoeu.com'
|
|
253
|
+
id2 = 'asdf.com'
|
|
254
|
+
refute_includes(Cisco::DnsDomain.dnsdomains, id1)
|
|
255
|
+
refute_includes(Cisco::DnsDomain.dnsdomains, id2)
|
|
256
|
+
|
|
257
|
+
ns1 = Cisco::DnsDomain.new(id1)
|
|
258
|
+
ns2 = Cisco::DnsDomain.new(id2)
|
|
259
|
+
assert_includes(Cisco::DnsDomain.dnsdomains, id1)
|
|
260
|
+
assert_includes(Cisco::DnsDomain.dnsdomains, id2)
|
|
261
|
+
assert_equal(Cisco::DnsDomain.dnsdomains[id1], ns1)
|
|
262
|
+
assert_equal(Cisco::DnsDomain.dnsdomains[id2], ns2)
|
|
263
|
+
|
|
264
|
+
ns1.destroy
|
|
265
|
+
refute_includes(Cisco::DnsDomain.dnsdomains, id1)
|
|
266
|
+
assert_includes(Cisco::DnsDomain.dnsdomains, id2)
|
|
267
|
+
ns2.destroy
|
|
268
|
+
refute_includes(Cisco::DnsDomain.dnsdomains, id2)
|
|
269
|
+
end
|
|
270
|
+
```
|
|
271
|
+
### <a name="co3">CO3: Use `''` rather than `nil` to represent "property is absent entirely"
|
|
272
|
+
|
|
273
|
+
Our convention is to let `''` represent 'not configured at all' rather than `nil`. For example, `interface.rb`:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
def vrf
|
|
277
|
+
vrf = config_get('interface', 'vrf', @name)
|
|
278
|
+
return '' if vrf.nil?
|
|
279
|
+
vrf.shift.strip
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def vrf=(vrf)
|
|
283
|
+
fail TypeError unless vrf.is_a?(String)
|
|
284
|
+
if vrf.empty?
|
|
285
|
+
config_set('interface', 'vrf', @name, 'no', '')
|
|
286
|
+
else
|
|
287
|
+
config_set('interface', 'vrf', @name, '', vrf)
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
However, if a property has a default value (it is never truly 'removed'), then we should do this instead:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
def access_vlan
|
|
295
|
+
vlan = config_get('interface', 'access_vlan', @name)
|
|
296
|
+
return default_access_vlan if vlan.nil?
|
|
297
|
+
vlan.shift.to_i
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def access_vlan=(vlan)
|
|
301
|
+
config_set('interface', 'access_vlan', @name, vlan)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### <a name="co4">CO4: Make sure all new properites have a `getter`, `setter` and `default_getter` method.
|
|
305
|
+
|
|
306
|
+
In order to have a complete set of api's for each property it is important that all properties have a `getter`, `setter` and `default_getter` method.
|
|
307
|
+
|
|
308
|
+
This can be seen in the following `router_id` property.
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
# Getter Method
|
|
312
|
+
def router_id
|
|
313
|
+
match = config_get('ospf', 'router_id', @get_args)
|
|
314
|
+
match.nil? ? default_router_id : match.first
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Setter Method
|
|
318
|
+
def router_id=(router_id)
|
|
319
|
+
if router_id == default_router_id
|
|
320
|
+
@set_args[:state] = 'no'
|
|
321
|
+
@set_args[:router_id] = ''
|
|
322
|
+
else
|
|
323
|
+
@set_args[:state] = ''
|
|
324
|
+
@set_args[:router_id] = router_id
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
config_set('ospf', 'router_id', @set_args)
|
|
328
|
+
delete_set_args_keys([:state, :router_id])
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Default Getter Method
|
|
332
|
+
def default_router_id
|
|
333
|
+
config_get_default('ospf', 'router_id')
|
|
334
|
+
end
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### <a name="co5">CO5: Use singleton-like design for resources that cannot have multiple instances.
|
|
338
|
+
|
|
339
|
+
See [TacacsServer](../lib/cisco_node_utils/tacacs_server.rb) and [SnmpServer](../lib/cisco_node_utils/snmpserver.rb) for examples.
|
|
340
|
+
|
|
341
|
+
## MiniTest Best Practices:
|
|
342
|
+
|
|
343
|
+
### <a name="mt1">MT1: Ensure that *all new API's* have minitest coverage.
|
|
344
|
+
|
|
345
|
+
### <a name="mt2">MT2: Use appropriate `assert_foo` and `refute_foo` statements rather than `assert_equal`.
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
Minitest has a bunch of different test methods that are more specific than assert_equal. See [test methods](http://docs.ruby-lang.org/en/2.1.0/MiniTest/Assertions.html) for a complete list, but here are some general guidelines:
|
|
349
|
+
|
|
350
|
+
| Instead of ... | Use ... |
|
|
351
|
+
| ------------------------------|:-----------------:|
|
|
352
|
+
| assert_equal(true, foo.bar?) | assert(foo.bar?) |
|
|
353
|
+
| assert_equal(false, foo.bar?) | refute(foo.bar?) |
|
|
354
|
+
| assert_equal(true, foo.nil?) | assert_nil(foo) |
|
|
355
|
+
| assert_equal(false, foo.nil?) | refute_nil(foo) |
|
|
356
|
+
| assert_equal(true, foo.empty?)| assert_empty(foo) |
|
|
357
|
+
|
|
358
|
+
The more specific assertions also produce more helpful failure messages if something is wrong.
|
|
359
|
+
|
|
360
|
+
### <a name="mt3">MT3: Do not hardcode interface names.
|
|
361
|
+
|
|
362
|
+
Rather then hardcode an interface name that may or may not exist, instead use
|
|
363
|
+
the `interfaces[]` array.
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
def create_interface(ifname=interfaces[0])
|
|
367
|
+
@default_show_command = show_cmd(ifname)
|
|
368
|
+
Interface.new(ifname)
|
|
369
|
+
end
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
If additional interfaces are needed array index `1` and `2` may be used.
|
|
373
|
+
|
|
374
|
+
### <a name="mt4">MT4: Make use of the `config` helper method for device configuration instead of `@device.cmd`.
|
|
375
|
+
|
|
376
|
+
For conveninence the `config` helper method has been provided for device configuration within the minitests.
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
config('no feature ospf')
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
config('feature ospf'; 'router ospf green')
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### <a name="mt5">MT5: Make use of the `assert_show_match` and `refute_show_match` helper methods to validate expected outcomes in the CLI instead of `@device.cmd("show...")`.
|
|
387
|
+
|
|
388
|
+
We have a very common pattern in minitest where we execute some show command over the telnet connection, match it against some regexp pattern, and succeed or fail based on the result. Helper methods `assert_show_match` and `refute_show_match` support this pattern.
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
assert_show_match(command: 'show run all | no-more',
|
|
392
|
+
pattern: /interface port-channel 1/,
|
|
393
|
+
msg: 'port-channel is not present but it should be')
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
If your `command` and/or `pattern` are the same throughout a test case or throughout a test suite, you can set the test case instance variables `@default_show_command` and/or `@default_output_pattern` which serve as defaults for these parameters:
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
@default_show_command = 'show run interface all | include "interface" | no-more'
|
|
400
|
+
assert_output_match(pattern: /interface port-channel 10/)
|
|
401
|
+
refute_output_match(pattern: /interface port-channel 11/)
|
|
402
|
+
refute_output_match(pattern: /interface port-channel 12/)
|
|
403
|
+
assert_output_match(pattern: /interface port-channel 13/)
|
|
404
|
+
```
|
|
@@ -3,258 +3,86 @@
|
|
|
3
3
|
#### Table of Contents
|
|
4
4
|
|
|
5
5
|
* [Overview](#overview)
|
|
6
|
-
* [
|
|
7
|
-
* [
|
|
8
|
-
|
|
9
|
-
* [Step 2. Create the node_utils API: feature bash-shell](#api)
|
|
10
|
-
* [Step 3. Create the Minitest: feature bash-shell](#minitest)
|
|
11
|
-
* [Step 4. rubocop / lint: feature bash-shell](#lint)
|
|
12
|
-
* [Advanced Example: router eigrp](#complex)
|
|
6
|
+
* [Before You Begin](#prerequisites)
|
|
7
|
+
* [Start here: Fork and Clone the Repo](#clone)
|
|
8
|
+
* [Example: router eigrp](#complex)
|
|
13
9
|
* [Step 1. YAML Definitions: router eigrp](#comp_yaml)
|
|
14
10
|
* [Step 2. Create the node_utils API: router eigrp](#comp_api)
|
|
15
11
|
* [Step 3. Create the Minitest: router eigrp](#comp_minitest)
|
|
16
12
|
* [Step 4. rubocop / lint: router eigrp](#comp_lint)
|
|
13
|
+
* [Step 5. Build and Install the gem](#comp_gem)
|
|
17
14
|
|
|
18
15
|
## <a name="overview">Overview</a>
|
|
19
16
|
|
|
20
|
-
This document is a HowTo guide for writing new
|
|
17
|
+
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.](./README-develop-best-practices.md)
|
|
21
18
|
|
|
22
|
-
There are multiple components involved when creating new resources. This document focuses on the
|
|
19
|
+
There are multiple components involved when creating new resources. This document focuses on the cisco_node_utils API, command reference YAML files, and minitests.
|
|
23
20
|
|
|
24
21
|

|
|
25
22
|
|
|
26
|
-
## <a name="
|
|
23
|
+
## <a name="prerequisites">Before You Begin</a>
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
Please note: A virtual Nexus N9000/N3000 may be helpful for development and testing. Users with a valid [cisco.com](http://cisco.com) user ID can obtain a copy of a virtual Nexus N9000/N3000 by sending their [cisco.com](http://cisco.com) user ID in an email to <get-n9kv@cisco.com>. If you do not have a [cisco.com](http://cisco.com) user ID please register for one at [https://tools.cisco.com/IDREG/guestRegistration](https://tools.cisco.com/IDREG/guestRegistration)
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
git clone https://github.com/cisco/cisco-network-node-utils.git
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## <a name="simple">Basic Example: feature bash-shell</a>
|
|
35
|
-
|
|
36
|
-
Writing a new node_utils API is often easier to understand through example code. The NX-OS CLI for `feature bash-shell` is a simple on / off style configuration and therefore a good candidate for a simple API:
|
|
37
|
-
|
|
38
|
-
`[no] feature bash-shell`
|
|
39
|
-
|
|
40
|
-
### <a name="yaml">Step 1. YAML Definitions: feature bash-shell</a>
|
|
41
|
-
|
|
42
|
-
The new API will need some basic YAML definitions. These are used with the `CommandReference` module as a way to abstract away platform CLI differences.
|
|
43
|
-
|
|
44
|
-
`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 `feature bash-shell` example uses the same cli syntax on all platforms, thus we only need to edit the common file:
|
|
45
|
-
|
|
46
|
-
`cisco_network_node_utils/lib/cisco_node_utils/command_reference_common.yaml`
|
|
47
|
-
|
|
48
|
-
Four basic command_reference parameters will be defined for each resource property:
|
|
49
|
-
|
|
50
|
-
1. `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.
|
|
51
|
-
2. `config_get_token:` A regexp pattern for extracting state values from the config_get output.
|
|
52
|
-
3. `config_set:` The NX-OS CLI configuration command(s) used to set the property configuration. May contain wildcards for variable parameters.
|
|
53
|
-
4. `default_value:` This is typically the "factory" default state of the property, expressed as an actual value (true, 12, "off", etc)
|
|
54
|
-
|
|
55
|
-
There are additional YAML command parameters available which are not covered by this document. Please see the [README_YAML.md](../lib/cisco_node_utils/README_YAML.md) document for more information on the structure and semantics of these files.
|
|
56
|
-
|
|
57
|
-
#### Example: YAML Property Definitions for feature bash-shell
|
|
58
|
-
|
|
59
|
-
The `feature bash-shell` configuration is displayed with the `show running-config` command. Anchor the config_get_token regexp pattern carefully as it may match on unwanted configurations.
|
|
60
|
-
|
|
61
|
-
*Note: YAML syntax has strict indentation rules. Do not use TABs.*
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
bash_shell:
|
|
65
|
-
feature:
|
|
66
|
-
config_get: 'show running' # get current bash config state
|
|
67
|
-
config_get_token: '/^feature bash-shell$/' # Match only 'feature bash-shell'
|
|
68
|
-
config_set: '<state> feature bash-shell' # Config needed to enable/disable
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### <a name="api">Step 2. cisco_node_utils API file: feature bash-shell</a>
|
|
72
|
-
|
|
73
|
-
* Before creating the new API, first add a new entry: `require "cisco_node_utils/bash_shell"` to the master list of resources in:
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
cisco_network_node_utils/lib/cisco_node_utils.rb
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
* There are template files in /docs that may help when writing new APIs. These templates provide most of the necessary code with just a few customizations required for a new resource. Copy the `template-feature.rb` file to use as the basis for `bash_shell.rb`:
|
|
27
|
+
This development guide uses tools that are packaged as gems that need to be installed on your server.
|
|
80
28
|
|
|
81
29
|
```bash
|
|
82
|
-
|
|
30
|
+
gem install cisco_nxapi
|
|
31
|
+
gem install rake
|
|
32
|
+
gem install rubocop
|
|
33
|
+
gem install simplecov
|
|
34
|
+
gem install minitest
|
|
83
35
|
```
|
|
84
36
|
|
|
85
|
-
|
|
37
|
+
**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`
|
|
86
38
|
|
|
87
39
|
```bash
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
40
|
+
gem install --user-install cisco_nxapi
|
|
41
|
+
gem install --user-install rake
|
|
42
|
+
gem install --user-install rubocop
|
|
43
|
+
gem install --user-install simplecov
|
|
44
|
+
gem install --user-install minitest
|
|
91
45
|
```
|
|
92
46
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
This is the completed bash_shell API based on `template-feature.rb`:
|
|
96
|
-
|
|
97
|
-
```ruby
|
|
47
|
+
## <a name="clone">Start here: Fork and Clone the Repo</a>
|
|
98
48
|
|
|
99
|
-
|
|
100
|
-
module Cisco
|
|
101
|
-
# Class name syntax will typically be the resource name in camelCase
|
|
102
|
-
# format; for example: 'tacacs server host' becomes TacacsServerHost.
|
|
103
|
-
class BashShell
|
|
104
|
-
# Establish connection to node
|
|
105
|
-
@@node = Cisco::Node.instance
|
|
106
|
-
|
|
107
|
-
def feature_enable
|
|
108
|
-
@@node.config_set('bash_shell', 'feature', { :state => '' })
|
|
109
|
-
end
|
|
49
|
+
First [fork](https://help.github.com/articles/fork-a-repo) the [cisco-network-node-utils](https://github.com/cisco/cisco-network-node-utils) git repository
|
|
110
50
|
|
|
111
|
-
|
|
112
|
-
@@node.config_set('bash_shell', 'feature', { :state => 'no' })
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Check current state of the configuration
|
|
116
|
-
def BashShell.feature_enabled
|
|
117
|
-
feat = @@node.config_get('bash_shell', 'feature')
|
|
118
|
-
return (!feat.nil? and !feat.empty?)
|
|
119
|
-
rescue Cisco::CliError => e
|
|
120
|
-
# This cmd will syntax reject if feature is not
|
|
121
|
-
# enabled. Just catch the reject and return false.
|
|
122
|
-
return false if e.clierror =~ /Syntax error/
|
|
123
|
-
raise
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### <a name="minitest">Step 3. Minitest: feature bash-shell</a>
|
|
130
|
-
|
|
131
|
-
* A minitest should be created to validate the new APIs. Minitests are stored in the tests directory: `cisco_network_node_utils/tests/`
|
|
132
|
-
|
|
133
|
-
* Tests may use `@device.cmd("show ...")` to access the CLI directly set up tests and validate expected outcomes. The tests directory contains many examples of how these are used.
|
|
134
|
-
|
|
135
|
-
* Our minitest will be very basic since the API itself is very basic. Use `template-test_feature.rb` to create a minitest for the bash_shell resource:
|
|
51
|
+
Next install the code base. Clone the cisco-network-node-utils repo from your fork into a workspace:
|
|
136
52
|
|
|
137
53
|
```bash
|
|
138
|
-
|
|
54
|
+
git clone https://github.com/YOUR-USERNAME/cisco-network-node-utils.git
|
|
55
|
+
cd cisco-network-node-utils/
|
|
139
56
|
```
|
|
140
57
|
|
|
141
|
-
|
|
58
|
+
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:
|
|
142
59
|
|
|
143
60
|
```bash
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
/X__RESOURCE_NAME__X/bash_shell/
|
|
147
|
-
|
|
148
|
-
/X__CLI_NAME__X/bash-shell/
|
|
61
|
+
git config --global user.name "John Doe"
|
|
62
|
+
git config --global user.email johndoe@example.com
|
|
149
63
|
```
|
|
150
64
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
This is the completed `bash_shell` minitest based on `template-test_feature.rb`:
|
|
154
|
-
|
|
155
|
-
```ruby
|
|
156
|
-
#
|
|
157
|
-
# Minitest for BashShell class
|
|
158
|
-
#
|
|
159
|
-
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
|
160
|
-
#
|
|
161
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
162
|
-
# you may not use this file except in compliance with the License.
|
|
163
|
-
# You may obtain a copy of the License at
|
|
164
|
-
#
|
|
165
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
166
|
-
#
|
|
167
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
168
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
169
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
170
|
-
# See the License for the specific language governing permissions and
|
|
171
|
-
# limitations under the License.
|
|
172
|
-
|
|
173
|
-
require File.expand_path("../ciscotest", __FILE__)
|
|
174
|
-
require File.expand_path("../../lib/cisco_node_utils/bash_shell", __FILE__)
|
|
175
|
-
|
|
176
|
-
class TestBashShell < CiscoTestCase
|
|
177
|
-
def setup
|
|
178
|
-
# setup automatically runs at the beginning of each test
|
|
179
|
-
super
|
|
180
|
-
no_feature
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def teardown
|
|
184
|
-
# teardown automatically runs at the end of each test
|
|
185
|
-
no_feature
|
|
186
|
-
super
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def no_feature
|
|
190
|
-
# setup/teardown helper. Turn the feature off for a clean testbed.
|
|
191
|
-
@device.cmd('conf t ; no feature bash-shell ; end')
|
|
192
|
-
node.cache_flush()
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def test_feature_on_off
|
|
196
|
-
feat = BashShell.new()
|
|
197
|
-
feat.feature_enable
|
|
198
|
-
assert(BashShell.feature_enabled)
|
|
199
|
-
|
|
200
|
-
feat.feature_disable
|
|
201
|
-
refute(BashShell.feature_enabled)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
end
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
We can now run the new minitest against our NX-OS device using this syntax:
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
ruby test_bash_shell.rb -- <node_ip_address> <user> <passwd>
|
|
212
|
-
```
|
|
213
|
-
*Note. The minitest requires that the NX-OS device have 'feature nxapi' enabled. This will typically be enabled by default.*
|
|
214
|
-
|
|
215
|
-
#### Example: Running bash_shell minitest
|
|
65
|
+
As a best practice create a topic/feature branch for your feature work using the `git branch feature/<feature_name>` command.
|
|
216
66
|
|
|
217
67
|
```bash
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
CiscoTestCase#test_placeholder =
|
|
224
|
-
Ruby Version - 1.9.3
|
|
225
|
-
Node in CiscoTestCase Class: 192.168.0.1
|
|
226
|
-
Platform:
|
|
227
|
-
- name - my_n9k
|
|
228
|
-
- type - N9K-C9504
|
|
229
|
-
- image - bootflash:///n9000-dk9.7.0.3.I2.0.509.bin
|
|
230
|
-
|
|
231
|
-
1.79 s = .
|
|
232
|
-
TestBashShell#test_feature_on_off = 1.42 s = .
|
|
233
|
-
TestBashShell#test_placeholder = 0.95 s = .
|
|
234
|
-
TestCase#test_placeholder = 0.81 s = .
|
|
235
|
-
|
|
236
|
-
Finished tests in 4.975186s, 0.8040 tests/s, 0.4020 assertions/s.
|
|
237
|
-
|
|
238
|
-
4 tests, 2 assertions, 0 failures, 0 errors, 0 skips
|
|
68
|
+
git branch feature/eigrp
|
|
69
|
+
git branch
|
|
70
|
+
* develop
|
|
71
|
+
feature/eigrp
|
|
239
72
|
```
|
|
240
73
|
|
|
241
|
-
*Note. The minitest harness counts the helper methods as tests which is why the final tally shows 4 tests instead of just 2 tests.*
|
|
242
74
|
|
|
243
|
-
|
|
75
|
+
## <a name="complex">Example: router eigrp</a>
|
|
244
76
|
|
|
245
|
-
|
|
77
|
+
Before you start working on the eigrp feature, checkout the feature branch you created earlier.
|
|
246
78
|
|
|
247
79
|
```bash
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
1 file inspected, no offenses detected
|
|
80
|
+
git checkout feature/eigrp
|
|
81
|
+
git branch
|
|
82
|
+
develop
|
|
83
|
+
* feature/eigrp
|
|
253
84
|
```
|
|
254
85
|
|
|
255
|
-
## <a name="complex">Advanced Example: router eigrp</a>
|
|
256
|
-
|
|
257
|
-
Now that we have a basic example working we can move on to a slightly more complex cli.
|
|
258
86
|
`router eigrp` requires feature enablement and supports multiple eigrp instances. It also has multiple configuration levels for vrf and address-family.
|
|
259
87
|
|
|
260
88
|
For the purposes of this example we will only implement the following properties:
|
|
@@ -274,10 +102,20 @@ Example:
|
|
|
274
102
|
|
|
275
103
|
### <a name="comp_yaml">Step 1. YAML Definitions: router eigrp</a>
|
|
276
104
|
|
|
277
|
-
|
|
105
|
+
The new API for `router eigrp` will need some basic YAML definitions.
|
|
278
106
|
|
|
279
|
-
`
|
|
107
|
+
`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:
|
|
280
108
|
|
|
109
|
+
`lib/cisco_node_utils/command_reference_common.yaml`
|
|
110
|
+
|
|
111
|
+
Four basic command_reference parameters will be defined for each resource property:
|
|
112
|
+
|
|
113
|
+
1. `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.
|
|
114
|
+
2. `config_get_token:` A regexp pattern for extracting state values from the config_get output.
|
|
115
|
+
3. `config_set:` The NX-OS CLI configuration command(s) used to set the property configuration. May contain wildcards for variable parameters.
|
|
116
|
+
4. `default_value:` This is typically the "factory" default state of the property, expressed as an actual value (true, 12, "off", etc)
|
|
117
|
+
|
|
118
|
+
There are additional YAML command parameters available which are not covered by this document. Please see the [README_YAML.md](../lib/cisco_node_utils/README_YAML.md) document for more information on the structure and semantics of these files.
|
|
281
119
|
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.
|
|
282
120
|
|
|
283
121
|
*Note: Eigrp also has vrf and address-family contexts. These contexts require additional coding and are beyond the scope of this document.*
|
|
@@ -317,16 +155,10 @@ eigrp:
|
|
|
317
155
|
|
|
318
156
|
### <a name="comp_api">Step 2. cisco_node_utils API: router eigrp</a>
|
|
319
157
|
|
|
320
|
-
* Add a new entry: `require "cisco_node_utils/router_eigrp"` to the master list in:
|
|
321
|
-
|
|
322
|
-
```
|
|
323
|
-
cisco_network_node_utils/lib/cisco_node_utils.rb
|
|
324
|
-
```
|
|
325
|
-
|
|
326
158
|
* The `template-router.rb` file provides a basic router API that we will use as the basis for `router_eigrp.rb`:
|
|
327
159
|
|
|
328
160
|
```bash
|
|
329
|
-
cp docs/template-router.rb
|
|
161
|
+
cp docs/template-router.rb lib/cisco_node_utils/router_eigrp.rb
|
|
330
162
|
```
|
|
331
163
|
|
|
332
164
|
* Our new `router_eigrp.rb` requires changes from the original template. Edit `router_eigrp.rb` and change the placeholder names as shown.
|
|
@@ -347,9 +179,6 @@ cp docs/template-router.rb cisco_network_node_utils/router_eigrp.rb
|
|
|
347
179
|
This is the completed `router_eigrp` API based on `template-router.rb`:
|
|
348
180
|
|
|
349
181
|
```ruby
|
|
350
|
-
#
|
|
351
|
-
# NXAPI implementation of RouterEigrp class
|
|
352
|
-
#
|
|
353
182
|
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
|
354
183
|
#
|
|
355
184
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -364,114 +193,111 @@ This is the completed `router_eigrp` API based on `template-router.rb`:
|
|
|
364
193
|
# See the License for the specific language governing permissions and
|
|
365
194
|
# limitations under the License.
|
|
366
195
|
|
|
367
|
-
|
|
196
|
+
require_relative 'node_util'
|
|
368
197
|
|
|
369
198
|
module Cisco
|
|
370
|
-
class
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
create if instantiate
|
|
382
|
-
end
|
|
199
|
+
# RouterEigrp - node utility class for EIGRP config management.
|
|
200
|
+
class RouterEigrp < NodeUtil
|
|
201
|
+
attr_reader :name
|
|
202
|
+
|
|
203
|
+
# name: name of the router instance
|
|
204
|
+
# instantiate: true = create router instance
|
|
205
|
+
def initialize(name, instantiate=true)
|
|
206
|
+
fail ArgumentError unless name.length > 0
|
|
207
|
+
@name = name
|
|
208
|
+
create if instantiate
|
|
209
|
+
end
|
|
383
210
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
211
|
+
# Create a hash of all current router instances.
|
|
212
|
+
def self.routers
|
|
213
|
+
instances = config_get('eigrp', 'router')
|
|
214
|
+
return {} if instances.nil?
|
|
215
|
+
hash = {}
|
|
216
|
+
instances.each do |name|
|
|
217
|
+
hash[name] = RouterEigrp.new(name, false)
|
|
218
|
+
end
|
|
219
|
+
return hash
|
|
220
|
+
rescue Cisco::CliError => e
|
|
221
|
+
# CLI will syntax reject when feature is not enabled
|
|
222
|
+
raise unless e.clierror =~ /Syntax error/
|
|
223
|
+
return {}
|
|
391
224
|
end
|
|
392
|
-
return hash
|
|
393
|
-
rescue Cisco::CliError => e
|
|
394
|
-
# cmd will syntax reject when feature is not enabled
|
|
395
|
-
raise unless e.clierror =~ /Syntax error/
|
|
396
|
-
return {}
|
|
397
|
-
end
|
|
398
225
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
226
|
+
def feature_enabled
|
|
227
|
+
feat = config_get('eigrp', 'feature')
|
|
228
|
+
return !(feat.nil? || feat.empty?)
|
|
229
|
+
rescue Cisco::CliError => e
|
|
230
|
+
# This cmd will syntax reject if feature is not
|
|
231
|
+
# enabled. Just catch the reject and return false.
|
|
232
|
+
return false if e.clierror =~ /Syntax error/
|
|
233
|
+
raise
|
|
234
|
+
end
|
|
408
235
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
236
|
+
def feature_enable
|
|
237
|
+
config_set('eigrp', 'feature', state: '')
|
|
238
|
+
end
|
|
412
239
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
240
|
+
def feature_disable
|
|
241
|
+
config_set('eigrp', 'feature', state: 'no')
|
|
242
|
+
end
|
|
416
243
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
244
|
+
# Enable feature and create router instance
|
|
245
|
+
def create
|
|
246
|
+
feature_enable unless feature_enabled
|
|
247
|
+
eigrp_router
|
|
248
|
+
end
|
|
422
249
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
250
|
+
# Destroy a router instance; disable feature on last instance
|
|
251
|
+
def destroy
|
|
252
|
+
ids = config_get('eigrp', 'router')
|
|
253
|
+
return if ids.nil?
|
|
254
|
+
if ids.size == 1
|
|
255
|
+
feature_disable
|
|
256
|
+
else
|
|
257
|
+
eigrp_router('no')
|
|
258
|
+
end
|
|
259
|
+
rescue Cisco::CliError => e
|
|
260
|
+
# CLI will syntax reject when feature is not enabled
|
|
261
|
+
raise unless e.clierror =~ /Syntax error/
|
|
431
262
|
end
|
|
432
|
-
rescue Cisco::CliError => e
|
|
433
|
-
# cmd will syntax reject when feature is not enabled
|
|
434
|
-
raise unless e.clierror =~ /Syntax error/
|
|
435
|
-
end
|
|
436
263
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
264
|
+
def eigrp_router(state='')
|
|
265
|
+
config_set('eigrp', 'router', name: @name, state: state)
|
|
266
|
+
end
|
|
440
267
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
268
|
+
# ----------
|
|
269
|
+
# PROPERTIES
|
|
270
|
+
# ----------
|
|
444
271
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
272
|
+
# Property methods for boolean property
|
|
273
|
+
def default_shutdown
|
|
274
|
+
config_get_default('eigrp', 'shutdown')
|
|
275
|
+
end
|
|
449
276
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
277
|
+
def shutdown
|
|
278
|
+
state = config_get('eigrp', 'shutdown', name: @name)
|
|
279
|
+
state ? true : false
|
|
280
|
+
end
|
|
454
281
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
282
|
+
def shutdown=(state)
|
|
283
|
+
state = (state ? '' : 'no')
|
|
284
|
+
config_set('eigrp', 'shutdown', name: @name, state: state)
|
|
285
|
+
end
|
|
459
286
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
287
|
+
# Property methods for integer property
|
|
288
|
+
def default_maximum_paths
|
|
289
|
+
config_get_default('eigrp', 'maximum_paths')
|
|
290
|
+
end
|
|
464
291
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
292
|
+
def maximum_paths
|
|
293
|
+
val = config_get('eigrp', 'maximum_paths', name: @name)
|
|
294
|
+
val.nil? ? default_maximum_paths : val.first.to_i
|
|
295
|
+
end
|
|
469
296
|
|
|
470
|
-
|
|
471
|
-
|
|
297
|
+
def maximum_paths=(val)
|
|
298
|
+
config_set('eigrp', 'maximum_paths', name: @name, val: val)
|
|
299
|
+
end
|
|
472
300
|
end
|
|
473
|
-
|
|
474
|
-
end
|
|
475
301
|
end
|
|
476
302
|
```
|
|
477
303
|
|
|
@@ -480,7 +306,7 @@ end
|
|
|
480
306
|
* Use `template-test_router.rb` to build the minitest for `router_eigrp.rb`:
|
|
481
307
|
|
|
482
308
|
```
|
|
483
|
-
cp docs/template-test_router.rb
|
|
309
|
+
cp docs/template-test_router.rb tests/test_router_eigrp.rb
|
|
484
310
|
```
|
|
485
311
|
* As with the API code, edit `test_router_eigrp.rb` and change the placeholder names as shown:
|
|
486
312
|
|
|
@@ -504,9 +330,6 @@ cp docs/template-test_router.rb cisco_network_node_utils/tests/test_router_eig
|
|
|
504
330
|
This is the completed `test_router_eigrp` minitest based on `template-test_router.rb`:
|
|
505
331
|
|
|
506
332
|
```ruby
|
|
507
|
-
#
|
|
508
|
-
# Minitest for RouterEigrp class
|
|
509
|
-
#
|
|
510
333
|
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
|
511
334
|
#
|
|
512
335
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -521,9 +344,10 @@ This is the completed `test_router_eigrp` minitest based on `template-test_route
|
|
|
521
344
|
# See the License for the specific language governing permissions and
|
|
522
345
|
# limitations under the License.
|
|
523
346
|
|
|
524
|
-
|
|
525
|
-
|
|
347
|
+
require_relative 'ciscotest'
|
|
348
|
+
require_relative '../lib/cisco_node_utils/router_eigrp'
|
|
526
349
|
|
|
350
|
+
# TestRouterEigrp - Minitest for RouterEigrp node utility class
|
|
527
351
|
class TestRouterEigrp < CiscoTestCase
|
|
528
352
|
def setup
|
|
529
353
|
# setup runs at the beginning of each test
|
|
@@ -539,75 +363,75 @@ class TestRouterEigrp < CiscoTestCase
|
|
|
539
363
|
|
|
540
364
|
def no_feature_eigrp
|
|
541
365
|
# Turn the feature off for a clean test.
|
|
542
|
-
|
|
543
|
-
node.cache_flush()
|
|
366
|
+
config('no feature eigrp')
|
|
544
367
|
end
|
|
545
368
|
|
|
546
369
|
# TESTS
|
|
547
370
|
|
|
548
371
|
def test_router_create_destroy_one
|
|
549
|
-
id =
|
|
372
|
+
id = 'blue'
|
|
550
373
|
rtr = RouterEigrp.new(id)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
374
|
+
@default_show_command = "show runn | i 'router eigrp #{id}'")
|
|
375
|
+
assert_show_match(pattern: /^router eigrp #{id}$/,
|
|
376
|
+
msg: "failed to create router eigrp #{id}")
|
|
554
377
|
|
|
555
378
|
rtr.destroy
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
"Error: failed to destroy router eigrp #{id}")
|
|
379
|
+
refute_show_match(pattern: /^router eigrp #{id}$/,
|
|
380
|
+
msg: "failed to destroy router eigrp #{id}")
|
|
559
381
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
382
|
+
refute_show_match(command: "show runn | i 'feature eigrp'",
|
|
383
|
+
pattern: /^feature eigrp$/,
|
|
384
|
+
msg: "failed to disable feature eigrp")
|
|
563
385
|
end
|
|
564
386
|
|
|
565
387
|
def test_router_create_destroy_multiple
|
|
566
|
-
id1 =
|
|
388
|
+
id1 = 'blue'
|
|
567
389
|
rtr1 = RouterEigrp.new(id1)
|
|
568
|
-
id2 =
|
|
390
|
+
id2 = 'red'
|
|
569
391
|
rtr2 = RouterEigrp.new(id2)
|
|
570
392
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
393
|
+
@default_show_command = "show runn | i 'router eigrp'"
|
|
394
|
+
|
|
395
|
+
assert_show_match(pattern: /^router eigrp #{id1}$/,
|
|
396
|
+
msg: "failed to create router eigrp #{id1}")
|
|
397
|
+
|
|
398
|
+
assert_show_match(pattern: /^router eigrp #{id2}$/,
|
|
399
|
+
msg: "failed to create router eigrp #{id2}")
|
|
574
400
|
|
|
575
401
|
rtr1.destroy
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
"Error: failed to destroy router eigrp #{id1}")
|
|
402
|
+
refute_show_match(pattern: /^router eigrp #{id1}$/,
|
|
403
|
+
msg: "failed to destroy router eigrp #{id1}")
|
|
579
404
|
|
|
580
405
|
rtr2.destroy
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
"Error: failed to destroy router eigrp #{id2}")
|
|
406
|
+
refute_show_match(pattern: /^router eigrp #{id2}$/,
|
|
407
|
+
msg: "failed to destroy router eigrp #{id2}")
|
|
584
408
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
409
|
+
refute_show_match(command: "show runn | i 'feature eigrp'",
|
|
410
|
+
pattern: /^feature eigrp$/,
|
|
411
|
+
msg: "failed to disable feature eigrp")
|
|
588
412
|
end
|
|
589
413
|
|
|
590
414
|
def test_router_maximum_paths
|
|
591
|
-
id =
|
|
415
|
+
id = 'blue'
|
|
592
416
|
rtr = RouterEigrp.new(id)
|
|
593
|
-
val = 5
|
|
417
|
+
val = 5 # This value depends on property bounds
|
|
594
418
|
rtr.maximum_paths = val
|
|
595
419
|
assert_equal(rtr.maximum_paths, val, "maximum_paths is not #{val}")
|
|
596
420
|
|
|
597
421
|
# Get default value from yaml
|
|
598
|
-
val = node.config_get_default(
|
|
422
|
+
val = node.config_get_default('eigrp', 'maximum_paths')
|
|
599
423
|
rtr.maximum_paths = val
|
|
600
424
|
assert_equal(rtr.maximum_paths, val, "maximum_paths is not #{val}")
|
|
601
425
|
end
|
|
602
426
|
|
|
603
427
|
def test_router_shutdown
|
|
604
|
-
id =
|
|
428
|
+
id = 'blue'
|
|
605
429
|
rtr = RouterEigrp.new(id)
|
|
606
430
|
rtr.shutdown = true
|
|
607
|
-
assert(rtr.shutdown,
|
|
431
|
+
assert(rtr.shutdown, 'shutdown state is not true')
|
|
608
432
|
|
|
609
433
|
rtr.shutdown = false
|
|
610
|
-
refute(rtr.shutdown,
|
|
434
|
+
refute(rtr.shutdown, 'shutdown state is not false')
|
|
611
435
|
end
|
|
612
436
|
end
|
|
613
437
|
```
|
|
@@ -615,22 +439,17 @@ end
|
|
|
615
439
|
Now run the test:
|
|
616
440
|
|
|
617
441
|
```bash
|
|
618
|
-
% ruby
|
|
442
|
+
% ruby test_router_eigrp.rb -v -- 192.168.0.1 admin admin
|
|
619
443
|
Run options: -v -- --seed 56593
|
|
620
444
|
|
|
621
|
-
# Running
|
|
445
|
+
# Running:
|
|
622
446
|
|
|
623
|
-
|
|
624
|
-
Ruby Version - 1.9.3
|
|
625
|
-
Node in CiscoTestCase Class: 192.168.0.1
|
|
626
|
-
Platform:
|
|
447
|
+
Node under test:
|
|
627
448
|
- name - my_n3k
|
|
628
449
|
- type - N3K-C3132Q-40GX
|
|
629
450
|
- image -
|
|
630
451
|
|
|
631
452
|
2.90 s = .
|
|
632
|
-
TestCase#test_placeholder = 0.92 s = .
|
|
633
|
-
TestRouterEigrp#test_placeholder = 0.97 s = .
|
|
634
453
|
TestRouterEigrp#test_router_create_destroy_multiple = 10.77 s = .
|
|
635
454
|
TestRouterEigrp#test_router_create_destroy_one = 6.14 s = .
|
|
636
455
|
TestRouterEigrp#test_router_maximum_paths = 9.41 s = .
|
|
@@ -639,19 +458,50 @@ TestRouterEigrp#test_router_shutdown = 6.40 s = .
|
|
|
639
458
|
|
|
640
459
|
Finished tests in 37.512356s, 0.1866 tests/s, 0.3199 assertions/s.
|
|
641
460
|
|
|
642
|
-
|
|
461
|
+
5 tests, 12 assertions, 0 failures, 0 errors, 0 skips
|
|
643
462
|
```
|
|
644
463
|
|
|
645
|
-
### <a name="comp_lint">Step 4. rubocop
|
|
464
|
+
### <a name="comp_lint">Step 4. rubocop: router eigrp</a>
|
|
646
465
|
|
|
647
|
-
rubocop is a Ruby static analysis tool. Run rubocop
|
|
466
|
+
rubocop is a Ruby static analysis tool. Run rubocop to validate the new code:
|
|
648
467
|
|
|
649
468
|
```bash
|
|
650
|
-
% rubocop
|
|
651
|
-
Inspecting
|
|
652
|
-
|
|
469
|
+
% rubocop lib/cisco_node_utils/router_eigrp.rb tests/test_router_eigrp.rb
|
|
470
|
+
Inspecting 2 file
|
|
471
|
+
..
|
|
472
|
+
|
|
473
|
+
2 file inspected, no offenses detected
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### <a name="comp_gem">Step 5. Build and Install the gem</a>
|
|
477
|
+
|
|
478
|
+
The final step is to build and install the gem that contains the new APIs.
|
|
479
|
+
|
|
480
|
+
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`:
|
|
653
481
|
|
|
654
|
-
|
|
482
|
+
```bash
|
|
483
|
+
git add lib/cisco_node_utils/router_eigrp.rb
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
From the root of the cisco-network-node-utils repository issue the following command.
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
% gem build cisco_node_utils.gemspec
|
|
490
|
+
Successfully built RubyGem
|
|
491
|
+
Name: cisco_node_utils
|
|
492
|
+
Version: 1.0.1
|
|
493
|
+
File: cisco_node_utils-1.0.1.gem
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Copy the new gem to your NX-OS device and then install it.
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
n9k#gem install --local /bootflash/cisco_node_utils-1.0.1.gem
|
|
500
|
+
Successfully installed cisco_node_utils-1.0.1
|
|
501
|
+
Parsing documentation for cisco_node_utils-1.0.1
|
|
502
|
+
Installing ri documentation for cisco_node_utils-1.0.1
|
|
503
|
+
Done installing documentation for cisco_node_utils after 2 seconds
|
|
504
|
+
1 gem installed
|
|
655
505
|
```
|
|
656
506
|
|
|
657
507
|
## Conclusion
|