diff --git a/.gitignore b/.gitignore index 8ca2b72f..8c97827e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ pkg/* vendor/* coverage Gemfile.lock -*.gem \ No newline at end of file +*.gem +*.swp diff --git a/CHANGELOG.md b/CHANGELOG.md index e78e3482..0267611e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,126 @@ Changelog ========= +## [v1.2.0] + +### New feature support +* ACL (platforms: Nexus 3k and Nexus 9k) + * acl (@saqibraza) + * ace (@yjyongz) + * remark ace (@bansalpradeep) +* EVPN (platforms: Nexus 3k and Nexus 9k) + * evpn_vni (@andish) +* Fabric Path (platforms: Nexus 7k) + * fabricpath_global (@dcheriancisco) + * fabricpath_topology (@dcheriancisco) +* Feature + * feature (@robert-w-gries) +* Interface (platforms: Nexus 3k, Nexus 5k, Nexus 6k, Nexus 7k and Nexus 9k) + * interface_channel_group (@chrisvanheuveln) + * interface_portchannel (@saichint) + * interface_service_vni (@chrisvanheuveln) +* PIM (platforms: Nexus 3k and Nexus 9k) + * pim (@smigopal) + * pim_group_list (@smigopal) + * pim_rp_address (@smigopal) +* Port Channel (platforms: Nexus 3k, Nexus 5k, Nexus 6k, Nexus 7k and Nexus 9k) + * interface_channel_group (@chrisvanheuveln) + * interface_portchannel (@saichint) + * portchannel_global (@saichint) +* SNMP (platforms: Nexus 3k, Nexus 5k, Nexus 6k, Nexus 7k and Nexus 9k) + * snmpnotification (@tphoney) +* VDC (platforms: Nexus 7k) + * vdc (@chrisvanheuveln) +* VPC (platforms: Nexus 3k, Nexus 5k, Nexus 6k, Nexus 7k and Nexus 9k) + * vpc (@dcheriancisco) +* VRF (platforms: Nexus 3k, Nexus 5k, Nexus 6k, Nexus 7k and Nexus 9k) + * vrf_af (@chrisvanheuveln) +* VXLAN (platforms: Nexus 9k) + * overlay_global (@alok-aggarwal) + * vxlan_vtep (@dcheriancisco) + * vxlan_vtep_vni (@mikewiebe) + + +### Additional platform support added to existing classes +#### Cisco Nexus 56xx, 60xx and 7xxx +* AAA + * aaa_authentication_login + * aaa_authentication_login_service + * aaa_authentication_service +* BGP + * bgp + * bgp_af + * bgp_af_neighobr + * bgp_neighbor_af +* COMMAND_CONFIG + * command_config (config_parser) +* DOMAIN + * dns_domain + * domain_name + * name_server +* INTERFACE + * interface +* NTP + * ntp_config + * ntp_server +* OSPF + * interface_ospf + * ospf + * ospf_vrf +* RADIUS + * radius_global +* SNMP + * snmp_community + * snmp_group + * snmp_notification_receiver + * snmp_server + * snmp_user +* SYSLOG + * syslog_server + * syslog_setting +* TACACS + * tacacs_server + * tacacs_server_group + * tacacs_server_host +* VLAN + * vlan + +### Added + +* `Cisco::UnsupportedError` exception class, raised when a command is explicitly marked as unsupported on a particular class of nodes. +* Extend bgp with attributes: + * `disable_policy_batching`, `disable_policy_batching_ipv4`, `disable_policy_batching_ipv6` + * `event_history_cli`, `event_history_detail`, `event_history_events`, `event_history_periodic` + * `fast_external_fallover` + * `flush_routes` + * `isolate` + * `neighbor_down_fib_accelerate` + * `route_distinguisher` +* Extend bgp_af with attributes: + * `default_metric` + * `distance_ebgp`, `distance_ibgp`, `distance_local` + * `inject_map` + * `suppress_inactive` + * `table_map` +* Extend interface with attributes: + * `fabric_forwarding_anycast_gateway` + * `ipv4_acl_in`, `ipv4_acl_out`, `ipv6_acl_in`, `ipv6_acl_out` + * `ipv4_address_secondary`, `ipv4_arp_timeout` + * `vlan_mapping` + * `vpc_id`, `vpc_peer_link` + * switchport mode `fabricpath` +* Extend vrf with attributes: + * `vni` +* Extend vlan with attribute: + * `mode` + +### Changed + +* Major refactor and enhancement of `CommandReference` YAML files: + - Added support for `auto_default`, `default_only`, `kind`, and `multiple` + - Added filtering by product ID (`/N7K/`) and by client type (`cli_nexus`) + - `CommandReference` methods that do key-value style wildcard substitution now raise an `ArgumentError` if the result is empty (because not enough parameters were supplied). + ## [v1.1.0] ### New feature support @@ -15,10 +135,12 @@ Changelog * RADIUS * radius_global (@jonnytpuppet) * radius_server (@jonnytpuppet) +* SNMP + * snmp_notification_receiver (@jonnytpuppet) * SYSLOG * syslog_server (@jonnytpuppet) * syslog_setting (@jonnytpuppet) -* Miscellaneous +* Miscellaneous * dns_domain (@hunner) * domain_name (@bmjen) * name_server (@hunner) @@ -43,6 +165,8 @@ Changelog * Added `config` and `(assert|refute)_show_match` helper methods for testing. * Added `bin/check_metric_limits.rb` helper script in support of refactoring. * Added best practices development guide. +* Added support for radius_global (@jonnytpuppet) +* Added support for radius_server_group (@jonnytpuppet) ### Fixed @@ -92,6 +216,7 @@ Changelog [git-flow]: https://github.com/petervanderdoes/gitflow-avh [SimpleCov]: https://github.com/colszowka/simplecov +[v1.2.0]: https://github.com/cisco/cisco-network-node-utils/compare/v1.1.0...v1.2.0 [v1.1.0]: https://github.com/cisco/cisco-network-node-utils/compare/v1.0.1...v1.1.0 [v1.0.1]: https://github.com/cisco/cisco-network-node-utils/compare/v1.0.0...v1.0.1 [v1.0.0]: https://github.com/cisco/cisco-network-node-utils/compare/v0.9.0...v1.0.0 diff --git a/README.md b/README.md index 6c6ef36e..441d0ed9 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,23 @@ Please see [Learning Resources](#resources) for additional references. The CiscoNodeUtils gem provides utilities for management of Cisco network nodes. It is designed to work with Puppet and Chef as well as other -open source management tools. This release supports Cisco NX-OS nodes -running NX-OS 7.0(3)I2(1) and later. +open source management tools. -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 . 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) +This CiscoNodeUtils gem release supports the following: + +Platform | OS | OS Version | +-----------------|-------|----------------------| +Cisco Nexus 30xx | NX-OS | 7.0(3)I2(1) and later +Cisco Nexus 31xx | NX-OS | 7.0(3)I2(1) and later +Cisco Nexus 93xx | NX-OS | 7.0(3)I2(1) and later +Cisco Nexus 95xx | NX-OS | 7.0(3)I2(1) and later +Cisco N9kv | NX-OS | 7.0(3)I2(1) and later +Cisco Nexus 56xx | NX-OS | 7.3(0)N1(1) and later +Cisco Nexus 60xx | NX-OS | 7.3(0)N1(1) and later +Cisco Nexus 7xxx | NX-OS | 7.3(0)D1(1) and later + + +Please note: For Cisco Nexus 3k and 9k platforms, 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 . 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) ## Installation @@ -97,15 +110,9 @@ network node. It provides the base APIs `config_set`, `config_get`, and ### CommandReference -The `CommandReference` module provides for the abstraction of NX-OS CLI, -especially to handle its variance between hardware platforms. -A series of YAML files are used to describe the CLI corresponding to a given -`(feature, attribute)` tuple for any given platform. When a `Node` is -connected, the platform identification of the Node is used to construct a -`CmdRef` object that corresponds to this platform. The `Node` APIs -`config_set`, `config_get`, and `config_get_default` all rely on the `CmdRef`. +The `CommandReference` class abstracts away the differences between various supported `Node` types, be that API differences (CLI vs. YANG), platform differences (NX-OS vs. IOS XR), or hardware differences (Nexus 9xxx vs. Nexus 3xxx). A series of YAML files describe various `feature` groupings. Each file describes a set of `attributes` of the given feature and the specifics of how to inspect and manage these attributes for any supported `Node` types. When a `Node` is connected, the platform identification of the Node is used to construct a `CommandReference` instance containing a set of `CmdRef` objects specific to this `Node`. The `Node` APIs `config_set`, `config_get`, and `config_get_default` all rely on the `CmdRef`. -See also [README_YAML](lib/cisco_node_utils/README_YAML.md). +See also [README_YAML](lib/cisco_node_utils/cmd_ref/README_YAML.md). ### Feature Providers @@ -161,7 +168,7 @@ See [CHANGELOG](CHANGELOG.md) for a list of changes. ## License Information -Copyright (c) 2013-2015 Cisco and/or its affiliates. +Copyright (c) 2013-2016 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. diff --git a/Rakefile b/Rakefile index b21b4b99..c498200a 100644 --- a/Rakefile +++ b/Rakefile @@ -18,4 +18,5 @@ Rake::TestTask.new do |t| t.pattern = 'tests/test_*.rb' t.warning = true t.verbose = true + t.options = '-v' end diff --git a/bin/git/hooks/commit-msg/enforce_style b/bin/git/hooks/commit-msg/enforce_style index 8bbd8d5a..42b757cd 100755 --- a/bin/git/hooks/commit-msg/enforce_style +++ b/bin/git/hooks/commit-msg/enforce_style @@ -76,6 +76,14 @@ if $errors > 0 # rubocop:disable Style/GlobalVars puts '{' puts message_lines.join("\n") puts '}' + + fd = IO.sysopen('/dev/tty', 'w+') + a = IO.new(fd, 'w+') + a.puts 'Continue anyway? [y/N] ' + response = a.gets.chomp + # rubocop:disable Style/GlobalVars + $errors = 0 if response.downcase[0] == 'y' + # rubocop:enable Style/GlobalVars end exit $errors # rubocop:disable Style/GlobalVars diff --git a/cisco_node_utils.gemspec b/cisco_node_utils.gemspec index 37c256e4..9aa42605 100644 --- a/cisco_node_utils.gemspec +++ b/cisco_node_utils.gemspec @@ -7,7 +7,8 @@ Gem::Specification.new do |spec| spec.name = 'cisco_node_utils' spec.version = CiscoNodeUtils::VERSION spec.authors = ['Alex Hunsberger', 'Glenn Matthews', - 'Chris Van Heuveln', 'Mike Wiebe', 'Jie Yang'] + 'Chris Van Heuveln', 'Mike Wiebe', 'Jie Yang', + 'Rob Gries'] spec.email = 'cisco_agent_gem@cisco.com' spec.summary = 'Utilities for management of Cisco network nodes' spec.description = <<-EOF @@ -30,7 +31,7 @@ Currently supports NX-OS nodes. spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'bundler', '~> 1.7' spec.add_development_dependency 'rake', '~> 10.0' - spec.add_development_dependency 'rubocop', '= 0.34.2' + spec.add_development_dependency 'rubocop', '= 0.35.1' spec.add_development_dependency 'simplecov', '~> 0.9' - spec.add_runtime_dependency 'cisco_nxapi', '~> 1.0' + spec.add_runtime_dependency 'cisco_nxapi', '~> 1.0', '>= 1.0.1' end diff --git a/docs/README-develop-best-practices.md b/docs/README-develop-best-practices.md index 0853d6de..a09fc521 100644 --- a/docs/README-develop-best-practices.md +++ b/docs/README-develop-best-practices.md @@ -12,14 +12,16 @@ This document is intended to assist in developing cisco_node_utils API's that ar ## YAML Development Best Practices -* [Y1](#yaml1): All yaml feature entries should be kept in alphabetical order. -* [Y2](#yaml2): Use *regexp* anchors where needed for `config_get` and `config_get_token` entries. -* Y3: Avoid nested optional matches. -* [Y4](#yaml4): Use the `_template` feature when getting/setting the same property value at multiple levels. -* [Y5](#yaml5): When possible include a `default_value` that represents the system default value. -* [Y6](#yaml6): When possible, use the same `config_get` show command for all properties and document any anomalies. -* [Y7](#yaml7): Use Key-value wildcards instead of Printf-style wildcards. -* [Y8](#yaml8): Selection of `show` commands for `config_get`. +* [Y1](#yaml1): One feature per YAML file +* [Y2](#yaml2): All attribute entries must be kept in alphabetical order. +* [Y3](#yaml3): Use *regexp* anchors where needed for `config_get` and `config_get_token` entries. +* [Y4](#yaml4): Avoid nested optional matches. +* [Y5](#yaml5): Use the `_template` feature when getting/setting the same property value at multiple levels. +* [Y6](#yaml6): When possible include a `default_value` that represents the system default value. +* [Y7](#yaml7): When possible, use the same `config_get` show command for all properties and document any anomalies. +* [Y8](#yaml8): Use Key-value wildcards instead of Printf-style wildcards. +* [Y9](#yaml9): Selection of `show` commands for `config_get`. +* [Y10](#yaml10): Use `true` and `false` for boolean values. @@ -43,132 +45,145 @@ This document is intended to assist in developing cisco_node_utils API's that ar ## YAML Best Practices: -### Y1: All yaml feature entries should be kept in alphabetical order. +### Y1: One feature per YAML file -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. +Each YAML file should define a single 'feature' (a closely related set of configuration properties). Don't create "one YAML file to rule them all". -Top level features in alpabetical order: +### Y2: All attribute entries must be kept in alphabetical order. -``` -aaa_authentication_login: -... -dnsclient: -... -interface: -``` - -Options under a feature: +All attribute entries in a given YAML file must be kept in alphabetical order. 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. -``` -interface: - access_vlan: - ... - all_interfaces: - ... - create: - ... - description: - ... -``` +This rule is enforced by the `Cisco::CommandReference` class itself - it will raise an exception if it detects any out-of-order entries. -### Y2: Use *regexp* anchors where needed for `config_get` and `config_get_token` entries. +### Y3: Use *regexp* anchors where needed for `config_get` and `config_get_token` entries. Please use *regexp* anchors `^$` to ensure you match the correct feature information in the `show` output. +```yaml +# syslog_settings.yaml +timestamp: + config_get: "show running-config all | include '^logging timestamp'" + config_get_token: '/^logging timestamp (.*)$/' + config_set: ' logging timestamp ' + default_value: 'seconds' ``` -syslog_settings: - timestamp: - config_get: "show running-config all | include '^logging timestamp'" - config_get_token: '/^logging timestamp (.*)$/' - config_set: ' logging timestamp ' - default_value: 'seconds' + +### Y4: Avoid nested optional matches. + +Regexps containing optional match strings inside other match strings become +complex to work with and difficult to maintain. + +One case where this may crop up is in trying to match both affirmative and +negative variants of a config command: + +```yaml +config_get_token: ['/^interface $/i', '/^((no )?switchport)$/'] + +config_get_token: '/^(no)? ?ip tacacs source-interface ?(\S+)?$/' ``` -### Y3: Avoid nested optional matches. +Instead, match the affirmative form of a command and treat its absence as +confirmation of the negative form: -### Y4: Use the `_template` feature when getting/setting the same property value at multiple levels. +```yaml +config_get_token: ['/^interface $/i', '/^switchport$/'] + +config_get_token: '/^tacacs-server source-interface (\S+)$/' +``` + +### Y5: Use the `_template` feature when getting/setting the same property value at multiple levels. Using the template below, `auto_cost` and `default_metric` can be set under `router ospf foo` and `router ospf foo; vrf blue`. -``` -ospf: - _template: - config_get: "show running ospf all" - config_get_token: '/^router ospf $/' - config_get_token_append: - - '/^vrf $/' - config_set: "router ospf " - config_set_append: - - "vrf " - - auto_cost: - config_get_token_append: '/^auto-cost reference-bandwidth (\d+)\s*(\S+)?$/' - config_set_append: "auto-cost reference-bandwidth " - default_value: [40, "Gbps"] - - default_metric: - config_get_token_append: '/^default-metric (\d+)?$/' - config_set_append: " default-metric " - default_value: 0 +```yaml +# ospf.yaml +_template: + config_get: "show running ospf all" + config_get_token: '/^router ospf $/' + config_get_token_append: + - '/^vrf $/' + config_set: "router ospf " + config_set_append: + - "vrf " + +auto_cost: + config_get_token_append: '/^auto-cost reference-bandwidth (\d+)\s*(\S+)?$/' + config_set_append: "auto-cost reference-bandwidth " + default_value: [40, "Gbps"] + +default_metric: + config_get_token_append: '/^default-metric (\d+)?$/' + config_set_append: " default-metric " + default_value: 0 ``` -### Y5: When possible include a `default_value` that represents the system default value. +### Y6: When possible include a `default_value` that represents the system default value. 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. Default value for `message_digest_alg_type` is `md5` -``` +```yaml message_digest_alg_type: - config_get: 'show running interface all' - config_get_token: ['/^interface %s$/i', '/^\s*ip ospf message-digest-key \d+ (\S+)/'] - default_value: 'md5' + config_get: 'show running interface all' + config_get_token: ['/^interface $/i', '/^\s*ip ospf message-digest-key \d+ (\S+)/'] + default_value: 'md5' ``` **NOTE1: Use strings rather then symbols when applicable**. -If the `default_value` differs between cisco platforms, the more specific `command_reference_[platform].yaml` file should be used. +If the `default_value` differs between cisco platforms, use per-API or per-platform keys in the YAML as needed. For example, if the default value on all platforms except the N9K is `md5` then you might do something like this: + +```yaml +message_digest_alg_type: + config_get: 'show running interface all' + config_get_token: ['/^interface $/i', '/^\s*ip ospf message-digest-key \d+ (\S+)/'] + /N9K/: + default_value: 'sha2' + else: + default_value: 'md5' +``` -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`. +See [README_YAML](../lib/cisco_node_utils/cmd_ref/README_YAML.md) for more details about this advanced feature. -### Y6: When possible, use the same `config_get` show command for all properties and document any anomalies. +### Y7: When possible, use the same `config_get` show command for all properties and document any anomalies. All properties below use the `show run tacacs all` command except `directed_request` which is documented. -``` -tacacs_server: - deadtime: - config_get: "show run tacacs all" - config_get_token: '/^tacacs-server deadtime\s+(\d+)/' - config_set: "%s tacacs-server deadtime %d" - default_value: 0 - - directed_request: - # oddly, directed request must be retrieved from aaa output - config_get: "show running aaa all" - config_get_token: '/(?:no)?\s*tacacs-server directed-request/' - config_set: "%s tacacs-server directed-request" - default_value: false - - encryption_type: - config_get: "show run tacacs all" - config_get_token: '/^tacacs-server key (\d+)\s+(\S+)/' - default_value: 0 - - encryption_password: - config_get: "show run tacacs all" - config_get_token: '/^tacacs-server key (\d+)\s+(\S+)/' - default_value: "" +```yaml +# tacacs_server.yaml +deadtime: + config_get: "show run tacacs all" + config_get_token: '/^tacacs-server deadtime\s+(\d+)/' + config_set: " tacacs-server deadtime Y7: Use Key-value wildcards instead of Printf-style wildcards. +### Y8: Use Key-value wildcards instead of Printf-style wildcards. -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. +Key-value wildcards are moderately more complex to implement than Printf-style wildcards but they are more readable in the Ruby code and are flexible enough to handle significant platform differences in CLI. Key-value wildcards are therefore the recommended approach for new development. **Key-value wildcards** -``` +```yaml config_set_append: " log-adjacency-changes " ``` @@ -176,17 +191,20 @@ This following approach is quick to implement and concise, but less flexible - i **Printf-style wildcards** -``` +```yaml config_set_append: "%s log-adjacency-changes %s" ``` -### Y8: Selection of `show` commands for `config_get`. +### Y9: Selection of `show` commands for `config_get`. The following commands should be preferred over `show [feature]` commands since not all `show [feature]` commands behave in the same manner across cisco platforms. * `show running [feature] all` if available. * `show running all` if `show running [feature] all` is *not* available. +### Y10: Use `true` and `false` for boolean values. + +YAML allows various synonyms for `true` and `false` such as `yes` and `no`, but for consistency and readability (especially to users more familiar with Ruby than with YAML), we recommend using `true` and `false` rather than any of their synonyms. ## Common Object Best Practices: @@ -196,7 +214,7 @@ Many cisco features can be configured under the default or global vrf and also u The following `initialize` and `self.vrfs` methods account for configuration under `default` and `non-default vrfs`. -``` +```ruby def initialize(router, name, instantiate=true) fail TypeError if router.nil? fail TypeError if name.nil? @@ -237,9 +255,9 @@ The following `initialize` and `self.vrfs` methods account for configuration und Having this logic defined in the common object lets the minitest easily check the specific instances. -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. +The built-in equality operator `==` returns true only if they are the same instance object. The `==` method below is used to override the built-in equality operator and return true even if they are different objects referring to the same configuration on the node. -``` +```ruby def ==(other) (name == other.name) && (vrf == other.vrf) end @@ -247,7 +265,7 @@ Without this equality operator `==` only passes if they are the same instance ob Example Usage: -``` +```ruby def test_dnsdomain_create_destroy_multiple id1 = 'aoeu.com' id2 = 'asdf.com' @@ -272,7 +290,7 @@ Example Usage: Our convention is to let `''` represent 'not configured at all' rather than `nil`. For example, `interface.rb`: -``` +```ruby def vrf vrf = config_get('interface', 'vrf', @name) return '' if vrf.nil? @@ -290,7 +308,7 @@ def vrf=(vrf) However, if a property has a default value (it is never truly 'removed'), then we should do this instead: -``` +```ruby def access_vlan vlan = config_get('interface', 'access_vlan', @name) return default_access_vlan if vlan.nil? @@ -307,7 +325,7 @@ In order to have a complete set of api's for each property it is important that This can be seen in the following `router_id` property. -``` +```ruby # Getter Method def router_id match = config_get('ospf', 'router_id', @get_args) @@ -362,7 +380,7 @@ The more specific assertions also produce more helpful failure messages if somet Rather then hardcode an interface name that may or may not exist, instead use the `interfaces[]` array. -``` +```ruby def create_interface(ifname=interfaces[0]) @default_show_command = show_cmd(ifname) Interface.new(ifname) @@ -375,11 +393,11 @@ If additional interfaces are needed array index `1` and `2` may be used. For conveninence the `config` helper method has been provided for device configuration within the minitests. -``` +```ruby config('no feature ospf') ``` -``` +```ruby config('feature ospf'; 'router ospf green') ``` @@ -387,7 +405,7 @@ config('feature ospf'; 'router ospf green') 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. -``` +```ruby assert_show_match(command: 'show run all | no-more', pattern: /interface port-channel 1/, msg: 'port-channel is not present but it should be') @@ -395,7 +413,7 @@ assert_show_match(command: 'show run all | no-more', 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: -``` +```ruby @default_show_command = 'show run interface all | include "interface" | no-more' assert_output_match(pattern: /interface port-channel 10/) refute_output_match(pattern: /interface port-channel 11/) diff --git a/docs/README-develop-node-utils-APIs.md b/docs/README-develop-node-utils-APIs.md index 468ccab7..6dfce3b8 100644 --- a/docs/README-develop-node-utils-APIs.md +++ b/docs/README-develop-node-utils-APIs.md @@ -102,20 +102,23 @@ Example: ### Step 1. YAML Definitions: router eigrp -The new API for `router eigrp` will need some basic YAML definitions. +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: -`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/cmd_ref/eigrp.yaml` -`lib/cisco_node_utils/command_reference_common.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 -Four basic command_reference parameters will be defined for each resource property: +The following basic command_reference parameters will be defined for each resource property: 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. 2. `config_get_token:` A regexp pattern for extracting state values from the config_get output. 3. `config_set:` The NX-OS CLI configuration command(s) used to set the property configuration. May contain wildcards for variable parameters. 4. `default_value:` This is typically the "factory" default state of the property, expressed as an actual value (true, 12, "off", etc) + 5. `kind:` The data type of this property. If omitted, the property will be a string by default. Commonly used values for this property are `int` and `boolean`. + 6. `multiple:` By default a property is assumed to be found once or not at all by the `config_get`/`config_get_token` lookup, and an error will be raised if multiple matches are found. If multiple matches are valid and expected, you must set `multiple: true` for this property. -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. +There are additional YAML command parameters available which are not covered by this document. Please see the [README_YAML.md](../lib/cisco_node_utils/cmd_ref/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.* @@ -124,33 +127,40 @@ The properties in this example require additional context for their config_get_t *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.* + ```yaml -eigrp: - feature: - # feature eigrp must be enabled before configuring router eigrp - config_get: 'show running eigrp all' - config_get_token: '/^feature eigrp$/' - config_set: ' 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: ' router eigrp ' # config to add or remove - - maximum_paths: - # This is an integer property - config_get: 'show running eigrp all' - config_get_token: ['/^router eigrp $/', '/^maximum-paths (\d+)/'] - config_set: ['router eigrp ', 'maximum-paths '] - default_value: 8 - - shutdown: - # This is a boolean property - config_get: 'show running eigrp all' - config_get_token: ['/^router eigrp $/', '/^shutdown$/'] - config_set: ['router eigrp ', ' shutdown'] - default_value: false +# eigrp.yaml +--- +feature: + # feature eigrp must be enabled before configuring router eigrp + kind: boolean + config_get: 'show running eigrp all' + config_get_token: '/^feature eigrp$/' + config_set: ' feature eigrp' + +maximum_paths: + # This is an integer property + kind: int + config_get: 'show running eigrp all' + config_get_token: ['/^router eigrp $/', '/^maximum-paths (\d+)/'] + config_set: ['router eigrp ', 'maximum-paths '] + default_value: 8 + +router: + # There can be multiple eigrp instances + multiple: true + config_get: 'show running eigrp all' # all eigrp-related configs + config_get_token: '/^router eigrp (\S+)$/' # Match instance name + config_set: ' router eigrp ' # config to add or remove + +shutdown: + # This is a boolean property + kind: boolean + config_get: 'show running eigrp all' + config_get_token: ['/^router eigrp $/', '/^shutdown$/'] + config_set: ['router eigrp ', ' shutdown'] + default_value: false ``` ### Step 2. cisco_node_utils API: router eigrp @@ -224,8 +234,7 @@ module Cisco end def feature_enabled - feat = config_get('eigrp', 'feature') - return !(feat.nil? || feat.empty?) + 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. @@ -275,8 +284,7 @@ module Cisco end def shutdown - state = config_get('eigrp', 'shutdown', name: @name) - state ? true : false + config_get('eigrp', 'shutdown', name: @name) end def shutdown=(state) @@ -290,8 +298,7 @@ module Cisco end def maximum_paths - val = config_get('eigrp', 'maximum_paths', name: @name) - val.nil? ? default_maximum_paths : val.first.to_i + config_get('eigrp', 'maximum_paths', name: @name) end def maximum_paths=(val) @@ -477,10 +484,11 @@ Inspecting 2 file 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`: +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`: ```bash -git add lib/cisco_node_utils/router_eigrp.rb +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. diff --git a/docs/template-router.rb b/docs/template-router.rb index bf938d88..751ebdfe 100644 --- a/docs/template-router.rb +++ b/docs/template-router.rb @@ -43,8 +43,7 @@ def self.routers end def feature_enabled - feat = config_get('X__RESOURCE_NAME__X', 'feature') - return !(feat.nil? || feat.empty?) + config_get('X__RESOURCE_NAME__X', 'feature') rescue Cisco::CliError => e # This cmd will syntax reject if feature is not # enabled. Just catch the reject and return false. @@ -94,9 +93,7 @@ def default_X__PROPERTY_BOOL__X end def X__PROPERTY_BOOL__X - state = config_get('X__RESOURCE_NAME__X', 'X__PROPERTY_BOOL__X', - name: @name) - state ? true : false + config_get('X__RESOURCE_NAME__X', 'X__PROPERTY_BOOL__X', name: @name) end def X__PROPERTY_BOOL__X=(state) @@ -111,8 +108,7 @@ def default_X__PROPERTY_INT__X end def X__PROPERTY_INT__X - val = config_get('X__RESOURCE_NAME__X', 'X__PROPERTY_INT__X', name: @name) - val.nil? ? default_X__PROPERTY_INT__X : val.first.to_i + config_get('X__RESOURCE_NAME__X', 'X__PROPERTY_INT__X', name: @name) end def X__PROPERTY_INT__X=(val) diff --git a/lib/.rubocop.yml b/lib/.rubocop.yml index 3db35fb8..30cfd013 100644 --- a/lib/.rubocop.yml +++ b/lib/.rubocop.yml @@ -3,16 +3,16 @@ inherit_from: ../.rubocop.yml # Baseline code complexity metrics for the lib/ subdirectory: Metrics/AbcSize: - Max: 47 + Max: 45 Metrics/CyclomaticComplexity: - Max: 17 + Max: 23 Metrics/MethodLength: - Max: 39 + Max: 48 Metrics/ParameterLists: Max: 9 Metrics/PerceivedComplexity: - Max: 19 + Max: 24 diff --git a/lib/cisco_node_utils.rb b/lib/cisco_node_utils.rb index cee6aa8e..95812815 100644 --- a/lib/cisco_node_utils.rb +++ b/lib/cisco_node_utils.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2015 Cisco and/or its affiliates. +# Copyright (c) 2014-2016 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. diff --git a/lib/cisco_node_utils/README_YAML.md b/lib/cisco_node_utils/README_YAML.md deleted file mode 100644 index f88a404c..00000000 --- a/lib/cisco_node_utils/README_YAML.md +++ /dev/null @@ -1,325 +0,0 @@ -# Command Reference YAML - -The `command_reference_*.yaml` files in this directory are used with the -`CommandReference` module as a way to abstract away platform CLI differences. - -This document describes the structure and semantics of these files. - -## Structure - -```yaml -FEATURE_1: - _template: - # base parameters for all attributes of this feature go here - - ATTRIBUTE_1: - config_get: 'string' - config_get_token: 'string' - config_get_token_append: 'string' - config_set: 'string' - config_set_append: 'string' - default_value: string, boolean, integer, or constant - test_config_get: 'string' - test_config_get_regexp: '/regexp/' - test_config_result: - input_1: output_1 - input_2: output_2 - - ATTRIBUTE_2: - ... - - ATTRIBUTE_3: - ... - -FEATURE_2: - ... -``` - -All parameters are optional and may be omitted if not needed. - -### Wildcard substitution - -The `config_get_token` and `config_set` (and their associated `_append` -variants) all support two forms of wildcarding - printf-style and key-value. - -#### Printf-style wildcards - -```yaml -tacacs_server_host: - encryption: - config_set: '%s tacacs-server host %s key %s %s' -``` - -This permits parameter values to be passed as a simple sequence: - -```ruby -config_set('tacacs_server_host', 'encryption', 'no', 'user', 'md5', 'password') - -# this becomes 'no tacacs-server host user key md5 password' -# ^^ ^^^^ ^^^ ^^^^^^^^ -``` - -This 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. - -#### Key-value wildcards - -```yaml -ospf: - auto_cost: - config_set: ['router ospf ', 'auto-cost reference-bandwidth '] -``` - -This requires parameter values to be passed as a hash: - -```ruby -config_set('ospf', 'auto_cost', {:name => 'red', - :cost => '40', - :type => 'Gbps'}) - -# this becomes the config sequence: -# router ospf red -# auto-cost reference-bandwidth 40 Gbps -``` - -This 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. - -### `_template` - -The optional `_template` section can be used to define base parameters for all -attributes of a given feature. For example, all interface attributes might be -checked with the `show running-config interface all` command, and all -attributes might be set by first entering the interface configuration submode -with the `interface ` configuration command. Thus, you might have: - -```yaml -interface: - _template: - config_get: 'show running-config interface all' - config_get_token: '/^interface $/' - config_set: 'interface ' - - access_vlan: - config_get_token_append: '/^switchport access vlan (.*)$/' - config_set_append: 'switchport access vlan ' - - description: - config_get_token_append: '/^description (.*)$/' - config_set_append: 'description ' - - ... -``` - -instead of the more repetitive (but equally valid): - -```yaml -interface: - access_vlan: - config_get: 'show running interface all' - config_get_token: ['/^interface %s$/i', '/^switchport access vlan (.*)$/'] - config_set: ['interface %s', 'switchport access vlan %s'] - - description: - config_get: 'show running-config interface all' - config_get_token: ['/^interface $/i', '/^description (.*)$/'] - config_set: ['interface ', 'description '] - - ... -``` - -### `config_get` - -`config_get` must be a single string representing the CLI command (usually a -`show` command) to be used to display the information needed to get the -current value of this attribute. - -```yaml - config_get: 'show running-config interface all' -``` - -### `config_get_token` - -`config_get_token` can be a single string, a single regex, an array of strings, -or an array of regexs. - -If this value is a string or array of strings, then the `config_get` command -will be executed to produce _structured_ output and the string(s) will be -used as lookup keys. - -```yaml -show_version - cpu: - config_get: 'show version' - config_get_token: 'cpu_name' - # config_get('show_version', 'cpu') returns structured_output['cpu_name'] -``` - -```yaml -inventory: - productid: - config_get: 'show inventory' - config_get_token: ['TABLE_inv', 'ROW_inv', 0, 'productid'] - # config_get('inventory', 'productid') returns - # structured_output['TABLE_inv']['ROW_inv'][0]['productid'] -``` - -If this value is a regex or array or regexs, then the `config_get` command -will be executed to produce _plaintext_ output. - -For a single regex, it will be used to match against the plaintext. - -```yaml -memory: - total: - config_get: 'show system resources' - config_get_token: '/Memory.* (\S+) total/' - # config_get('memory', 'total') returns - # plaintext_output.scan(/Memory.* (\S+) total/) -``` - -For an array of regex, then the plaintext is assumed to be hierarchical in -nature (like `show running-config`) and the regexs are used to filter down -through the hierarchy. - -```yaml -interface: - description: - config_get: 'show running interface all' - config_get_token: ['/^interface %s$/i', '/^description (.*)/'] - # config_get('interface', 'description', 'Ethernet1/1') gets the plaintext - # output, finds the subsection under /^interface Ethernet1/1$/i, then finds - # the line matching /^description (.*)$/ in that subsection -``` - -### `config_get_token_append` - -When using a `_template` section, an attribute can use -`config_get_token_append` to extend the `config_get_token` value provided by -the template instead of replacing it: - -```yaml -interface: - _template: - config_get: 'show running-config interface all' - config_get_token: '/^interface $/' - - description: - config_get_token_append: '/^description (.*)$/' - # config_get_token value for 'description' is now: - # ['/^interface %s$/i', '/^description (.*)$/'] -``` - -This can also be used to specify conditional tokens which may or may not be -used depending on the set of parameters passed into `config_get()`: - -```yaml -bgp: - _template: - config_get: 'show running bgp all' - config_get_token: '/^router bgp $/' - config_get_token_append: - - '/^vrf $/' - - router_id: - config_get_token_append: '/^router-id (\S+)$/' -``` - -In this example, both `config_get('bgp', 'router_id', {:asnum => '1'})` and -`config_get('bgp', 'router_id', {:asnum => '1', :vrf => 'red'})` are valid - -the former will match 'router bgp 1' followed by 'router-id', while the latter -will match 'router bgp 1' followed by 'vrf red' followed by 'router-id'. - -### `config_set` - -The `config_set` parameter is a string or array of strings representing the -configuration CLI command(s) used to set the value of the attribute. - -```yaml -interface: - create: - config_set: 'interface ' - - description: - config_set: ['interface ', 'description '] -``` - -### `config_set_append` - -When using a `_template` section, an attribute can use `config_set_append` to -extend the `config_set` value provided by the template instead of replacing it: - -```yaml -interface: - _template: - config_set: 'interface ' - - access_vlan: - config_set_append: 'switchport access vlan ' - # config_set value for 'access_vlan' is now: - # ['interface ', 'switchport access vlan '] -``` - -Much like `config_get_token_append`, this can also be used to specify optional -commands that can be included or omitted as needed: - -```yaml -bgp: - _template: - config_set: 'router bgp ' - config_set_append: - - 'vrf ' -``` - -### `default_value` - -If there is a default value for this attribute when not otherwise specified by -the user, the `default_value` parameter describes it. This can be a string, -boolean, integer, or array. - -```yaml -interface: - description: - default_value: '' - -interface_ospf: - hello_interval: - default_value: 10 - -ospf: - auto_cost: - default_value: [40, 'Gbps'] -``` - -### `test_config_get` and `test_config_get_regex` - -Test-only equivalents to `config_get` and `config_get_token` - a show command -to be executed over telnet by the minitest unit test scripts, and a regex -(or array thereof) to match in the resulting plaintext output. -Should only be referenced by test scripts, never by a feature provider itself. - -```yaml -show_version: - boot_image: - test_config_get: 'show version | no-more' - test_config_get_regex: '/NXOS image file is: (.*)$/' -``` - -### `test_config_result` - -Test-only container for input-result pairs that might differ by platform. -Should only be referenced by test scripts, never by a feature provider itself. - -```yaml -vtp: - version: - test_config_result: - 3: 'Cisco::CliError' -``` - -## Style Guide - -Please see [YAML Best Practices](../../docs/README-develop-best-practices.md#ydbp). \ No newline at end of file diff --git a/lib/cisco_node_utils/aaa_authentication_login.rb b/lib/cisco_node_utils/aaa_authentication_login.rb new file mode 100644 index 00000000..125aed74 --- /dev/null +++ b/lib/cisco_node_utils/aaa_authentication_login.rb @@ -0,0 +1,96 @@ +# +# NXAPI implementation of AaaAuthenticationLogin class +# +# April 2015, Alex Hunsberger +# +# Copyright (c) 2015-2016 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 + # NXAPI implementation of AAA Authentication Login class + class AaaAuthenticationLogin < NodeUtil + # rubocop:disable DoubleNegation + # There is no "feature aaa" or "aaa new-model" on nxos, and only one + # instance which is always available + def self.ascii_authentication + !!config_get('aaa_authentication_login', 'ascii_authentication') + end + + def self.ascii_authentication=(val) + no_cmd = val ? '' : 'no' + config_set('aaa_authentication_login', + 'ascii_authentication', no_cmd) + end + + def self.default_ascii_authentication + config_get_default('aaa_authentication_login', + 'ascii_authentication') + end + + def self.chap + !!config_get('aaa_authentication_login', 'chap') + end + + def self.chap=(val) + no_cmd = val ? '' : 'no' + config_set('aaa_authentication_login', 'chap', no_cmd) + end + + def self.default_chap + config_get_default('aaa_authentication_login', 'chap') + end + + def self.error_display + !!config_get('aaa_authentication_login', 'error_display') + end + + def self.error_display=(val) + no_cmd = val ? '' : 'no' + config_set('aaa_authentication_login', 'error_display', no_cmd) + end + + def self.default_error_display + config_get_default('aaa_authentication_login', 'error_display') + end + + def self.mschap + !!config_get('aaa_authentication_login', 'mschap') + end + + def self.mschap=(val) + no_cmd = val ? '' : 'no' + config_set('aaa_authentication_login', 'mschap', no_cmd) + end + + def self.default_mschap + config_get_default('aaa_authentication_login', 'mschap') + end + + def self.mschapv2 + !!config_get('aaa_authentication_login', 'mschapv2') + end + + def self.mschapv2=(val) + no_cmd = val ? '' : 'no' + config_set('aaa_authentication_login', 'mschapv2', no_cmd) + end + + def self.default_mschapv2 + config_get_default('aaa_authentication_login', 'mschapv2') + end + end +end diff --git a/lib/cisco_node_utils/aaa_authentication_login_service.rb b/lib/cisco_node_utils/aaa_authentication_login_service.rb new file mode 100644 index 00000000..825d2ece --- /dev/null +++ b/lib/cisco_node_utils/aaa_authentication_login_service.rb @@ -0,0 +1,133 @@ +# +# NXAPI implementation of AaaAuthenticationLoginService class +# +# May 2015, Alex Hunsberger +# +# Copyright (c) 2015-2016 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 + # NXAPI implementation of AAA Authentication Login Service class + class AaaAuthenticationLoginService < NodeUtil + attr_reader :name + + def initialize(name, create=true) + fail TypeError unless name.is_a? String + # only console and default are supported currently + fail ArgumentError unless %w(console default).include? name + @name = name + + # console needs to be explicitly created before it appears in + # "show run aaa all" but oddly not before it shows up in + # "show aaa authentication" + return unless create + m = default_method.to_s + config_set('aaa_auth_login_service', 'method', '', name, m) + end + + def self.services + servs = {} + servs_arr = config_get('aaa_auth_login_service', 'services') + unless servs_arr.nil? + servs_arr.each do |s| + servs[s] = AaaAuthenticationLoginService.new(s, false) + end + end + servs + end + + def destroy + # must specify exact current config string to unconfigure + m = method + m_str = m == :unselected ? '' : m.to_s + g_str = groups.join(' ') + + if g_str.empty? + # cannot remove default local, so do nothing in this case + unless m == :local && @name == 'default' + config_set('aaa_auth_login_service', 'method', + 'no', @name, m_str) + end + else + config_set('aaa_auth_login_service', 'groups', + 'no', @name, g_str, m_str) + end + end + + # groups aren't retrieved via the usual CLI regex memory method because + # there can be an arbitrary number of groups and specifying a repeating + # memory regex only captures the last match + # ex: aaa authentication login default group group1 group2 group3 none + def groups + # config_get returns the following format: + # [{service:"default",method:"group group1 none "}, + # {service:"console",method:"local "}] + hsh_arr = config_get('aaa_auth_login_service', 'groups') + fail 'unable to retrieve aaa groups information' if hsh_arr.empty? + hsh = hsh_arr.find { |x| x['service'] == @name } + # this should never happen unless @name is invalid + fail "no aaa info found for service #{@name}" if hsh.nil? + fail "no method found for #{@name} - api or feature change?" unless + hsh.key? 'method' + # ex: ["group", "group1", "local"] or maybe ["none"] + grps = hsh['method'].strip.split + return [] if grps.size == 1 + # remove local, none, group keywords + grps -= %w(none local group) + grps + end + + # default is [] + def default_groups + config_get_default('aaa_auth_login_service', 'groups') + end + + def method + m = config_get('aaa_auth_login_service', 'method', @name) + m.nil? ? :unselected : m.to_sym + end + + # default is :local + def default_method + config_get_default('aaa_auth_login_service', 'method') + end + + # groups and method must be set in the same CLI string + # aaa authentication login { console | default } / + # none | local | group [none] + def groups_method_set(grps, m) + fail TypeError unless grps.is_a? Array + fail TypeError unless grps.all? { |x| x.is_a? String } + fail TypeError unless m.is_a? Symbol + # only the following 3 are supported (unselected = blank) + fail ArgumentError unless [:none, :local, :unselected].include? m + + fail "method 'local' not allowed when groups are configured" if + m == :local && !grps.empty? + m_str = m == :unselected ? '' : m.to_s + g_str = grps.join(' ') + + # config_set depends on whether we're setting groups or not + if g_str.empty? + config_set('aaa_auth_login_service', 'method', + '', @name, m_str) + else + config_set('aaa_auth_login_service', 'groups', + '', @name, g_str, m_str) + end + end + end +end diff --git a/lib/cisco_node_utils/aaa_authorization_service.rb b/lib/cisco_node_utils/aaa_authorization_service.rb new file mode 100644 index 00000000..f3f78fc7 --- /dev/null +++ b/lib/cisco_node_utils/aaa_authorization_service.rb @@ -0,0 +1,150 @@ +# NXAPI implementation of AaaAuthorizationService class +# +# May 2015, Alex Hunsberger +# +# Copyright (c) 2015-2016 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 + # AaaAuthorizationService - node util class for aaa authorization management + class AaaAuthorizationService < NodeUtil + attr_reader :name, :type + + def initialize(type, name, create=true) + fail TypeError unless name.is_a? String + fail TypeError unless type.is_a? Symbol + # only console and default are supported currently + fail ArgumentError unless %w(console default).include? name + fail ArgumentError unless + %i(commands config_commands ssh_certificate ssh_publickey).include? type + @name = name + @type = type + type_str = AaaAuthorizationService.auth_type_sym_to_str(type) + + return unless create + + config_set('aaa_authorization_service', 'method', '', type_str, name) + end + + def self.services + servs = {} + servs_arr = config_get('aaa_authorization_service', 'services') + unless servs_arr.nil? + servs_arr.each do |type, name| + type = auth_type_str_to_sym(type) + servs[type] ||= {} + servs[type][name] = AaaAuthorizationService.new(type, name, false) + end + end + servs + end + + def destroy + # must specify exact current config string to unconfigure + m = method + m_str = m == :unselected ? '' : m.to_s + g_str = groups.join(' ') + t_str = AaaAuthorizationService.auth_type_sym_to_str(@type) + + if g_str.empty? + # cannot remove no groups + local, so do nothing in this case + unless m == :local + config_set('aaa_authorization_service', 'method', + 'no', t_str, @name) + end + else + config_set('aaa_authorization_service', 'groups', + 'no', t_str, @name, g_str, m_str) + end + end + + # groups aren't retrieved via the usual CLI regex memory type because + # there can be an arbitrary number of groups and specifying a repeating + # memory regex only captures the last match + # ex: aaa authorization console group group1 group2 group3 local + def groups + # config_get returns the following format: + # [{"appl_subtype": "console", + # "cmd_type": "config-commands", + # "methods": "group foo bar local "}], ... + hsh_arr = config_get('aaa_authorization_service', 'groups') + fail 'unable to retrieve aaa groups information' if hsh_arr.empty? + type_s = AaaAuthorizationService.auth_type_sym_to_str(@type) + hsh = hsh_arr.find do |x| + x['appl_subtype'] == @name && x['cmd_type'] == type_s + end + fail "no aaa info for #{@type},#{@name}" if hsh.nil? + fail "no aaa info for #{@type},#{@name}. api/feature change?" unless + hsh.key? 'methods' + # ex: ["group", "group1", "local"] + grps = hsh['methods'].strip.split + # return [] if grps.size == 1 + # remove local, group keywords + grps -= %w(local group) + grps + end + + # default is [] + def default_groups + config_get_default('aaa_authorization_service', 'groups') + end + + def method + t_str = AaaAuthorizationService.auth_type_sym_to_str(@type) + m = config_get('aaa_authorization_service', 'method', @name, t_str) + m.nil? ? :unselected : m.to_sym + end + + # default is :local + def default_method + config_get_default('aaa_authorization_service', 'method') + end + + # groups and method must be set in the same CLI string + # aaa authorization login / + # local | group [local] + def groups_method_set(grps, m) + fail TypeError unless grps.is_a? Array + fail TypeError unless grps.all? { |x| x.is_a? String } + fail TypeError unless m.is_a? Symbol + # only the following are supported (unselected = blank) + fail ArgumentError unless [:local, :unselected].include? m + + # raise "type 'local' not allowed when groups are configured" if + # m == :local and not grps.empty? + m_str = m == :unselected ? '' : m.to_s + g_str = grps.join(' ') + t_str = AaaAuthorizationService.auth_type_sym_to_str(@type) + + # config_set depends on whether we're setting groups or not + if g_str.empty? + config_set('aaa_authorization_service', 'method', + '', t_str, @name) + else + config_set('aaa_authorization_service', 'groups', + '', t_str, @name, g_str, m_str) + end + end + + def self.auth_type_sym_to_str(sym) + sym.to_s.sub('_', '-') + end + + def self.auth_type_str_to_sym(str) + str.sub('-', '_').to_sym + end + end +end diff --git a/lib/cisco_node_utils/ace.rb b/lib/cisco_node_utils/ace.rb new file mode 100644 index 00000000..bd1989c8 --- /dev/null +++ b/lib/cisco_node_utils/ace.rb @@ -0,0 +1,196 @@ +# Copyright (c) 2015-2016 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 + # Ace - node utility class for Ace Configuration + class Ace < NodeUtil + attr_reader :afi, :acl_name, :seqno + + def initialize(afi, acl_name, seqno) + @afi = Acl.afi_cli(afi) + @acl_name = acl_name.to_s + @seqno = seqno.to_s + set_args_keys_default + end + + # Create a hash of all aces under a given acl_name. + def self.aces + afis = %w(ipv4 ipv6) + hash = {} + afis.each do |afi| + hash[afi] = {} + acls = config_get('acl', 'all_acls', afi: Acl.afi_cli(afi)) + next if acls.nil? + + acls.each do |acl_name| + hash[afi][acl_name] = {} + aces = config_get('acl', 'all_aces', + afi: Acl.afi_cli(afi), acl_name: acl_name) + next if aces.nil? + + aces.each do |seqno| + hash[afi][acl_name][seqno] = Ace.new(afi, acl_name, seqno) + end + end + end + hash + end + + def destroy + set_args_keys(state: 'no') + config_set('acl', 'ace_destroy', @set_args) + end + + def set_args_keys_default + keys = { afi: @afi, acl_name: @acl_name, seqno: @seqno } + @get_args = @set_args = keys + end + + # rubocop:disable Style/AccessorMethodName + def set_args_keys(hash={}) + set_args_keys_default + @set_args = @get_args.merge!(hash) unless hash.empty? + end + + # common ace getter + def ace_get + str = config_get('acl', 'ace', @get_args) + return nil if str.nil? + + # remark is a description field, needs a separate regex + # Example: + remark = Regexp.new('(?\d+) remark (?.*)').match(str) + return remark unless remark.nil? + + # rubocop:disable Metrics/LineLength + regexp = Regexp.new('(?\d+) (?\S+)'\ + ' *(?\d+|\S+)'\ + ' *(?any|host \S+|\S+\/\d+|\S+ [:\.0-9a-fA-F]+|addrgroup \S+)*'\ + ' *(?eq \S+|neq \S+|lt \S+|''gt \S+|range \S+ \S+|portgroup \S+)?'\ + ' *(?any|host \S+|\S+\/\d+|\S+ [:\.0-9a-fA-F]+|addrgroup \S+)'\ + ' *(?eq \S+|neq \S+|lt \S+|gt \S+|range \S+ \S+|portgroup \S+)?') + # rubocop:enable Metrics/LineLength + regexp.match(str) + end + + # common ace setter. Put the values you need in a hash and pass it in. + # attrs = {:action=>'permit', :proto=>'tcp', :src =>'host 1.1.1.1'} + def ace_set(attrs) + if attrs.empty? + attrs[:state] = 'no' + else + # remove existing ace first + destroy if seqno + attrs[:state] = '' + end + + if attrs[:remark] + cmd = 'ace_remark' + else + cmd = 'ace' + [:action, + :proto, + :src_addr, + :src_port, + :dst_addr, + :dst_port, + ].each do |p| + attrs[p] = '' if attrs[p].nil? + end + end + set_args_keys(attrs) + config_set('acl', cmd, @set_args) + end + + # PROPERTIES + # ---------- + def seqno + match = ace_get + return nil if match.nil? + match.names.include?('seqno') ? match[:seqno] : nil + end + + def action + match = ace_get + return nil if match.nil? + match.names.include?('action') ? match[:action] : nil + end + + def action=(action) + @set_args[:action] = action + end + + def remark + match = ace_get + return nil if match.nil? + match.names.include?('remark') ? match[:remark] : nil + end + + def remark=(remark) + @set_args[:remark] = remark + end + + def proto + match = ace_get + return nil if match.nil? + match.names.include?('proto') ? match[:proto] : nil + end + + def proto=(proto) + @set_args[:proto] = proto # TBD ip vs ipv4 + end + + def src_addr + match = ace_get + return nil if match.nil? + match.names.include?('src_addr') ? match[:src_addr] : nil + end + + def src_addr=(src_addr) + @set_args[:src_addr] = src_addr + end + + def src_port + match = ace_get + return nil if match.nil? + match.names.include?('src_port') ? match[:src_port] : nil + end + + def src_port=(src_port) + @set_args[:src_port] = src_port + end + + def dst_addr + match = ace_get + return nil if match.nil? + match.names.include?('dst_addr') ? match[:dst_addr] : nil + end + + def dst_addr=(dst_addr) + @set_args[:dst_addr] = dst_addr + end + + def dst_port + match = ace_get + return nil if match.nil? + match.names.include?('dst_port') ? match[:dst_port] : nil + end + + def dst_port=(src_port) + @set_args[:dst_port] = src_port + end + end +end diff --git a/lib/cisco_node_utils/acl.rb b/lib/cisco_node_utils/acl.rb new file mode 100644 index 00000000..553b0af0 --- /dev/null +++ b/lib/cisco_node_utils/acl.rb @@ -0,0 +1,100 @@ +# Copyright (c) 2014-2016 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 + # Acl - node utility class for ACL configuration + class Acl < NodeUtil + attr_reader :acl_name, :afi + + def initialize(afi, acl_name, instantiate=true) + @set_args = @get_args = { afi: Acl.afi_cli(afi), acl_name: acl_name.to_s } + create if instantiate + end + + # Return all acls currently on the switch + def self.acls + afis = %w(ipv4 ipv6) + acl_hash = {} + afis.each do |afi| + acl_hash[afi] = {} + afi_cli = Acl.afi_cli(afi) + instances = config_get('acl', 'all_acls', afi: afi_cli) + + next if instances.nil? + instances.each do |acl_name| + acl_hash[afi][acl_name] = Acl.new(afi, acl_name, false) + end + end + acl_hash + end + + # Platform-specific afi cli string + def self.afi_cli(afi) + fail ArgumentError, "Argument afi must be 'ipv4' or 'ipv6'" unless + afi[/(ipv4|ipv6)/] + afi[/ipv4/] ? 'ip' : afi + end + + def create + config_acl('') + end + + def destroy + config_acl('no') + end + + def config_acl(state) + @set_args[:state] = state + config_set('acl', 'acl', @set_args) + end + + # ---------- + # PROPERTIES + # ---------- + def stats_per_entry + config_get('acl', 'stats_per_entry', @get_args) + end + + def stats_per_entry=(state) + @set_args[:state] = (state ? '' : 'no') + config_set('acl', 'stats_per_entry', @set_args) + end + + def default_stats_per_entry + config_get_default('acl', 'stats_per_entry') + end + + def fragments + config_get('acl', 'fragments', @get_args) + end + + def fragments=(action) + @set_args[:state] = (action ? '' : 'no') + action = fragments unless action + @set_args[:action] = action + config_set('acl', 'fragments', @set_args) if action + end + + def default_fragments + config_get_default('acl', 'fragments') + end + + # acl == overide func + def ==(other) + acl_name == other.acl_name && afi == other.afi + end + end +end diff --git a/lib/cisco_node_utils/bgp.rb b/lib/cisco_node_utils/bgp.rb index 7c62d733..0a6004ab 100644 --- a/lib/cisco_node_utils/bgp.rb +++ b/lib/cisco_node_utils/bgp.rb @@ -1,6 +1,6 @@ # June 2015, Michael G Wiebe # -# Copyright (c) 2015 Cisco and/or its affiliates. +# Copyright (c) 2015-2016 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. @@ -15,6 +15,8 @@ # limitations under the License. require_relative 'node_util' +require_relative 'feature' +require_relative 'bgp_af' module Cisco # RouterBgp - node utility class for BGP general config management @@ -24,49 +26,30 @@ class RouterBgp < NodeUtil def initialize(asnum, vrf='default', instantiate=true) fail ArgumentError unless vrf.is_a? String fail ArgumentError unless vrf.length > 0 - @asnum = RouterBgp.process_asnum(asnum) + @asnum = RouterBgp.validate_asnum(asnum) @vrf = vrf - if @vrf == 'default' - @get_args = @set_args = { asnum: @asnum } - else - @get_args = @set_args = { asnum: @asnum, vrf: @vrf } - end + set_args_keys_default create if instantiate end - def self.process_asnum(asnum) - err_msg = "BGP asnum must be either a 'String' or an" \ - " 'Integer' object" - fail ArgumentError, err_msg unless asnum.is_a?(Integer) || - asnum.is_a?(String) - if asnum.is_a? String - # Match ASDOT '1.5' or ASPLAIN '55' strings - fail ArgumentError unless /^(\d+|\d+\.\d+)$/.match(asnum) - asnum = RouterBgp.dot_to_big(asnum) if /\d+\.\d+/.match(asnum) - end - asnum.to_i - end - # Create a hash of all router bgp default and non-default # vrf instances def self.routers - bgp_ids = config_get('bgp', 'router') - return {} if bgp_ids.nil? + asnum = config_get('bgp', 'router') + return {} if asnum.nil? hash_final = {} - # TODO: Remove loop if only single ASN supported by RFC? - bgp_ids.each do |asnum| - asnum = asnum.to_i unless /\d+.\d+/.match(asnum) - hash_tmp = { asnum => - { 'default' => RouterBgp.new(asnum, 'default', false) } } - vrf_ids = config_get('bgp', 'vrf', asnum: asnum) - unless vrf_ids.nil? - vrf_ids.each do |vrf| - hash_tmp[asnum][vrf] = RouterBgp.new(asnum, vrf, false) - end + asnum = asnum.to_i unless /\d+.\d+/.match(asnum) + hash_tmp = { + asnum => { 'default' => RouterBgp.new(asnum, 'default', false) } + } + vrf_ids = config_get('bgp', 'vrf', asnum: asnum) + unless vrf_ids.nil? + vrf_ids.each do |vrf| + hash_tmp[asnum][vrf] = RouterBgp.new(asnum, vrf, false) end - hash_final.merge!(hash_tmp) end + hash_final.merge!(hash_tmp) return hash_final rescue Cisco::CliError => e # cmd will syntax reject when feature is not enabled @@ -74,44 +57,18 @@ def self.routers return {} end - def self.enabled - feat = config_get('bgp', 'feature') - return !(feat.nil? || feat.empty?) - rescue Cisco::CliError => e - # cmd will syntax reject when feature is not enabled - raise unless e.clierror =~ /Syntax error/ - return false - end - - def self.enable(state='') - config_set('bgp', 'feature', state: state) - end - - # Convert BGP ASN ASDOT+ to ASPLAIN - def self.dot_to_big(dot_str) - fail ArgumentError unless dot_str.is_a? String - return dot_str unless /\d+\.\d+/.match(dot_str) - mask = 0b1111111111111111 - high = dot_str.to_i - low = 0 - low_match = dot_str.match(/\.(\d+)/) - low = low_match[1].to_i if low_match - high_bits = (mask & high) << 16 - low_bits = mask & low - high_bits + low_bits - end - - def router_bgp(asnum, vrf, state='') - # Only one bgp autonomous system number is allowed - # Raise an error if one is already configured that - # differs from the one being created. - configured = config_get('bgp', 'router') - if !configured.nil? && configured.first.to_s != asnum.to_s - fail %( - Changing the BGP Autonomous System Number is not allowed. - Current BGP asn: #{configured.first} - Attempted change to asn: #{asnum}) + def self.validate_asnum(asnum) + err_msg = 'BGP asnum must be type String or Integer' + fail ArgumentError, err_msg unless asnum.is_a?(Integer) || + asnum.is_a?(String) + if asnum.is_a? String + # Match ASDOT '1.5' or ASPLAIN '55' strings + fail ArgumentError unless /^(\d+|\d+\.\d+)$/.match(asnum) end + asnum.to_s + end + + def router_bgp(state='') @set_args[:state] = state if vrf == 'default' config_set('bgp', 'router', @set_args) @@ -121,79 +78,53 @@ def router_bgp(asnum, vrf, state='') set_args_keys_default end - def enable_create_router_bgp(asnum, vrf) - RouterBgp.enable - router_bgp(asnum, vrf) - end - # Create one router bgp instance def create - if RouterBgp.enabled - router_bgp(@asnum, @vrf) - else - enable_create_router_bgp(@asnum, @vrf) - end + Feature.bgp_enable + router_bgp end # Destroy router bgp instance def destroy - vrf_ids = config_get('bgp', 'vrf', asnum: @asnum) - vrf_ids = ['default'] if vrf_ids.nil? - if vrf_ids.size == 1 || @vrf == 'default' - RouterBgp.enable('no') - else - router_bgp(asnum, @vrf, 'no') - end - rescue Cisco::CliError => e - # cmd will syntax reject when feature is not enabled - raise unless e.clierror =~ /Syntax error/ + router_bgp('no') end # Helper method to delete @set_args hash keys def set_args_keys_default - if @vrf == 'default' - @set_args = { asnum: @asnum } - else - @set_args = { asnum: @asnum, vrf: @vrf } - end + @set_args = { asnum: @asnum } + @set_args[:vrf] = @vrf unless @vrf == 'default' + @get_args = @set_args end # Attributes: # Bestpath Getters def bestpath_always_compare_med - match = config_get('bgp', 'bestpath_always_compare_med', @get_args) - match.nil? ? default_bestpath_always_compare_med : true + config_get('bgp', 'bestpath_always_compare_med', @get_args) end def bestpath_aspath_multipath_relax - match = config_get('bgp', 'bestpath_aspath_multipath_relax', @get_args) - match.nil? ? default_bestpath_aspath_multipath_relax : true + config_get('bgp', 'bestpath_aspath_multipath_relax', @get_args) end def bestpath_compare_routerid - match = config_get('bgp', 'bestpath_compare_routerid', @get_args) - match.nil? ? default_bestpath_compare_routerid : true + config_get('bgp', 'bestpath_compare_routerid', @get_args) end def bestpath_cost_community_ignore - match = config_get('bgp', 'bestpath_cost_community_ignore', @get_args) - match.nil? ? default_bestpath_cost_community_ignore : true + config_get('bgp', 'bestpath_cost_community_ignore', @get_args) end def bestpath_med_confed - match = config_get('bgp', 'bestpath_med_confed', @get_args) - match.nil? ? default_bestpath_med_confed : true + config_get('bgp', 'bestpath_med_confed', @get_args) end def bestpath_med_missing_as_worst - match = config_get('bgp', 'bestpath_med_missing_as_worst', @get_args) - match.nil? ? default_bestpath_med_missing_as_worst : true + config_get('bgp', 'bestpath_med_missing_as_worst', @get_args) end def bestpath_med_non_deterministic - match = config_get('bgp', 'bestpath_med_non_deterministic', @get_args) - match.nil? ? default_bestpath_med_non_deterministic : true + config_get('bgp', 'bestpath_med_non_deterministic', @get_args) end # Bestpath Setters @@ -270,8 +201,7 @@ def default_bestpath_med_non_deterministic # Cluster Id (Getter/Setter/Default) def cluster_id - match = config_get('bgp', 'cluster_id', @get_args) - match.nil? ? default_cluster_id : match.first + config_get('bgp', 'cluster_id', @get_args) end def cluster_id=(id) @@ -279,7 +209,7 @@ def cluster_id=(id) # 'no bgp cluster-id'. IMO this should be possible because you # can only configure a single bgp cluster-id. # - # HACK: specify a dummy id when removing the feature. + # HACK: specify a dummy id when removing the property. dummy_id = 1 if id == default_cluster_id @set_args[:state] = 'no' @@ -298,8 +228,7 @@ def default_cluster_id # Confederation Id (Getter/Setter/Default) def confederation_id - match = config_get('bgp', 'confederation_id', @get_args) - match.nil? ? default_confederation_id : match.first + config_get('bgp', 'confederation_id', @get_args) end def confederation_id=(id) @@ -307,7 +236,7 @@ def confederation_id=(id) # 'no bgp confederation id'. IMO this should be possible # because you can only configure a single bgp confed id. # - # HACK: specify a dummy id when removing the feature. + # HACK: specify a dummy id when removing the property. dummy_id = 1 if id == default_confederation_id @set_args[:state] = 'no' @@ -324,10 +253,74 @@ def default_confederation_id config_get_default('bgp', 'confederation_id') end + # + # disable-policy-batching (Getter/Setter/Default) + # + def disable_policy_batching + config_get('bgp', 'disable_policy_batching', @get_args) + end + + def disable_policy_batching=(enable) + @set_args[:state] = (enable ? '' : 'no') + config_set('bgp', 'disable_policy_batching', @set_args) + set_args_keys_default + end + + def default_disable_policy_batching + config_get_default('bgp', 'disable_policy_batching') + end + + # + # disable-policy-batching ipv4 prefix-list + # + def disable_policy_batching_ipv4 + config_get('bgp', 'disable_policy_batching_ipv4', @get_args) + end + + def disable_policy_batching_ipv4=(prefix_list) + dummy_prefixlist = 'x' + if prefix_list == default_disable_policy_batching_ipv4 + @set_args[:state] = 'no' + @set_args[:prefix_list] = dummy_prefixlist + else + @set_args[:state] = '' + @set_args[:prefix_list] = prefix_list + end + config_set('bgp', 'disable_policy_batching_ipv4', @set_args) + set_args_keys_default + end + + def default_disable_policy_batching_ipv4 + config_get_default('bgp', 'disable_policy_batching_ipv4') + end + + # + # disable-policy-batching ipv6 prefix-list + # + def disable_policy_batching_ipv6 + config_get('bgp', 'disable_policy_batching_ipv6', @get_args) + end + + def disable_policy_batching_ipv6=(prefix_list) + dummy_prefixlist = 'x' + if prefix_list == default_disable_policy_batching_ipv6 + @set_args[:state] = 'no' + @set_args[:prefix_list] = dummy_prefixlist + else + @set_args[:state] = '' + @set_args[:prefix_list] = prefix_list + end + config_set('bgp', 'disable_policy_batching_ipv6', @set_args) + set_args_keys_default + end + + def default_disable_policy_batching_ipv6 + config_get_default('bgp', 'disable_policy_batching_ipv6') + end + # Enforce First As (Getter/Setter/Default) def enforce_first_as - match = config_get('bgp', 'enforce_first_as', @get_args) - match.nil? ? false : true + config_get('bgp', 'enforce_first_as', @get_args) end def enforce_first_as=(enable) @@ -340,10 +333,133 @@ def default_enforce_first_as config_get_default('bgp', 'enforce_first_as') end + # event-history + # event-history cli [ size ] + # Nvgen as True With optional 'size + def event_history_cli + match = config_get('bgp', 'event_history_cli', @get_args) + if match.is_a?(Array) + return 'false' if match[0] == 'no ' + return 'size_' + match[1] if match[1] + end + default_event_history_cli + end + + def event_history_cli=(val) + size = val[/small|medium|large|disable/] + @set_args[:size] = size.nil? ? '' : "size #{size}" + @set_args[:state] = val[/false/] ? 'no' : '' + config_set('bgp', 'event_history_cli', @set_args) + set_args_keys_default + end + + def default_event_history_cli + config_get_default('bgp', 'event_history_cli') + end + + # event-history detail [ size ] + # Nvgen as True With optional 'size + def event_history_detail + match = config_get('bgp', 'event_history_detail', @get_args) + # This property requires auto_default=false + if match.is_a?(Array) + return 'false' if match[0] == 'no ' + return 'size_' + match[1] if match[1] + end + default_event_history_detail + end + + def event_history_detail=(val) + size = val[/small|medium|large|disable/] + @set_args[:size] = size.nil? ? '' : "size #{size}" + @set_args[:state] = val[/false/] ? 'no' : '' + config_set('bgp', 'event_history_detail', @set_args) + set_args_keys_default + end + + def default_event_history_detail + config_get_default('bgp', 'event_history_detail') + end + + # event-history events [ size ] + # Nvgen as True With optional 'size + def event_history_events + match = config_get('bgp', 'event_history_events', @get_args) + if match.is_a?(Array) + return 'false' if match[0] == 'no ' + return 'size_' + match[1] if match[1] + end + default_event_history_events + end + + def event_history_events=(val) + size = val[/small|medium|large|disable/] + @set_args[:size] = size.nil? ? '' : "size #{size}" + @set_args[:state] = val[/false/] ? 'no' : '' + config_set('bgp', 'event_history_events', @set_args) + set_args_keys_default + end + + def default_event_history_events + config_get_default('bgp', 'event_history_events') + end + + # event-history periodic [ size ] + # Nvgen as True With optional 'size + def event_history_periodic + match = config_get('bgp', 'event_history_periodic', @get_args) + if match.is_a?(Array) + return 'false' if match[0] == 'no ' + return 'size_' + match[1] if match[1] + end + default_event_history_periodic + end + + def event_history_periodic=(val) + size = val[/small|medium|large|disable/] + @set_args[:size] = size.nil? ? '' : "size #{size}" + @set_args[:state] = val[/false/] ? 'no' : '' + config_set('bgp', 'event_history_periodic', @set_args) + set_args_keys_default + end + + def default_event_history_periodic + config_get_default('bgp', 'event_history_periodic') + end + + # Fast External fallover (Getter/Setter/Default) + def fast_external_fallover + config_get('bgp', 'fast_external_fallover', @get_args) + end + + def fast_external_fallover=(enable) + @set_args[:state] = (enable ? '' : 'no') + config_set('bgp', 'fast_external_fallover', @set_args) + set_args_keys_default + end + + def default_fast_external_fallover + config_get_default('bgp', 'fast_external_fallover') + end + + # Flush Routes (Getter/Setter/Default) + def flush_routes + config_get('bgp', 'flush_routes', @get_args) + end + + def flush_routes=(enable) + @set_args[:state] = (enable ? '' : 'no') + config_set('bgp', 'flush_routes', @set_args) + set_args_keys_default + end + + def default_flush_routes + config_get_default('bgp', 'flush_routes') + end + # Confederation Peers (Getter/Setter/Default) def confederation_peers - match = config_get('bgp', 'confederation_peers', @get_args) - match.nil? ? default_confederation_peers : match.first + config_get('bgp', 'confederation_peers', @get_args) end def confederation_peers_set(peers) @@ -368,28 +484,19 @@ def default_confederation_peers # Graceful Restart Getters def graceful_restart - match = config_get('bgp', 'graceful_restart', @get_args) - match.nil? ? false : true + config_get('bgp', 'graceful_restart', @get_args) end def graceful_restart_timers_restart - match = config_get('bgp', 'graceful_restart_timers_restart', @get_args) - match.nil? ? default_graceful_restart_timers_restart : match.first.to_i + config_get('bgp', 'graceful_restart_timers_restart', @get_args) end def graceful_restart_timers_stalepath_time - match = config_get('bgp', 'graceful_restart_timers_stalepath_time', - @get_args) - if match.nil? - default_graceful_restart_timers_stalepath_time - else - match.first.to_i - end + config_get('bgp', 'graceful_restart_timers_stalepath_time', @get_args) end def graceful_restart_helper - match = config_get('bgp', 'graceful_restart_helper', @get_args) - match.nil? ? default_graceful_restart_helper : true + config_get('bgp', 'graceful_restart_helper', @get_args) end # Graceful Restart Setters @@ -446,10 +553,24 @@ def default_graceful_restart_helper config_get_default('bgp', 'graceful_restart_helper') end + # Isolate (Getter/Setter/Default) + def isolate + config_get('bgp', 'isolate', @get_args) + end + + def isolate=(enable) + @set_args[:state] = (enable ? '' : 'no') + config_set('bgp', 'isolate', @set_args) + set_args_keys_default + end + + def default_isolate + config_get_default('bgp', 'isolate') + end + # MaxAs Limit (Getter/Setter/Default) def maxas_limit - match = config_get('bgp', 'maxas_limit', @get_args) - match.nil? ? default_maxas_limit : match.first.to_i + config_get('bgp', 'maxas_limit', @get_args) end def maxas_limit=(limit) @@ -470,8 +591,7 @@ def default_maxas_limit # Log Neighbor Changes (Getter/Setter/Default) def log_neighbor_changes - match = config_get('bgp', 'log_neighbor_changes', @get_args) - match.nil? ? default_log_neighbor_changes : true + config_get('bgp', 'log_neighbor_changes', @get_args) end def log_neighbor_changes=(enable) @@ -484,26 +604,24 @@ def default_log_neighbor_changes config_get_default('bgp', 'log_neighbor_changes') end - # Neighbor fib down accelerate (Getter/Setter/Default) - def neighbor_fib_down_accelerate - match = config_get('bgp', 'neighbor_fib_down_accelerate', @get_args) - match.nil? ? default_neighbor_fib_down_accelerate : true + # Neighbor down fib accelerate (Getter/Setter/Default) + def neighbor_down_fib_accelerate + config_get('bgp', 'neighbor_down_fib_accelerate', @get_args) end - def neighbor_fib_down_accelerate=(enable) + def neighbor_down_fib_accelerate=(enable) @set_args[:state] = (enable ? '' : 'no') - config_set('bgp', 'neighbor_fib_down_accelerate', @set_args) + config_set('bgp', 'neighbor_down_fib_accelerate', @set_args) set_args_keys_default end - def default_neighbor_fib_down_accelerate - config_get_default('bgp', 'neighbor_fib_down_accelerate') + def default_neighbor_down_fib_accelerate + config_get_default('bgp', 'neighbor_down_fib_accelerate') end # Reconnect Interval (Getter/Setter/Default) def reconnect_interval - match = config_get('bgp', 'reconnect_interval', @get_args) - match.nil? ? default_reconnect_interval : match.first.to_i + config_get('bgp', 'reconnect_interval', @get_args) end def reconnect_interval=(seconds) @@ -522,19 +640,43 @@ def default_reconnect_interval config_get_default('bgp', 'reconnect_interval') end + # route_distinguisher + # Note that this property is supported by both bgp and vrf providers. + def route_distinguisher + config_get('bgp', 'route_distinguisher', @get_args) + end + + def route_distinguisher=(rd) + Feature.nv_overlay_evpn_enable + if rd == default_route_distinguisher + @set_args[:state] = 'no' + @set_args[:rd] = '' + else + @set_args[:state] = '' + @set_args[:rd] = rd + end + config_set('bgp', 'route_distinguisher', @set_args) + set_args_keys_default + end + + def default_route_distinguisher + config_get_default('bgp', 'route_distinguisher') + end + # Router ID (Getter/Setter/Default) def router_id - match = config_get('bgp', 'router_id', @get_args) - match.nil? ? default_router_id : match.first + config_get('bgp', 'router_id', @get_args) end def router_id=(id) # In order to remove a bgp router-id you cannot simply issue - # 'no bgp router-id'. Dummy-id specified to work around this. - dummy_id = '1.2.3.4' + # 'no bgp router-id'. On some platforms you can specify a dummy + # value, but on N7K at least you need the current router_id. if id == default_router_id + # Nothing to do if router_id is already set to default. + return if router_id == default_router_id @set_args[:state] = 'no' - @set_args[:id] = dummy_id + @set_args[:id] = router_id else @set_args[:state] = '' @set_args[:id] = id @@ -549,8 +691,7 @@ def default_router_id # Shutdown (Getter/Setter/Default) def shutdown - match = config_get('bgp', 'shutdown', @asnum) - match.nil? ? default_shutdown : true + config_get('bgp', 'shutdown', @asnum) end def shutdown=(enable) @@ -565,8 +706,7 @@ def default_shutdown # Supress Fib Pending (Getter/Setter/Default) def suppress_fib_pending - match = config_get('bgp', 'suppress_fib_pending', @get_args) - match.nil? ? default_suppress_fib_pending : true + config_get('bgp', 'suppress_fib_pending', @get_args) end def suppress_fib_pending=(enable) @@ -582,7 +722,7 @@ def default_suppress_fib_pending # BGP Timers Getters def timer_bgp_keepalive_hold match = config_get('bgp', 'timer_bgp_keepalive_hold', @get_args) - match.nil? ? default_timer_bgp_keepalive_hold : match.first + match.nil? ? default_timer_bgp_keepalive_hold : match end def timer_bgp_keepalive @@ -598,13 +738,11 @@ def timer_bgp_holdtime end def timer_bestpath_limit - match = config_get('bgp', 'timer_bestpath_limit', @get_args) - match.nil? ? default_timer_bestpath_limit : match.first.to_i + config_get('bgp', 'timer_bestpath_limit', @get_args) end def timer_bestpath_limit_always - match = config_get('bgp', 'timer_bestpath_limit_always', @get_args) - match.nil? ? default_timer_bestpath_limit_always : true + config_get('bgp', 'timer_bestpath_limit_always', @get_args) end # BGP Timers Setters @@ -625,9 +763,9 @@ def timer_bgp_keepalive_hold_set(keepalive, hold) def timer_bestpath_limit_set(seconds, always=false) if always - feature = 'timer_bestpath_limit_always' + opt = 'timer_bestpath_limit_always' else - feature = 'timer_bestpath_limit' + opt = 'timer_bestpath_limit' end if seconds == default_timer_bestpath_limit @set_args[:state] = 'no' @@ -636,7 +774,7 @@ def timer_bestpath_limit_set(seconds, always=false) @set_args[:state] = '' @set_args[:seconds] = seconds end - config_set('bgp', feature, @set_args) + config_set('bgp', opt, @set_args) set_args_keys_default end diff --git a/lib/cisco_node_utils/bgp_af.rb b/lib/cisco_node_utils/bgp_af.rb index ed7ebb59..118d4b29 100644 --- a/lib/cisco_node_utils/bgp_af.rb +++ b/lib/cisco_node_utils/bgp_af.rb @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # August 2015, Richard Wellum # -# Copyright (c) 2015 Cisco and/or its affiliates. +# Copyright (c) 2015-2016 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. @@ -17,6 +17,7 @@ require_relative 'cisco_cmn_utils' require_relative 'node_util' +require_relative 'feature' require_relative 'bgp' module Cisco @@ -27,7 +28,7 @@ def initialize(asn, vrf, af, instantiate=true) err_msg = '"af" argument must be an array of two string values ' \ 'containing an afi + safi tuple' fail ArgumentError, err_msg unless af.is_a?(Array) || af.length == 2 - @asn = RouterBgp.process_asnum(asn) + @asn = RouterBgp.validate_asnum(asn) @vrf = vrf @afi, @safi = af set_args_keys_default @@ -40,7 +41,7 @@ def self.afs af_hash[asn] = {} vrfs.keys.each do |vrf_name| get_args = { asnum: asn } - get_args[:vrf] = vrf_name unless (vrf_name == 'default') + get_args[:vrf] = vrf_name unless vrf_name == 'default' # Call yaml and search for address-family statements af_list = config_get('bgp_af', 'all_afs', get_args) @@ -57,6 +58,7 @@ def self.afs end def create + Feature.bgp_enable set_args_keys(state: '') config_set('bgp', 'address_family', @set_args) end @@ -124,9 +126,7 @@ def default_default_information_originate # Next Hop route map (Getter/Setter/Default) # def next_hop_route_map - route_map = config_get('bgp_af', 'next_hop_route_map', @get_args) - return '' if route_map.nil? - route_map.shift.strip + config_get('bgp_af', 'next_hop_route_map', @get_args) end def next_hop_route_map=(route_map) @@ -202,9 +202,7 @@ def default_additional_paths_install # additional_paths_selection def additional_paths_selection - route_map = config_get('bgp_af', 'additional_paths_selection', @get_args) - return '' if route_map.nil? - route_map.shift.strip + config_get('bgp_af', 'additional_paths_selection', @get_args) end def additional_paths_selection=(route_map) @@ -226,14 +224,33 @@ def default_additional_paths_selection config_get_default('bgp_af', 'additional_paths_selection') end + # advertise_l2vpn_evpn + def advertise_l2vpn_evpn + config_get('bgp_af', 'advertise_l2vpn_evpn', @get_args) + end + + def advertise_l2vpn_evpn=(state) + Feature.nv_overlay_evpn_enable + set_args_keys(state: (state ? '' : 'no')) + config_set('bgp_af', 'advertise_l2vpn_evpn', @set_args) + end + + def default_advertise_l2vpn_evpn + config_get_default('bgp_af', 'advertise_l2vpn_evpn') + end + # # dampen_igp_metric (Getter/Setter/Default) # # dampen_igp_metric def dampen_igp_metric - result = config_get('bgp_af', 'dampen_igp_metric', @get_args) - result ? result.first.to_i : nil + match = config_get('bgp_af', 'dampen_igp_metric', @get_args) + if match.is_a?(Array) + return nil if match[0] == 'no ' + return match[1].to_i if match[1] + end + default_dampen_igp_metric end def dampen_igp_metric=(val) @@ -422,14 +439,112 @@ def default_dampening_suppress_time config_get_default('bgp_af', 'dampening_suppress_time') end + # + # Distance (Getter/Setter/Default) + # + def distance_set(ebgp, ibgp, local) + set_args_keys(state: '', ebgp: ebgp, ibgp: ibgp, local: local) + config_set('bgp_af', 'distance', @set_args) + end + + def distance_ebgp + ebgp, _ibgp, _local = distance + return default_distance_ebgp if ebgp.nil? + ebgp.to_i + end + + def distance_ibgp + _ebgp, ibgp, _local = distance + return default_distance_ibgp if ibgp.nil? + ibgp.to_i + end + + def distance_local + _ebgp, _ibgp, local = distance + return default_distance_local if local.nil? + local.to_i + end + + def distance + match = config_get('bgp_af', 'distance', @get_args) + match.nil? ? default_distance : match + end + + def default_distance_ebgp + config_get_default('bgp_af', 'distance_ebgp') + end + + def default_distance_ibgp + config_get_default('bgp_af', 'distance_ibgp') + end + + def default_distance_local + config_get_default('bgp_af', 'distance_local') + end + + def default_distance + ["#{default_distance_ebgp}", "#{default_distance_ibgp}", + "#{default_distance_local}"] + end + + # + # default_metric (Getter/Setter/Default) + # + + # default_metric + def default_metric + config_get('bgp_af', 'default_metric', @get_args) + end + + def default_metric=(val) + # To remove the default_metric you can not use 'no default_metric' + # dummy metric to work around this + dummy_metric = 1 + set_args_keys(state: (val == default_default_metric) ? 'no' : '', + num: (val == default_default_metric) ? dummy_metric : val) + config_set('bgp_af', 'default_metric', @set_args) + end + + def default_default_metric + config_get_default('bgp_af', 'default_metric') + end + + # + # inject_map (Getter/Setter/Default) + # + + def inject_map + cmds = config_get('bgp_af', 'inject_map', @get_args).each(&:compact!) + cmds.sort + end + + def inject_map=(should_list) + delta_hash = Utils.delta_add_remove(should_list, inject_map) + return if delta_hash.values.flatten.empty? + [:add, :remove].each do |action| + CiscoLogger.debug("inject_map delta #{@get_args}\n #{action}: " \ + "#{delta_hash[action]}") + delta_hash[action].each do |inject, exist, copy| + # inject & exist are mandatory, copy is optional + state = (action == :add) ? '' : 'no' + copy = 'copy-attributes' unless copy.nil? + set_args_keys(state: state, inject: inject, exist: exist, copy: copy) + config_set('bgp_af', 'inject_map', @set_args) + end + end + end + + def default_inject_map + config_get_default('bgp_af', 'inject_map') + end + # # maximum_paths (Getter/Setter/Default) # # maximum_paths def maximum_paths - result = config_get('bgp_af', 'maximum_paths', @get_args) - result.nil? ? default_maximum_paths : result.first.to_i + config_get('bgp_af', 'maximum_paths', @get_args) end def maximum_paths=(val) @@ -448,8 +563,7 @@ def default_maximum_paths # maximum_paths_ibgp def maximum_paths_ibgp - result = config_get('bgp_af', 'maximum_paths_ibgp', @get_args) - result.nil? ? default_maximum_paths_ibgp : result.first.to_i + config_get('bgp_af', 'maximum_paths_ibgp', @get_args) end def maximum_paths_ibgp=(val) @@ -468,8 +582,7 @@ def default_maximum_paths_ibgp # Build an array of all network commands currently on the device def networks - cmds = config_get('bgp_af', 'network', @get_args) - cmds.nil? ? default_networks : cmds.each(&:compact!) + config_get('bgp_af', 'network', @get_args).each(&:compact!) end # networks setter. @@ -500,8 +613,7 @@ def default_networks # Build an array of all redistribute commands currently on the device def redistribute - cmds = config_get('bgp_af', 'redistribute', @get_args) - cmds.nil? ? default_redistribute : cmds.each(&:compact!) + config_get('bgp_af', 'redistribute', @get_args).each(&:compact!) end # redistribute setter. @@ -526,5 +638,61 @@ def redistribute=(should) def default_redistribute config_get_default('bgp_af', 'redistribute') end + + # + # Suppress Inactive (Getter/Setter/Default) + # + def suppress_inactive + config_get('bgp_af', 'suppress_inactive', @get_args) + end + + def suppress_inactive=(state) + set_args_keys(state: state ? '' : 'no') + config_set('bgp_af', 'suppress_inactive', @set_args) + end + + def default_suppress_inactive + config_get_default('bgp_af', 'suppress_inactive') + end + + # + # Table Map (Getter/Setter/Default) + # + + def table_map + config_get('bgp_af', 'table_map', @get_args) + end + + def table_map_filter + config_get('bgp_af', 'table_map_filter', @get_args) + end + + def table_map_set(map, filter=false) + # To remove table map we can not use 'no table-map' + # Dummy-map specified to work around this + if filter + attr = 'table_map_filter' + else + attr = 'table_map' + end + dummy_map = 'dummy' + if map == default_table_map + @set_args[:state] = 'no' + @set_args[:map] = dummy_map + else + @set_args[:state] = '' + @set_args[:map] = map + end + config_set('bgp_af', attr, @set_args) + set_args_keys_default + end + + def default_table_map + config_get_default('bgp_af', 'table_map') + end + + def default_table_map_filter + config_get_default('bgp_af', 'table_map_filter') + end end end diff --git a/lib/cisco_node_utils/bgp_neighbor.rb b/lib/cisco_node_utils/bgp_neighbor.rb index b7d02940..6ba97c0e 100644 --- a/lib/cisco_node_utils/bgp_neighbor.rb +++ b/lib/cisco_node_utils/bgp_neighbor.rb @@ -2,7 +2,7 @@ # # August 2015, Jie Yang # -# Copyright (c) 2015 Cisco and/or its affiliates. +# Copyright (c) 2015-2016 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. @@ -19,6 +19,7 @@ require 'ipaddr' require_relative 'cisco_cmn_utils' require_relative 'node_util' +require_relative 'feature' require_relative 'bgp' module Cisco @@ -32,7 +33,7 @@ def initialize(asn, vrf, nbr, instantiate=true) # we need to mask the address using prefix length, so that it becomes # something like "1.1.1.0/24" or "2000:123:38::/64" @nbr = Utils.process_network_mask(nbr) - @asn = asn + @asn = RouterBgp.validate_asnum(asn) @vrf = vrf @get_args = @set_args = { asnum: @asn, nbr: @nbr } @get_args[:vrf] = @set_args[:vrf] = vrf if vrf != 'default' @@ -66,6 +67,7 @@ def self.neighbors end def create + Feature.bgp_enable set_args_keys(state: '') config_set('bgp', 'create_destroy_neighbor', @set_args) end @@ -95,9 +97,7 @@ def description=(desc) end def description - desc = config_get('bgp_neighbor', 'description', @get_args) - return '' if desc.nil? - desc.shift.strip + config_get('bgp_neighbor', 'description', @get_args) end def default_description @@ -158,7 +158,7 @@ def ebgp_multihop=(ttl) def ebgp_multihop result = config_get('bgp_neighbor', 'ebgp_multihop', @get_args) - result.nil? ? default_ebgp_multihop : result.first.to_i + result.nil? ? default_ebgp_multihop : result.to_i end def default_ebgp_multihop @@ -166,8 +166,7 @@ def default_ebgp_multihop end def local_as=(val) - asnum = RouterBgp.process_asnum(val) - if asnum == default_local_as + if val == default_local_as set_args_keys(state: 'no', local_as: '') else set_args_keys(state: '', local_as: val) @@ -176,14 +175,11 @@ def local_as=(val) end def local_as - result = config_get('bgp_neighbor', 'local_as', @get_args) - return default_local_as if result.nil? - return result.first.to_i unless /\d+\.\d+$/.match(result.first) - result.first + config_get('bgp_neighbor', 'local_as', @get_args).to_s end def default_local_as - config_get_default('bgp_neighbor', 'local_as') + config_get_default('bgp_neighbor', 'local_as').to_s end def log_neighbor_changes=(val) @@ -206,7 +202,7 @@ def log_neighbor_changes def default_log_neighbor_changes result = config_get_default('bgp_neighbor', 'log_neighbor_changes') - result.to_sym + result.to_sym unless result.nil? end def low_memory_exempt=(val) @@ -230,8 +226,7 @@ def maximum_peers=(val) end def maximum_peers - result = config_get('bgp_neighbor', 'maximum_peers', @get_args) - result.nil? ? default_maximum_peers : result.first.to_i + config_get('bgp_neighbor', 'maximum_peers', @get_args) end def default_maximum_peers @@ -255,8 +250,7 @@ def password_set(val, type=nil) end def password - result = config_get('bgp_neighbor', 'password', @get_args) - result.nil? ? '' : result.first.to_s + config_get('bgp_neighbor', 'password', @get_args) end def default_password @@ -265,11 +259,7 @@ def default_password def password_type result = config_get('bgp_neighbor', 'password_type', @get_args) - if result.nil? - default_password_type - else - Encryption.cli_to_symbol(result.first.to_i) - end + Encryption.cli_to_symbol(result.to_i) end def default_password_type @@ -278,8 +268,7 @@ def default_password_type end def remote_as=(val) - asnum = RouterBgp.process_asnum(val) - if asnum == default_remote_as + if val == default_remote_as set_args_keys(state: 'no', remote_as: '') else set_args_keys(state: '', remote_as: val) @@ -288,14 +277,11 @@ def remote_as=(val) end def remote_as - result = config_get('bgp_neighbor', 'remote_as', @get_args) - return default_remote_as if result.nil? - return result.first.to_i unless /\d+\.\d+$/.match(result.first) - result.first + config_get('bgp_neighbor', 'remote_as', @get_args).to_s end def default_remote_as - config_get_default('bgp_neighbor', 'remote_as') + config_get_default('bgp_neighbor', 'remote_as').to_s end def remove_private_as=(val) @@ -362,7 +348,7 @@ def timers_set(keepalive, hold) def timers_keepalive_hold match = config_get('bgp_neighbor', 'timers_keepalive_hold', @get_args) - match.nil? ? default_timers_keepalive_hold : match.first + match.nil? ? default_timers_keepalive_hold : match end def timers_keepalive @@ -414,8 +400,7 @@ def update_source=(val) def update_source result = config_get('bgp_neighbor', 'update_source', @get_args) - return default_update_source if result.nil? || result.first.nil? - result.first.downcase.strip + result.downcase.strip end def default_update_source diff --git a/lib/cisco_node_utils/bgp_neighbor_af.rb b/lib/cisco_node_utils/bgp_neighbor_af.rb index 4e882bef..357e05c2 100644 --- a/lib/cisco_node_utils/bgp_neighbor_af.rb +++ b/lib/cisco_node_utils/bgp_neighbor_af.rb @@ -3,7 +3,7 @@ # # August 2015 Chris Van Heuveln # -# Copyright (c) 2015 Cisco and/or its affiliates. +# Copyright (c) 2015-2016 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. @@ -19,6 +19,7 @@ require_relative 'cisco_cmn_utils' require_relative 'node_util' +require_relative 'feature' require_relative 'bgp' module Cisco @@ -37,7 +38,7 @@ def self.afs vrfs.keys.each do |vrf| af_hash[asn][vrf] = {} get_args = { asnum: asn } - get_args[:vrf] = vrf unless (vrf == 'default') + get_args[:vrf] = vrf unless vrf == 'default' nbrs = config_get('bgp_neighbor', 'all_neighbors', get_args) next if nbrs.nil? @@ -62,7 +63,6 @@ def self.afs end def validate_args(asn, vrf, nbr, af) - asn = RouterBgp.process_asnum(asn) fail ArgumentError unless vrf.is_a?(String) && (vrf.length > 0) fail ArgumentError unless @@ -71,7 +71,7 @@ def validate_args(asn, vrf, nbr, af) af.is_a?(Array) || af.length == 2 nbr = Utils.process_network_mask(nbr) - @asn = asn + @asn = RouterBgp.validate_asnum(asn) @vrf = vrf @nbr = nbr @afi, @safi = af @@ -92,6 +92,7 @@ def set_args_keys(hash={}) # rubocop:enable Style/AccessorMethodNamefor def create + Feature.bgp_enable set_args_keys(state: '') config_set('bgp_neighbor', 'af', @set_args) end @@ -110,9 +111,7 @@ def destroy # Returns ['', ''] def advertise_map_exist - arr = config_get('bgp_neighbor_af', 'advertise_map_exist', @get_args) - return default_advertise_map_exist if arr.nil? - arr.shift + config_get('bgp_neighbor_af', 'advertise_map_exist', @get_args) end def advertise_map_exist=(arr) @@ -135,9 +134,7 @@ def default_advertise_map_exist # Returns ['', ''] def advertise_map_non_exist - arr = config_get('bgp_neighbor_af', 'advertise_map_non_exist', @get_args) - return default_advertise_map_non_exist if arr.nil? - arr.shift + config_get('bgp_neighbor_af', 'advertise_map_non_exist', @get_args) end def advertise_map_non_exist=(arr) @@ -161,7 +158,7 @@ def default_advertise_map_non_exist def allowas_in_get val = config_get('bgp_neighbor_af', 'allowas_in', @get_args) return nil if val.nil? - val.shift.split.last.to_i + val.split.last.to_i end def allowas_in @@ -213,7 +210,7 @@ def default_as_override def additional_paths_receive val = config_get('bgp_neighbor_af', 'additional_paths_receive', @get_args) return default_additional_paths_receive if val.nil? - /disable/.match(val.first) ? :disable : :enable + /disable/.match(val) ? :disable : :enable end def additional_paths_receive=(val) @@ -238,7 +235,7 @@ def default_additional_paths_receive def additional_paths_send val = config_get('bgp_neighbor_af', 'additional_paths_send', @get_args) return default_additional_paths_send if val.nil? - /disable/.match(val.first) ? :disable : :enable + /disable/.match(val) ? :disable : :enable end def additional_paths_send=(val) @@ -260,8 +257,7 @@ def default_additional_paths_send # Nvgens as True with optional 'route-map ' def default_originate_get val = config_get('bgp_neighbor_af', 'default_originate', @get_args) - return nil if val.nil? - val = val.shift + return nil unless val (val[/route-map/]) ? val.split.last : true end @@ -308,9 +304,7 @@ def default_disable_peer_as_check # ----------------------- # filter-list in def filter_list_in - str = config_get('bgp_neighbor_af', 'filter_list_in', @get_args) - return default_filter_list_in if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'filter_list_in', @get_args) end def filter_list_in=(str) @@ -332,9 +326,7 @@ def default_filter_list_in # ----------------------- # filter-list out def filter_list_out - str = config_get('bgp_neighbor_af', 'filter_list_out', @get_args) - return default_filter_list_out if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'filter_list_out', @get_args) end def filter_list_out=(str) @@ -366,7 +358,7 @@ def max_prefix_get ' *(?\d+)?' \ ' *(?restart|warning-only)?' \ ' *(?\d+)?') - regexp.match(str.shift) + regexp.match(str) end def max_prefix_set(limit, threshold=nil, opt=nil) @@ -454,9 +446,7 @@ def default_next_hop_third_party # ----------------------- # prefix-list in def prefix_list_in - str = config_get('bgp_neighbor_af', 'prefix_list_in', @get_args) - return default_prefix_list_in if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'prefix_list_in', @get_args) end def prefix_list_in=(str) @@ -478,9 +468,7 @@ def default_prefix_list_in # ----------------------- # prefix-list out def prefix_list_out - str = config_get('bgp_neighbor_af', 'prefix_list_out', @get_args) - return default_prefix_list_out if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'prefix_list_out', @get_args) end def prefix_list_out=(str) @@ -501,9 +489,7 @@ def default_prefix_list_out # ----------------------- # route-map in def route_map_in - str = config_get('bgp_neighbor_af', 'route_map_in', @get_args) - return default_route_map_in if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'route_map_in', @get_args) end def route_map_in=(str) @@ -525,9 +511,7 @@ def default_route_map_in # ----------------------- # route-map out def route_map_out - str = config_get('bgp_neighbor_af', 'route_map_out', @get_args) - return default_route_map_out if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'route_map_out', @get_args) end def route_map_out=(str) @@ -548,8 +532,7 @@ def default_route_map_out # ----------------------- # soo def soo - str = config_get('bgp_neighbor_af', 'soo', @get_args) - return default_soo if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'soo', @get_args) end def soo=(str) @@ -651,8 +632,7 @@ def default_soo # ----------------------- # suppress-inactive def suppress_inactive - state = config_get('bgp_neighbor_af', 'suppress_inactive', @get_args) - state ? true : false + config_get('bgp_neighbor_af', 'suppress_inactive', @get_args) end def suppress_inactive=(state) @@ -667,9 +647,7 @@ def default_suppress_inactive # ----------------------- # unsuppress-map def unsuppress_map - str = config_get('bgp_neighbor_af', 'unsuppress_map', @get_args) - return default_unsuppress_map if str.nil? - str.shift.strip + config_get('bgp_neighbor_af', 'unsuppress_map', @get_args) end def unsuppress_map=(str) @@ -689,8 +667,7 @@ def default_unsuppress_map # ----------------------- # weight def weight - int = config_get('bgp_neighbor_af', 'weight', @get_args) - int.nil? ? default_weight : int.shift + config_get('bgp_neighbor_af', 'weight', @get_args) end def weight=(int) diff --git a/lib/cisco_node_utils/cisco_cmn_utils.rb b/lib/cisco_node_utils/cisco_cmn_utils.rb index fc65af56..c7e01e48 100644 --- a/lib/cisco_node_utils/cisco_cmn_utils.rb +++ b/lib/cisco_node_utils/cisco_cmn_utils.rb @@ -1,6 +1,6 @@ # Common Utilities for Puppet Resources. # -# Copyright (c) 2014-2015 Cisco and/or its affiliates. +# Copyright (c) 2014-2016 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. @@ -111,16 +111,35 @@ def self.process_network_mask(network) # Useful for network, redistribute, etc. # should: an array of expected cmds (manifest/recipe) # current: an array of existing cmds on the device - def self.delta_add_remove(should, current=[]) + def self.depth(a) + return 0 unless a.is_a?(Array) + 1 + depth(a[0]) + end + + def self.delta_add_remove(should, current=[], opt=nil) # Remove nil entries from array - should.each(&:compact!) unless should.empty? + should.each(&:compact!) if depth(should) > 1 delta = { add: should - current, remove: current - should } + # Some cli properties cannot be updated, thus must be removed first + return delta if opt == :updates_not_allowed + # Delete entries from :remove if f1 is an update to an existing command delta[:add].each do |id, _| - delta[:remove].delete_if { |f1, f2| [f1, f2] if f1.to_s == id.to_s } + if depth(should) == 1 + delta[:remove].delete_if { |f1| [f1] if f1.to_s == id.to_s } + else + delta[:remove].delete_if { |f1, f2| [f1, f2] if f1.to_s == id.to_s } + end end delta end # delta_add_remove + + # Helper to 0-pad a mac address. + def self.zero_pad_macaddr(mac) + return nil if mac.nil? || mac.empty? + o1, o2, o3 = mac.split('.').map { |o| o.to_i(16).to_s(10) } + sprintf('%04x.%04x.%04x', o1, o2, o3) + end end # class Utils end # module Cisco diff --git a/lib/cisco_node_utils/cmd_ref/README_YAML.md b/lib/cisco_node_utils/cmd_ref/README_YAML.md new file mode 100644 index 00000000..3f7579a0 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/README_YAML.md @@ -0,0 +1,593 @@ +# Command Reference YAML + +The [YAML](http://yaml.org) files in this directory are used with the +`Cisco::CommandReference` module as a way to abstract away differences +between client APIs as well as differences between platforms sharing +the same client API. + +This document describes the structure and semantics of these files. + +* [Introduction](#introduction) +* [Basic attribute definition](#basic-attribute-definition) + * [Wildcard substitution](#wildcard-substitution) + * [Printf-style wildcards](#printf-style-wildcards) + * [Key-value wildcards](#key-value-wildcards) +* [Advanced attribute definition](#advanced-attribute-definition) + * [`_template`](#_template) + * [Platform and API variants](#platform-and-api-variants) + * [Product variants](#product-variants) + * [`_exclude`](#_exclude) + * [Combinations of these](#combinations-of-these) +* [Attribute properties](#attribute-properties) + * [`config_get`](#config_get) + * [`config_get_token`](#config_get_token) + * [`config_get_token_append`](#config_get_token_append) + * [`config_set`](#config_set) + * [`config_set_append`](#config_set_append) + * [`default_value`](#default_value) + * [`default_only`](#default_only) + * [`kind`](#kind) + * [`multiple`](#multiple) + * [`auto_default`](#auto_default) + * [`test_config_get` and `test_config_get_regex`](#test_config_get-and-test_config_get_regex) + * [`test_config_result`](#test_config_result) +* [Style Guide](#style-guide) + +## Introduction + +Each file describes a single 'feature' (a closely related set of +configurable attributes). The YAML within the file defines the set of +'attributes' belonging to this feature. When a `CommandReference` object +is instantiated, the user can look up any given attribute using the +`lookup('feature_name', 'attribute_name')` API. Usually, instead of calling +this API directly, node utility classes will call the various `Node` APIs, +for example: + +```ruby +config_set('feature_name', 'attribute_name', *args) +value = config_get('feature_name', 'attribute_name') +default = config_get_default('feature_name', 'attribute_name') +``` + +## Basic attribute definition + +The simplest definition of an attribute directly sets one or more properties +of this attribute. These properties' values can generally be set to any +basic Ruby type such as string, boolean, integer, array, or regexp. +An example: + +```yaml +# vtp.yaml +domain: + config_get: "show vtp status" + config_get_token: "domain_name" + config_set: "vtp domain " + +filename: + config_get: "show running vtp" + config_get_token: '/vtp file (\S+)/' + config_set: " vtp file " + default_value: "" +``` + +In the above example, two attributes are defined: ('vtp', 'domain') and ('vtp', +'filename'). + +Note that all attribute properties are optional and may be omitted if not +needed. In the above, example 'domain' does not have a value defined for +`default_value` but 'filename' does have a default. + +### Wildcard substitution + +The `config_get_token` and `config_set` properties (and their associated +`_append` variants) all support two forms of wildcarding - printf-style and +key-value. Key-value is generally preferred, as described below. + +#### Printf-style wildcards + +```yaml +# tacacs_server_host.yaml +encryption: + config_set: '%s tacacs-server host %s key %s %s' +``` + +This permits parameter values to be passed as a simple sequence to generate the resulting string or regexp: + +```ruby +irb(main):009:0> ref = cr.lookup('tacacs_server_host', 'encryption') +irb(main):010:0> ref.config_set('no', 'myhost', 'md5', 'mypassword') +=> ["no tacacs-server host myhost key md5 mypassword"] +``` + +Printf-style wildcards are quick to implement and concise, but less flexible - in particular they cannot handle a case where different platforms (or different +client APIs!) take parameters in a different order - and less readable in +the Ruby code. + +#### Key-value wildcards + +```yaml +# ospf.yaml +auto_cost: + config_set: ['router ospf ', 'auto-cost reference-bandwidth '] +``` + +This requires parameter values to be passed as a hash: + +```ruby +irb(main):015:0> ref = cr.lookup('ospf', 'auto_cost') +irb(main):016:0> ref.config_set(name: 'red', cost: '40', type: 'Gbps') +=> ["router ospf red", "auto-cost reference-bandwidth 40 Gbps"] +``` + +Array elements that contain a parameter that is *not* included in the argument hash are not included in the result: + +```ruby +irb(main):017:0> ref.config_set(name: 'red', cost: '40') +=> ["router ospf red"] +``` + +If this process results in an empty array, then an `ArgumentError` is raised to indicate that not enough parameters were supplied. + +Key-value wildcards are moderately more complex to implement than Printf-style wildcards but they are more readable in the Ruby code and are flexible enough to handle significant platform differences in CLI. Key-value wildcards are therefore the recommended approach for new development. + +## Advanced attribute definition + +### `_template` + +The optional `_template` section can be used to define base parameters for all +attributes of a given feature. For example, all interface attributes might be +checked with the `show running-config interface all` command, and all +attributes might be set by first entering the interface configuration submode +with the `interface ` configuration command. Thus, you might have: + +```yaml +# interface.yaml +_template: + config_get: 'show running-config interface all' + config_get_token: '/^interface $/' + config_set: 'interface ' + +access_vlan: + config_get_token_append: '/^switchport access vlan (.*)$/' + config_set_append: 'switchport access vlan ' + +description: + config_get_token_append: '/^description (.*)$/' + config_set_append: 'description ' + +... +``` + +instead of the more repetitive (but equally valid): + +```yaml +# interface.yaml +access_vlan: + config_get: 'show running interface all' + config_get_token: ['/^interface $/i', '/^switchport access vlan (.*)$/'] + config_set: ['interface ', 'switchport access vlan '] + +description: + config_get: 'show running-config interface all' + config_get_token: ['/^interface $/i', '/^description (.*)$/'] + config_set: ['interface ', 'description '] + +... +``` + +### Platform and API variants + +Clients for different Cisco platforms may use different APIs. Currently the only supported API is NXAPI (CLI-based API used for Cisco Nexus platforms). Often the CLI or other input/output formats (YANG, etc.) needed will vary between APIs, so the YAML must be able to accomodate this. + +Any of the attribute properties can be subdivided by platform and API type by using the +combination of API type and platform type as a key. For example, interface VRF membership defaults to "" (no VRF) on both Nexus and IOS XR platforms, but the CLI is 'vrf member ' for Nexus and 'vrf ' for IOS XR. Thus, the YAML could be written as: + +```yaml +# interface.yaml +vrf: + default_value: "" + cli_nexus: + config_get_token_append: '/^vrf member (.*)/' + config_set_append: " vrf member " +``` + +and later, once we have a CLI-based API for IOS XR, this could be extended: + +```yaml +# interface.yaml +vrf: + default_value: "" + cli_nexus: + config_get_token_append: '/^vrf member (.*)/' + config_set_append: " vrf member " + cli_ios_xr: + config_get_token_append: '/^vrf (.*)/' + config_set_append: " vrf " +``` + +### Product variants + +Any of the attribute properties can be subdivided by platform product ID string +using a regexp against the product ID as a key. When one or more regexp keys +are defined thus, you can also use the special key `else` to provide values +for all products that do not match any of the given regexps: + +```yaml +# show_version.yaml +system_image: + /N9/: + config_get_token: "kick_file_name" + test_config_get_regex: '/.*NXOS image file is: (.*)$.*/' + else: + config_get_token: "isan_file_name" + test_config_get_regex: '/.*system image file is: (.*)$.*/' +``` + +### `_exclude` + +Related to product variants, an `_exclude` entry can be used to mark an entire feature or a given feature attribute as not applicable to a particular set of products. For example, if feature 'fabricpath' doesn't apply to the N3K or N9K platforms, it can be excluded altogether from those platforms by a single `_exclude` entry at the top of the file: + +```yaml +# fabricpath.yaml +--- +_exclude: [/N3/, /N9/] + +_template: +... +``` + +Individual feature attributes can also be excluded in this way: + +```yaml +attribute: + _exclude: + - /N7/ + default_value: true + config_get: 'show attribute' + config_set: 'attribute' +``` + +When a feature or attribute is excluded in this way, attempting to call `config_get` or `config_set` on an excluded node will result in a `Cisco::UnsupportedError` being raised. Calling `config_get_default` on such a node will always return `nil`. + +### Combinations of these + +In many cases, supporting multiple platforms and multiple products will require +using several or all of the above options. + +Using `_template` in combination with API variants: + +```yaml +# inventory.yaml +_template: + cli_ios_xr: + config_get: 'show inventory | begin "Rack 0"' + test_config_get: 'show inventory' + cli_nexus: + config_get: 'show inventory' + test_config_get: 'show inventory | no-more' + +productid: + cli_ios_xr: + config_get_token: '/PID: ([^ ,]+)/' + cli_nexus: + config_get_token: ["TABLE_inv", "ROW_inv", 0, "productid"] +``` + +Using platform variants and product variants together: + +```yaml +# inventory.yaml +description: + config_get_token: "chassis_id" + cli_nexus: + /N7/: + test_config_get_regex: '/.*Hardware\n cisco (\w+ \w+ \(\w+ \w+\) \w+).*/' + else: + test_config_get_regex: '/Hardware\n cisco (([^(\n]+|\(\d+ Slot\))+\w+)/' + cli_ios_xr: + config_get: 'show inventory | inc "Rack 0"' + config_get_token: '/DESCR: "(.*)"/' + test_config_get: 'show inventory | inc "Rack 0"' + test_config_get_regex: '/DESCR: "(.*)"/' +``` + +## Attribute properties + +### `config_get` + +`config_get` must be a single string representing the CLI command (usually a +`show` command) to be used to display the information needed to get the +current value of this attribute. + +```yaml +# interface_ospf.yaml +area: + config_get: 'show running interface all' +``` + +### `config_get_token` + +`config_get_token` can be a single string, a single regex, an array of strings, +or an array of regexs. + +If this value is a string or array of strings, then the `config_get` command +will be executed to produce _structured_ output and the string(s) will be +used as lookup keys. + +**WARNING: structured output, although elegant, may not be supported for all commands or all platforms. Use with caution.** + +```yaml +# show_version.yaml +cpu: + config_get: 'show version' + config_get_token: 'cpu_name' + # config_get('show_version', 'cpu') returns structured_output['cpu_name'] +``` + +```yaml +# inventory.yaml +productid: + config_get: 'show inventory' + config_get_token: ['TABLE_inv', 'ROW_inv', 0, 'productid'] + # config_get('inventory', 'productid') returns + # structured_output['TABLE_inv']['ROW_inv'][0]['productid'] +``` + +If this value is a regexp or array of regexps, then the `config_get` command +will be executed to produce _plaintext_ output. + +For a single regexp, it will be used to match against the plaintext. + +```yaml +# memory.yaml +total: + config_get: 'show system resources' + config_get_token: '/Memory.* (\S+) total/' + # config_get('memory', 'total') returns + # plaintext_output.scan(/Memory.* (\S+) total/) +``` + +For an array of regex, then the plaintext is assumed to be hierarchical in +nature (like `show running-config`) and the regexs are used to filter down +through the hierarchy. + +```yaml +# interface.yaml +description: + config_get: 'show running interface all' + config_get_token: ['/^interface $/i', '/^description (.*)/'] + # config_get('interface', 'description', name: 'Ethernet1/1') gets the + # plaintext output, finds the subsection under /^interface Ethernet1/1$/i, + # then finds the line matching /^description (.*)$/ in that subsection +``` + +### `config_get_token_append` + +When using a `_template` section, an attribute can use +`config_get_token_append` to extend the `config_get_token` value provided by +the template instead of replacing it: + +```yaml +# interface.yaml +_template: + config_get: 'show running-config interface all' + config_get_token: '/^interface $/i' + +description: + config_get_token_append: '/^description (.*)$/' + # config_get_token value for 'description' is now: + # ['/^interface $/i', '/^description (.*)$/'] +``` + +This can also be used to specify conditional tokens which may or may not be +used depending on the set of parameters passed into `config_get()`: + +```yaml +# ospf.yaml +_template: + config_get: 'show running ospf all' + config_get_token: '/^router ospf $/' + config_get_token_append: + - '/^vrf $/' + +router_id: + config_get_token_append: '/^router-id (\S+)$/' +``` + +In this example, the `vrf` parameter is optional and a different +`config_get_token` value will be generated depending on its presence or absence: + +```ruby +irb(main):008:0> ref = cr.lookup('ospf', 'router_id') +irb(main):012:0> ref.config_get_token(name: 'red') +=> [/^router ospf red$/, /^router-id (\S+)?$/] +irb(main):013:0> ref.config_get_token(name: 'red', vrf: 'blue') +=> [/^router ospf red$/, /^vrf blue$/, /^router-id (\S+)?$/] +``` + +### `config_set` + +The `config_set` parameter is a string or array of strings representing the +configuration CLI command(s) used to set the value of the attribute. + +```yaml +# interface.yaml +create: + config_set: 'interface ' + +description: + config_set: ['interface ', 'description '] +``` + +### `config_set_append` + +When using a `_template` section, an attribute can use `config_set_append` to +extend the `config_set` value provided by the template instead of replacing it: + +```yaml +# interface.yaml +_template: + config_set: 'interface ' + +access_vlan: + config_set_append: 'switchport access vlan ' + # config_set value for 'access_vlan' is now: + # ['interface ', 'switchport access vlan '] +``` + +Much like `config_get_token_append`, this can also be used to specify optional +commands that can be included or omitted as needed: + +```yaml +# ospf.yaml +_template: + config_set: 'router ospf ' + config_set_append: + - 'vrf ' + +router_id: + config_set_append: 'router-id ' +``` + +```ruby +irb(main):008:0> ref = cr.lookup('ospf', 'router_id') +irb(main):017:0> ref.config_set(name: 'red', state: nil, router_id: '1.1.1.1') +=> ["router ospf red", " router-id 1.1.1.1"] +irb(main):019:0> ref.config_set(name: 'red', vrf: 'blue', + state: 'no', router_id: '1.1.1.1') +=> ["router ospf red", "vrf blue", "no router-id 1.1.1.1"] +``` + +### `default_value` + +If there is a default value for this attribute when not otherwise specified by the user, the `default_value` parameter describes it. This can be a string, boolean, integer, array, or nil. + +```yaml +description: + default_value: '' + +hello_interval: + default_value: 10 + +auto_cost: + default_value: [40, 'Gbps'] + +ipv4_address: + # YAML represents nil as ~ + default_value: ~ +``` + +By convention, a `default_value` of `''` (empty string) represents a configurable property that defaults to absent, while a default of `nil` (Ruby) or `~` (YAML) represents a property that has no meaningful default at all. + +`config_get()` will return the defined `default_value` if the defined `config_get_token` does not match anything on the node. Normally this is desirable behavior, but you can use [`auto_default`](#auto_default) to change this behavior if needed. + +### `default_only` + +Some attributes may be hard-coded in such a way that they have a meaningful default value but no relevant `config_get_token` or `config_set` behavior. For such attributes, the key `default_only` should be used as an alternative to `default_value`. The benefit of using this key is that it causes the `config_get()` API to always return the default value and `config_set()` to raise a `Cisco::UnsupportedError`. + +```yaml +negotiate_auto_ethernet: + kind: boolean + cli_nexus: + /(N7|C3064)/: + # this feature is always off on these platforms and cannot be changed + default_only: false + else: + config_get_token_append: '/^(no )?negotiate auto$/' + config_set_append: "%s negotiate auto" + default_value: true +``` + +### `kind` + +The `kind` attribute is used to specify the type of value that is returned by `config_get()`. If unspecified, no attempt will be made to guess the return type and it will typically be one of string, array, or `nil`. If `kind` is specified, type conversion will automatically be performed as follows: + +* `kind: boolean` - value will be coerced to `true`/`false`, and if no `default_value` is set, a `nil` result will be returned as `false`. +* `kind: int` - value will be coerced to an integer, and if no `default_value` is set, a `nil` result will be returned as `0`. +* `kind: string` - value will be coerced to a string, leading/trailing whitespace will be stripped, and if no `default_value` is set, a `nil` result will be returned as `''`. + +```yaml +# interface.yaml +--- +access_vlan: + config_get_token_append: '/^switchport access vlan (.*)$/' + config_set_append: "switchport access vlan %s" + kind: int + default_value: 1 + +description: + kind: string + config_get_token_append: '/^description (.*)/' + config_set_append: "%s description %s" + default_value: "" + +feature_lacp: + kind: boolean + config_get: "show running | i ^feature" + config_get_token: '/^feature lacp$/' + config_set: "%s feature lacp" +``` + +### `multiple` + +By default, `config_get_token` should uniquely identify a single configuration entry, and `config_get()` will raise an error if more than one match is found. For a small number of attributes, it may be desirable to permit multiple matches (in particular, '`all_*`' attributes that are used up to look up all interfaces, all VRFs, etc.). For such attributes, you must specify the key `multiple:`. When this key is present, `config_get()` will permit multiple matches and will return an array of matches (even if there is only a single match). + +```yaml +# interface.yaml +--- +all_interfaces: + multiple: + config_get_token: '/^interface (.*)/' +``` + +### `auto_default` + +Normally, if `config_get_token` produces no match, `config_get()` will return the defined `default_value` for this attribute. For some attributes, this may not be desirable. Setting `auto_default: false` will force `config_get()` to return `nil` in the non-matching case instead. + +```yaml +# bgp_af.yaml +--- +dampen_igp_metric: + # dampen_igp_metric defaults to nil (disabled), + # but its default numeric value when enabled is 600. + # If disabled, we want config_get() to return nil, not 600. + default_value: 600 + auto_default: false + kind: int + config_get_token_append: '/^dampen-igp-metric (\d+)$/' + config_set_append: ' dampen-igp-metric ' +``` + +### `test_config_get` and `test_config_get_regex` + +Test-only equivalents to `config_get` and `config_get_token` - a show command +to be executed over telnet by the minitest unit test scripts, and a regex +(or array thereof) to match in the resulting plaintext output. +Should only be referenced by test scripts, never by a feature provider itself. + +```yaml +# show_version.yaml +boot_image: + test_config_get: 'show version | no-more' + test_config_get_regex: '/NXOS image file is: (.*)$/' +``` + +### `test_config_result` + +Test-only container for input-result pairs that might differ by platform. +Should only be referenced by test scripts, never by a feature provider itself. + +```yaml +# vtp.yaml +version: + /N7/: + test_config_result: + 3: 3 + else: + test_config_result: + 3: 'Cisco::CliError' +``` + +## Style Guide + +Please see [YAML Best Practices](../../../docs/README-develop-best-practices.md#ydbp). diff --git a/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml b/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml new file mode 100644 index 00000000..742ecce6 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml @@ -0,0 +1,22 @@ +# aaa_auth_login_service +--- +groups: + config_get: "show aaa authentication" + config_get_token: ["TABLE_AuthenMethods", "ROW_AuthenMethods"] + # this set is only used when there are groups to configure + config_set: "%s aaa authentication login %s group %s %s" + default_value: [] + multiple: + +method: + auto_default: false + config_get: "show aaa authentication" + config_get_token: '/^\s*%s:.*(local|none)\s*$/' + # this set is only used when there are no groups to configure + config_set: "%s aaa authentication login %s %s" + default_value: :local + +services: + config_get: "show run aaa all" + config_get_token: '/^aaa authentication login (\S+) (?:none|group|local)/' + multiple: diff --git a/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml b/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml new file mode 100644 index 00000000..532a49dd --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml @@ -0,0 +1,31 @@ +# aaa_authentication_login +--- +ascii_authentication: + config_get: "show run aaa all" + config_get_token: '/^aaa authentication login ascii-authentication/' + config_set: "%s aaa authentication login ascii-authentication" + default_value: false + +chap: + config_get: "show run aaa all" + config_get_token: '/^aaa authentication login chap enable/' + config_set: "%s aaa authentication login chap enable" + default_value: false + +error_display: + config_get: "show run aaa all" + config_get_token: '/^aaa authentication login error-enable/' + config_set: "%s aaa authentication login error-enable" + default_value: false + +mschap: + config_get: "show run aaa all" + config_get_token: '/^aaa authentication login mschap enable/' + config_set: "%s aaa authentication login mschap enable" + default_value: false + +mschapv2: + config_get: "show run aaa all" + config_get_token: '/^aaa authentication login mschapv2 enable/' + config_set: "%s aaa authentication login mschapv2 enable" + default_value: false diff --git a/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml b/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml new file mode 100644 index 00000000..4a8dd09b --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml @@ -0,0 +1,22 @@ +# aaa_authorization_service +--- +groups: + config_get: "show aaa authorization all" + config_get_token: ["TABLE_cmd_methods", "ROW_cmd_methods"] + # this set is only used when there are groups to configure + config_set: "%s aaa authorization %s %s group %s %s" + default_value: [] + multiple: + +method: + auto_default: false + config_get: "show aaa authorization all" + config_get_token: '/^\s+%s authorization for %s:.*(local) ?$/' + # this set is only used when there are no groups to configure + config_set: "%s aaa authorization %s %s local" + default_value: :local + +services: + config_get: "show run aaa all" + config_get_token: '/^aaa authorization (\S+) (\S+) .*(?:local)? ?$/' + multiple: diff --git a/lib/cisco_node_utils/cmd_ref/acl.yaml b/lib/cisco_node_utils/cmd_ref/acl.yaml new file mode 100644 index 00000000..808c23d3 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/acl.yaml @@ -0,0 +1,43 @@ +# Command Reference Common ACL +# +# For documentation please see: +# - README_YAML.md +# +--- +_template: + config_get: 'show run aclmgr' + config_get_token: '/^ access-list $/' + config_set: ' access-list ' + +ace: + config_get_token_append: '/^ .+$/' + config_set_append: ' ' + +ace_destroy: + config_set_append: 'no ' + +ace_remark: + config_set_append: ' remark ' + +acl: + config_get_token: '/^ access-list (\S+)$/' + config_set: ' access-list ' + +all_aces: + multiple: + config_get_token_append: '/^(\d+) .+$/' + +all_acls: + multiple: + config_get_token: '/^ access-list (\S+)$/' + +fragments: + config_get_token_append: '/fragments (\S+)$/' + config_set_append: ' fragments ' + default_value: ~ + +stats_per_entry: + kind: boolean + config_get_token_append: '/statistics per-entry$/' + config_set_append: ' statistics per-entry' + default_value: false diff --git a/lib/cisco_node_utils/cmd_ref/bgp.yaml b/lib/cisco_node_utils/cmd_ref/bgp.yaml new file mode 100644 index 00000000..28e51966 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/bgp.yaml @@ -0,0 +1,242 @@ +# bgp.yaml +--- +_template: + config_get: "show running bgp all" + config_get_token: '/^router bgp $/' + config_get_token_append: + - '/^vrf $/' + config_set: "router bgp " + config_set_append: + - "vrf " + +address_family: + config_set_append: ' address-family ' + +bestpath_always_compare_med: + kind: boolean + config_get_token_append: '/^bestpath always-compare-med$/' + config_set_append: ' bestpath always-compare-med' + default_value: false + +bestpath_aspath_multipath_relax: + kind: boolean + config_get_token_append: '/^bestpath as-path multipath-relax$/' + config_set_append: ' bestpath as-path multipath-relax' + default_value: false + +bestpath_compare_routerid: + kind: boolean + config_get_token_append: '/^bestpath compare-routerid$/' + config_set_append: ' bestpath compare-routerid' + default_value: false + +bestpath_cost_community_ignore: + kind: boolean + config_get_token_append: '/^bestpath cost-community ignore$/' + config_set_append: ' bestpath cost-community ignore' + default_value: false + +bestpath_med_confed: + kind: boolean + config_get_token_append: '/^bestpath med confed$/' + config_set_append: ' bestpath med confed' + default_value: false + +bestpath_med_missing_as_worst: + kind: boolean + config_get_token_append: '/^bestpath med missing-as-worst$/' + config_set_append: ' bestpath med missing-as-worst' + default_value: false + +bestpath_med_non_deterministic: + kind: boolean + config_get_token_append: '/^bestpath med non-deterministic$/' + config_set_append: ' bestpath med non-deterministic' + default_value: false + +cluster_id: + config_get_token_append: '/^cluster-id (\S+)$/' + config_set_append: ' cluster-id ' + default_value: "" + +confederation_id: + config_get_token_append: '/^confederation identifier (\d+|\d+.\d+)$/' + config_set_append: ' confederation identifier ' + default_value: "" + +confederation_peers: + config_get_token_append: '/^confederation peers (.*)$/' + config_set_append: ' confederation peers ' + default_value: "" + +create_destroy_neighbor: + config_set_append: ' neighbor ' + +disable_policy_batching: + config_get_token_append: '/^disable-policy-batching$/' + config_set_append: ' disable-policy-batching' + default_value: false + +disable_policy_batching_ipv4: + _exclude: [/N(5|6|7)/] + config_get_token_append: '/^disable-policy-batching ipv4 prefix-list (\S+)$/' + config_set_append: ' disable-policy-batching ipv4 prefix-list ' + default_value: "" + +disable_policy_batching_ipv6: + _exclude: [/N(5|6|7)/] + config_get_token_append: '/^disable-policy-batching ipv6 prefix-list (\S+)$/' + config_set_append: ' disable-policy-batching ipv6 prefix-list ' + default_value: "" + +enforce_first_as: + kind: boolean + config_get_token_append: '/^(no )?enforce-first-as$/' + config_set_append: ' enforce-first-as' + default_value: true + +event_history_cli: + config_get_token_append: '/^(no )?event-history cli(?: size (\S+))?$/' + config_set_append: ' event-history cli ' + default_value: 'size_small' + +event_history_detail: + config_get_token_append: '/^(no )?event-history detail(?: size (\S+))?$/' + config_set_append: ' event-history detail ' + auto_default: false + default_value: 'size_disable' + +event_history_events: + config_get_token_append: '/^(no )?event-history events(?: size (\S+))?$/' + config_set_append: ' event-history events ' + default_value: 'size_small' + +event_history_periodic: + config_get_token_append: '/^(no )?event-history periodic(?: size (\S+))?$/' + config_set_append: ' event-history periodic ' + default_value: 'size_small' + +fast_external_fallover: + kind: boolean + config_get_token_append: '/^(no )?fast-external-fallover$/' + config_set_append: ' fast-external-fallover' + default_value: true + +flush_routes: + kind: boolean + config_get_token_append: '/^flush-routes$/' + config_set_append: ' flush-routes' + default_value: false + +graceful_restart: + kind: boolean + config_get_token_append: '/^(no )?graceful-restart$/' + config_set_append: ' graceful-restart' + default_value: true + +graceful_restart_helper: + kind: boolean + config_get_token_append: '/^graceful-restart-helper$/' + config_set_append: ' graceful-restart-helper' + default_value: false + +graceful_restart_timers_restart: + kind: int + config_get_token_append: '/^graceful-restart restart-time (\d+)$/' + config_set_append: ' graceful-restart restart-time ' + default_value: 120 + +graceful_restart_timers_stalepath_time: + kind: int + config_get_token_append: '/^graceful-restart stalepath-time (\d+)$/' + config_set_append: ' graceful-restart stalepath-time ' + default_value: 300 + +isolate: + kind: boolean + config_get_token_append: '/^isolate$/' + config_set_append: ' isolate' + default_value: false + +log_neighbor_changes: + kind: boolean + config_get_token_append: '/^log-neighbor-changes$/' + config_set_append: ' log-neighbor-changes' + default_value: false + +maxas_limit: + kind: int + config_get_token_append: '/^maxas-limit (\d+)$/' + config_set_append: ' maxas-limit ' + default_value: false + +neighbor_down_fib_accelerate: + _exclude: [/N(5|6|7)/] + kind: boolean + config_get_token_append: '/^neighbor-down fib-accelerate$/' + config_set_append: ' neighbor-down fib-accelerate' + default_value: false + +reconnect_interval: + _exclude: [/N(5|6|7)/] + kind: int + config_get_token_append: '/^reconnect-interval (\d+)$/' + config_set_append: ' reconnect-interval ' + default_value: 60 + +route_distinguisher: + # This property is also supported by vrf.yaml + config_get_token: ['/^vrf context $/i', '/^rd (\S+)$/'] + config_set: ["vrf context ", " rd "] + default_value: "" + +router: + config_get: "show running bgp" + config_get_token: '/^router bgp ([\d.]+)$/' + config_set: " router bgp " + +router_id: + config_get_token_append: '/^router-id (\S+)$/' + config_set_append: ' router-id ' + default_value: "" + +shutdown: + # Shutdown only applies to global bgp + kind: boolean + config_get: "show running bgp" + config_get_token: ['/^router bgp %s$/i', '/^shutdown$/'] + config_set: ["router bgp ", " shutdown"] + default_value: false + +suppress_fib_pending: + kind: boolean + config_get_token_append: '/^suppress-fib-pending$/' + config_set_append: ' suppress-fib-pending' + default_value: false + +timer_bestpath_limit: + kind: int + config_get_token_append: '/^timers bestpath-limit (\d+)(?: always)?$/' + config_set_append: ' timers bestpath-limit ' + default_value: 300 + +timer_bestpath_limit_always: + kind: boolean + config_get_token_append: '/^timers bestpath-limit \d+ always$/' + config_set_append: ' timers bestpath-limit always' + default_value: false + +timer_bgp_hold: + default_value: 180 + +timer_bgp_keepalive: + default_value: 60 + +timer_bgp_keepalive_hold: + config_get_token_append: '/^timers bgp (\d+) (\d+)$/' + config_set_append: ' timers bgp ' + +vrf: + multiple: true + config_get_token_append: '/^vrf\s+(\S+)$/' + config_set: ["router bgp ", " vrf ", "end"] diff --git a/lib/cisco_node_utils/cmd_ref/bgp_af.yaml b/lib/cisco_node_utils/cmd_ref/bgp_af.yaml new file mode 100644 index 00000000..86a41ffa --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/bgp_af.yaml @@ -0,0 +1,164 @@ +# bgp_af.yaml +--- +_template: + config_get: 'show running bgp all' + config_get_token: '/^router bgp $/' + config_get_token_append: + - '/^vrf $/' + - '/^address-family $/' + config_set: "router bgp " + config_set_append: + - 'vrf ' + - 'address-family ' + +additional_paths_install: + kind: boolean + config_get_token_append: '/^additional-paths install backup$/' + config_set_append: ' additional-paths install backup' + default_value: false + +additional_paths_receive: + kind: boolean + config_get_token_append: '/^additional-paths receive$/' + config_set_append: ' additional-paths receive' + default_value: false + +additional_paths_selection: + kind: string + config_get_token_append: '/^additional-paths selection route-map (.*)$/' + config_set_append: ' additional-paths selection route-map ' + default_value: "" + +additional_paths_send: + kind: boolean + config_get_token_append: '/^additional-paths send$/' + config_set_append: ' additional-paths send' + default_value: false + +advertise_l2vpn_evpn: + _exclude: [/N(3|6)/] + kind: boolean + config_get_token_append: '/^advertise l2vpn evpn$/' + config_set_append: ' advertise l2vpn evpn' + default_value: false + +all_afs: + multiple: true + config_get_token_append: '/^address-family (\S+) (\S+)$/' + +client_to_client: + kind: boolean + config_get_token_append: '/^(no )?client-to-client reflection$/' + config_set_append: ' client-to-client reflection' + default_value: true + +dampen_igp_metric: + config_get_token_append: '/^(no )?dampen-igp-metric *(\d+)?$/' + config_set_append: ' dampen-igp-metric ' + default_value: 600 + +dampening: + auto_default: false + config_get_token_append: '/^dampening(?: (?:(\d+) (\d+) (\d+) (\d+)|route-map (.*)))?$/' + config_set_append: ' dampening ' + default_value: "" + +dampening_half_time: + default_value: 15 + +dampening_max_suppress_time: + default_value: 45 + +dampening_reuse_time: + default_value: 750 + +dampening_routemap: + default_value: "" + +dampening_state: + default_value: true + +dampening_suppress_time: + default_value: 2000 + +default_information: + kind: boolean + config_get_token_append: '/^default-information originate$/' + config_set_append: ' default-information originate' + default_value: false + +default_metric: + kind: int + config_get_token_append: '/^default-metric (\d+)$/' + config_set_append: ' default-metric ' + default_value: false + +distance: + config_get_token_append: '/^distance (\d+) (\d+) (\d+)$/' + config_set_append: ' distance ' + +distance_ebgp: + default_value: 20 + +distance_ibgp: + default_value: 200 + +distance_local: + default_value: 220 + +inject_map: + multiple: true + config_get_token_append: '/^inject-map (\S+) exist-map (\S+) *(copy-attributes)?$/' + config_set_append: ' inject-map exist-map ' + default_value: [] + +maximum_paths: + kind: int + config_get_token_append: '/^maximum-paths (\d+)$/' + config_set_append: ' maximum-paths ' + default_value: 1 + +maximum_paths_ibgp: + kind: int + config_get_token_append: '/^maximum-paths ibgp (\d+)$/' + config_set_append: ' maximum-paths ibgp ' + default_value: 1 + +network: + multiple: true + config_get_token_append: '/^network (\S+) ?(?:route-map )?(\S+)?$/' + config_set_append: ' network ' + default_value: [] + +next_hop_route_map: + config_get_token_append: '/^nexthop route-map (.*)$/' + config_set_append: ' nexthop route-map ' + default_value: "" + +redistribute: + multiple: true + config_get_token_append: '/^redistribute (\S+ ?\S+?) ?(?:route-map (\S+))?$/' + config_set_append: ' redistribute ' + default_value: [] + +redistribute_policy: + # route-map/policy is optional on some platforms, required on others + config_set_append: ' redistribute route-map ' + +suppress_inactive: + kind: boolean + config_get_token_append: '/^suppress-inactive$/' + config_set_append: ' suppress-inactive' + default_value: false + +table_map: + kind: string + config_get_token_append: '/^table-map (\S+)(?: filter)?$/' + config_set_append: ' table-map ' + default_value: "" + +table_map_filter: + kind: boolean + config_get_token_append: '/^table-map \S+ filter$/' + config_set_append: ' table-map filter' + default_value: false diff --git a/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml b/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml new file mode 100644 index 00000000..203d5392 --- /dev/null +++ b/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml @@ -0,0 +1,131 @@ +# bgp_neighbor.yaml +--- +_template: + config_get: "show running bgp all" + config_get_token: '/^router bgp $/' + config_get_token_append: + - '/^vrf $/' + - '/^neighbor $/' + config_set: "router bgp " + config_set_append: + - "vrf " + - "neighbor " + +af: + config_set_append: ' address-family ' + +all_neighbors: + multiple: true + config_get_token_append: '/^neighbor (\S+)$/' + +capability_negotiation: + auto_default: false + kind: boolean + config_get_token_append: '/^dont-capability-negotiate$/' + config_set_append: ' dont-capability-negotiate' + default_value: true + +connected_check: + auto_default: false + kind: boolean + config_get_token_append: '/^disable-connected-check$/' + config_set_append: ' disable-connected-check' + default_value: true + +description: + kind: string + config_get_token_append: '/^description (.*)/' + config_set_append: ' description ' + default_value: "" + +dynamic_capability: + kind: boolean + config_get_token_append: '/^(no )?dynamic-capability$/' + config_set_append: ' dynamic-capability' + default_value: true + +ebgp_multihop: + auto_default: false + config_get_token_append: '/^ebgp-multihop (\d+)$/' + config_set_append: ' ebgp-multihop ' + default_value: false + +local_as: + config_get_token_append: '/^local-as (\d*?.?\d+?)$/' + config_set_append: ' local-as ' + default_value: 0 + +log_neighbor_changes: + _exclude: [/N(5|6|7)/] + multiple: true # not actually, but we get an array of matches + auto_default: false + config_get_token_append: '/^log-neighbor-changes\s+??(\S+)?\s+??$/' + config_set_append: ' log-neighbor-changes ' + default_value: "inherit" + +low_memory_exempt: + kind: boolean + config_get_token_append: '/^low-memory exempt$/' + config_set_append: ' low-memory exempt' + default_value: false + +maximum_peers: + kind: int + config_get_token_append: '/^maximum-peers (\d+)$/' + config_set_append: ' maximum-peers ' + default_value: 0 + +password: + config_get_token_append: '/^password \d+ (\S+)$/' + config_set_append: ' password ' + default_value: "" + +password_type: + kind: int + config_get_token_append: '/^password (\d+)/' + default_value: 0 + +remote_as: + config_get_token_append: '/^remote-as (\d*?.?\d+?)$/' + config_set_append: ' remote-as ' + default_value: 0 + +remove_private_as: + multiple: true # not actually, but we get an array of matches + auto_default: false + config_get_token_append: '/^remove-private-as\s+??(\S+)?\s+??$/' + config_set_append: ' remove-private-as