├── .gitignore ├── .pmtignore ├── .rubocop.yml ├── .simplecov ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── SUPPORT.md ├── docs ├── README-agent-install.md ├── README-develop-beaker-scripts.md ├── README-test-execution.md └── README-utilities.md ├── examples ├── beaker_test_template.rb └── site.pp ├── files └── models │ └── defaults │ ├── vrf-voip.json │ ├── vrfs.json │ └── vrfs.xml ├── lib ├── .rubucop.yml ├── facter │ └── cisco_yang.rb ├── minitest │ ├── environment_plugin.rb │ └── log_level_plugin.rb ├── puppet │ ├── feature │ │ ├── json.rb │ │ └── node_util.rb │ ├── property │ │ ├── yang_json.rb │ │ ├── yang_netconf.rb │ │ ├── yang_netconf_property.rb │ │ └── yang_property.rb │ ├── provider │ │ ├── cisco_yang │ │ │ └── cisco.rb │ │ └── cisco_yang_netconf │ │ │ └── cisco.rb │ └── type │ │ ├── cisco_yang.rb │ │ └── cisco_yang_netconf.rb └── util │ ├── client.rb │ ├── client │ ├── client.rb │ ├── grpc.rb │ ├── grpc │ │ ├── client.rb │ │ ├── ems.proto │ │ ├── ems.rb │ │ └── ems_services.rb │ ├── netconf.rb │ ├── netconf │ │ ├── client.rb │ │ ├── netconf.rb │ │ └── netconf_client.rb │ └── utils.rb │ ├── constants.rb │ ├── environment.rb │ ├── exceptions.rb │ ├── logger.rb │ ├── node.rb │ ├── node_util.rb │ ├── platform.rb │ ├── show_running_yang.rb │ ├── yang.rb │ └── yang_accessor.rb ├── metadata.json └── tests ├── .rubopcop.yml ├── beaker_tests ├── all │ ├── cisco_yang │ │ ├── model_tests │ │ │ ├── test_cisco_ios_xr_infra_rsi_cfg_vrf_groups.rb │ │ │ ├── test_cisco_ios_xr_infra_rsi_cfg_vrfs.rb │ │ │ ├── test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb │ │ │ └── yang │ │ │ │ └── bgp.yang │ │ ├── test_create_vrf.rb │ │ ├── test_delete_properties.rb │ │ ├── test_delete_vrf.rb │ │ ├── test_file.rb │ │ ├── test_merge_properties_vrf.rb │ │ ├── test_merge_vrf.rb │ │ ├── test_replace_properties_vrf.rb │ │ ├── test_replace_srlg.rb │ │ └── test_replace_vrf.rb │ └── cisco_yang_netconf │ │ ├── test_create_vrf.rb │ │ ├── test_file_merge_vrf.rb │ │ ├── test_file_replace_vrf.rb │ │ ├── test_merge_properties_vrf.rb │ │ ├── test_merge_vrf.rb │ │ ├── test_replace_properties_vrf.rb │ │ ├── test_replace_srlg.rb │ │ └── test_replace_vrf.rb └── lib │ ├── utilitylib.rb │ ├── yang_netconf_util.rb │ └── yang_util.rb ├── init.pp └── minitest ├── ciscotest.rb ├── platform_info.rb ├── test_grpc.rb ├── test_netconf.rb ├── test_netconf_yang.rb ├── test_netconf_yang_offline.rb └── test_yang.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | junit/ 3 | log/ 4 | pkg/ 5 | Gemfile.lock 6 | *~ 7 | *.swp 8 | tests/minitest/coverage/ 9 | tests/minitest/run_grpc.sh 10 | tests/minitest/run_netconf.sh 11 | tests/beaker_tests/hosts.cfg 12 | hosts.cfg 13 | -------------------------------------------------------------------------------- /.pmtignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | examples/ 3 | junit/ 4 | log/ 5 | pkg/ 6 | tests/ 7 | Gemfile.lock 8 | *~ 9 | *.swp 10 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Rubocop configuration 2 | 3 | AllCops: 4 | Exclude: 5 | - 'pkg/**/*' 6 | # Files we don't own 7 | - 'vendor/**/*' 8 | # Auto-generated files 9 | - 'lib/util/client/grpc/ems.rb' 10 | - 'lib/util/client/grpc/ems_services.rb' 11 | 12 | # Baseline code complexity metrics - we should try and reduce these over time 13 | Metrics/AbcSize: 14 | Max: 47 15 | 16 | Metrics/CyclomaticComplexity: 17 | Max: 18 18 | 19 | Metrics/ClassLength: 20 | Enabled: false 21 | 22 | Metrics/LineLength: 23 | Max: 167 24 | 25 | Metrics/MethodLength: 26 | Max: 75 27 | 28 | Metrics/ModuleLength: 29 | Enabled: false 30 | 31 | Metrics/PerceivedComplexity: 32 | Max: 20 33 | 34 | Metrics/ParameterLists: 35 | Max: 6 36 | 37 | # We like table alignment for readability 38 | Style/AlignHash: 39 | EnforcedHashRocketStyle: table 40 | EnforcedColonStyle: table 41 | 42 | # Template files have wildcard strings in class names 43 | Style/ClassAndModuleCamelCase: 44 | Exclude: 45 | - 'docs/*.rb' 46 | 47 | # Permit is_a? and kind_of? interchangeably 48 | Style/ClassCheck: 49 | Enabled: false 50 | 51 | # Template files have atypical file names on purpose 52 | Style/FileName: 53 | Exclude: 54 | - 'docs/*.rb' 55 | 56 | # As a team we like 'sprintf' rather than 'format' 57 | Style/FormatString: 58 | EnforcedStyle: sprintf 59 | 60 | # The Beaker installer script uses a number of global variables. 61 | # This is OK for now since it's a standalone script. 62 | Style/GlobalVars: 63 | Exclude: 64 | - 'utilities/installer/install_puppet.rb' 65 | 66 | # Mixed keys are ugly. Use one or the other as needed 67 | Style/HashSyntax: 68 | EnforcedStyle: ruby19_no_mixed_keys 69 | 70 | # Template files have wildcard strings in method names 71 | Style/MethodName: 72 | Exclude: 73 | - 'docs/*.rb' 74 | 75 | # To stop rubocop from complaining about overriding is_to_s 76 | Style/PredicateName: 77 | NameWhitelist: 78 | - is_a? 79 | - is_to_s 80 | 81 | # "def foo(bar=baz)" not "def foo(bar = baz)" 82 | Style/SpaceAroundEqualsInParameterDefault: 83 | EnforcedStyle: no_space 84 | 85 | # Template file has atypical variable name that rubocop doesn't like 86 | Style/SpaceAroundOperators: 87 | Exclude: 88 | - 'docs/template-__PROVIDER__-ensurabilitytest.rb' 89 | 90 | # Make it neater to extend multi-line arrays and hashes 91 | Style/TrailingComma: 92 | EnforcedStyleForMultiline: comma 93 | 94 | # Template files have atypical variable names on purpose 95 | Style/VariableName: 96 | Exclude: 97 | - 'docs/*.rb' 98 | 99 | Style/ClassAndModuleChildren: 100 | Enabled: false 101 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.root(File.expand_path('..', __FILE__)) 2 | SimpleCov.coverage_dir(File.expand_path('../tests/minitest/coverage', __FILE__)) 3 | SimpleCov.command_name("test:#{ENV['COV_TEST_SUITE']}") 4 | SimpleCov.merge_timeout(3600) # one hour 5 | SimpleCov.start do 6 | add_filter '/tests/' 7 | end 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 1.0.2 - 2017-01-19 6 | ### Changed 7 | - Tweaked Puppet requirements in metadata.json 8 | 9 | ## 1.0.1 - 2017-01-18 10 | ### Changed 11 | - Added Puppet requirements to metadata.json 12 | 13 | ## 1.0.0 - 2017-01-17 14 | ### Added 15 | - Official release of the ciscoyang module, supporting configuration of IOS-XR through Cisco supported YANG data models in JSON/XML format. 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | Cisco IOS XR Software is a modular and fully distributed network operating system for service provider networks. This project enables configuration and management of IOS XR by Puppet via YANG models. Contributions to this project are welcome. To ensure code quality, contributers will be requested to follow a few guidelines. 3 | 4 | ## Getting Started 5 | 6 | * Create a [GitHub account](https://github.com/signup/free) 7 | * A virtual XRv 9000 may be helpful for development and testing. 8 | 9 | ## Making Changes 10 | 11 | * Fork the repository 12 | * Pull a branch under the "develop" branch for your changes. 13 | * Make changes in your branch. 14 | * Testing 15 | * Add/update minitest test cases to validate your changes (under `tests/minitest`). 16 | * Add/update beaker test cases to validate your changes (under `tests/beaker`). 17 | * Run all the tests to ensure there was no collateral damage to existing code. 18 | * Check for unnecessary whitespace with `git diff --check` 19 | * Run `rubocop --lint` against all changed files. See [https://rubygems.org/gems/rubocop](https://rubygems.org/gems/rubocop) 20 | * Ensure that your commit messages clearly describe the problem you are trying to solve and the proposed solution. 21 | 22 | ## Submitting Changes 23 | 24 | * All contributions you submit to this project are voluntary and subject to the terms of the Apache 2.0 license. 25 | * Submit a pull request for commit approval to the "develop" branch. 26 | * A core team consisting of Cisco and Puppet Labs employees will looks at Pull Requests and provide feedback. 27 | * After feedback has been given, we expect responses within two weeks. After two weeks we may close the pull request if it isn't showing any activity. 28 | * All code commits must be associated with your github account and email address. Before committing any code use the following commands to update your workspace with your credentials: 29 | 30 | ```bash 31 | git config --global user.name "John Doe" 32 | git config --global user.email johndoe@example.com 33 | ``` 34 | 35 | # Additional Resources 36 | 37 | * [General GitHub documentation](http://help.github.com/) 38 | * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) 39 | * \#puppet-dev IRC channel on freenode.org ([Archive](https://botbot.me/freenode/puppet-dev/)) 40 | * [puppet-dev mailing list](https://groups.google.com/forum/#!forum/puppet-dev) 41 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 15 | 16 | puppet_version = ENV['PUPPET_VERSION'] || '>= 4.0' 17 | gem 'puppet', puppet_version 18 | 19 | beaker_version = ENV['BEAKER_VERSION'] || '>= 2.38.1' 20 | gem 'beaker', beaker_version 21 | 22 | facter_version = ENV['FACTER_VERSION'] || '>= 1.7.0' 23 | gem 'facter', facter_version 24 | 25 | gem 'puppetlabs_spec_helper', '>= 0.8.2' 26 | gem 'puppet-lint', '>= 1.0.0' 27 | gem 'rubocop', '= 0.35.1', require: false 28 | gem 'rake', '>= 10.5.0', require: false 29 | gem 'metadata-json-lint' 30 | gem 'grpc', '~> 0.15.0' 31 | gem 'net-telnet', '>= 0.1.1' 32 | gem 'simplecov', require: false, group: test 33 | 34 | # vim:ft=ruby 35 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'rubocop/rake_task' 16 | require 'rake/testtask' 17 | 18 | task default: %w(rubocop test) 19 | 20 | RuboCop::RakeTask.new 21 | 22 | Rake::TestTask.new do |t| 23 | t.test_files = FileList['tests/minitest/test_*.rb'] 24 | t.warning = true 25 | t.verbose = true 26 | t.options = '-v' 27 | end 28 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | Supported versions of this Puppet Module are available at [Puppet Forge](http://forge.puppetlabs.com/puppetlabs/ciscoyang) 2 | 3 | Please report any issues with this Puppet Module to [https://tickets.puppetlabs.com/browse/MODULES/](https://tickets.puppetlabs.com/browse/MODULES/) 4 | -------------------------------------------------------------------------------- /docs/README-agent-install.md: -------------------------------------------------------------------------------- 1 | # Puppet Agent Installation & Setup 2 | 3 | #### Table of Contents 4 | 5 | 1. [Overview](#overview) 6 | 1. [Pre-Install Tasks](#pre-install) 7 | 1. [Puppet Agent Environment: bash-shell](#env-bs) 8 | 1. [Puppet Agent Installation, Configuration and Usage](#agent-config) 9 | 1. [References](#references) 10 | 11 | ## Overview 12 | 13 | This document describes Puppet agent installation and setup on Cisco IOS XR devices 14 | 15 | ## Pre-Install Tasks 16 | 17 | #### Platform and Software Minimum Requirements 18 | 19 | The Cisco IOS XR Puppet implementation requires open source Puppet version 4.3.2 or Puppet Enterprise 2015.3.2. Currently, Cisco IOS XRv9k version 6.1.1 and later are supported. 20 | 21 | #### Set Up the Network 22 | 23 | Ensure that you have network connectivity prior to Puppet installation. Some basic CLI configuration may be necessary. 24 | 25 | **Example:** Connectivity via GigabitEthernet interface - IOS XR 26 | 27 | See also the [Cisco IOS XR Application Hosting Configuration Guide](http://www.cisco.com/c/en/us/td/docs/iosxr/AppHosting/App-Hosting-Config-Guide.html) 28 | 29 | ~~~ 30 | config 31 | ! 32 | hostname xrv9k 33 | domain name mycompany.com 34 | ! 35 | interface GigabitEthernet0/0/0/0 36 | ipv4 address 10.0.0.98 255.255.255.0 37 | no shutdown 38 | ! 39 | router static 40 | address-family ipv4 unicast 41 | 0.0.0.0/0 GigabitEthernet0/0/0/0 10.0.0.1 42 | ! 43 | tpa 44 | address-family ipv4 45 | update-source GigabitEthernet0/0/0/0 46 | ! 47 | commit 48 | end 49 | ~~~ 50 | 51 | #### Enable gRPC server 52 | 53 | ~~~ 54 | config 55 | ! 56 | grpc 57 | port 57799 # optional - default is 57400 58 | ! 59 | commit 60 | end 61 | ~~~ 62 | 63 | For more options, refer to the gRPC configuration guide for your specific version of XR. 64 | 65 | #### Enable Netconf SSH server 66 | ~~~ 67 | config 68 | ! 69 | ssh server v2 70 | ssh server netconf vrf default 71 | ! next line might be needed if you receive connection reset errors 72 | ssh server rate-limit 120 73 | netconf-yang agent 74 | ssh 75 | ! 76 | commit 77 | end 78 | ~~~ 79 | 80 | For more options, refer to the NETCONF configuration guide for your specific version of XR. 81 | 82 | After applying the correct config, you need to validate that there is an RSA key generated 83 | ~~~ 84 | show crypto key mypubkey rsa 85 | ~~~ 86 | If the above show command indicates that there is no RSA key, generate one. Use 2048 bits when prompted. 87 | ~~~ 88 | crypto key generate rsa 89 | ~~~ 90 | 91 | You should now be able to connect to the Netconf service via an external device, using a management interface address. 92 | 93 | #### Enable access to Netconf inside the third-party namespace 94 | To enable access to Netconf from the puppet module, you will need to enable a few loopback interfaces. 95 | Create loopback 0 and loopback 1. Give them both addresses, and use the address for loopback 1 when using netconf from inside the third-party namespace. 96 | ~~~ 97 | config 98 | ! 99 | interface loopback 0 100 | ipv4 address 1.1.1.1 255.255.255.255 101 | no shutdown 102 | ! 103 | interface loopback 1 104 | ipv4 address 10.10.10.10 255.255.255.255 105 | no shutdown 106 | ! 107 | end 108 | ~~~ 109 | 110 | ## Puppet Agent Environment: bash-shell 111 | 112 | **Example:** 113 | 114 | ~~~bash 115 | xrv9k# run bash 116 | bash-4.3# ip netns exec global-vrf bash 117 | ~~~ 118 | 119 | Set up DNS configuration: 120 | 121 | ~~~ 122 | cat >> /etc/resolv.conf << EOF 123 | nameserver 10.0.0.202 124 | domain mycompany.com 125 | search mycompany.com 126 | EOF 127 | ~~~ 128 | 129 | ## Puppet Agent Installation, Configuration, and Usage 130 | 131 | #### Install Puppet Agent 132 | 133 | If needed, configure a proxy server to gain network access to `yum.puppetlabs.com`: 134 | 135 | ~~~bash 136 | export http_proxy=http://proxy.yourdomain.com: 137 | export https_proxy=https://proxy.yourdomain.com: 138 | ~~~ 139 | 140 | Import the Puppet GPG keys. 141 | 142 | ~~~ 143 | rpm --import http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs 144 | rpm --import http://yum.puppetlabs.com/RPM-GPG-KEY-reductive 145 | ~~~ 146 | 147 | The recommended Puppet RPM for IOS XR is [http://yum.puppetlabs.com/puppetlabs-release-pc1-cisco-wrlinux-7.noarch.rpm](http://yum.puppetlabs.com/puppetlabs-release-pc1-cisco-wrlinux-7.noarch.rpm). 148 | 149 | Using the appropriate RPM, do: 150 | 151 | ~~~bash 152 | yum install http://yum.puppetlabs.com/puppetlabs-release-pc1-cisco-wrlinux-7.noarch.rpm 153 | yum install puppet 154 | ~~~ 155 | 156 | *Note that in order to perform the above yum RPM install, you must have at least one yum repo configured.* 157 | 158 | Update PATH var: 159 | 160 | ~~~bash 161 | export PATH=/opt/puppetlabs/puppet/bin:/opt/puppetlabs/puppet/lib:$PATH 162 | ~~~ 163 | 164 | #### Edit the Puppet config file: 165 | 166 | **/etc/puppetlabs/puppet/puppet.conf** 167 | 168 | This file can be used to override the default Puppet settings. At a minimum, the following settings should be used: 169 | 170 | ~~~bash 171 | [main] 172 | server = mypuppetmaster.mycompany.com 173 | 174 | [agent] 175 | pluginsync = true 176 | ignorecache = true 177 | ~~~ 178 | 179 | See the following references for more puppet.conf settings: 180 | 181 | * https://docs.puppetlabs.com/puppet/latest/reference/config_important_settings.html 182 | * https://docs.puppetlabs.com/puppet/latest/reference/config_about_settings.html 183 | * https://docs.puppetlabs.com/puppet/latest/reference/config_file_main.html 184 | * https://docs.puppetlabs.com/references/latest/configuration.html 185 | 186 | #### Edit the module config file: 187 | 188 | The `ciscoyang` puppet module requires configuration through a yaml file. Two configuration file locations are supported: 189 | 190 | * `/etc/cisco_yang.yaml` (system and/or root user configuration) 191 | * `~/cisco_yang.yaml` (per-user configuration) 192 | 193 | If both files exist and are readable, configuration in the user-specific file will take precedence over the system configuration. 194 | 195 | This file specifies the host, port, username, and/or password to be used to connect to gRPC and/or NETCONF. Here is an example configuration file: 196 | 197 | ~~~bash 198 | grpc: 199 | host: 127.0.0.1 200 | port: 57400 201 | username: admin 202 | password: admin 203 | 204 | netconf: 205 | host: 10.10.10.10 206 | username: admin 207 | password: admin 208 | ~~~ 209 | 210 | The `cisco_yang` puppet type uses the `grpc` configuration options and the `cisco_yang_netconf` type uses the `netconf` configuration options. While the gRPC host address can be the standard loopback address (127.0.0.1), the NETCONF host address must be the `loopback 1` address that you configured earlier. 211 | 212 | *For security purposes, it is highly recommended that access to this file be restricted to only the owning user (`chmod 0600`).* 213 | 214 | #### Install the grpc gem 215 | 216 | ~~~bash 217 | gem install grpc 218 | ~~~ 219 | 220 | *grpc gem version 0.15.0 or higher is required.* 221 | 222 | #### Run the Puppet Agent 223 | 224 | ~~~bash 225 | puppet agent -t 226 | ~~~ 227 | 228 | #### Service Management 229 | 230 | It may be desirable to set up automatic restart of the Puppet agent in the event of a system reset. 231 | 232 | #### Optional: bash-shell / init.d 233 | 234 | The `bash-shell` environment uses **init.d** for service management. 235 | The Puppet agent provides a generic init.d script when installed, but a slight 236 | modification is needed to ensure that Puppet runs in the correct namespace: 237 | 238 | ~~~diff 239 | --- /etc/init.d/puppet.old 240 | +++ /etc/init.d/puppet 241 | @@ -38,7 +38,7 @@ 242 | 243 | start() { 244 | echo -n $"Starting puppet agent: " 245 | - daemon $daemonopts $puppetd ${PUPPET_OPTS} ${PUPPET_EXTRA_OPTS} 246 | + daemon $daemonopts ip netns exec global-vrf $puppetd ${PUPPET_OPTS} ${PUPPET_EXTRA_OPTS} 247 | RETVAL=$? 248 | echo 249 | [ $RETVAL = 0 ] && touch ${lockfile} 250 | ~~~ 251 | 252 | Next, enable the puppet service to be automatically started at boot time, and optionally start it now: 253 | 254 | ~~~bash 255 | chkconfig --add puppet 256 | chkconfig --level 345 puppet on 257 | 258 | service puppet start 259 | ~~~ 260 | 261 | ## References 262 | 263 | [Cisco IOS XR Application Hosting Configuration Guide](http://www.cisco.com/c/en/us/td/docs/iosxr/AppHosting/App-Hosting-Config-Guide.html) 264 | 265 | [Cisco IOS XR Data Models Configuration Guide](http://www.cisco.com/c/en/us/td/docs/iosxr/ncs5500/DataModels/b-Datamodels-cg-ncs5500/b-Datamodels-cg-ncs5500_chapter_010.html#concept_700172ED7CF44313B0D7E521B2983F32) - gRPC Server Documentation 266 | 267 | 268 | ---- 269 | ~~~ 270 | Copyright (c) 2016 Cisco and/or its affiliates. 271 | 272 | Licensed under the Apache License, Version 2.0 (the "License"); 273 | you may not use this file except in compliance with the License. 274 | You may obtain a copy of the License at 275 | 276 | http://www.apache.org/licenses/LICENSE-2.0 277 | 278 | Unless required by applicable law or agreed to in writing, software 279 | distributed under the License is distributed on an "AS IS" BASIS, 280 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 281 | See the License for the specific language governing permissions and 282 | limitations under the License. 283 | ~~~ 284 | -------------------------------------------------------------------------------- /docs/README-develop-beaker-scripts.md: -------------------------------------------------------------------------------- 1 | # How To Create and Run Beaker Test Cases 2 | 3 | #### Table of Contents 4 | 5 | * [Overview](#overview) 6 | * [Prerequisites](#prereqs) 7 | * [Basic Example: new YANG model test](#new-yang-model) 8 | * [Completed Example](#completed-example) 9 | * [Reading YANG source from file](#yang-from-file) 10 | * [Run the test script](#run-test) 11 | 12 | ## Overview 13 | 14 | This document describes the process for writing [Beaker](https://github.com/puppetlabs/beaker/blob/master/README.md) Test Cases for the `ciscoyang` Puppet module. 15 | 16 | ## Prerequisites 17 | 18 | Refer to [README-test-execution.md](README-test-execution.md) for required setup steps for Beaker and the node(s) to be tested. 19 | 20 | ## Basic Example: new YANG model test 21 | 22 | This example will demonstrate how to add a new Beaker test for a specific YANG model and container. We've chosen the Cisco-IOS-XR-ipv4-bgp-cfg:bgp model:container for this example. 23 | 24 | * The `./examples/beaker_test_template.rb` file provides a template for simple Beaker tests; copy it into the proper test directory. It is recommended that you create a separate test file for each YANG model/container combination that you wish to test. Name your Beaker test file as `test_`, plus the YANG model filename, plus the container (all lower-case with underscores in place of other symbols). Here, the YANG model being tested is named `Cisco-IOS-XR-ipv4-bgp-cfg.yang` and the container we are testing is the `bgp` container, so the test file will be named `test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb`. 25 | 26 | ```bash 27 | cp examples/beaker_test_template.rb tests/beaker_tests/all/cisco_yang/model_tests/test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb 28 | ``` 29 | 30 | * Our new `test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb` requires changes from the original template. Edit `test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb` and make any necessary changes. In particular, you will want to: 31 | 32 | 1. Update the test at line 33: 33 | * Change the test ID 34 | * Change the test description 35 | * Change the title to the target container in the YANG document 36 | * Change the source to the desired YANG configuration to test 37 | 38 | 2. Update the test ID at line 49 39 | 40 | ## Completed Example 41 | 42 | This is the completed `test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb` test file based on `template-beaker_test_template.rb`: 43 | 44 | ~~~ruby 45 | ############################################################################### 46 | # Copyright (c) 2016 Cisco and/or its affiliates. 47 | # 48 | # Licensed under the Apache License, Version 2.0 (the "License"); 49 | # you may not use this file except in compliance with the License. 50 | # You may obtain a copy of the License at 51 | # 52 | # http://www.apache.org/licenses/LICENSE-2.0 53 | # 54 | # Unless required by applicable law or agreed to in writing, software 55 | # distributed under the License is distributed on an "AS IS" BASIS, 56 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 57 | # See the License for the specific language governing permissions and 58 | # limitations under the License. 59 | ############################################################################### 60 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 61 | require File.expand_path('../../util.rb', __FILE__) 62 | 63 | # Test hash top-level keys 64 | tests = { 65 | master: master, 66 | agent: agent, 67 | resource_name: 'cisco_yang', 68 | os: 'ios_xr', 69 | os_version: '6.1.1', 70 | } 71 | 72 | # skip entire file if os, version, etc. don't match 73 | skip_unless_supported(tests) 74 | 75 | # Define a test for the bgp YANG container. 76 | tests[:bgp] = { 77 | desc: 'Configure BGP', 78 | title: '{"Cisco-IOS-XR-ipv4-bgp-cfg:bgp": [null]}', 79 | manifest_props: { 80 | source: '{ 81 | "Cisco-IOS-XR-ipv4-bgp-cfg:bgp":{ 82 | "instance":[ 83 | { 84 | "instance-name":"default", 85 | "instance-as":[ 86 | { 87 | "as":0, 88 | "four-byte-as":[ 89 | { 90 | "as":55, 91 | "bgp-running":[null], 92 | "default-vrf":{ 93 | "global":{ 94 | "nsr":false, 95 | "global-timers":{ 96 | "keepalive":60, 97 | "hold-time":120 98 | }, 99 | "enforce-ibgp-out-policy":[null], 100 | "global-afs":{ 101 | "global-af":[ 102 | { 103 | "af-name":"ipv4-multicast", 104 | "enable":[null], 105 | "update-limit-address-family":256, 106 | "ebgp":{ 107 | "paths-value":32, 108 | "unequal-cost":false, 109 | "selective":false, 110 | "order-by-igp-metric":false 111 | } 112 | } 113 | ] 114 | } 115 | }, 116 | "bgp-entity":{ 117 | "neighbors":{ 118 | "neighbor":[ 119 | { 120 | "neighbor-address":"5.5.5.5", 121 | "remote-as":{ 122 | "as-xx":0, 123 | "as-yy":12 124 | }, 125 | "bfd-enable-modes":"default", 126 | "ebgp-multihop":{ 127 | "max-hop-count":10, 128 | "mpls-deactivation":false 129 | }, 130 | "description":"Neighbor A", 131 | "msg-log-out":{ 132 | "msg-buf-count":10 133 | } 134 | }, 135 | { 136 | "neighbor-address":"6.6.6.6", 137 | "remote-as":{ 138 | "as-xx":0, 139 | "as-yy":13 140 | }, 141 | "description":"Neighbor B" 142 | } 143 | ] 144 | } 145 | } 146 | } 147 | } 148 | ] 149 | } 150 | ] 151 | } 152 | ] 153 | } 154 | }', 155 | mode: 'replace', 156 | }, 157 | } 158 | 159 | ################################################################# 160 | # Execute the test 161 | ################################################################# 162 | 163 | test_name 'Model Test' do 164 | # a simple run with pre/post clean 165 | # (reference our test above using the key) 166 | test_harness_run_clean(tests, :bgp) 167 | end 168 | 169 | # report on skipped tests 170 | skipped_tests_summary(tests) 171 | ~~~ 172 | 173 | ### Reading YANG source from file 174 | 175 | As an alternative to putting the YANG source directly in the test file, you can read it from an external file like this: 176 | 177 | ~~~ruby 178 | : 179 | 180 | # Define a test for the bgp YANG container. 181 | tests[:bgp] = { 182 | desc: 'Configure BGP', 183 | title: '{"Cisco-IOS-XR-ipv4-bgp-cfg:bgp": [null]}', 184 | manifest_props: { 185 | source: File.read(File.expand_path('../yang/bgp.yang', __FILE__)), 186 | mode: 'replace', 187 | }, 188 | } 189 | 190 | : 191 | ~~~ 192 | 193 | **Note: You can find some example model test scripts in the `tests/beaker_tests/all/cisco_yang/model_tests` directory.** 194 | 195 | ### Run the test script 196 | 197 | Refer to [README-test-execution.md](README-test-execution.md#beaker) for information on running Beaker tests. From the `tests/beaker_tests/all` directory: 198 | 199 | ```bash 200 | beaker --hosts hosts.cfg --test cisco_yang/model_tests/test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb 201 | ``` 202 | -------------------------------------------------------------------------------- /docs/README-test-execution.md: -------------------------------------------------------------------------------- 1 | # Executing the Tests Provided for This Module 2 | 3 | ### Table of Contents 4 | 5 | * [Overview](#overview) 6 | * [Minitest Tests](#minitest) 7 | * [Edit or create a minitest config file](#minitest-config) 8 | * [Running a single minitest test](#minitest-single-test) 9 | * [Running a single testcase](#minitest-single-testcase) 10 | * [Running all minitest tests](#minitest-all) 11 | * [Beaker Tests](#beaker) 12 | * [Prerequisites](#beaker-prereqs) 13 | * [Beaker Configuration](#beaker-config) 14 | * [Running a single test](#beaker-single-test) 15 | * [Running all Beaker tests](#beaker-all) 16 | 17 | ## Overview 18 | 19 | This document describes the process for executing tests provided for the code in this Puppet module. The instructions below assume you have cloned this repository to a "test driver" workstation and will reference subdirectories under the root `cisco-yang-puppet-module` directory: 20 | 21 | ~~~ 22 | $ git clone https://github.com/cisco/cisco-yang-puppet-module.git 23 | $ cd cisco-network-yang-module/tests 24 | ~~~ 25 | 26 | ## Minitest Tests 27 | 28 | The test files located in the `tests/minitest` directory use [minitest](https://github.com/seattlerb/minitest/) as their test framework. In addition to the test driver workstation, these tests generally require a Cisco router, switch, or virtual machine to test against. The executable minitest files are all named `test_*.rb`. 29 | 30 | ### Edit or create a minitest config file 31 | 32 | You must create a `cisco_yang.yaml` file on the driver workstation to specify the XR node under test. Two configuration file locations are supported: 33 | 34 | * `/etc/cisco_yang.yaml` (system and/or root user configuration) 35 | * `~/cisco_yang.yaml` (per-user configuration) 36 | 37 | If both files exist and are readable, configuration in the user-specific file will take precedence over the system configuration. 38 | 39 | This file specifies the host, port, username, and/or password to be used to connect to gRPC and/or NETCONF on the XR node. Here is an example configuration file: 40 | 41 | ~~~bash 42 | grpc: 43 | host: 192.168.1.65 44 | port: 57400 45 | username: admin 46 | password: admin 47 | 48 | netconf: 49 | host: 192.168.1.65 50 | username: admin 51 | password: admin 52 | ~~~ 53 | 54 | Each minitest file/class uses either the `grpc` client options or the `netconf` options (or neither, if the file contains only offline tests). 55 | 56 | ### Running a single minitest test 57 | 58 | You can execute a single test file by name: 59 | 60 | ```bash 61 | $ ruby tests/test_yang.rb 62 | $ ruby tests/test_netconf_yang.rb -v 63 | ``` 64 | 65 | ### Running a single testcase 66 | 67 | You can execute a single testcase in a file using the '-n' or '--name' option: 68 | 69 | ```bash 70 | $ ruby tests/test_yang.rb -n test_merge 71 | ``` 72 | 73 | ### Running all minitest tests 74 | 75 | 76 | Using rake, you can execute all tests: 77 | 78 | ```bash 79 | $ rake test 80 | ``` 81 | 82 | 83 | ## Beaker Tests 84 | 85 | The test files located in the `tests/beaker_tests/all` directory use [Beaker](https://github.com/puppetlabs/beaker) as their test framework. In addition to the test driver workstation, these tests require a Puppet Master workstation and an XR Puppet Agent device. The executable Beaker Ruby files are in subdirectories and are named `test_*.rb`. 86 | 87 | ### Prerequisites 88 | 89 | * [Install Beaker](https://github.com/puppetlabs/beaker/wiki/Beaker-Installation) (release 2.38.1 or later) on the driver workstation. 90 | * [Prepare the Puppet Master](../README.md#puppet-master-setup) 91 | * [Prepare the XR Puppet Agent](README-agent-install.md) 92 | 93 | ### Beaker Configuration 94 | 95 | Under the `tests/beaker_tests/all` directory, create file named `hosts.cfg` and add the following content: 96 | 97 | *Replace the `< >` markers with specific information.* 98 | 99 | ```bash 100 | HOSTS: 101 | : 102 | roles: 103 | - agent 104 | platform: cisco_ios_xr-6-x86_64 105 | ip: 106 | ssh: 107 | auth_methods: ["password"] 108 | # SSHd for third-party network namespace (TPNNS) uses port 57722 109 | port: 57722 110 | user: 111 | password: 112 | 113 | 114 | #: 115 | # <...> 116 | 117 | #: 118 | # <...> 119 | 120 | : 121 | # Note: Only one master configuration block allowed 122 | roles: 123 | - master 124 | platform: 125 | ip: 126 | ssh: 127 | # Root user/password must be configured for master. 128 | auth_methods: ["password"] 129 | user: root 130 | password: 131 | ``` 132 | 133 | Here is a sample `hosts.cfg` file where the `< >` markers have been replaced with actual data. 134 | 135 | ```bash 136 | HOSTS: 137 | xr-agent: 138 | roles: 139 | - agent 140 | platform: cisco_ios_xr-6-x86_64 141 | ip: xr-agent.domain.com 142 | ssh: 143 | auth_methods: ["password"] 144 | port: 57722 145 | user: admin 146 | password: adminpassword 147 | 148 | puppetmaster1: 149 | roles: 150 | - master 151 | platform: ubuntu-1404-x86_64 152 | ip: puppetmaster1.domain.com 153 | ssh: 154 | auth_methods: ["password"] 155 | user: root 156 | password: rootpassword 157 | ``` 158 | 159 | ### Running a single test 160 | 161 | To run a single beaker test from the `tests/beaker_tests/all` directory, use the following command: 162 | 163 | ```bash 164 | beaker --hosts hosts.cfg --test all/cisco_yang/test_create_vrf.rb 165 | ``` 166 | 167 | **NOTE:** This runs a `cisco_yang` test to create a vrf, but any other tests under the `tests/beaker_tests/all` directory can be run in the same manner. 168 | 169 | ### Running all Beaker tests 170 | 171 | To run all the Beaker tests under the `tests/beaker_tests/all` directory, use the `--test` parameter 172 | and specify the `all` directory: 173 | 174 | ```bash 175 | beaker --hosts hosts.cfg --test all 176 | ``` 177 | 178 | You can also specify a subdirectory of Beaker tests: 179 | 180 | ```bash 181 | beaker --hosts hosts.cfg --test all/cisco_yang 182 | beaker --hosts hosts.cfg --test all/cisco_yang_netconf 183 | ``` 184 | -------------------------------------------------------------------------------- /docs/README-utilities.md: -------------------------------------------------------------------------------- 1 | # Utilities included in this module 2 | 3 | ## show_running_yang.rb 4 | 5 | This is a Ruby utility to output the current state of an XR configuration. In order 6 | to run, this utility needs access to one or more *.yang files (found in the 7 | `/pkg/yang` directory on the XR device, as well as from other sources). Usually, this 8 | would be run from the bash-shell on the XR device, but can be run remotely if 9 | applicable .yang files are available. Client connection information (host, username, etc.) 10 | is read from the [module configuration file](README-agent-install.md#module-config). 11 | 12 | Running `puppet agent -t` on XR caches the module source on the client, usually under the 13 | `/opt/puppetlabs/puppet/cache` directory. The `show_running_yang.rb` Ruby file will be 14 | in the `/opt/puppetlabs/puppet/cache/lib/util` directory, so can be executed using the 15 | following command: 16 | 17 | ```bash 18 | [xrv9k:~]$ ruby /opt/puppetlabs/puppet/cache/lib/util/show_running_yang.rb --help 19 | 20 | Usage: ruby [path]show_running_yang.rb [options] [file_or_directory_path] 21 | -m, --manifest Output config in a form suitable for inclusion in a Puppet manifest 22 | -o, --oper Retrieve operational data instead of configuration (experimental; use at own risk) 23 | -c, --client CLIENT The client to use to connect. 24 | grpc|netconf (defaults to grpc 25 | -d, --debug Enable debug-level logging 26 | -v, --verbose Enable verbose messages 27 | -h, --help Print this help 28 | ``` 29 | 30 | If you find yourself running the utility often, consider creating an alias shortcut: 31 | 32 | 33 | ```bash 34 | [xrv9k:~]$ alias show_running_yang='ruby /opt/puppetlabs/puppet/cache/lib/util/show_running_yang.rb' 35 | [xrv9k:~]$ show_running_yang 36 | [xrv9k:~]$ show_running_yang -c netconf /pkg/yang/Cisco-IOS-XR-ipv4-bgp-cfg.yang 37 | ``` 38 | -------------------------------------------------------------------------------- /examples/beaker_test_template.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', # 'cisco_yang' or 'cisco_yang_netconf' 24 | os: 'ios_xr', 25 | os_version: '6.2.1', # indicates that this test shoule be run on version 6.2.1 or later 26 | } 27 | 28 | # skip entire file if os, version, etc. don't match 29 | skip_unless_supported(tests) 30 | 31 | # define a test (or tests) 32 | # (e.g. description, title, manifest) 33 | tests[:my_test] = { # user-defined key for this test 34 | desc: '', 35 | title: '', 36 | manifest_props: { 37 | source: '', 38 | mode: 'replace', # usually will want this to be 'replace' 39 | }, 40 | } 41 | 42 | ################################################################# 43 | # Execute the test 44 | ################################################################# 45 | 46 | test_name 'Model Test' do 47 | # a simple run with pre/post clean 48 | # (reference our test above using the key) 49 | test_harness_run_clean(tests, :my_test) # same ID as above 50 | end 51 | 52 | # report on skipped tests 53 | skipped_tests_summary(tests) 54 | -------------------------------------------------------------------------------- /examples/site.pp: -------------------------------------------------------------------------------- 1 | node 'default' { 2 | file { '/root/temp/vrfs.json': source => 'puppet:///modules/ciscoyang/models/defaults/vrfs.json'} 3 | 4 | # Configure two vrfs (VOIP & INTERNET) 5 | cisco_yang { '{"Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null]}': 6 | ensure => present, 7 | source => '/root/temp/vrfs.json', 8 | } 9 | 10 | # other examples 11 | 12 | file { '/root/temp/vrf-voip.json': source => 'puppet:///modules/ciscoyang/models/defaults/vrf-voip.json'} 13 | 14 | # Add a single vrf (VOIP) 15 | cisco_yang { '{"Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null]}': 16 | ensure => present, 17 | source => '/root/temp/vrf-voip.json' 18 | } 19 | 20 | # Remove a single vrf (VOIP) 21 | cisco_yang { '{"Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null]}': 22 | ensure => absent, 23 | source => '/root/temp/vrf-voip.json', 24 | } 25 | 26 | # Remove all vrfs 27 | cisco_yang { '{"Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null]}': 28 | ensure => absent 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /files/models/defaults/vrf-voip.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs":{ 3 | "vrf":[ 4 | { 5 | "vrf-name":"VOIP", 6 | "description":"Voice over IP", 7 | "vpn-id":{ 8 | "vpn-oui":875, 9 | "vpn-index":3 10 | }, 11 | "create":[ 12 | null 13 | ] 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /files/models/defaults/vrfs.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs":{ 3 | "vrf":[ 4 | { 5 | "vrf-name":"VOIP", 6 | "description":"Voice over IP", 7 | "vpn-id":{ 8 | "vpn-oui":87, 9 | "vpn-index":3 10 | }, 11 | "create":[ 12 | null 13 | ] 14 | }, 15 | { 16 | "vrf-name":"INTERNET", 17 | "description":"Generic external traffic", 18 | "vpn-id":{ 19 | "vpn-oui":85, 20 | "vpn-index":22 21 | }, 22 | "create":[ 23 | null 24 | ] 25 | } 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /files/models/defaults/vrfs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VOIP 4 | 5 | Voice over IP 6 | 7 | 875 8 | 3 9 | 10 | 11 | 12 | INTERNET 13 | 14 | Generic external traffic 15 | 16 | 875 17 | 22 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/.rubucop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../.rubocop.yml 2 | 3 | # Baseline code complexity metrics for the lib/ subdirectory: 4 | 5 | Metrics/AbcSize: 6 | Max: 49 7 | 8 | Metrics/CyclomaticComplexity: 9 | Max: 23 10 | 11 | Metrics/MethodLength: 12 | Max: 50 13 | 14 | Metrics/ParameterLists: 15 | Max: 10 16 | 17 | Metrics/PerceivedComplexity: 18 | Max: 24 19 | -------------------------------------------------------------------------------- /lib/facter/cisco_yang.rb: -------------------------------------------------------------------------------- 1 | require 'facter' 2 | require File.expand_path('../../util/environment', __FILE__) 3 | # require File.expand_path('../../util/platform', __FILE__) 4 | 5 | Facter.add(:cisco_yang) do 6 | confine operatingsystem: [:ios_xr] 7 | 8 | setcode do 9 | hash = {} 10 | 11 | hash['configured_envs'] = Cisco::Util::Environment.environment_names 12 | 13 | # don't do this, for now. It's slow, so wait until we need it 14 | # Platform = Cisco::Platform 15 | # hash['system'] = Platform.system 16 | # hash['system_time'] = Platform.system_time 17 | # hash['host_name'] = Platform.host_name 18 | # hash['product_id'] = Platform.product_id 19 | 20 | hash 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/minitest/environment_plugin.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative '../util/environment' 16 | 17 | # Add environment option to minitest 18 | module Minitest 19 | def self.plugin_environment_options(opts, options) 20 | opts.on('-e', '--environment NAME', 'Select environment by name') do |name| 21 | options[:environment] = name 22 | end 23 | end 24 | 25 | def self.plugin_environment_init(options) 26 | name = options[:environment] 27 | Cisco::Environment.default_environment_name = name unless name.nil? 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/minitest/log_level_plugin.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'logger' 16 | require_relative '../util/logger' 17 | 18 | # Add logging level option to minitest 19 | module Minitest 20 | LEVEL_ALIASES = { 21 | 'debug' => Logger::DEBUG, 22 | 'info' => Logger::INFO, 23 | 'warning' => Logger::WARN, 24 | 'error' => Logger::ERROR, 25 | } 26 | def self.plugin_log_level_options(opts, options) 27 | opts.on( 28 | '-l', '--log-level LEVEL', LEVEL_ALIASES, 29 | 'Configure logging level for tests', 30 | "(#{LEVEL_ALIASES.keys.join(', ')})" 31 | ) do |level| 32 | options[:log_level] = level 33 | end 34 | end 35 | 36 | def self.plugin_log_level_init(options) 37 | Cisco::Logger.level = options[:log_level] if options[:log_level] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/puppet/feature/json.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/feature' 2 | 3 | # custom feature for json 4 | Puppet.features.add(:json, libs: ['json']) 5 | -------------------------------------------------------------------------------- /lib/puppet/feature/node_util.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/util/feature' 2 | 3 | # custom feature for node_util 4 | path = File.expand_path('../../../util/node_util', __FILE__) 5 | Puppet.features.add(:node_util, libs: [path]) 6 | -------------------------------------------------------------------------------- /lib/puppet/property/yang_json.rb: -------------------------------------------------------------------------------- 1 | require_relative 'yang_property' 2 | 3 | # This subclass of {YangProperty} manages YANG JSON content. 4 | # 5 | class YangJson < YangProperty 6 | # Determine if the specified value is inline YANG or a file path 7 | def inline_yang?(yang_or_file) 8 | !/^{/.match(yang_or_file).nil? 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/puppet/property/yang_netconf.rb: -------------------------------------------------------------------------------- 1 | require_relative 'yang_netconf_property' 2 | 3 | # This subclass of {YangProperty} manages YANG XML Netconf content. 4 | # 5 | class YangNetconf < YangNetconfProperty 6 | # Determine if the specified value is inline YANG or a file path 7 | def inline_yang?(yang_or_file) 8 | !/^ read YANG from file #{yang_or_file}:" 20 | debug content 21 | # rescue Exception => e 22 | # puts "**************** ERROR DETECTED WHILE READING YANG FILE #{yang_or_file} ****************" 23 | # puts e.message 24 | # content = nil 25 | # end 26 | content 27 | else 28 | debug "-----> value is inline YANG: #{yang_or_file}" 29 | yang_or_file 30 | end 31 | end 32 | 33 | def should 34 | return @should_yang if @should_yang 35 | 36 | result = super 37 | 38 | # need better way to determine life-cycle stage of provider 39 | if provider && provider.respond_to?(:active?) && provider.active? 40 | @should_yang = result = yang_content(result) 41 | end 42 | 43 | result 44 | end 45 | 46 | # Determine if the "is" value is the same as the "should" value 47 | # (has the value of this property changed?) 48 | def insync?(is) 49 | replace = @resource && @resource[:mode] == :replace 50 | 51 | should_yang = should 52 | 53 | if is == :unknown 54 | # if the current config is unknown, assume configs are not in-sync 55 | insync = false 56 | else 57 | if replace 58 | insync = Cisco::Netconf.insync_for_replace(should_yang, is) 59 | else 60 | insync = Cisco::Netconf.insync_for_merge(should_yang, is) 61 | end 62 | end 63 | 64 | if insync 65 | debug '**************** IDEMPOTENT -- NO CHANGES DETECTED ****************' 66 | elsif debug '**************** IS vs SHOULD CHANGES DETECTED ****************' 67 | end 68 | 69 | insync 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/puppet/property/yang_property.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/property' 2 | require_relative '../../util/node_util' if Puppet.features.node_util? 3 | 4 | # This subclass of {Puppet::Property} manages YANG content. 5 | # 6 | class YangProperty < Puppet::Property 7 | # Determine if the specified value is inline YANG or a file path 8 | def inline_yang?(_yang_or_file) 9 | false 10 | end 11 | 12 | def yang_content(yang_or_file) 13 | return '' if yang_or_file.nil? 14 | 15 | # If it's not inline YANG, then assume it's a file, so read it in. 16 | if !inline_yang?(yang_or_file) 17 | # begin 18 | content = File.read(yang_or_file) 19 | debug "-----> read YANG from file #{yang_or_file}:" 20 | debug content 21 | # rescue Exception => e 22 | # puts "**************** ERROR DETECTED WHILE READING YANG FILE #{yang_or_file} ****************" 23 | # puts e.message 24 | # content = nil 25 | # end 26 | content 27 | else 28 | debug "-----> value is inline YANG: #{yang_or_file}" 29 | yang_or_file 30 | end 31 | end 32 | 33 | def should 34 | return @should_yang if @should_yang 35 | 36 | result = super 37 | 38 | # need better way to determine life-cycle stage of provider 39 | if provider && provider.respond_to?(:active?) && provider.active? 40 | @should_yang = result = yang_content(result) 41 | end 42 | 43 | result 44 | end 45 | 46 | # Determine if the "is" value is the same as the "should" value 47 | # (has the value of this property changed?) 48 | def insync?(is) 49 | replace = @resource && @resource[:mode] == :replace 50 | 51 | should_yang = should 52 | 53 | if is == :unknown 54 | # if the current config is unknown, assume configs are not in-sync 55 | insync = false 56 | else 57 | if replace 58 | insync = Cisco::Yang.insync_for_replace?(should_yang, is) 59 | else 60 | insync = Cisco::Yang.insync_for_merge?(should_yang, is) 61 | end 62 | end 63 | 64 | if insync 65 | debug '**************** IDEMPOTENT -- NO CHANGES DETECTED ****************' 66 | elsif debug '**************** IS vs SHOULD CHANGES DETECTED ****************' 67 | end 68 | 69 | insync 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/puppet/provider/cisco_yang/cisco.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative '../../../util/node_util' if Puppet.features.node_util? 16 | require_relative '../../../util/yang_accessor' if Puppet.features.node_util? 17 | 18 | Puppet::Type.type(:cisco_yang).provide(:cisco) do 19 | desc 'IOS-XR configuration management via YANG.' 20 | defaultfor operatingsystem: [:ios_xr] 21 | 22 | confine feature: :json 23 | confine feature: :node_util 24 | 25 | def initialize(value={}) 26 | super(value) 27 | @node = Cisco::Node.instance(Cisco::Client::GRPC) 28 | debug 'Created provider instance of cisco_yang.' 29 | end 30 | 31 | def exists? 32 | activate 33 | source && source != :absent 34 | end 35 | 36 | def create 37 | setyang(@resource[:source]) 38 | end 39 | 40 | def destroy 41 | @source = nil # clear the cached value 42 | src = @resource[:source] || @resource[:target] 43 | debug '**************** REMOVING CONFIG ****************' 44 | @node.delete_yang(src) 45 | debug '**************** REMOVE SUCCESSFUL ****************' 46 | end 47 | 48 | def resource_mode 49 | @resource && @resource[:mode] == :replace ? :replace : :merge 50 | end 51 | 52 | def resource_force 53 | @resource && @resource[:force] ? true : false 54 | end 55 | 56 | # Return the current source YANG 57 | def source 58 | return @source if @source # return the cached value, if it's there 59 | 60 | if resource_force 61 | # If instructed to force the configuration, then there is no reason 62 | # to query the current configuration; just return :unknown. 63 | source_yang = :unknown 64 | else 65 | source_yang = @node.get_yang(@resource[:target]) 66 | 67 | debug '**************** CURRENT CONFIG ****************' 68 | debug source_yang 69 | 70 | source_yang = :absent if !source_yang || source_yang.empty? 71 | end 72 | 73 | @source = source_yang 74 | rescue StandardError => e 75 | unless e.message =~ /unknown-namespace/ || 76 | e.message =~ /not recognized or supported/ 77 | raise 78 | end 79 | error e.message 80 | @source = nil 81 | end 82 | 83 | # Set the source YANG. 84 | def source=(value) 85 | setyang(value) 86 | end 87 | 88 | def setyang(value) 89 | @source = nil # clear the cached value 90 | debug '**************** SETTING CONFIG ****************' 91 | debug "Value: #{value}" 92 | debug "Resource Mode #{resource_mode}" 93 | if resource_mode == :replace 94 | @node.replace_yang(value) 95 | else 96 | @node.merge_yang(value) 97 | end 98 | debug '**************** SET SUCCESSFUL ****************' 99 | end 100 | 101 | def self.instances 102 | ya = Cisco::YangAccessor.new 103 | targets = ya.targets(client_class: Cisco::Client::GRPC) 104 | 105 | targets.map do |target| 106 | new(name: target) 107 | end 108 | end 109 | 110 | def activate 111 | @active = true 112 | end 113 | 114 | def active? 115 | !@active.nil? 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /lib/puppet/provider/cisco_yang_netconf/cisco.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative '../../../util/node_util' if Puppet.features.node_util? 16 | require_relative '../../../util/yang_accessor' if Puppet.features.node_util? 17 | # require 'rubygems' 18 | 19 | Puppet::Type.type(:cisco_yang_netconf).provide(:cisco) do 20 | desc 'IOS-XR configuration management via YANG.' 21 | defaultfor operatingsystem: [:ios_xr] 22 | 23 | def initialize(value={}) 24 | super(value) 25 | activate 26 | @node = Cisco::Node.instance(Cisco::Client::NETCONF) 27 | end 28 | 29 | def create 30 | setyang(@resource[:source]) 31 | end 32 | 33 | def destroy 34 | @source = nil # clear the cached value 35 | src = @resource[:source] || @resource[:target] 36 | debug '**************** REMOVING CONFIG ****************' 37 | debug '**************** Delete is not available. ****************' 38 | debug src 39 | # @node.delete_yang(src) 40 | debug '**************** REMOVE SUCCESSFUL ****************' 41 | end 42 | 43 | def resource_mode 44 | @resource && @resource[:mode] == :replace ? :replace : :merge 45 | end 46 | 47 | def resource_force 48 | @resource && @resource[:force] ? true : false 49 | end 50 | 51 | # Return the current source YANG 52 | def source 53 | return @source if @source # return the cached value, if it's there 54 | 55 | if resource_force 56 | # If instructed to force the configuration, then there is no reason 57 | # to query the current configuration; just return :unknown. 58 | source_yang = :unknown 59 | else 60 | source_yang = @node.get_netconf(@resource[:target]) 61 | 62 | debug '**************** CURRENT CONFIG ****************' 63 | debug source_yang 64 | 65 | # somewhere someone reads source_yang as a string. This is a workaround. 66 | # source_yang = :absent if !source_yang || source_yang.empty? 67 | end 68 | 69 | @source = source_yang 70 | rescue StandardError => e 71 | raise unless e.message =~ /unknown-namespace/ 72 | error e.message 73 | @source = nil 74 | end 75 | 76 | # Set the source YANG. 77 | def source=(value) 78 | setyang(value) 79 | end 80 | 81 | def setyang(value) 82 | @source = nil # clear the cached value 83 | debug '**************** SETTING CONFIG ****************' 84 | debug "Value: #{value}" 85 | debug "Resource Mode #{resource_mode}" 86 | if resource_mode == :replace 87 | @node.replace_netconf(value) 88 | else 89 | @node.merge_netconf(value) 90 | end 91 | debug '**************** SET SUCCESSFUL ****************' 92 | end 93 | 94 | def self.instances 95 | ya = Cisco::YangAccessor.new 96 | targets = ya.targets(client_class: Cisco::Client::NETCONF) 97 | 98 | targets.map do |target| 99 | new(name: target) 100 | end 101 | end 102 | 103 | def activate 104 | @active = true 105 | end 106 | 107 | def active? 108 | !@active.nil? 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/puppet/type/cisco_yang.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../property/yang_json.rb', __FILE__) 2 | 3 | Puppet::Type.newtype(:cisco_yang) do 4 | @doc = "IOS-XR configuration management via YANG. 5 | 6 | ~~~puppet 7 | cisco_yang { '': 8 | ..attributes.. 9 | } 10 | ~~~ 11 | `<title>` is the title of the yang resource. 12 | Example: 13 | ~~~puppet 14 | cisco_yang { 'blue vrf': 15 | ensure => present, 16 | target => '{\"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": [null]}', 17 | source => '{\"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": { 18 | \"vrf\":[ 19 | { 20 | \"vrf-name\":\"blue\", 21 | \"vpn-id\":{ 22 | \"vpn-oui\":875, 23 | \"vpn-index\":3 24 | }, 25 | \"create\":[null] 26 | } 27 | ] 28 | } 29 | }', 30 | } 31 | ~~~ 32 | ~~~puppet 33 | cisco_yang { '{\"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": [null]}': 34 | ensure => present, 35 | source => '{\"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": { 36 | \"vrf\":[ 37 | { 38 | \"vrf-name\":\"red\", 39 | \"vpn-id\":{ 40 | \"vpn-oui\":875, 41 | \"vpn-index\":22 42 | }, 43 | \"create\":[null] 44 | } 45 | ] 46 | } 47 | }', 48 | } 49 | ~~~ 50 | ~~~puppet 51 | cisco_yang { '{\"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": [null]}': 52 | ensure => absent, 53 | ~~~ 54 | " 55 | 56 | ensurable 57 | 58 | newparam(:target, parent: YangJson) do 59 | isnamevar 60 | desc 'String containing the model path of the target node in YANG JSON '\ 61 | 'format, or a reference to a local file containing the model path.' 62 | end 63 | 64 | newparam(:mode) do 65 | desc 'Determines the mode to use when setting configuration via '\ 66 | "ensure=>present. If 'replace' is specified, the current "\ 67 | 'configuration will be replaced by the configuration in the '\ 68 | "'source' property. If 'merge' is specified, the configuration "\ 69 | "in the 'source' property will be merged into the current "\ 70 | "configuration. Valid values are 'replace' and 'merge' (which "\ 71 | 'is the default.' 72 | munge(&:to_sym) 73 | newvalues(:replace, :merge) 74 | end 75 | 76 | newparam(:force) do 77 | desc 'If :true is specified, then the specified value of the source '\ 78 | 'property is set on the device, regardless of the current value. '\ 79 | 'If :false is specified (or no value is specified), the default '\ 80 | 'behavior is to only set the configuration if it is different '\ 81 | 'from the running configuration.' 82 | newvalues(:true, :false) 83 | munge do |force| 84 | force == true || force == 'true' || force == :true 85 | end 86 | end 87 | 88 | newproperty(:source, parent: YangJson) do 89 | desc 'The model data in YANG JSON format, or a reference to a local file '\ 90 | 'containing the model data. This property is only used when '\ 91 | 'ensure=>present is used.' 92 | end 93 | 94 | # 95 | # VALIDATIONS 96 | # 97 | validate do 98 | fail("The 'target' parameter must be set in the manifest.") if self[:target].nil? 99 | if self[:source].nil? && self[:ensure] == :present 100 | fail("The 'source' parameter must be set in the manifest when mode is replace.") if self[:mode] == :replace 101 | self[:source] = self[:target] 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/puppet/type/cisco_yang_netconf.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../property/yang_netconf.rb', __FILE__) 2 | 3 | Puppet::Type.newtype(:cisco_yang_netconf) do 4 | @doc = "IOS-XR configuration management via YANG Netconf. 5 | 6 | ~~~puppet 7 | cisco_yang_netconf { '<title>': 8 | ..attributes.. 9 | } 10 | ~~~ 11 | `<title>` is the title of the yang resource. 12 | This example demonstrates changing the VRF table to contain only the vrf with name \"blue\". 13 | ~~~puppet 14 | cisco_yang_netconf { 'blue vrf': 15 | target => '<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\"/>', 16 | source => '<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\"> 17 | <vrf> 18 | <vrf-name>blue</vrf-name> 19 | <create/> 20 | <vpn-id> 21 | <vpn-oui>875</vpn-oui> 22 | <vpn-index>3</vpn-index> 23 | </vpn-id> 24 | </vrf> 25 | </vrfs>', 26 | mode => replace 27 | } 28 | ~~~ 29 | This example demonstrates inserting the vrf with name \"blue\" into the table, with the values provided. 30 | ~~~puppet 31 | cisco_yang_netconf { '<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\"/>': 32 | source => '<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\"> 33 | <vrf> 34 | <vrf-name>blue</vrf-name> 35 | <create/> 36 | <vpn-id> 37 | <vpn-oui>875</vpn-oui> 38 | <vpn-index>3</vpn-index> 39 | </vpn-id> 40 | </vrf> 41 | </vrfs>' 42 | } 43 | ~~~ 44 | This example demonstrates removing the vrf with name \"red\" from the vrf table. 45 | ~~~puppet 46 | cisco_yang_netconf { '<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\"/>': 47 | source => '<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\"> 48 | <vrf xmlns:xc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" xc:operation=\"delete\"> 49 | <vrf-name>red</vrf-name> 50 | <create/> 51 | </vrf> 52 | </vrfs>' 53 | } 54 | ~~~ 55 | " 56 | 57 | newparam(:target, parent: YangNetconf) do 58 | isnamevar 59 | desc 'XML formatted string or file location of an XML formatted string ' \ 60 | 'that contains the filter text used in a netconf query.' 61 | end 62 | 63 | newparam(:mode) do 64 | desc 'Determines the mode to use when setting configuration.'\ 65 | "If 'replace' is specified, the current "\ 66 | 'configuration will be replaced by the configuration in the '\ 67 | "'source' property. If 'merge' is specified, the configuration "\ 68 | "in the 'source' property will be merged into the current "\ 69 | "configuration. Valid values are 'replace' and 'merge' (which "\ 70 | 'is the default.' 71 | munge(&:to_sym) 72 | newvalues(:replace, :merge) 73 | end 74 | 75 | newparam(:force) do 76 | desc 'If :true is specified, then the specified value of the source '\ 77 | 'property is set on the device, regardless of the current value. '\ 78 | 'If :false is specified (or no value is specified), the default '\ 79 | 'behavior is to only set the configuration if it is different '\ 80 | 'from the running configuration.' 81 | newvalues(:true, :false) 82 | munge do |force| 83 | force == true || force == 'true' || force == :true 84 | end 85 | end 86 | 87 | newproperty(:source, parent: YangNetconf) do 88 | desc 'The model data in YANG XML Netconf format, or a reference to a local file '\ 89 | 'containing the model data.' 90 | end 91 | 92 | # 93 | # VALIDATIONS 94 | # 95 | validate do 96 | fail("The 'target' parameter must be set in the manifest.") if self[:target].nil? 97 | if self[:source].nil? 98 | fail("The 'source' parameter must be set in the manifest when mode is replace.") if self[:mode] == :replace 99 | self[:source] = self[:target] 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/util/client.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Namespace for Cisco-specific code 16 | module Cisco 17 | # Namespace for Cisco Client shim 18 | class Client 19 | end 20 | end 21 | 22 | require_relative 'client/client' 23 | 24 | # Try to load known extensions 25 | extensions = ['client/grpc', 26 | 'client/netconf', 27 | ] 28 | extensions.each do |ext| 29 | begin 30 | require_relative ext 31 | rescue LoadError => e 32 | # ignore missing clients, they're not always required 33 | raise unless e.message =~ /#{Regexp.escape(ext)}/ 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/util/client/client.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative '../environment' 16 | require_relative '../exceptions' 17 | require_relative 'utils' 18 | require_relative '../constants' 19 | require_relative '../logger' 20 | 21 | include Cisco::Logger 22 | 23 | # Base class for clients of various RPC formats 24 | class Cisco::Client 25 | @@clients = [] # rubocop:disable Style/ClassVars 26 | 27 | def self.clients 28 | @@clients 29 | end 30 | 31 | # Each subclass should call this method to register itself. 32 | def self.register_client(client) 33 | @@clients << client 34 | end 35 | 36 | attr_reader :platform, :host, :port, :address, :username, :password 37 | 38 | def initialize(platform: nil, 39 | **kwargs) 40 | if self.class == Cisco::Client 41 | fail NotImplementedError, 'Cisco::Client is an abstract class. ' \ 42 | "Instantiate one of #{@@clients} or use Cisco::Client.create() instead" 43 | end 44 | self.class.validate_args(**kwargs) 45 | @host = kwargs[:host] 46 | @port = kwargs[:port] 47 | @address = @port.nil? ? @host : "#{@host}:#{@port}" 48 | @username = kwargs[:username] 49 | @password = kwargs[:password] 50 | self.platform = platform 51 | end 52 | 53 | def self.validate_args(**kwargs) 54 | host = kwargs[:host] 55 | unless host.nil? 56 | fail TypeError, 'invalid address' unless host.is_a?(String) 57 | fail ArgumentError, 'empty address' if host.empty? 58 | end 59 | username = kwargs[:username] 60 | unless username.nil? 61 | fail TypeError, 'invalid username' unless username.is_a?(String) 62 | fail ArgumentError, 'empty username' if username.empty? 63 | end 64 | password = kwargs[:password] 65 | unless password.nil? 66 | fail TypeError, 'invalid password' unless password.is_a?(String) 67 | fail ArgumentError, 'empty password' if password.empty? 68 | end 69 | end 70 | 71 | def self.environment_name(client_class) 72 | client_class.name.split('::').last.downcase 73 | end 74 | 75 | def self.environment(client_class) 76 | Cisco::Util::Environment.environment(environment_name(client_class)) 77 | end 78 | 79 | # Try to create an instance of the specified subclass 80 | def self.create(client_class) 81 | env = environment(client_class) 82 | env_name = environment_name(client_class) 83 | 84 | fail Cisco::ClientError, "No client environment configured for '#{env_name}'" unless env 85 | 86 | host = env[:host] 87 | port = env[:port] 88 | debug "Trying to connect to #{host}:#{port} as #{client_class}" 89 | errors = [] 90 | begin 91 | client = client_class.new(**env) 92 | debug "#{client_class} connected successfully" 93 | return client 94 | rescue Cisco::ClientError, TypeError, ArgumentError => e 95 | debug "Unable to connect to #{host} as #{client_class}: #{e.message}" 96 | debug e.backtrace.join("\n ") 97 | errors << e 98 | end 99 | handle_errors(errors) 100 | end 101 | 102 | def self.handle_errors(errors) 103 | # ClientError means we tried to connect but failed, 104 | # so it's 'more significant' than input validation errors. 105 | client_errors = errors.select { |e| e.kind_of? Cisco::ClientError } 106 | if !client_errors.empty? 107 | # Reraise the specific error if just one 108 | fail client_errors[0] if client_errors.length == 1 109 | # Otherwise clump them together into a new error 110 | e_cls = client_errors[0].class 111 | unless client_errors.all? { |e| e.class == e_cls } 112 | e_cls = Cisco::ClientError 113 | end 114 | fail e_cls, ("Unable to establish any client connection:\n" + 115 | errors.each(&:message).join("\n")) 116 | elsif errors.any? { |e| e.kind_of? ArgumentError } 117 | fail ArgumentError, ("Invalid arguments:\n" + 118 | errors.each(&:message).join("\n")) 119 | elsif errors.any? { |e| e.kind_of? TypeError } 120 | fail TypeError, ("Invalid arguments:\n" + 121 | errors.each(&:message).join("\n")) 122 | end 123 | fail Cisco::ClientError, 'No client connected, but no errors were reported?' 124 | end 125 | 126 | def to_s 127 | @address.to_s 128 | end 129 | 130 | def inspect 131 | "<#{self.class} of #{@address}>" 132 | end 133 | 134 | # Configure the given state on the device. 135 | # 136 | # @param values [String, Array<String>] Actual configuration to set 137 | # @param kwargs data-format-specific args 138 | def set(values: nil, 139 | **_kwargs) 140 | # subclasses will generally want to call Client.munge_to_array() 141 | # on values before calling super() 142 | Cisco::Logger.debug("values: #{values})") \ 143 | unless values.nil? || values.empty? 144 | # to be implemented by subclasses 145 | end 146 | 147 | # Get the given state from the device. 148 | # 149 | # @param command [String] the get command to execute 150 | # @param value [String, Regexp] Specific key or regexp to look up 151 | # @param kwargs data-format-specific args 152 | # @return [String, Hash, nil] The state found, or nil if not found. 153 | def get(command: nil, 154 | **_kwargs) 155 | # subclasses will generally want to call Client.munge_to_array() 156 | # on value before calling super() 157 | Cisco::Logger.debug(" executing command:\n #{command}") \ 158 | unless command.nil? || command.empty? 159 | # to be implemented by subclasses 160 | end 161 | 162 | private 163 | 164 | # Set the platform of the node managed by this client. 165 | def platform=(platform) 166 | fail ArgumentError, "unknown platform #{platform}" \ 167 | unless Cisco::Util::PLATFORMS.include?(platform) 168 | @platform = platform 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /lib/util/client/grpc.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative 'client' 16 | 17 | # Fail gracefully if submodule dependencies are not met 18 | begin 19 | Cisco::Client.silence_warnings do 20 | require 'grpc' 21 | end 22 | rescue LoadError => e 23 | raise unless e.message =~ /-- grpc/ 24 | # If grpc is not installed, raise an error that client understands. 25 | raise LoadError, "Unable to load client/grpc -- #{e}" 26 | end 27 | 28 | # Namespace for Cisco EMS gRPC-specific code 29 | class Cisco::Client::GRPC < Cisco::Client 30 | end 31 | 32 | # Auto-load all Ruby files in the subdirectory 33 | Dir.glob(__dir__ + '/grpc/*.rb') { |file| require file } 34 | -------------------------------------------------------------------------------- /lib/util/client/grpc/ems.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package IOSXRExtensibleManagabilityService; 4 | 5 | service gRPCConfigOper { 6 | 7 | // Configuration related commands 8 | 9 | rpc GetConfig(ConfigGetArgs) returns(stream ConfigGetReply) {}; 10 | // 6.0 Config commands do implicit commits 11 | rpc MergeConfig(ConfigArgs) returns(ConfigReply) {}; 12 | 13 | rpc DeleteConfig(ConfigArgs) returns(ConfigReply) {}; 14 | 15 | rpc ReplaceConfig(ConfigArgs) returns(ConfigReply) {}; 16 | 17 | rpc CliConfig(CliConfigArgs) returns(CliConfigReply) {}; 18 | 19 | // not implemented for 6.0 20 | rpc CommitReplace(CommitReplaceArgs) returns (CommitReplaceReply) {}; 21 | // not implemented for 6.0 22 | rpc CommitConfig(CommitArgs) returns(CommitReply) {}; 23 | // not implemented for 6.0 24 | rpc ConfigDiscardChanges(DiscardChangesArgs) returns(DiscardChangesReply) {}; 25 | 26 | 27 | // Get only returns oper data 28 | // 29 | rpc GetOper(GetOperArgs) returns(stream GetOperReply) {}; 30 | // Do we need "Get" also to give combined oper and config? 31 | } 32 | 33 | // 34 | // Should we seperate Exec from Config/Oper? 35 | // 36 | 37 | service gRPCExec { 38 | // Exec commands 39 | rpc ShowCmdTextOutput(ShowCmdArgs) returns(stream ShowCmdTextReply) {}; 40 | rpc ShowCmdJSONOutput(ShowCmdArgs) returns(stream ShowCmdJSONReply) {}; 41 | 42 | 43 | } 44 | 45 | message ConfigGetArgs { 46 | int64 ReqId = 1; 47 | string yangpathjson = 2; 48 | } 49 | 50 | message ConfigGetReply { 51 | int64 ResReqId = 1; 52 | string yangjson = 2; 53 | string errors = 3; 54 | } 55 | 56 | message GetOperArgs { 57 | int64 ReqId = 1; 58 | string yangpathjson = 2; 59 | } 60 | 61 | message GetOperReply { 62 | int64 ResReqId = 1; 63 | string yangjson = 2; 64 | string errors = 3; 65 | } 66 | 67 | 68 | message ConfigArgs { 69 | int64 ReqId = 1; 70 | string yangjson = 2; 71 | 72 | } 73 | 74 | message ConfigReply { 75 | int64 ResReqId = 1; 76 | string errors = 2; 77 | } 78 | 79 | message CliConfigArgs { 80 | int64 ReqId = 1; 81 | string cli = 2; 82 | } 83 | 84 | message CliConfigReply { 85 | int64 ResReqId = 1; 86 | string errors = 2; 87 | } 88 | 89 | 90 | message CommitReplaceArgs { 91 | int64 ReqId = 1; 92 | string cli = 2; 93 | string yangjson = 3; 94 | } 95 | 96 | message CommitReplaceReply { 97 | int64 ResReqId = 1; 98 | string errors = 2; 99 | } 100 | 101 | message CommitMsg { 102 | string label = 1; 103 | string comment = 2; 104 | } 105 | 106 | enum CommitResult { 107 | CHANGE = 0; 108 | NO_CHANGE = 1; 109 | FAIL = 2; 110 | } 111 | 112 | message CommitArgs { 113 | CommitMsg msg = 1; 114 | int64 ReqId = 2; 115 | } 116 | 117 | message CommitReply { 118 | CommitResult result = 1; 119 | int64 ResReqId = 2; 120 | string errors = 3; 121 | } 122 | 123 | 124 | message DiscardChangesArgs { 125 | int64 ReqId = 1; 126 | } 127 | 128 | message DiscardChangesReply { 129 | int64 ResReqId = 1; 130 | string errors = 2; 131 | } 132 | 133 | 134 | message ShowCmdArgs { 135 | int64 ReqId = 1; 136 | string cli = 2; 137 | } 138 | 139 | message ShowCmdTextReply { 140 | int64 ResReqId =1; 141 | string output = 2; 142 | string errors = 3; 143 | } 144 | message ShowCmdJSONReply { 145 | int64 ResReqId =1; 146 | string jsonoutput = 2; 147 | string errors = 3; 148 | } 149 | -------------------------------------------------------------------------------- /lib/util/client/grpc/ems.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: ems.proto 3 | 4 | require 'google/protobuf' 5 | 6 | Google::Protobuf::DescriptorPool.generated_pool.build do 7 | add_message "IOSXRExtensibleManagabilityService.ConfigGetArgs" do 8 | optional :ReqId, :int64, 1 9 | optional :yangpathjson, :string, 2 10 | end 11 | add_message "IOSXRExtensibleManagabilityService.ConfigGetReply" do 12 | optional :ResReqId, :int64, 1 13 | optional :yangjson, :string, 2 14 | optional :errors, :string, 3 15 | end 16 | add_message "IOSXRExtensibleManagabilityService.GetOperArgs" do 17 | optional :ReqId, :int64, 1 18 | optional :yangpathjson, :string, 2 19 | end 20 | add_message "IOSXRExtensibleManagabilityService.GetOperReply" do 21 | optional :ResReqId, :int64, 1 22 | optional :yangjson, :string, 2 23 | optional :errors, :string, 3 24 | end 25 | add_message "IOSXRExtensibleManagabilityService.ConfigArgs" do 26 | optional :ReqId, :int64, 1 27 | optional :yangjson, :string, 2 28 | end 29 | add_message "IOSXRExtensibleManagabilityService.ConfigReply" do 30 | optional :ResReqId, :int64, 1 31 | optional :errors, :string, 2 32 | end 33 | add_message "IOSXRExtensibleManagabilityService.CliConfigArgs" do 34 | optional :ReqId, :int64, 1 35 | optional :cli, :string, 2 36 | end 37 | add_message "IOSXRExtensibleManagabilityService.CliConfigReply" do 38 | optional :ResReqId, :int64, 1 39 | optional :errors, :string, 2 40 | end 41 | add_message "IOSXRExtensibleManagabilityService.CommitReplaceArgs" do 42 | optional :ReqId, :int64, 1 43 | optional :cli, :string, 2 44 | optional :yangjson, :string, 3 45 | end 46 | add_message "IOSXRExtensibleManagabilityService.CommitReplaceReply" do 47 | optional :ResReqId, :int64, 1 48 | optional :errors, :string, 2 49 | end 50 | add_message "IOSXRExtensibleManagabilityService.CommitMsg" do 51 | optional :label, :string, 1 52 | optional :comment, :string, 2 53 | end 54 | add_message "IOSXRExtensibleManagabilityService.CommitArgs" do 55 | optional :msg, :message, 1, "IOSXRExtensibleManagabilityService.CommitMsg" 56 | optional :ReqId, :int64, 2 57 | end 58 | add_message "IOSXRExtensibleManagabilityService.CommitReply" do 59 | optional :result, :enum, 1, "IOSXRExtensibleManagabilityService.CommitResult" 60 | optional :ResReqId, :int64, 2 61 | optional :errors, :string, 3 62 | end 63 | add_message "IOSXRExtensibleManagabilityService.DiscardChangesArgs" do 64 | optional :ReqId, :int64, 1 65 | end 66 | add_message "IOSXRExtensibleManagabilityService.DiscardChangesReply" do 67 | optional :ResReqId, :int64, 1 68 | optional :errors, :string, 2 69 | end 70 | add_message "IOSXRExtensibleManagabilityService.ShowCmdArgs" do 71 | optional :ReqId, :int64, 1 72 | optional :cli, :string, 2 73 | end 74 | add_message "IOSXRExtensibleManagabilityService.ShowCmdTextReply" do 75 | optional :ResReqId, :int64, 1 76 | optional :output, :string, 2 77 | optional :errors, :string, 3 78 | end 79 | add_message "IOSXRExtensibleManagabilityService.ShowCmdJSONReply" do 80 | optional :ResReqId, :int64, 1 81 | optional :jsonoutput, :string, 2 82 | optional :errors, :string, 3 83 | end 84 | add_enum "IOSXRExtensibleManagabilityService.CommitResult" do 85 | value :CHANGE, 0 86 | value :NO_CHANGE, 1 87 | value :FAIL, 2 88 | end 89 | end 90 | 91 | module IOSXRExtensibleManagabilityService 92 | ConfigGetArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ConfigGetArgs").msgclass 93 | ConfigGetReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ConfigGetReply").msgclass 94 | GetOperArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.GetOperArgs").msgclass 95 | GetOperReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.GetOperReply").msgclass 96 | ConfigArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ConfigArgs").msgclass 97 | ConfigReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ConfigReply").msgclass 98 | CliConfigArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CliConfigArgs").msgclass 99 | CliConfigReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CliConfigReply").msgclass 100 | CommitReplaceArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CommitReplaceArgs").msgclass 101 | CommitReplaceReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CommitReplaceReply").msgclass 102 | CommitMsg = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CommitMsg").msgclass 103 | CommitArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CommitArgs").msgclass 104 | CommitReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CommitReply").msgclass 105 | DiscardChangesArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.DiscardChangesArgs").msgclass 106 | DiscardChangesReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.DiscardChangesReply").msgclass 107 | ShowCmdArgs = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ShowCmdArgs").msgclass 108 | ShowCmdTextReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ShowCmdTextReply").msgclass 109 | ShowCmdJSONReply = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.ShowCmdJSONReply").msgclass 110 | CommitResult = Google::Protobuf::DescriptorPool.generated_pool.lookup("IOSXRExtensibleManagabilityService.CommitResult").enummodule 111 | end 112 | -------------------------------------------------------------------------------- /lib/util/client/grpc/ems_services.rb: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # Source: ems.proto for package 'IOSXRExtensibleManagabilityService' 3 | 4 | require 'grpc' 5 | require_relative 'ems' 6 | 7 | module IOSXRExtensibleManagabilityService 8 | module GRPCConfigOper 9 | 10 | # TODO: add proto service documentation here 11 | class Service 12 | 13 | include GRPC::GenericService 14 | 15 | self.marshal_class_method = :encode 16 | self.unmarshal_class_method = :decode 17 | self.service_name = 'IOSXRExtensibleManagabilityService.gRPCConfigOper' 18 | 19 | rpc :GetConfig, ConfigGetArgs, stream(ConfigGetReply) 20 | rpc :MergeConfig, ConfigArgs, ConfigReply 21 | rpc :DeleteConfig, ConfigArgs, ConfigReply 22 | rpc :ReplaceConfig, ConfigArgs, ConfigReply 23 | rpc :CliConfig, CliConfigArgs, CliConfigReply 24 | rpc :CommitReplace, CommitReplaceArgs, CommitReplaceReply 25 | rpc :CommitConfig, CommitArgs, CommitReply 26 | rpc :ConfigDiscardChanges, DiscardChangesArgs, DiscardChangesReply 27 | rpc :GetOper, GetOperArgs, stream(GetOperReply) 28 | end 29 | 30 | Stub = Service.rpc_stub_class 31 | end 32 | module GRPCExec 33 | 34 | # TODO: add proto service documentation here 35 | class Service 36 | 37 | include GRPC::GenericService 38 | 39 | self.marshal_class_method = :encode 40 | self.unmarshal_class_method = :decode 41 | self.service_name = 'IOSXRExtensibleManagabilityService.gRPCExec' 42 | 43 | rpc :ShowCmdTextOutput, ShowCmdArgs, stream(ShowCmdTextReply) 44 | rpc :ShowCmdJSONOutput, ShowCmdArgs, stream(ShowCmdJSONReply) 45 | end 46 | 47 | Stub = Service.rpc_stub_class 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/util/client/netconf.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative 'client' 16 | 17 | # Namespace for Cisco netconf-specific code 18 | class Cisco::Client::NETCONF < Cisco::Client 19 | end 20 | 21 | # Auto-load all Ruby files in the subdirectory 22 | Dir.glob(__dir__ + '/netconf/*.rb') { |file| require file } 23 | -------------------------------------------------------------------------------- /lib/util/client/netconf/netconf.rb: -------------------------------------------------------------------------------- 1 | # June 2016, Chris Frisz 2 | # 3 | # Copyright (c) 2015-2016 Cisco and/or its affiliates. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | require_relative '../../yang' 18 | require 'rexml/document' 19 | 20 | module Cisco 21 | # Cisco module 22 | module Netconf 23 | # Netconf module performs conversion of REXML documents into 24 | # a format with types that are comparable to the output of 25 | # JSON.parse 26 | NC_BASE_1_0_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0' 27 | 28 | def self.empty?(nc) 29 | !nc || nc.empty? 30 | end 31 | 32 | def self.convert_xml(xml) 33 | fail "#{xml} is not an XML document" unless xml.is_a?(REXML::Document) 34 | convert_xml_node(xml.root) 35 | end 36 | 37 | def self.convert_xml_node(node) 38 | fail "#{node} is not an XML node" unless node.is_a?(REXML::Element) 39 | out_hash = {} 40 | children = node.to_a 41 | if children.length == 1 && node.has_text? 42 | out_hash[node.name] = [children[0].value.strip] 43 | elsif !node.has_elements? 44 | out_hash[node.name] = [] 45 | else 46 | out_hash[node.name] = children.map { |child| convert_xml_node(child) } 47 | end 48 | # Looking for operation=delete in the netconf:base:1.0 namespace 49 | if node.attributes.get_attribute_ns(NC_BASE_1_0_NS, 50 | 'operation').to_s == 'delete' 51 | out_hash[:delete] = :delete 52 | end 53 | out_hash 54 | end 55 | 56 | def self.convert_rexml_from_string(input) 57 | if empty?(input) 58 | out = {} 59 | else 60 | unless defined? @iw 61 | @iw = {} 62 | @iw[:ignore_whitespace_nodes] = :all 63 | end 64 | out = convert_xml(REXML::Document.new(input, @iw)) 65 | end 66 | out 67 | end 68 | 69 | def self.insync_for_merge(target, current) 70 | !Yang.needs_something?(:merge, 71 | convert_rexml_from_string(target), 72 | convert_rexml_from_string(current)) 73 | end 74 | 75 | def self.insync_for_replace(target, current) 76 | !Yang.needs_something?(:replace, 77 | convert_rexml_from_string(target), 78 | convert_rexml_from_string(current)) 79 | end 80 | end # Netconf 81 | end # Cisco 82 | -------------------------------------------------------------------------------- /lib/util/client/utils.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # January 2016, Glenn F. Matthews 4 | # 5 | # Copyright (c) 2015-2016 Cisco and/or its affiliates. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | require_relative '../constants' 20 | require_relative '../logger' 21 | 22 | # Utility methods for clients of various RPC formats 23 | class Cisco::Client 24 | # Make a best effort to convert a given input value to an Array. 25 | # Strings are split by newlines, and nil becomes an empty Array. 26 | def self.munge_to_array(val) 27 | val = [] if val.nil? 28 | val = val.split("\n") if val.is_a?(String) 29 | val 30 | end 31 | 32 | def munge_to_array(val) 33 | self.class.munge_to_array(val) 34 | end 35 | 36 | # Helper function that subclasses may use with get(data_format: :cli) 37 | # Method for working with hierarchical show command output such as 38 | # "show running-config". Searches the given multi-line string 39 | # for all matches to the given value query. If context is provided, 40 | # the matches will be filtered to only those that are located "under" 41 | # the given context sequence (as determined by indentation). 42 | # 43 | # @param cli_output [String] The body of text to search 44 | # @param context [*Regex] zero or more regular expressions defining 45 | # the parent configs to filter by. 46 | # @param value [Regex] The regular expression to match 47 | # @return [[String], nil] array of matching (sub)strings, else nil. 48 | # 49 | # @example Find all OSPF router names in the running-config 50 | # ospf_names = filter_cli(cli_output: running_cfg, 51 | # value: /^router ospf (\d+)/) 52 | # 53 | # @example Find all address-family types under the given BGP router 54 | # bgp_afs = filter_cli(cli_output: show_run_bgp, 55 | # context: [/^router bgp #{ASN}/], 56 | # value: /^address-family (.*)/) 57 | def self.filter_cli(cli_output: nil, 58 | context: nil, 59 | value: nil) 60 | return cli_output if cli_output.nil? 61 | context ||= [] 62 | context.each { |filter| cli_output = find_subconfig(cli_output, filter) } 63 | return nil if cli_output.nil? || cli_output.empty? 64 | return cli_output if value.nil? 65 | value = to_regexp(value) 66 | match = cli_output.scan(value) 67 | return nil if match.empty? 68 | # find matches and return as array of String if it only does one match. 69 | # Otherwise return array of array. 70 | match.flatten! if match[0].is_a?(Array) && match[0].length == 1 71 | match 72 | end 73 | 74 | # Returns the subsection associated with the given 75 | # line of config 76 | # @param [String] the body of text to search 77 | # @param [Regex] the regex key of the config for which 78 | # to retrieve the subsection 79 | # @return [String, nil] the subsection of body, de-indented 80 | # appropriately, or nil if no such subsection exists. 81 | def self.find_subconfig(body, regexp_query) 82 | return nil if body.nil? || regexp_query.nil? 83 | regexp_query = to_regexp(regexp_query) 84 | 85 | rows = body.split("\n") 86 | match_row_index = rows.index { |row| regexp_query =~ row } 87 | return nil if match_row_index.nil? 88 | 89 | cur = match_row_index + 1 90 | subconfig = [] 91 | 92 | until (/\A\s+.*/ =~ rows[cur]).nil? || cur == rows.length 93 | subconfig << rows[cur] 94 | cur += 1 95 | end 96 | return nil if subconfig.empty? 97 | # Strip an appropriate minimal amount of leading whitespace from 98 | # all lines in the subconfig 99 | min_leading = subconfig.map { |line| line[/\A */].size }.min 100 | subconfig = subconfig.map { |line| line[min_leading..-1] } 101 | subconfig.join("\n") 102 | end 103 | 104 | # Helper method for CLI getters 105 | # 106 | # Convert a string or array of strings to a Regexp or array thereof 107 | def self.to_regexp(input) 108 | if input.is_a?(Regexp) 109 | return input 110 | elsif input.is_a?(Array) 111 | return input.map { |item| to_regexp(item) } 112 | else 113 | # The string might be explicitly formatted as a regexp 114 | if input[0] == '/' && input[-1] == '/' 115 | # '/foo/' => %r{foo} 116 | return Regexp.new(input[1..-2]) 117 | elsif input[0] == '/' && input[-2..-1] == '/i' 118 | # '/foo/i' => %r{foo}i 119 | return Regexp.new(input[1..-3], Regexp::IGNORECASE) 120 | else 121 | # 'foo' => %r{^foo$}i 122 | return Regexp.new("^#{input}$", Regexp::IGNORECASE) 123 | end 124 | end 125 | end 126 | 127 | # Helper method for calls into third-party code - suppresses Ruby warnings 128 | # for the given block since we have no control over that code. 129 | def self.silence_warnings(&block) 130 | warn_level = $VERBOSE 131 | $VERBOSE = nil 132 | result = block.call 133 | $VERBOSE = warn_level 134 | result 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /lib/util/constants.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Shared constants for the Cisco module 16 | module Cisco 17 | # Shared constants for the Cisco module 18 | module Util 19 | PLATFORMS = [ 20 | # Cisco IOS XR 21 | :ios_xr 22 | ] 23 | 24 | DATA_FORMATS = [ 25 | :cli, # Cisco CLI. Indentation is significant. 26 | :xml, # Netconf XML 27 | :yang_json, # YANG JSON 28 | ] 29 | 30 | NETCONF_SET_MODE = [ 31 | :merge, 32 | :replace, 33 | :delete, 34 | ] 35 | 36 | YANG_SET_MODE = [ 37 | :merge_config, 38 | :replace_config, 39 | :delete_config, 40 | ] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/util/environment.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'yaml' 16 | require_relative 'logger' 17 | 18 | module Cisco 19 | module Util 20 | # Class representing the configuration environment 21 | class Environment 22 | @environments = {} 23 | @default_environment_name = 'default' 24 | 25 | # Autogenerate Cisco::Uitl::Environment.default_environment_name and 26 | # Cisco::Util::Environment.default_environment_name= class methods. 27 | class << self 28 | attr_accessor :default_environment_name 29 | end 30 | 31 | # We have three tiers of configuration: 32 | # 1) default (defined in this file) 33 | # 2) System-wide gem configuration in /etc/cisco_node_utils.yaml 34 | # 3) User configuration in ~/cisco_node_utils.yaml 35 | 36 | DEFAULT_ENVIRONMENT = { 37 | host: nil, # localhost by default 38 | port: nil, # only applicable to gRPC 39 | username: nil, 40 | password: nil, 41 | } 42 | 43 | def self.environments 44 | if @environments.empty? 45 | @environments = merge_config('/etc/cisco_yang.yaml', 46 | @environments) 47 | @environments = merge_config('~/cisco_yang.yaml', 48 | @environments) 49 | @environments.each do |name, config| 50 | Cisco::Logger.debug("Environment '#{name}': #{config}") 51 | end 52 | end 53 | @environments 54 | end 55 | 56 | def self.environment_names 57 | names = [] 58 | environments.each do |name, _config| 59 | names << name 60 | end 61 | names 62 | end 63 | 64 | def self.merge_config(path, current_config) 65 | data = data_from_file(path) 66 | data.each do |name, config| 67 | # in case config is nil: 68 | config ||= {} 69 | # in case current_config has no entry for this name: 70 | current_config[name] ||= DEFAULT_ENVIRONMENT.clone 71 | # merge it on in! 72 | current_config[name].merge!(strings_to_symbols(config)) 73 | end 74 | current_config 75 | end 76 | 77 | def self.data_from_file(path) 78 | begin 79 | path = File.expand_path(path) 80 | rescue ArgumentError => e 81 | # Can happen if path includes '~' but $HOME is not defined 82 | Cisco::Logger.debug "Failed to load #{path}: #{e}" 83 | return {} 84 | end 85 | unless File.file?(path) 86 | Cisco::Logger.debug "No file found at #{path}" 87 | return {} 88 | end 89 | unless File.readable?(path) 90 | Cisco::Logger.debug "No permissions to read #{path}" 91 | return {} 92 | end 93 | Cisco::Logger.debug "File found at #{path}" 94 | YAML.load_file(path) 95 | rescue Psych::SyntaxError => e 96 | Cisco::Logger.error("Error loading #{path}: #{e}") 97 | {} 98 | end 99 | 100 | def self.strings_to_symbols(hash) 101 | Hash[hash.map { |k, v| [k.to_sym, v] }] 102 | end 103 | 104 | def self.environment(name) 105 | name ||= @default_environment_name 106 | Cisco::Logger.debug("Getting environment '#{name}'") 107 | environments.fetch(name, nil) 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/util/exceptions.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Add generic exception classes to the Cisco module 16 | # The hierarchy is: 17 | # RuntimeError 18 | # Cisco::CiscoError 19 | # Cisco::ClientError 20 | # Cisco::ConnectionRefused 21 | # Cisco::AuthenticationFailed 22 | # Cisco::RequestFailed 23 | # Cisco::CliError 24 | # Cisco::RequestNotSupported 25 | # Cisco::UnsupportedError 26 | module Cisco 27 | # Generic class for exceptions raised by this module 28 | class CiscoError < RuntimeError 29 | attr_reader :kwargs 30 | 31 | def initialize(message=nil, **kwargs) 32 | @kwargs = kwargs 33 | super(message) 34 | end 35 | 36 | def respond_to?(method_sym, include_private=false) 37 | @kwargs.key?(method_sym) ? true : super 38 | end 39 | 40 | def method_missing(method_sym, *args, **kwargs, &block) 41 | @kwargs.key?(method_sym) ? @kwargs[method_sym] : super 42 | end 43 | end 44 | 45 | # Exception class for fundamental client failures 46 | class ClientError < CiscoError 47 | end 48 | 49 | # ConnectionRefused means the server isn't listening 50 | class ConnectionRefused < ClientError 51 | end 52 | 53 | # AuthenticationFailed means we were able to connect but not login 54 | class AuthenticationFailed < ClientError 55 | end 56 | 57 | # Exception class for failures of a specific request to the client 58 | class RequestFailed < CiscoError 59 | end 60 | 61 | # Extension of RequestFailed class specifically for CLI errors 62 | class CliError < RequestFailed 63 | def initialize(message=nil, 64 | clierror: nil, 65 | rejected_input: nil, 66 | successful_input: [], 67 | **kwargs) 68 | unless message 69 | if rejected_input.is_a?(Array) 70 | if rejected_input.length > 1 71 | message = "The following commands were rejected:\n" 72 | message += " #{rejected_input.join("\n ")}\n" 73 | else 74 | message = "The command '#{rejected_input.first}' was rejected " 75 | end 76 | else 77 | message = "The command '#{rejected_input}' was rejected " 78 | end 79 | message += "with error:\n#{clierror}" 80 | end 81 | super(message, 82 | :clierror => clierror, 83 | :rejected_input => rejected_input, 84 | :successful_input => successful_input, 85 | **kwargs) 86 | end 87 | end 88 | 89 | # Extension of RequestFailed class specifically for YANG errors 90 | class YangError < RequestFailed 91 | def initialize(message=nil, 92 | error: nil, 93 | rejected_input: nil, 94 | successful_input: [], 95 | **kwargs) 96 | unless message 97 | if rejected_input.is_a?(Array) 98 | if rejected_input.length > 1 99 | message = "The following configs were rejected:\n" 100 | message += " #{rejected_input.join("\n ")}\n" 101 | else 102 | message = "The config '#{rejected_input.first}' was rejected " 103 | end 104 | else 105 | message = "The config '#{rejected_input}' was rejected " 106 | end 107 | 108 | message += "with error:\n#{error}" 109 | end 110 | super(message, 111 | :error => error, 112 | :rejected_input => rejected_input, 113 | :successful_input => successful_input, 114 | **kwargs) 115 | end 116 | end 117 | 118 | # RequestNotSupported means we made a request that was validly 119 | # constructed but includes options that are unsupported. 120 | # 121 | # An example would be requesting structured output on a CLI command 122 | # that only supports ASCII output. 123 | class RequestNotSupported < RequestFailed 124 | end 125 | 126 | # Exception class raised by CommandReference to indicate that 127 | # a particular feature/attribute is explicitly excluded on the given node. 128 | class UnsupportedError < CiscoError 129 | def initialize(feature, name, oper=nil, msg=nil) 130 | message = "Feature '#{feature}'" 131 | message += ", attribute '#{name}'" unless name.nil? 132 | message += ", operation '#{oper}'" unless oper.nil? 133 | message += ' is unsupported on this node' 134 | message += ": #{msg}" unless msg.nil? 135 | super(message, feature: feature, name: name, oper: oper) 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /lib/util/logger.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'logger' 16 | 17 | # Ensure module Cisco is defined 18 | module Cisco 19 | end 20 | 21 | # Module for logging. Will use the Puppet logging module if 22 | # available, otherwise will create a Logger to use 23 | module Cisco::Logger 24 | module_function 25 | 26 | # Figure out what provider logging utility we 27 | # should use: Puppet or Ruby Logger/STDOUT/INFO. 28 | if defined? (Puppet::Util::Logging) 29 | @@logger = Puppet # rubocop:disable Style/ClassVars 30 | def error(string) 31 | @@logger.err(string) 32 | end 33 | 34 | def warn(string) 35 | @@logger.warning(string) 36 | end 37 | else 38 | @@logger = Logger.new(STDOUT) # rubocop:disable Style/ClassVars 39 | @@logger.level = Logger::INFO 40 | 41 | def level 42 | @@logger.level 43 | end 44 | 45 | def level=(level) 46 | @@logger.level = level 47 | end 48 | 49 | def error(string) 50 | @@logger.error(string) 51 | end 52 | 53 | def warn(string) 54 | @@logger.warn(string) 55 | end 56 | end 57 | 58 | def debug(string) 59 | @@logger.debug(string) 60 | end 61 | 62 | def info(string) 63 | @@logger.info(string) 64 | end 65 | end # module 66 | -------------------------------------------------------------------------------- /lib/util/node.rb: -------------------------------------------------------------------------------- 1 | # Cisco node helper class. Abstracts away the details of the underlying 2 | # transport (grpc/netconf) and provides various convenience methods. 3 | 4 | # Copyright (c) 2016 Cisco and/or its affiliates. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | require_relative 'client' 19 | require_relative 'client/client' 20 | require_relative 'exceptions' 21 | require_relative 'logger' 22 | 23 | # Add node management classes and APIs to the Cisco namespace. 24 | module Cisco 25 | # class Cisco::Node 26 | # Pseudo-singleton representing the network node (switch/router) that is 27 | # running this code. The singleton is lazily instantiated, meaning that 28 | # it doesn't exist until some client requests it (with Node.instance(...)) 29 | class Node 30 | @instance_hash = {} 31 | @instance = nil 32 | 33 | # Here and below are implementation details and private APIs that most 34 | # providers shouldn't need to know about or use. 35 | 36 | attr_reader :client 37 | 38 | # Return a node instance that wraps a client of the specified class. 39 | def self.instance(client_class=nil) 40 | return @instance_hash[client_class] ||= new(client_class) unless client_class.nil? 41 | 42 | # just return one of the cached instances 43 | return @instance_hash[hash.keys[0]] unless @instance_hash.empty? 44 | 45 | # no nodes currently cached, so create one 46 | debug 'Attempting to create a client (type not specified)...' 47 | env_names = Cisco::Util::Environment.environment_names 48 | 49 | Cisco::Client.clients.each do |c| 50 | client_env_name = Cisco::Client.environment_name(c) 51 | if env_names.include?(client_env_name) 52 | debug "Environment configuration found for client #{c}" 53 | return instance(c) 54 | end 55 | end 56 | 57 | error 'No clients configured' 58 | end 59 | 60 | def self.instance_exists(client_class) 61 | !@instance_hash[client_class].nil? 62 | end 63 | 64 | def initialize(client_class) 65 | @client = Cisco::Client.create(client_class) 66 | end 67 | 68 | def to_s 69 | client.to_s 70 | end 71 | 72 | def inspect 73 | "Node: client:'#{client.inspect}'" 74 | end 75 | 76 | # Send a config command to the device. 77 | # @raise [Cisco::RequestFailed] if any command is rejected by the device. 78 | def set(**kwargs) 79 | @client.set(**kwargs) 80 | end 81 | 82 | # Send a show command to the device. 83 | # @raise [Cisco::RequestFailed] if any command is rejected by the device. 84 | def get(**kwargs) 85 | @client.get(**kwargs) 86 | end 87 | 88 | # Merge the specified config with the running config on the device. 89 | # using netconf 90 | def merge_netconf(config) 91 | @client.set(values: config, mode: :merge) 92 | end 93 | 94 | # Replace the running config on the device with the specified 95 | # config using netconf client. 96 | def replace_netconf(config) 97 | @client.set(values: config, mode: :replace) 98 | end 99 | 100 | # Retrieve config from the device for the specified path using netconf. 101 | def get_netconf(xpath) 102 | @client.get(command: xpath) 103 | end 104 | 105 | # Merge the specified JSON YANG config with the running config on 106 | # the device. 107 | def merge_yang(yang) 108 | @client.set(values: yang, mode: :merge_config) 109 | end 110 | 111 | # Replace the running config on the device with the specified 112 | # JSON YANG config. 113 | def replace_yang(yang) 114 | @client.set(values: yang, 115 | mode: :replace_config) 116 | end 117 | 118 | # Delete the specified JSON YANG config from the device. 119 | def delete_yang(yang) 120 | @client.set(values: yang, mode: :delete_config) 121 | end 122 | 123 | # Retrieve JSON YANG config from the device for the specified path. 124 | def get_yang(yang_path) 125 | @client.get(command: yang_path) 126 | end 127 | 128 | # Retrieve JSON YANG operational data for the specified path. 129 | def get_yang_oper(yang_path) 130 | @client.get(command: yang_path, mode: :get_oper) 131 | end 132 | 133 | # @return [String] such as "Cisco Nexus Operating System (NX-OS) Software" 134 | def os 135 | @client.os 136 | end 137 | 138 | # @return [String] such as "6.0(2)U5(1) [build 6.0(2)U5(0.941)]" 139 | def os_version 140 | @client.os_version 141 | end 142 | 143 | # @return [String] such as "Nexus 3048 Chassis" 144 | def product_description 145 | @client.product_description 146 | end 147 | 148 | # @return [String] such as "N3K-C3048TP-1GE" 149 | def product_id 150 | @client.product_id 151 | end 152 | 153 | # @return [String] such as "V01" 154 | def product_version_id 155 | @client.product_version_id 156 | end 157 | 158 | # @return [String] such as "FOC1722R0ET" 159 | def product_serial_number 160 | @client.product_serial_number 161 | end 162 | 163 | # @return [String] such as "bxb-oa-n3k-7" 164 | def host_name 165 | @client.host_name 166 | end 167 | 168 | # @return [String] such as "example.com" 169 | def domain_name 170 | @client.domain_name 171 | end 172 | 173 | # @return [Integer] System uptime, in seconds 174 | def system_uptime 175 | @client.system_uptime 176 | end 177 | 178 | # @return [String] timestamp of last reset time 179 | def last_reset_time 180 | @client.get_last_reset_time 181 | end 182 | 183 | # @return [String] such as "Reset Requested by CLI command reload" 184 | def last_reset_reason 185 | @client.get_last_reset_reason 186 | end 187 | 188 | # @return [Float] combined user/kernel CPU utilization 189 | def system_cpu_utilization 190 | @client.system_cpu_utilization 191 | end 192 | 193 | # @return [String] such as 194 | # "bootflash:///n3000-uk9-kickstart.6.0.2.U5.0.941.bin" 195 | def boot 196 | @client.get_boot 197 | end 198 | 199 | # @return [String] such as 200 | # "bootflash:///n3000-uk9.6.0.2.U5.0.941.bin" 201 | def system 202 | @client.system 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /lib/util/node_util.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative 'node' 16 | require_relative 'exceptions' 17 | 18 | module Cisco 19 | # NodeUtil - generic functionality for node utility subclasses to use 20 | class NodeUtil 21 | def self.node 22 | @node ||= Cisco::Node.instance 23 | end 24 | 25 | def node 26 | self.class.node 27 | end 28 | 29 | def self.client 30 | node.client 31 | end 32 | 33 | def client 34 | node.client 35 | end 36 | 37 | def self.supports?(api) 38 | client.supports?(api) 39 | end 40 | 41 | def supports?(api) 42 | client.supports?(api) 43 | end 44 | 45 | def self.platform 46 | client.platform 47 | end 48 | 49 | def platform 50 | client.platform 51 | end 52 | 53 | def get(**kwargs) 54 | node.get(**kwargs) 55 | rescue Cisco::RequestFailed => e 56 | e2 = e.class.new("[#{self}] #{e}", **e.kwargs) 57 | e2.set_backtrace(e.backtrace) 58 | raise e2 59 | end 60 | 61 | def ios_xr? 62 | platform == :ios_xr 63 | end 64 | 65 | def nexus? 66 | platform == :nexus 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/util/platform.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require_relative 'node_util' 16 | 17 | module Cisco 18 | # Platform - class for gathering platform hardware and software information 19 | class Platform < NodeUtil 20 | # XR: 6.1.1.04I 21 | def self.image_version 22 | config_get('show_version', 'version') 23 | end 24 | 25 | # ex: 'n3500-uk9.6.0.2.A3.0.40.bin' 26 | def self.system_image 27 | config_get('show_version', 'boot_image') 28 | end 29 | 30 | def self.system_time 31 | client.system_time 32 | end 33 | 34 | def self.host_name 35 | client.host_name 36 | end 37 | 38 | def self.product_id 39 | client.product_id 40 | end 41 | 42 | def self.system 43 | client.system 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/util/show_running_yang.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # This is a utility to output the current state of an XR configuration. 16 | # In order to run, this utility needs access to one or more *.yang files 17 | # (found in the /pkg/yang directory on the XR box, as well as from other 18 | # sources). 19 | 20 | require 'optparse' 21 | require_relative 'yang_accessor' 22 | 23 | # Utility class to output the current state of an XR configuration. 24 | 25 | options = {} 26 | optparse = OptionParser.new do |opts| 27 | opts.banner = 'Usage: ruby [path]show_running_yang.rb [options] [file_or_directory_path]' 28 | 29 | opts.on('-m', '--manifest', 'Output config in a form suitable '\ 30 | 'for inclusion in a Puppet manifest') do |_arg| 31 | options[:manifest] = true 32 | end 33 | 34 | opts.on('-o', '--oper', 35 | 'Retrieve operational data instead of configuration '\ 36 | '(experimental; use at own risk)') do 37 | options[:oper] = true 38 | end 39 | 40 | opts.on('-c', '--client CLIENT', 'The client to use to connect.', 41 | 'grpc|netconf (defaults to grpc') do |client| 42 | options[:client] = client 43 | end 44 | 45 | # opts.on('-e', '--environment node', 'The node in cisco_node_utils.yaml '\ 46 | # 'from which to retrieve data') do |env| 47 | # options[:environment] = env 48 | # end 49 | 50 | opts.on('-d', '--debug', 'Enable debug-level logging') do 51 | Cisco::Logger.level = Logger::DEBUG 52 | end 53 | 54 | opts.on('-v', '--verbose', 'Enable verbose messages') do 55 | options[:verbose] = true 56 | end 57 | 58 | opts.on('-h', '--help', 'Print this help') do 59 | puts optparse 60 | exit(0) 61 | end 62 | end 63 | optparse.parse! 64 | 65 | if options[:oper] && options[:manifest] 66 | STDERR.puts '!! Operational data cannot be set in a manifest, '\ 67 | 'so option -m does not make sense in conjunction with -o.' 68 | exit(-1) 69 | end 70 | 71 | case options[:client] 72 | when 'netconf' 73 | options[:client_class] = Cisco::Client::NETCONF 74 | when 'grpc', nil 75 | options[:client_class] = Cisco::Client::GRPC 76 | else 77 | STDERR.puts "!! Invalid client specified: #{options[:client]}" 78 | exit(-1) 79 | end 80 | 81 | # If there is a single ARGV left, use is as the file/dir path 82 | if ARGV.length == 1 83 | options[:path] = ARGV[0] 84 | elsif ARGV.length > 1 85 | puts optparse 86 | exit(-1) 87 | end 88 | 89 | ya = Cisco::YangAccessor.new 90 | ya.process(options) 91 | -------------------------------------------------------------------------------- /lib/util/yang.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'json' 16 | 17 | module Cisco 18 | # The Yang class is a container for the needs_something? logic, which 19 | # attempts to determine if a given operation and data structures 20 | # will require setting the configuration of the remote device. 21 | class Yang 22 | # Is the specified yang string empty? 23 | def self.empty?(yang) 24 | !yang || yang.empty? 25 | end 26 | 27 | # Given a current and target YANG configuration, returns true if 28 | # the configurations are in-sync, relative to a "merge_config" action 29 | def self.insync_for_merge?(target, current) 30 | target_hash = self.empty?(target) ? {} : JSON.parse(target) 31 | current_hash = self.empty?(current) ? {} : JSON.parse(current) 32 | 33 | !needs_something?(:merge, target_hash, current_hash) 34 | end 35 | 36 | # Given a current and target YANG configuration, returns true if 37 | # the configuration are in-sync, relative to a "replace_config" action 38 | def self.insync_for_replace?(target, current) 39 | target_hash = self.empty?(target) ? {} : JSON.parse(target) 40 | current_hash = self.empty?(current) ? {} : JSON.parse(current) 41 | 42 | !needs_something?(:replace, target_hash, current_hash) 43 | end 44 | 45 | # usage: 46 | # needs_something?(op, target, run) 47 | # 48 | # op - symbol - If value is not :replace, it's assumed to be :merge. 49 | # Indicates to the function whether to check for a 50 | # possible merge vs. replace 51 | # 52 | # target - JSON - JSON tree representing target configuration 53 | # 54 | # run - JSON - JSON tree representing target configuration 55 | # 56 | # 57 | # Needs merge will determine if target and run differ 58 | # sufficiently to necessitate running the merge command. 59 | # 60 | # The logic here amounts to determining if target is a subtree 61 | # of run, with a tiny bit of domain trickiness surrounding 62 | # elements that are arrays that contain a single nil element 63 | # that is required for "creating" certain configuration elements. 64 | # 65 | # There are ultimately 3 different types of elements in a json 66 | # tree. Hashes, Arrays, and leaves. While hashes and array values 67 | # are organized with an order, the logic here ignores the order. 68 | # In fact, it specifically attempts to match assuming order 69 | # doesn't matter. This is largely to allow users some freedom 70 | # in specifying the config in the manifest. The gRPC interface 71 | # doesn't seem to care about order. If that changes, then so 72 | # should this code. 73 | # 74 | # Arrays and Hashes are compared by iterating over every element 75 | # in target, and ensuring it is within run. 76 | # 77 | # Leaves are directly compared for equality, excepting the 78 | # condition that the target leaf is in fact an array with one 79 | # element that is nil. 80 | # 81 | # Needs replace will determine if target and run differ 82 | # sufficiently to necessitate running the replace command. 83 | # 84 | # The logic is the same as merge, except when comparing 85 | # hashes, if the run hash table has elements that are not 86 | # in target, we ultimately indicate that replace is needed 87 | def self.needs_something?(op, target, run) 88 | !hash_equiv?(op, target, run) 89 | end 90 | 91 | def self.nil_array(elt) 92 | elt.nil? || (elt.is_a?(Array) && elt.length == 1 && elt[0].nil?) 93 | end 94 | 95 | def self.sub_elt(op, target, run) 96 | if target.is_a?(Hash) && run.is_a?(Hash) 97 | hash_equiv?(op, target, run) 98 | elsif target.is_a?(Array) && run.is_a?(Array) 99 | array_equiv?(op, target, run) 100 | else 101 | delete = target.is_a?(Hash) && (target[:delete] == :delete) 102 | delete || target == run || nil_array(target) 103 | end 104 | end 105 | 106 | def self.array_equiv?(op, target, run) 107 | n = target.length 108 | # We need to count the number of times that we've had a miss because 109 | # the target element was marked delete 110 | no_op_delete_count = 0 111 | loop = lambda do |i| 112 | if i == n 113 | if op == :replace 114 | # NB: We could end up with arrays that have only nil in them 115 | # sometimes, so correct for that, then normalize for the 116 | # number of elements that are no-ops because they are marked 117 | # delete 118 | rl = run.clone.delete_if(&:nil?).length 119 | tl = target.clone.delete_if(&:nil?).length - no_op_delete_count 120 | rl == tl 121 | else 122 | true 123 | end 124 | else 125 | target_elt = target[i] 126 | if target_elt.is_a?(Hash) && (target_elt[:delete] == :delete) 127 | target_elt = target_elt.clone 128 | target_elt.delete(:delete) 129 | delete = true 130 | end 131 | run_elt = run.find { |re| sub_elt(op, target_elt, re) } 132 | 133 | if run_elt.nil? && !nil_array(target_elt) 134 | if delete 135 | no_op_delete_count += 1 136 | loop.call(i + 1) 137 | else 138 | target_elt.nil? 139 | end 140 | else 141 | if delete 142 | false 143 | else 144 | loop.call(i + 1) 145 | end 146 | end 147 | end 148 | end 149 | loop.call(0) 150 | end 151 | 152 | def self.hash_equiv?(op, target, run) 153 | keys = target.keys 154 | n = keys.length 155 | loop = lambda do |i| 156 | if i == n 157 | if op == :replace 158 | run.keys.length == target.keys.length 159 | else 160 | true 161 | end 162 | else 163 | k = keys[i] 164 | run_v = run[k] 165 | target_v = target[k] 166 | if run_v.nil? && !nil_array(target_v) 167 | false 168 | else 169 | sub_elt(op, target_v, run_v) && loop.call(i + 1) 170 | end 171 | end 172 | end 173 | loop.call(0) 174 | end 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /lib/util/yang_accessor.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # This is a utility to output the current state of an XR configuration. 16 | # In order to run, this utility needs access to one or more *.yang files 17 | # (found in the /pkg/yang directory on the XR box, as well as from other 18 | # sources). 19 | 20 | require 'optparse' 21 | require_relative 'node_util' 22 | 23 | module Cisco 24 | # Utility class to output the current state of an XR configuration. 25 | class YangAccessor 26 | def process(options) 27 | @options = options 28 | 29 | # suppress stdout 30 | old_stdout = $stdout 31 | $stdout = StringIO.new if @options[:quiet] 32 | 33 | client # initialize the client 34 | 35 | dir_or_file = options[:path] || '/pkg/yang' 36 | 37 | file = nil 38 | dir = nil 39 | 40 | if File.exist?(dir_or_file) 41 | if File.directory?(dir_or_file) 42 | dir = dir_or_file 43 | else 44 | file = dir_or_file 45 | end 46 | else 47 | puts "Directory or file not found: #{dir_or_file}" 48 | exit(-1) 49 | end 50 | 51 | puts "File found: #{file}" if file 52 | puts "Directory found: #{dir}" if dir 53 | puts 'Searching for configuration data...' unless @options[:oper] 54 | puts 'Searching for operational data...' if @options[:oper] 55 | 56 | t1 = Time.now 57 | 58 | @files = 0 59 | @cnrs = 0 60 | @errors = 0 61 | 62 | targets = [] 63 | 64 | if file 65 | targets.concat(process_file(file)) 66 | @files += 1 67 | else 68 | Dir.glob(dir + '/*.yang').sort.each do |item| 69 | targets.concat(process_file(item)) 70 | @files += 1 71 | end 72 | end 73 | 74 | delta = Time.now - t1 75 | puts '---------------------------------------------' 76 | puts "Files Processed: #{@files}" 77 | puts "Containers Processed: #{@cnrs}" 78 | puts "Errors: #{@errors}" 79 | puts "Time: #{delta.round(2)} seconds" 80 | puts # spacer 81 | 82 | $stdout = old_stdout 83 | 84 | targets 85 | end 86 | 87 | def targets(options) 88 | options[:parse_only] = true 89 | options[:quiet] = true 90 | 91 | process(options) 92 | end 93 | 94 | def process_file(file) 95 | @module = nil 96 | @namespace = nil 97 | @containers = {} 98 | 99 | targets = [] 100 | puts "[ Processing file #{file} ]" if @options[:verbose] 101 | 102 | File.open(file) do |f| 103 | loop do 104 | break if (line = f.gets).nil? 105 | target = process_line(line, f) 106 | targets << target if target 107 | end 108 | end 109 | targets 110 | end 111 | 112 | def process_line(line, file) 113 | if @module.nil? 114 | @module = Regexp.last_match(1) if line =~ /^module (.+) {/ 115 | elsif @namespace.nil? 116 | # handle split lines (move this to the process_file line 117 | # loop if more general handling is needed) 118 | until (m = line.match(/(.*)"\+$/)).nil? 119 | line2 = file.gets 120 | break if line2.nil? 121 | line2.match(/^\s*"(.*)/) do |m2| 122 | line = m[1] + m2[1] 123 | end 124 | end 125 | 126 | @namespace = Regexp.last_match(1) if line =~ /^ namespace "(.+)"/ 127 | elsif line =~ /^ container (.+) {/ 128 | return process_root_container(@module, @namespace, Regexp.last_match(1), file) 129 | end 130 | nil 131 | end 132 | 133 | def process_root_container(module_name, namespace, container, file) 134 | operation = :get_config 135 | loop do 136 | line = file.gets 137 | break if !line || line.strip == '' 138 | if line =~ /^ config false;/ # abort cnr if not config 139 | operation = :get_oper 140 | break 141 | end 142 | end 143 | 144 | # only output config or operational data, depending on options 145 | if @options[:oper] 146 | return if operation == :get_config 147 | else 148 | return unless operation == :get_config 149 | end 150 | 151 | # guard against duplicate containers 152 | if @containers.key?(container) 153 | puts "[ Duplicate container #{container} ]" if @options[:verbose] 154 | return 155 | end 156 | 157 | yang_target = client.yang_target(module_name, namespace, container) 158 | 159 | @containers[container] = true 160 | @cnrs += 1 161 | 162 | unless @options[:parse_only] 163 | begin 164 | puts "[ Processing container #{container}... ]"\ 165 | if @options[:verbose] 166 | data = client.get(data_format: :yang_json, 167 | command: yang_target, 168 | mode: operation) 169 | if data && data.strip.length > 0 170 | puts '[ Data returned ]'\ 171 | if @options[:verbose] 172 | output_data(yang_target, data) 173 | else 174 | puts '[ No data returned ]'\ 175 | if @options[:verbose] 176 | end 177 | rescue Cisco::ClientError, Cisco::YangError => e 178 | @errors += 1 179 | puts "!!Error on '#{yang_target}': #{e}" 180 | debug e.backtrace 181 | puts # spacer 182 | end 183 | end 184 | 185 | yang_target 186 | end 187 | 188 | def output_data(yang_target, data) 189 | if @options[:manifest] 190 | if @options[:client_class] == Cisco::Client::GRPC 191 | puppet_type = 'cisco_yang' 192 | ensure_prop = " ensure => present,\n" 193 | else 194 | puppet_type = 'cisco_yang_netconf' 195 | ensure_prop = '' 196 | end 197 | 198 | puts " #{puppet_type} { '#{yang_target}':\n#{ensure_prop}"\ 199 | " source => '#{data.chomp.gsub(/\n/, "\n ")}'\n"\ 200 | ' }' 201 | else 202 | puts data 203 | end 204 | puts # spacer 205 | end 206 | 207 | def client 208 | unless @client 209 | @client = Cisco::Client.create(@options[:client_class]) 210 | 211 | puts "[ Connected to client: #{@client} ]"\ 212 | if @options[:verbose] 213 | end 214 | @client 215 | rescue Cisco::AuthenticationFailed 216 | abort 'Unauthorized to connect' 217 | rescue Cisco::ClientError, TypeError, ArgumentError => e 218 | abort "Error in establishing connection: #{e}" 219 | end 220 | end # YangAccessor 221 | end # Cisco 222 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ciscoeng-ciscoyang", 3 | "version": "1.0.2", 4 | "author": "ciscoeng", 5 | "summary": "Cisco IOS-XR configuration management via YANG.", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/cisco/cisco-yang-puppet-module", 8 | "project_page": "https://github.com/cisco/cisco-yang-puppet-module", 9 | "issues_url": "https://github.com/cisco/cisco-yang-puppet-module/issues", 10 | "dependencies": [ 11 | ], 12 | "requirements": [ 13 | { 14 | "name": "pe", 15 | "version_requirement": "2015.3.x" 16 | }, 17 | { 18 | "name": "puppet", 19 | "version_requirement": "4.x" 20 | } 21 | ], 22 | "data_provider": null, 23 | "tags": [ 24 | "cisco", 25 | "ios-xr", 26 | "xr", 27 | "yang" 28 | ], 29 | "operatingsystem_support": [ 30 | { 31 | "operatingsystem": "IOS-XR", 32 | "operatingsystemrelease": [ 33 | "6.1.x", "6.2.x" 34 | ] 35 | } 36 | ] 37 | } 38 | 39 | -------------------------------------------------------------------------------- /tests/.rubopcop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../.rubocop.yml 2 | 3 | # Code complexity metrics for the tests/ subdirectory 4 | 5 | Metrics/AbcSize: 6 | Max: 158 # ouch! 7 | 8 | Metrics/CyclomaticComplexity: 9 | Max: 15 10 | 11 | Metrics/MethodLength: 12 | Max: 100 13 | 14 | Metrics/PerceivedComplexity: 15 | Max: 17 16 | 17 | Metrics/LineLength: 18 | Max: 120 19 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/model_tests/test_cisco_ios_xr_infra_rsi_cfg_vrf_groups.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | os: 'ios_xr', 25 | os_version: '6.1.1', 26 | } 27 | 28 | # skip entire file if os, version, etc. don't match 29 | skip_unless_supported(tests) 30 | 31 | # define a test (or tests) 32 | # (e.g. description, title, manifest) 33 | tests[:vrf_groups] = { 34 | desc: 'Configure VRFs', 35 | title: '{"Cisco-IOS-XR-infra-rsi-cfg:vrf-groups": [null]}', 36 | manifest_props: { 37 | source: ' 38 | {"Cisco-IOS-XR-infra-rsi-cfg:vrf-groups": 39 | { 40 | "vrf-group": [ 41 | { 42 | "vrf-group-name": "TEST-GROUP", 43 | "enable": [null], 44 | "vrfs": { 45 | "vrf": [ 46 | { 47 | "vrf-name": "ORANGE" 48 | } 49 | ] 50 | } 51 | } 52 | ] 53 | } 54 | }', 55 | mode: 'replace', 56 | }, 57 | } 58 | 59 | ################################################################# 60 | # Execute the test 61 | ################################################################# 62 | 63 | test_name 'Model Test' do 64 | # a simple run with pre/post clean 65 | # (reference our test above using the key) 66 | test_harness_run_clean(tests, :vrf_groups) 67 | end 68 | 69 | # report on skipped tests 70 | skipped_tests_summary(tests) 71 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/model_tests/test_cisco_ios_xr_infra_rsi_cfg_vrfs.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | os: 'ios_xr', 25 | os_version: '6.1.1', 26 | } 27 | 28 | # skip entire file if os, version, etc. don't match 29 | skip_unless_supported(tests) 30 | 31 | # define a test (or tests) 32 | # (e.g. description, title, manifest) 33 | tests[:vrfs] = { 34 | desc: 'Configure VRFs', 35 | title: '{"Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null]}', 36 | manifest_props: { 37 | source: ' 38 | {"Cisco-IOS-XR-infra-rsi-cfg:vrfs": 39 | { 40 | "vrf": 41 | [ 42 | { 43 | "vrf-name":"BLUE", 44 | "create":[null] 45 | }, 46 | { 47 | "vrf-name":"GREEN", 48 | "create":[null] 49 | } 50 | ] 51 | } 52 | }', 53 | mode: 'replace', 54 | }, 55 | } 56 | 57 | ################################################################# 58 | # Execute the test 59 | ################################################################# 60 | 61 | test_name 'Model Test' do 62 | # a simple run with pre/post clean 63 | # (reference our test above using the key) 64 | test_harness_run_clean(tests, :vrfs) 65 | end 66 | 67 | # report on skipped tests 68 | skipped_tests_summary(tests) 69 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/model_tests/test_cisco_ios_xr_ipv4_bgp_cfg_bgp.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | os: 'ios_xr', 25 | os_version: '6.1.1', 26 | } 27 | 28 | # skip entire file if os, version, etc. don't match 29 | skip_unless_supported(tests) 30 | 31 | # Define a test for the bgp YANG container. 32 | tests[:bgp] = { 33 | desc: 'Configure BGP', 34 | title: '{"Cisco-IOS-XR-ipv4-bgp-cfg:bgp": [null]}', 35 | manifest_props: { 36 | source: File.read(File.expand_path('../yang/bgp.yang', __FILE__)), 37 | mode: 'replace', 38 | }, 39 | } 40 | 41 | ################################################################# 42 | # Execute the test 43 | ################################################################# 44 | 45 | test_name 'Model Test' do 46 | # a simple run with pre/post clean 47 | # (reference our test above using the key) 48 | test_harness_run_clean(tests, :bgp) 49 | end 50 | 51 | # report on skipped tests 52 | skipped_tests_summary(tests) 53 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/model_tests/yang/bgp.yang: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-ipv4-bgp-cfg:bgp":{ 3 | "instance":[ 4 | { 5 | "instance-name":"default", 6 | "instance-as":[ 7 | { 8 | "as":0, 9 | "four-byte-as":[ 10 | { 11 | "as":55, 12 | "bgp-running":[null], 13 | "default-vrf":{ 14 | "global":{ 15 | "nsr":false, 16 | "global-timers":{ 17 | "keepalive":60, 18 | "hold-time":120 19 | }, 20 | "enforce-ibgp-out-policy":[null], 21 | "global-afs":{ 22 | "global-af":[ 23 | { 24 | "af-name":"ipv4-multicast", 25 | "enable":[null], 26 | "update-limit-address-family":256, 27 | "ebgp":{ 28 | "paths-value":32, 29 | "unequal-cost":false, 30 | "selective":false, 31 | "order-by-igp-metric":false 32 | } 33 | } 34 | ] 35 | } 36 | }, 37 | "bgp-entity":{ 38 | "neighbors":{ 39 | "neighbor":[ 40 | { 41 | "neighbor-address":"5.5.5.5", 42 | "remote-as":{ 43 | "as-xx":0, 44 | "as-yy":12 45 | }, 46 | "bfd-enable-modes":"default", 47 | "ebgp-multihop":{ 48 | "max-hop-count":10, 49 | "mpls-deactivation":false 50 | }, 51 | "description":"Neighbor A", 52 | "msg-log-out":{ 53 | "msg-buf-count":10 54 | } 55 | }, 56 | { 57 | "neighbor-address":"6.6.6.6", 58 | "remote-as":{ 59 | "as-xx":0, 60 | "as-yy":13 61 | }, 62 | "description":"Neighbor B" 63 | } 64 | ] 65 | } 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_create_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:create] = CREATE 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 31 | end 32 | 33 | teardown do 34 | resource_absent_by_title(agent, 'cisco_yang', BLUE_VRF_WO_PROPERTY) 35 | end 36 | 37 | ################################################################# 38 | # TEST CASE EXECUTION 39 | ################################################################# 40 | test_name 'TestCase :: VRF Present' do 41 | id = :create 42 | tests[id][:ensure] = :present 43 | test_harness_run(tests, id) 44 | skipped_tests_summary(tests) 45 | end 46 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_delete_properties.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:delete_property] = DELETE_PROPERTY 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource = { 31 | name: 'cisco_yang', 32 | title: BLUE_VRF_W_PROPERTY12, 33 | property: 'ensure', 34 | value: 'present', 35 | } 36 | resource_set(agent, resource, 'Create a VRF BLUE with properties.') 37 | end 38 | 39 | teardown do 40 | resource_absent_by_title(agent, 'cisco_yang', BLUE_VRF_W_PROPERTY2) 41 | end 42 | 43 | ################################################################# 44 | # TEST CASE EXECUTION 45 | ################################################################# 46 | test_name 'TestCase :: VRF Absent' do 47 | # ------------------------------------------------------------------- 48 | id = :delete_property 49 | tests[id][:ensure] = :absent 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | # ------------------------------------------------------------------- 53 | end 54 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_delete_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:delete] = DELETE 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource = { 31 | name: 'cisco_yang', 32 | title: BLUE_VRF_WO_PROPERTY, 33 | property: 'ensure', 34 | value: 'present', 35 | } 36 | resource_set(agent, resource, 'Create a VRF BLUE.') 37 | end 38 | 39 | teardown do 40 | resource_absent_by_title(agent, 'cisco_yang', BLUE_VRF_WO_PROPERTY) 41 | end 42 | 43 | ################################################################# 44 | # TEST CASE EXECUTION 45 | ################################################################# 46 | test_name 'TestCase :: VRF Absent' do 47 | # ------------------------------------------------------------------- 48 | id = :delete 49 | tests[id][:ensure] = :absent 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | # ------------------------------------------------------------------- 53 | end 54 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_file.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:file_merge] = FILE_MERGE 26 | tests[:replace_merge] = FILE_REPLACE 27 | 28 | skip_unless_supported(tests) 29 | 30 | def dependency_manifest(_tests, _id) 31 | setup_manifest = \ 32 | "file {'/root/temp': 33 | ensure => 'directory', 34 | } 35 | 36 | file { '/root/temp/vrfs.json': 37 | source => 'puppet:///modules/ciscoyang/models/defaults/vrfs.json' 38 | }" 39 | setup_manifest 40 | end 41 | 42 | step 'Setup' do 43 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 44 | end 45 | 46 | teardown do 47 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 48 | resource_absent_by_title(agent, 'file', '/root/temp/vrfs.json') 49 | end 50 | 51 | ################################################################# 52 | # TEST CASE EXECUTION 53 | ################################################################# 54 | test_name 'TestCase :: read config from vrfs.json file' do 55 | id = :file_merge 56 | tests[id][:ensure] = :present 57 | test_harness_run(tests, id) 58 | 59 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 60 | 61 | id = :replace_merge 62 | tests[id][:ensure] = :present 63 | test_harness_run(tests, id) 64 | 65 | skipped_tests_summary(tests) 66 | end 67 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_merge_properties_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:merge12] = MERGE12 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 31 | resource = { 32 | name: 'cisco_yang', 33 | title: BLUE_VRF_W_PROPERTY1, 34 | property: 'ensure', 35 | value: 'present', 36 | } 37 | resource_set(agent, resource, 'Create a VRF BLUE with property vpn id.') 38 | end 39 | 40 | teardown do 41 | resource_absent_by_title(agent, 'cisco_yang', BLUE_VRF_W_PROPERTY12) 42 | end 43 | 44 | ################################################################# 45 | # TEST CASE EXECUTION 46 | ################################################################# 47 | test_name 'TestCase :: Merge12 VRF BLUE' do 48 | id = :merge12 49 | tests[id][:ensure] = :present 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | end 53 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_merge_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:merge] = MERGE 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 31 | resource = { 32 | name: 'cisco_yang', 33 | title: GREEN_VRF_WO_PROPERTY, 34 | property: 'ensure', 35 | value: 'present', 36 | } 37 | resource_set(agent, resource, 'Create a VRF GREEN.') 38 | end 39 | 40 | teardown do 41 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 42 | end 43 | 44 | ################################################################# 45 | # TEST CASE EXECUTION 46 | ################################################################# 47 | test_name 'TestCase :: Merge VRF BLUE' do 48 | id = :merge 49 | tests[id][:ensure] = :present 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | end 53 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_replace_properties_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:replace12] = REPLACE12 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 31 | resource = { 32 | name: 'cisco_yang', 33 | title: BLUE_VRF_W_PROPERTY1, 34 | property: 'ensure', 35 | value: 'present', 36 | } 37 | resource_set(agent, resource, 'Create a VRF BLUE with properties.') 38 | end 39 | 40 | teardown do 41 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 42 | end 43 | 44 | ################################################################# 45 | # TEST CASE EXECUTION 46 | ################################################################# 47 | test_name 'TestCase :: VRF Present' do 48 | id = :replace12 49 | tests[id][:ensure] = :present 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | end 53 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_replace_srlg.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:replace_srlg] = REPLACE_SRLG 26 | tests[:create_srlg] = CREATE_SRLG 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | resource_absent_by_title(agent, 'cisco_yang', ROOT_SRLG) 32 | end 33 | 34 | teardown do 35 | resource_absent_by_title(agent, 'cisco_yang', ROOT_SRLG) 36 | end 37 | 38 | ################################################################# 39 | # TEST CASE EXECUTION 40 | ################################################################# 41 | test_name 'TestCase :: Replace GE0 by GE0 updated' do 42 | id = :create_srlg 43 | tests[id][:ensure] = :present 44 | test_harness_run(tests, id) 45 | 46 | id = :replace_srlg 47 | tests[id][:ensure] = :present 48 | test_harness_run(tests, id) 49 | skipped_tests_summary(tests) 50 | end 51 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang/test_replace_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | resource_name: 'cisco_yang', 24 | } 25 | tests[:replace] = REPLACE 26 | 27 | skip_unless_supported(tests) 28 | 29 | step 'Setup' do 30 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 31 | resource = { 32 | name: 'cisco_yang', 33 | title: GREEN_VRF_WO_PROPERTY, 34 | property: 'ensure', 35 | value: 'present', 36 | } 37 | resource_set(agent, resource, 'Create a VRF GREEN.') 38 | end 39 | 40 | teardown do 41 | resource_absent_by_title(agent, 'cisco_yang', ROOT_VRF) 42 | end 43 | 44 | ################################################################# 45 | # TEST CASE EXECUTION 46 | ################################################################# 47 | test_name 'TestCase :: VRF Present' do 48 | id = :replace 49 | tests[id][:ensure] = :present 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | end 53 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_create_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:create] = NETCONF_CREATE 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | end 33 | 34 | teardown do 35 | clear_vrf 36 | end 37 | 38 | ################################################################# 39 | # TEST CASE EXECUTION 40 | ################################################################# 41 | test_name 'TestCase :: VRF Present' do 42 | id = :create 43 | test_harness_run(tests, id) 44 | skipped_tests_summary(tests) 45 | end 46 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_file_merge_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:file_merge] = NETCONF_FILE_MERGE 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | on(agent, puppet_resource('file', '/root/temp/', 'ensure=directory')) 33 | on(agent, puppet_resource('file', \ 34 | '/root/temp/vrfs.xml', \ 35 | 'source="puppet:///modules/ciscoyang/models/defaults/vrfs.xml"', \ 36 | 'ensure=present')) 37 | end 38 | 39 | teardown do 40 | clear_vrf 41 | on(agent, puppet_resource('file', \ 42 | '/root/temp/vrfs.xml', \ 43 | 'ensure=absent')) 44 | end 45 | 46 | ################################################################# 47 | # TEST CASE EXECUTION 48 | ################################################################# 49 | test_name 'TestCase :: read config from vrfs.xml file and merge with current config' do 50 | id = :file_merge 51 | test_harness_run(tests, id) 52 | skipped_tests_summary(tests) 53 | end 54 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_file_replace_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:replace_merge] = NETCONF_FILE_REPLACE 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | on(agent, puppet_resource('file', '/root/temp/', 'ensure=directory')) 33 | on(agent, puppet_resource('file', \ 34 | '/root/temp/vrfs.xml', \ 35 | 'source="puppet:///modules/ciscoyang/models/defaults/vrfs.xml"', \ 36 | 'ensure=present')) 37 | end 38 | 39 | teardown do 40 | clear_vrf 41 | on(agent, puppet_resource('file', \ 42 | '/root/temp/vrfs.xml', \ 43 | 'ensure=absent')) 44 | end 45 | 46 | ################################################################# 47 | # TEST CASE EXECUTION 48 | ################################################################# 49 | test_name 'TestCase :: read config from vrfs.xml file and replace current config with it' do 50 | id = :replace_merge 51 | test_harness_run(tests, id) 52 | skipped_tests_summary(tests) 53 | end 54 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_merge_properties_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:merge12] = NETCONF_MERGE12 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | title_string = NETCONF_BLUE_VRF_W_PROPERTY1 33 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' mode=merge" 34 | on(agent, cmd) 35 | end 36 | 37 | teardown do 38 | clear_vrf 39 | end 40 | 41 | ################################################################# 42 | # TEST CASE EXECUTION 43 | ################################################################# 44 | test_name 'TestCase :: Merge12 VRF BLUE' do 45 | id = :merge12 46 | test_harness_run(tests, id) 47 | skipped_tests_summary(tests) 48 | end 49 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_merge_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:merge] = NETCONF_MERGE 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | title_string = NETCONF_GREEN_VRF_WO_PROPERTY 33 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' mode=merge" 34 | on(agent, cmd) 35 | end 36 | 37 | teardown do 38 | clear_vrf 39 | end 40 | 41 | ################################################################# 42 | # TEST CASE EXECUTION 43 | ################################################################# 44 | test_name 'TestCase :: Merge VRF BLUE' do 45 | id = :merge 46 | test_harness_run(tests, id) 47 | skipped_tests_summary(tests) 48 | end 49 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_replace_properties_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:replace12] = NETCONF_REPLACE12 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | title_string = NETCONF_BLUE_VRF_WO_PROPERTY 33 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' mode=merge" 34 | on(agent, cmd) 35 | end 36 | 37 | teardown do 38 | clear_vrf 39 | end 40 | 41 | ################################################################# 42 | # TEST CASE EXECUTION 43 | ################################################################# 44 | test_name 'TestCase :: VRF Present' do 45 | id = :replace12 46 | test_harness_run(tests, id) 47 | skipped_tests_summary(tests) 48 | end 49 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_replace_srlg.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:replace_srlg] = NETCONF_REPLACE_SRLG 27 | tests[:create_srlg] = NETCONF_CREATE_SRLG 28 | 29 | skip_unless_supported(tests) 30 | 31 | step 'Setup' do 32 | clear_srlg 33 | end 34 | 35 | teardown do 36 | clear_srlg 37 | end 38 | 39 | ################################################################# 40 | # TEST CASE EXECUTION 41 | ################################################################# 42 | test_name 'TestCase :: Replace GE0 by GE0 updated' do 43 | id = :create_srlg 44 | test_harness_run(tests, id) 45 | 46 | id = :replace_srlg 47 | test_harness_run(tests, id) 48 | skipped_tests_summary(tests) 49 | end 50 | -------------------------------------------------------------------------------- /tests/beaker_tests/all/cisco_yang_netconf/test_replace_vrf.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../../lib/utilitylib.rb', __FILE__) 17 | require File.expand_path('../../../lib/yang_netconf_util.rb', __FILE__) 18 | 19 | # Test hash top-level keys 20 | tests = { 21 | master: master, 22 | agent: agent, 23 | ensurable: false, 24 | resource_name: 'cisco_yang_netconf', 25 | } 26 | tests[:replace] = NETCONF_REPLACE 27 | 28 | skip_unless_supported(tests) 29 | 30 | step 'Setup' do 31 | clear_vrf 32 | title_string = NETCONF_BLUE_VRF_WO_PROPERTY 33 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' mode=merge" 34 | on(agent, cmd) 35 | 36 | title_string = NETCONF_GREEN_VRF_WO_PROPERTY 37 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' mode=merge" 38 | on(agent, cmd) 39 | end 40 | 41 | teardown do 42 | clear_vrf 43 | end 44 | 45 | ################################################################# 46 | # TEST CASE EXECUTION 47 | ################################################################# 48 | test_name 'TestCase :: VRF Present' do 49 | id = :replace 50 | test_harness_run(tests, id) 51 | skipped_tests_summary(tests) 52 | end 53 | -------------------------------------------------------------------------------- /tests/beaker_tests/lib/yang_netconf_util.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../lib/utilitylib.rb', __FILE__) 17 | 18 | NETCONF_ROOT_VRF = \ 19 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"/>' 20 | 21 | NETCONF_BLUE_VRF_WO_PROPERTY = \ 22 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 23 | <vrf> 24 | <vrf-name>BLUE</vrf-name> 25 | <create/> 26 | </vrf> 27 | </vrfs>' 28 | 29 | NETCONF_BLUE_VRF_W_PROPERTY1 = \ 30 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 31 | <vrf> 32 | <vrf-name>BLUE</vrf-name> 33 | <create/> 34 | <vpn-id> 35 | <vpn-oui>9</vpn-oui> 36 | <vpn-index>9</vpn-index> 37 | </vpn-id> 38 | </vrf> 39 | </vrfs>' 40 | 41 | NETCONF_BLUE_VRF_W_PROPERTY2 = \ 42 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 43 | <vrf> 44 | <vrf-name>BLUE</vrf-name> 45 | <create/> 46 | <description>Generic external traffic</description> 47 | </vrf> 48 | </vrfs>' 49 | 50 | NETCONF_BLUE_VRF_W_PROPERTY12 = \ 51 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 52 | <vrf> 53 | <vrf-name>BLUE</vrf-name> 54 | <create/> 55 | <description>Generic external traffic</description> 56 | <vpn-id> 57 | <vpn-oui>9</vpn-oui> 58 | <vpn-index>9</vpn-index> 59 | </vpn-id> 60 | </vrf> 61 | </vrfs>' 62 | 63 | NETCONF_GREEN_VRF_WO_PROPERTY = \ 64 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 65 | <vrf> 66 | <vrf-name>GREEN</vrf-name> 67 | <create/> 68 | </vrf> 69 | </vrfs>' 70 | 71 | NETCONF_BLUE_GREEN_VRF_WO_PROPERTY = \ 72 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 73 | <vrf> 74 | <vrf-name>BLUE</vrf-name> 75 | <create/> 76 | </vrf> 77 | <vrf> 78 | <vrf-name>GREEN</vrf-name> 79 | <create/> 80 | </vrf> 81 | </vrfs>' 82 | 83 | NETCONF_INTERNET_VOIP_VRF = \ 84 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 85 | <vrf> 86 | <vrf-name>VOIP</vrf-name> 87 | <create/> 88 | <description>Voice over IP</description> 89 | <vpn-id> 90 | <vpn-oui>875</vpn-oui> 91 | <vpn-index>3</vpn-index> 92 | </vpn-id> 93 | </vrf> 94 | <vrf> 95 | <vrf-name>INTERNET</vrf-name> 96 | <create/> 97 | <description>Generic external traffic</description> 98 | <vpn-id> 99 | <vpn-oui>875</vpn-oui> 100 | <vpn-index>22</vpn-index> 101 | </vpn-id> 102 | </vrf> 103 | </vrfs>' 104 | 105 | NETCONF_ROOT_SRLG = \ 106 | '<srlg xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"/>' 107 | 108 | NETCONF_SRLG_GE_01 = \ 109 | '<srlg xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 110 | <interfaces> 111 | <interface> 112 | <interface-name> 113 | GigabitEthernet0/0/0/0 114 | </interface-name> 115 | <enable/> 116 | <values> 117 | <value> 118 | <srlg-index> 119 | 10 120 | </srlg-index> 121 | <srlg-value> 122 | 100 123 | </srlg-value> 124 | <srlg-priority> 125 | default 126 | </srlg-priority> 127 | </value> 128 | <value> 129 | <srlg-index> 130 | 20 131 | </srlg-index> 132 | <srlg-value> 133 | 200 134 | </srlg-value> 135 | <srlg-priority> 136 | default 137 | </srlg-priority> 138 | </value> 139 | </values> 140 | <interface-group> 141 | <enable/> 142 | <group-names> 143 | <group-name> 144 | <group-name-index> 145 | 1 146 | </group-name-index> 147 | <group-name> 148 | 2 149 | </group-name> 150 | <srlg-priority> 151 | default 152 | </srlg-priority> 153 | </group-name> 154 | </group-names> 155 | </interface-group> 156 | </interface> 157 | </interfaces> 158 | <enable/> 159 | </srlg>' 160 | 161 | NETCONF_SRLG_GE_01_UPDATE = \ 162 | '<srlg xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 163 | <interfaces> 164 | <interface> 165 | <interface-name> 166 | GigabitEthernet0/0/0/0 167 | </interface-name> 168 | <enable/> 169 | <values> 170 | <value> 171 | <srlg-index> 172 | 20 173 | </srlg-index> 174 | <srlg-value> 175 | 200 176 | </srlg-value> 177 | <srlg-priority> 178 | default 179 | </srlg-priority> 180 | </value> 181 | <value> 182 | <srlg-index> 183 | 90 184 | </srlg-index> 185 | <srlg-value> 186 | 900 187 | </srlg-value> 188 | <srlg-priority> 189 | default 190 | </srlg-priority> 191 | </value> 192 | </values> 193 | <interface-group> 194 | <enable/> 195 | <group-names> 196 | <group-name> 197 | <group-name-index> 198 | 1 199 | </group-name-index> 200 | <group-name> 201 | 9 202 | </group-name> 203 | <srlg-priority> 204 | default 205 | </srlg-priority> 206 | </group-name> 207 | </group-names> 208 | </interface-group> 209 | </interface> 210 | </interfaces> 211 | <enable/> 212 | </srlg>' 213 | 214 | NETCONF_DELETE_SRLG = '<srlg xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg" xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0" xc:operation="delete"/>' 215 | 216 | def clear_vrf 217 | title_string = NETCONF_ROOT_VRF 218 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' source='#{title_string}' mode=replace" 219 | on(agent, cmd) 220 | end 221 | 222 | def clear_srlg 223 | title_string = NETCONF_ROOT_SRLG 224 | cmd = PUPPET_BINPATH + "resource cisco_yang_netconf '#{title_string}' source='#{title_string}' mode=replace" 225 | on(agent, cmd) 226 | end 227 | 228 | def massage_path(str) 229 | ret = str.clone 230 | ret.delete!("\n").gsub!(/\s*{\s*/, '{').gsub!(/\s*}\s*/, '}').gsub!(/\s*:\s*/, ':') 231 | ret.gsub!(/\s*\[\s*/, '[').gsub!(/\s*\]\s*/, ']').gsub!(/\s*,\s*/, ',') 232 | end 233 | 234 | NETCONF_CREATE = { 235 | desc: 'Create VRF BLUE', 236 | title: NETCONF_ROOT_VRF, 237 | manifest_props: { 238 | source: NETCONF_BLUE_VRF_WO_PROPERTY 239 | }, 240 | } 241 | 242 | NETCONF_CREATE_SRLG = { 243 | desc: 'CREATE SRLG GE0 and GE1', 244 | title: NETCONF_ROOT_SRLG, 245 | manifest_props: { 246 | source: NETCONF_SRLG_GE_01 247 | }, 248 | } 249 | 250 | NETCONF_REPLACE = { 251 | desc: 'Replace VRF GREEN with BLUE', 252 | title: NETCONF_ROOT_VRF, 253 | manifest_props: { 254 | source: NETCONF_BLUE_VRF_WO_PROPERTY, 255 | mode: 'replace', 256 | }, 257 | } 258 | 259 | NETCONF_REPLACE12 = { 260 | desc: 'Replace VRF BLUE with BLUE', 261 | title: NETCONF_ROOT_VRF, 262 | manifest_props: { 263 | # Replace NETCONF_BLUE_VRF_W_PROPERTY1 by NETCONF_BLUE_VRF_W_PROPERTY2. 264 | source: NETCONF_BLUE_VRF_W_PROPERTY2, 265 | mode: 'replace', 266 | }, 267 | } 268 | 269 | NETCONF_MERGE = { 270 | desc: 'Merge VRF BLUE with GREEN', 271 | title: NETCONF_ROOT_VRF, 272 | manifest_props: { 273 | source: NETCONF_BLUE_VRF_WO_PROPERTY, 274 | mode: 'merge', 275 | }, 276 | resource: { 277 | source: NETCONF_BLUE_GREEN_VRF_WO_PROPERTY 278 | }, 279 | } 280 | 281 | NETCONF_MERGE12 = { 282 | desc: 'Merge VRF BLUE with BLUE', 283 | title: NETCONF_ROOT_VRF, 284 | manifest_props: { 285 | # merge NETCONF_BLUE_VRF_W_PROPERTY2 with existing configuration. 286 | # Expecting existing configuration to be NETCONF_BLUE_VRF_W_PROPERTY1 287 | # resulting NETCONF_BLUE_VRF_W_PROPERTY12 288 | source: NETCONF_BLUE_VRF_W_PROPERTY2, 289 | mode: 'merge', 290 | }, 291 | resource: { 292 | source: NETCONF_BLUE_VRF_W_PROPERTY12 293 | }, 294 | } 295 | 296 | NETCONF_REPLACE_SRLG = { 297 | desc: 'Update SRLG GE0 properties', 298 | title: NETCONF_ROOT_SRLG, 299 | manifest_props: { 300 | source: NETCONF_SRLG_GE_01_UPDATE, 301 | mode: 'replace', 302 | }, 303 | } 304 | 305 | NETCONF_FILE_MERGE = { 306 | desc: 'Merge VOIP and INTERNET VRFs with current config', 307 | title: NETCONF_ROOT_VRF, 308 | manifest_props: { 309 | source: '/root/temp/vrfs.xml', 310 | mode: 'merge', 311 | }, 312 | resource: { 313 | source: NETCONF_INTERNET_VOIP_VRF 314 | }, 315 | } 316 | 317 | NETCONF_FILE_REPLACE = { 318 | desc: 'Replace current config by VOIP and INTERNET VRFs', 319 | title: NETCONF_ROOT_VRF, 320 | manifest_props: { 321 | source: '/root/temp/vrfs.xml', 322 | mode: 'replace', 323 | }, 324 | resource: { 325 | source: NETCONF_INTERNET_VOIP_VRF 326 | }, 327 | } 328 | -------------------------------------------------------------------------------- /tests/beaker_tests/lib/yang_util.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2016 Cisco and/or its affiliates. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ############################################################################### 16 | require File.expand_path('../../lib/utilitylib.rb', __FILE__) 17 | 18 | ROOT_VRF = \ 19 | '{ 20 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null] 21 | }' 22 | 23 | BLUE_VRF_WO_PROPERTY = \ 24 | '{ 25 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": 26 | { 27 | "vrf": 28 | [ 29 | { 30 | "vrf-name":"BLUE", 31 | "create":[null] 32 | } 33 | ] 34 | } 35 | }' 36 | 37 | BLUE_VRF_W_PROPERTY1 = \ 38 | '{ 39 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": 40 | { 41 | "vrf": 42 | [ 43 | { 44 | "vrf-name":"BLUE", 45 | "create":[null], 46 | "remote-route-filter-disable": [null] 47 | } 48 | ] 49 | } 50 | }' 51 | 52 | BLUE_VRF_W_PROPERTY2 = \ 53 | '{ 54 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": 55 | { 56 | "vrf": 57 | [ 58 | { 59 | "vrf-name":"BLUE", 60 | "create":[null], 61 | "description":"Sample description" 62 | } 63 | ] 64 | } 65 | }' 66 | 67 | BLUE_VRF_W_PROPERTY12 = \ 68 | '{ 69 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": 70 | { 71 | "vrf": 72 | [ 73 | { 74 | "vrf-name":"BLUE", 75 | "create":[null], 76 | "description":"Sample description", 77 | "remote-route-filter-disable": [null] 78 | } 79 | ] 80 | } 81 | }' 82 | 83 | GREEN_VRF_WO_PROPERTY = \ 84 | '{ 85 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": 86 | { 87 | "vrf": 88 | [ 89 | { 90 | "vrf-name":"GREEN", 91 | "create":[null] 92 | } 93 | ] 94 | } 95 | }' 96 | 97 | BLUE_GREEN_VRF_WO_PROPERTY = \ 98 | '{ 99 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs": 100 | { 101 | "vrf": 102 | [ 103 | { 104 | "vrf-name":"BLUE", 105 | "create":[null] 106 | }, 107 | { 108 | "vrf-name":"GREEN", 109 | "create":[null] 110 | } 111 | ] 112 | } 113 | }' 114 | INTERNET_VOIP_VRF = \ 115 | '{ 116 | "Cisco-IOS-XR-infra-rsi-cfg:vrfs":{ 117 | "vrf":[ 118 | { 119 | "vrf-name":"VOIP", 120 | "create":[ 121 | null 122 | ], 123 | "description":"Voice over IP", 124 | "vpn-id":{ 125 | "vpn-oui":87, 126 | "vpn-index":3 127 | } 128 | }, 129 | { 130 | "vrf-name":"INTERNET", 131 | "create":[ 132 | null 133 | ], 134 | "description":"Generic external traffic", 135 | "vpn-id":{ 136 | "vpn-oui":85, 137 | "vpn-index":22 138 | } 139 | } 140 | ] 141 | } 142 | }' 143 | 144 | ROOT_SRLG = \ 145 | '{ 146 | "Cisco-IOS-XR-infra-rsi-cfg:srlg": [null] 147 | }' 148 | 149 | SRLG_GE_01 = \ 150 | '{ 151 | "Cisco-IOS-XR-infra-rsi-cfg:srlg": { 152 | "interfaces": { 153 | "interface": [ 154 | { 155 | "interface-name": "GigabitEthernet0/0/0/0", 156 | "enable": [ 157 | null 158 | ], 159 | "values": { 160 | "value": [ 161 | { 162 | "srlg-index": 10, 163 | "srlg-value": 100, 164 | "srlg-priority": "default" 165 | }, 166 | { 167 | "srlg-index": 20, 168 | "srlg-value": 200, 169 | "srlg-priority": "default" 170 | } 171 | ] 172 | }, 173 | "interface-group": { 174 | "enable": [ 175 | null 176 | ], 177 | "group-names": { 178 | "group-name": [ 179 | { 180 | "group-name-index": 1, 181 | "group-name": "2", 182 | "srlg-priority": "default" 183 | } 184 | ] 185 | } 186 | } 187 | }, 188 | { 189 | "interface-name": "GigabitEthernet0/0/0/1", 190 | "enable": [ 191 | null 192 | ] 193 | } 194 | ] 195 | }, 196 | "enable": [ 197 | null 198 | ] 199 | } 200 | }' 201 | 202 | SRLG_GE_01_UPDATE = \ 203 | '{ 204 | "Cisco-IOS-XR-infra-rsi-cfg:srlg": { 205 | "interfaces": { 206 | "interface": [ 207 | { 208 | "interface-name": "GigabitEthernet0/0/0/0", 209 | "enable": [ 210 | null 211 | ], 212 | "values": { 213 | "value": [ 214 | { 215 | "srlg-index": 80, 216 | "srlg-value": 800, 217 | "srlg-priority": "default" 218 | }, 219 | { 220 | "srlg-index": 90, 221 | "srlg-value": 900, 222 | "srlg-priority": "default" 223 | } 224 | ] 225 | }, 226 | "interface-group": { 227 | "enable": [ 228 | null 229 | ], 230 | "group-names": { 231 | "group-name": [ 232 | { 233 | "group-name-index": 1, 234 | "group-name": "2", 235 | "srlg-priority": "default" 236 | } 237 | ] 238 | } 239 | } 240 | }, 241 | { 242 | "interface-name": "GigabitEthernet0/0/0/1", 243 | "enable": [ 244 | null 245 | ] 246 | } 247 | ] 248 | }, 249 | "enable": [ 250 | null 251 | ] 252 | } 253 | }' 254 | 255 | def massage_path(str) 256 | ret = str.clone 257 | ret.delete!("\n").gsub!(/\s*{\s*/, '{').gsub!(/\s*}\s*/, '}').gsub!(/\s*:\s*/, ':') 258 | ret.gsub!(/\s*\[\s*/, '[').gsub!(/\s*\]\s*/, ']').gsub!(/\s*,\s*/, ',') 259 | end 260 | 261 | DELETE = { 262 | desc: 'Delete VRF BLUE', 263 | title: BLUE_VRF_WO_PROPERTY, 264 | manifest_props: { 265 | }, 266 | } 267 | 268 | DELETE_PROPERTY = { 269 | desc: 'Delete VRF BLUE description', 270 | title: BLUE_VRF_W_PROPERTY2, 271 | manifest_props: { 272 | }, 273 | } 274 | 275 | CREATE = { 276 | desc: 'Create VRF BLUE', 277 | title: ROOT_VRF, 278 | manifest_props: { 279 | source: BLUE_VRF_WO_PROPERTY 280 | }, 281 | } 282 | 283 | CREATE_SRLG = { 284 | desc: 'CREATE SRLG GE0 and GE1', 285 | title: ROOT_SRLG, 286 | manifest_props: { 287 | source: SRLG_GE_01 288 | }, 289 | } 290 | 291 | REPLACE = { 292 | desc: 'Replace VRF GREEN with BLUE', 293 | title: ROOT_VRF, 294 | manifest_props: { 295 | source: BLUE_VRF_WO_PROPERTY, 296 | mode: 'replace', 297 | }, 298 | } 299 | 300 | REPLACE12 = { 301 | desc: 'Replace VRF BLUE with BLUE', 302 | title: ROOT_VRF, 303 | manifest_props: { 304 | # Replace BLUE_VRF_W_PROPERTY1 by BLUE_VRF_W_PROPERTY2. 305 | source: BLUE_VRF_W_PROPERTY2, 306 | mode: 'replace', 307 | }, 308 | } 309 | 310 | MERGE = { 311 | desc: 'Merge VRF BLUE with GREEN', 312 | title: ROOT_VRF, 313 | manifest_props: { 314 | source: BLUE_VRF_WO_PROPERTY, 315 | mode: 'merge', 316 | }, 317 | resource: { 318 | source: BLUE_GREEN_VRF_WO_PROPERTY 319 | }, 320 | } 321 | 322 | MERGE12 = { 323 | desc: 'Merge VRF BLUE with BLUE', 324 | title: ROOT_VRF, 325 | manifest_props: { 326 | # merge BLUE_VRF_W_PROPERTY2 with existing configuration (BLUE_VRF_W_PROPERTY1) 327 | # resulting BLUE_VRF_W_PROPERTY12 328 | source: BLUE_VRF_W_PROPERTY2, 329 | mode: 'merge', 330 | }, 331 | resource: { 332 | source: BLUE_VRF_W_PROPERTY12 333 | }, 334 | } 335 | 336 | REPLACE_SRLG = { 337 | desc: 'Update SRLG GE0 properties', 338 | title: ROOT_SRLG, 339 | manifest_props: { 340 | source: SRLG_GE_01_UPDATE, 341 | mode: 'replace', 342 | }, 343 | } 344 | 345 | FILE_MERGE = { 346 | desc: 'Merge VOIP and INTERNET VRFs with current config', 347 | title: ROOT_VRF, 348 | manifest_props: { 349 | source: '/root/temp/vrfs.json', 350 | mode: 'merge', 351 | }, 352 | resource: { 353 | source: INTERNET_VOIP_VRF 354 | }, 355 | } 356 | 357 | FILE_REPLACE = { 358 | desc: 'Replace current config by VOIP and INTERNET VRFs', 359 | title: ROOT_VRF, 360 | manifest_props: { 361 | source: '/root/temp/vrfs.json', 362 | mode: 'replace', 363 | }, 364 | resource: { 365 | source: INTERNET_VOIP_VRF 366 | }, 367 | } 368 | -------------------------------------------------------------------------------- /tests/init.pp: -------------------------------------------------------------------------------- 1 | # The baseline for module testing used by Puppet Labs is that each manifest 2 | # should have a corresponding test manifest that declares that class or defined 3 | # type. 4 | # 5 | # Tests are then run by using puppet apply --noop (to check for compilation 6 | # errors and view a log of events) or by fully applying the test in a virtual 7 | # environment (to compare the resulting system state to the desired state). 8 | # 9 | # Learn more about module testing here: 10 | # http://docs.puppetlabs.com/guides/tests_smoke.html 11 | # 12 | include ciscoyang 13 | -------------------------------------------------------------------------------- /tests/minitest/ciscotest.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Minitest needs to have this path in order to discover our plugins 16 | $LOAD_PATH.push File.expand_path('../../../lib', __FILE__) 17 | 18 | require 'simplecov' 19 | require 'ipaddr' 20 | require 'resolv' 21 | require_relative 'platform_info' 22 | gem 'minitest', '~> 5.0' 23 | require 'minitest/autorun' 24 | require 'net/telnet' 25 | require_relative '../../lib/util/client' 26 | require_relative '../../lib/util/client/grpc/client' 27 | require_relative '../../lib/util/node' 28 | require_relative '../../lib/util/environment' 29 | require_relative '../../lib/util/logger' 30 | 31 | include Cisco 32 | 33 | # CiscoTestCase - base class for all node utility minitests 34 | class CiscoTestCase < Minitest::Test 35 | # rubocop:disable Style/ClassVars 36 | @@device_hash = {} 37 | # rubocop:enable Style/ClassVars 38 | 39 | @node = nil 40 | 41 | @client_class = nil 42 | 43 | class << self 44 | attr_accessor :client_class 45 | end 46 | 47 | def client_class 48 | self.class.client_class 49 | end 50 | 51 | def self.runnable_methods 52 | cc = client_class 53 | return super unless cc 54 | env = cc ? cc.environment(cc) : nil 55 | return super if env 56 | 57 | # no environment configured for this test suite, so skip all tests 58 | remove_method :setup if instance_methods(false).include?(:setup) 59 | remove_method :teardown if instance_methods(false).include?(:teardown) 60 | [:all_skipped] 61 | end 62 | 63 | def all_skipped 64 | skip("Skipping #{self.class}; #{client_class} is not configured/supported on this node") 65 | end 66 | 67 | def node 68 | cc = client_class 69 | assert(cc, 'No client_class defined') 70 | 71 | return nil unless cc 72 | 73 | is_new = !Node.instance_exists(cc) 74 | @node = Node.instance(cc) 75 | 76 | if is_new 77 | # Record the platform we're running on 78 | puts "\nNode under test via #{cc}:" 79 | puts " - name - #{@node.host_name}" 80 | puts " - type - #{@node.product_id}" 81 | puts " - image - #{@node.system}\n\n" 82 | end 83 | 84 | @node 85 | rescue Cisco::AuthenticationFailed 86 | abort "Unauthorized to connect as #{username}:#{password}@#{address}" 87 | rescue Cisco::ClientError, TypeError, ArgumentError => e 88 | abort "Error in establishing connection: #{e}" 89 | end 90 | 91 | def client 92 | node ? node.client : nil 93 | end 94 | 95 | def environment(default=nil) 96 | Cisco::Client.environment(client.class) || default 97 | end 98 | 99 | def device 100 | @@device_hash[node.client.class] ||= create_device 101 | end 102 | 103 | def create_device 104 | login = proc do 105 | puts "====> ciscotest.create_device - login address: #{node.client.host}, username: #{node.client.username}, object_id: #{object_id}" 106 | d = Net::Telnet.new('Host' => node.client.host, 107 | 'Timeout' => 240, 108 | # NX-OS has a space after '#', IOS XR does not 109 | 'Prompt' => /[$%#>] *\z/n, 110 | ) 111 | d.login('Name' => node.client.username, 112 | 'Password' => node.client.password, 113 | # NX-OS uses 'login:' while IOS XR uses 'Username:' 114 | 'LoginPrompt' => /(?:[Ll]ogin|[Uu]sername)[: ]*\z/n, 115 | ) 116 | d 117 | end 118 | 119 | begin 120 | new_device = login.call 121 | rescue Errno::ECONNRESET 122 | new_device.close 123 | puts 'Connection reset by peer? Try again' 124 | sleep 1 125 | 126 | new_device = login.call 127 | end 128 | new_device.cmd('term len 0') 129 | new_device 130 | rescue Errno::ECONNREFUSED 131 | puts 'Telnet login refused - please check that the IP address is correct' 132 | puts " and that you have configured 'telnet ipv4 server...' on the UUT" 133 | exit 134 | end 135 | 136 | def cmd_ref 137 | node.cmd_ref 138 | end 139 | 140 | def platform 141 | node.client.platform 142 | end 143 | 144 | def config_and_warn_on_match(warn_match, *args) 145 | if node.client.platform == :ios_xr 146 | result = super(warn_match, *args, 'commit') 147 | else 148 | result = super 149 | end 150 | result 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /tests/minitest/platform_info.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2016 Cisco and/or its affiliates. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'yaml' 16 | 17 | # Class: PlatformInfo 18 | # This class reads device specific details from platform_info.yaml 19 | # These details can be used to customize unit tests for a device 20 | class PlatformInfo 21 | # Default constructor for the PlatformInfo class. This class 22 | # requires the hostname of the device on which UTs are to run 23 | # to be passed in. The constructor reads platform_info.yaml 24 | # and stores info for this device in the instance variable 25 | # @platform_info_hash 26 | # 27 | # @param[in] device_name hostname of device on which 28 | # UTs are to be run 29 | # 30 | def initialize(device_name, platform) 31 | if device_name.nil? || device_name.empty? 32 | fail 'device name must be specified in PlatformInfo constructor.' 33 | end 34 | @platform_info_hash = {} 35 | 36 | begin 37 | project_info_hash = YAML.load_file(File.join(File.dirname(__FILE__), 38 | 'platform_info.yaml')) 39 | rescue RuntimeError 40 | raise 'Error - could not open platform file - platform_info.yaml' 41 | end 42 | 43 | @platform_info_hash = project_info_hash[device_name] 44 | @platform_info_hash ||= project_info_hash['default'][platform.to_s] 45 | fail "Error - could not find #{device_name} device specific information " \ 46 | 'in platform_info.yaml' if @platform_info_hash.nil? 47 | end 48 | 49 | # The following instance method will return the value associated with 50 | # the specified key from the instance variable @platform_info_hash. 51 | # 52 | # @param[in] key String value indicating the key to be searched for 53 | # in @platform_info_hash 54 | # 55 | def get_value_from_key(key) 56 | fail 'key must be specified in the method get_value_from_key' if key.nil? 57 | 58 | value = @platform_info_hash[key] 59 | fail "no value exists for the key #{key}" if value.nil? 60 | 61 | value 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /tests/minitest/test_grpc.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # October 2015, Glenn F. Matthews 4 | # 5 | # Copyright (c) 2015-2016 Cisco and/or its affiliates. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | require_relative 'ciscotest' 20 | 21 | # Test case for Cisco::Client::GRPC::Client class 22 | class TestGRPC < CiscoTestCase 23 | @client_class = Cisco::Client::GRPC 24 | RED_VRF = \ 25 | "{\n \"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": {\n \"vrf\": [\n {\n \"vrf-name\": \"RED\",\n \"create\": [\n null\n ]\n }\n ]\n }\n}\n" 26 | FOO_VRF = \ 27 | "{\n \"Cisco-IOS-XR-infra-rsi-cfg:vrfs\": {\n \"vrf\": "\ 28 | "[\n {\n \"vrf-name\": \"foo-should-not-be-there\",\n \"create\": [\n null\n ]\n }\n ]\n }\n}\n" 29 | ROOT_VRF = '{"Cisco-IOS-XR-infra-rsi-cfg:vrfs": [null]}' 30 | INVALID_VRF = '{"Cisco-IOS-XR-infra-rsi-cfg:invalid": [null]}' 31 | 32 | def test_auth_failure 33 | env = environment.merge(password: 'wrong password') 34 | # Cisco::Client::GRPC.new(**env) 35 | e = assert_raises Cisco::AuthenticationFailed do 36 | Cisco::Client::GRPC.new(**env) 37 | end 38 | assert_equal('gRPC client creation failure: Failed authentication', 39 | e.message) 40 | end 41 | 42 | def test_connection_failure 43 | # Failure #1: connecting to a port that's listening for a non-gRPC protocol 44 | env = environment.merge(port: '57722') # sshd 45 | e = assert_raises Cisco::ConnectionRefused do 46 | Cisco::Client::GRPC.new(**env) 47 | end 48 | assert_match('gRPC client creation failure: Connection refused: ', 49 | e.message) 50 | # Failure #2: Connecting to a port that's not listening at all 51 | env = environment.merge(port: '0') 52 | e = assert_raises Cisco::ConnectionRefused do 53 | Cisco::Client::GRPC.new(**env) 54 | end 55 | # for now, make this test a little more lenient 56 | # assert_equal('gRPC client creation failure: ' \ 57 | # 'timed out during initial connection: Deadline Exceeded', 58 | # e.message) 59 | assert_match('gRPC client creation failure: ', e.message) 60 | end 61 | 62 | def test_set_string 63 | client.set(values: RED_VRF, 64 | mode: :replace_config) 65 | run = client.get(command: ROOT_VRF) 66 | assert_match(RED_VRF, run) 67 | end 68 | 69 | def test_set_invalid 70 | e = assert_raises Cisco::YangError do 71 | client.set(values: INVALID_VRF) 72 | end 73 | assert_equal("The config '{\"Cisco-IOS-XR-infra-rsi-cfg:invalid\": [null]}' was rejected with error: 74 | unknown-element: Cisco-IOS-XR-infra-rsi-cfg:ns1:invalid", e.message) 75 | assert_empty(e.successful_input) 76 | assert_equal(INVALID_VRF, 77 | e.rejected_input) 78 | end 79 | 80 | def test_get_invalid 81 | assert_raises Cisco::YangError do 82 | client.get(command: INVALID_VRF) 83 | end 84 | end 85 | 86 | def test_get_incomplete 87 | assert_raises Cisco::YangError do 88 | client.get(command: INVALID_VRF) 89 | end 90 | end 91 | 92 | def test_get_empty 93 | result = client.get(command: FOO_VRF) 94 | assert_empty(result) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /tests/minitest/test_netconf.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # June 2016, Sushrut Shirole 4 | # 5 | # Copyright (c) 2016 Cisco and/or its affiliates. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | require_relative 'ciscotest' 20 | 21 | # Test case for Cisco::Client::NETCONF::Client class 22 | class TestNetconf < CiscoTestCase 23 | @client_class = Cisco::Client::NETCONF 24 | 25 | RED_VRF = \ 26 | "<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\">\n <vrf>\n <vrf-name>\n red\n </vrf-name>\n <create/>\n </vrf>\n</vrfs>" 27 | BLUE_VRF = \ 28 | "<vrfs xmlns=\"http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg\">\n <vrf>\n <vrf-name>\n blue\n </vrf-name>\n <create/>\n </vrf>\n</vrfs>" 29 | ROOT_VRF = '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"/>' 30 | INVALID_VRF = '<infra-rsi-cfg:vrfs-invalid xmlns:infra-rsi-cfg-invalid="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg-invalid"/>' 31 | 32 | def test_auth_failure 33 | env = environment({}).merge(password: 'wrong password') 34 | e = assert_raises Cisco::AuthenticationFailed do 35 | Cisco::Client::NETCONF.new(**env) 36 | end 37 | assert_equal('Netconf client creation failure: Authentication failed for user ' \ 38 | + environment[:username] + '@' + environment[:host], e.message) 39 | end 40 | 41 | def test_connection_failure 42 | # Failure #1: connecting to a host that's listening for a non-Netconf protocol 43 | env = environment.merge(host: '1.1.1.1') 44 | e = assert_raises Cisco::YangError do 45 | Cisco::Client::NETCONF.new(**env) 46 | end 47 | assert_match('No route to host - connect(2)', 48 | e.message) 49 | end 50 | 51 | def test_set_string 52 | client.set(values: RED_VRF, 53 | mode: :replace) 54 | run = client.get(command: ROOT_VRF) 55 | assert_match(RED_VRF, run) 56 | end 57 | 58 | def test_set_invalid 59 | e = assert_raises Cisco::YangError do 60 | client.set(values: INVALID_VRF) 61 | end 62 | assert_equal('The config \'apply of <infra-rsi-cfg:vrfs-invalid ' \ 63 | 'xmlns:infra-rsi-cfg-invalid="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg-invalid"/>\' was rejected with error: 64 | error-type => rpc 65 | error-tag => malformed-message 66 | error-severity => error 67 | ', e.message) 68 | assert_empty(e.successful_input) 69 | assert_equal('apply of <infra-rsi-cfg:vrfs-invalid xmlns:infra-rsi-cfg-invalid="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg-invalid"/>', e.rejected_input) 70 | end 71 | 72 | def test_get_invalid 73 | assert_raises Cisco::YangError do 74 | client.get(command: INVALID_VRF) 75 | end 76 | end 77 | 78 | def test_get_incomplete 79 | assert_raises Cisco::YangError do 80 | client.get(command: INVALID_VRF) 81 | end 82 | end 83 | 84 | def test_get_empty 85 | result = client.get(command: BLUE_VRF) 86 | assert_empty(result) 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /tests/minitest/test_netconf_yang_offline.rb: -------------------------------------------------------------------------------- 1 | require_relative 'ciscotest' 2 | require_relative '../../lib/util/client/netconf/netconf' 3 | 4 | # Two elts in Current, one will be deleted by operation=delete clause 5 | CURRENT_BLUE_RED_VRF = 6 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 7 | <vrf> 8 | <vrf-name>blue</vrf-name> 9 | </vrf> 10 | <vrf> 11 | <vrf-name>red</vrf-name> 12 | </vrf> 13 | </vrfs>' 14 | 15 | TARGET_BLUE_RED_VRF = 16 | '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 17 | <infra-rsi-cfg:vrf> 18 | <infra-rsi-cfg:vrf-name>blue</infra-rsi-cfg:vrf-name> 19 | </infra-rsi-cfg:vrf> 20 | <infra-rsi-cfg:vrf xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> 21 | <infra-rsi-cfg:vrf-name>red</infra-rsi-cfg:vrf-name> 22 | </infra-rsi-cfg:vrf> 23 | </infra-rsi-cfg:vrfs>' 24 | 25 | # Two elts in Current, delete in target is a no-op. Merge should be a no-op, but replace should not 26 | CURRENT_BLUE_GREEN_VRF = 27 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 28 | <vrf> 29 | <vrf-name>blue</vrf-name> 30 | </vrf> 31 | <vrf> 32 | <vrf-name>green</vrf-name> 33 | </vrf> 34 | </vrfs>' 35 | 36 | TARGET_BLUE_GREEN_VRF = 37 | '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 38 | <infra-rsi-cfg:vrf> 39 | <infra-rsi-cfg:vrf-name>blue</infra-rsi-cfg:vrf-name> 40 | </infra-rsi-cfg:vrf> 41 | <infra-rsi-cfg:vrf xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> 42 | <infra-rsi-cfg:vrf-name>red</infra-rsi-cfg:vrf-name> 43 | </infra-rsi-cfg:vrf> 44 | </infra-rsi-cfg:vrfs>' 45 | 46 | # Single elt in Current, operation=delete in target results in no-op 47 | CURRENT_DELETE_DELETED_VRF = 48 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 49 | <vrf> 50 | <vrf-name>blue</vrf-name> 51 | <description>Blue description</description> 52 | <create/> 53 | </vrf> 54 | </vrfs>' 55 | 56 | TARGET_DELETE_DELETED_VRF = 57 | '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 58 | <infra-rsi-cfg:vrf> 59 | <infra-rsi-cfg:vrf-name>blue</infra-rsi-cfg:vrf-name> 60 | <infra-rsi-cfg:description>Blue description</infra-rsi-cfg:description> 61 | <infra-rsi-cfg:create/> 62 | </infra-rsi-cfg:vrf> 63 | <infra-rsi-cfg:vrf xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> 64 | <infra-rsi-cfg:vrf-name>red</infra-rsi-cfg:vrf-name> 65 | <infra-rsi-cfg:description>Red description</infra-rsi-cfg:description> 66 | <infra-rsi-cfg:create/> 67 | </infra-rsi-cfg:vrf> 68 | </infra-rsi-cfg:vrfs>' 69 | 70 | # No elts in current, operation=delete in target results in no-op 71 | CURRENT_DELETE_DELETED_VRF_2 = 72 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 73 | </vrfs>' 74 | 75 | TARGET_DELETE_DELETED_VRF_2 = 76 | '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 77 | <infra-rsi-cfg:vrf xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> 78 | <infra-rsi-cfg:vrf-name>red</infra-rsi-cfg:vrf-name> 79 | </infra-rsi-cfg:vrf> 80 | </infra-rsi-cfg:vrfs>' 81 | 82 | # Simple case to validate namespace normalization 83 | CURRENT_BLUE_VRF = 84 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 85 | <vrf> 86 | <vrf-name>blue</vrf-name> 87 | <description>Blue description</description> 88 | <create/> 89 | </vrf> 90 | </vrfs>' 91 | 92 | TARGET_BLUE_VRF = 93 | '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 94 | <infra-rsi-cfg:vrf> 95 | <infra-rsi-cfg:vrf-name>blue</infra-rsi-cfg:vrf-name> 96 | <infra-rsi-cfg:description>Blue description</infra-rsi-cfg:description> 97 | <infra-rsi-cfg:create/> 98 | </infra-rsi-cfg:vrf> 99 | </infra-rsi-cfg:vrfs>' 100 | 101 | # Delete a non-container element 102 | CURRENT_BLUE_VRF_DELETE_DESCRIPTION = 103 | '<vrfs xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 104 | <vrf> 105 | <vrf-name>blue</vrf-name> 106 | <description>Blue description</description> 107 | <create/> 108 | </vrf> 109 | </vrfs>' 110 | 111 | TARGET_BLUE_VRF_DELETE_DESCRIPTION = 112 | '<infra-rsi-cfg:vrfs xmlns:infra-rsi-cfg="http://cisco.com/ns/yang/Cisco-IOS-XR-infra-rsi-cfg"> 113 | <infra-rsi-cfg:vrf> 114 | <infra-rsi-cfg:vrf-name>blue</infra-rsi-cfg:vrf-name> 115 | <infra-rsi-cfg:description xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete"> 116 | Blue description 117 | </infra-rsi-cfg:description> 118 | <infra-rsi-cfg:create/> 119 | </infra-rsi-cfg:vrf> 120 | </infra-rsi-cfg:vrfs>' 121 | 122 | # TestNetconfOffline - Offline Minitest for Netconf class 123 | class TestNetconfOffline < CiscoTestCase 124 | def test_netconf_prefix_merge 125 | assert(Cisco::Netconf.insync_for_merge(TARGET_BLUE_VRF, 126 | CURRENT_BLUE_VRF), 127 | 'expected in-sync') 128 | end 129 | 130 | def test_netconf_prefix_replace 131 | assert(Cisco::Netconf.insync_for_replace(TARGET_BLUE_VRF, 132 | CURRENT_BLUE_VRF), 133 | 'expected in-sync') 134 | end 135 | 136 | def test_netconf_delete_vrf_description_merge 137 | refute(Cisco::Netconf.insync_for_merge(TARGET_BLUE_VRF_DELETE_DESCRIPTION, 138 | CURRENT_BLUE_VRF_DELETE_DESCRIPTION), 139 | 'expected not in-sync') 140 | end 141 | 142 | def test_netconf_delete_vrf_description_replace 143 | refute(Cisco::Netconf.insync_for_replace(TARGET_BLUE_VRF_DELETE_DESCRIPTION, 144 | CURRENT_BLUE_VRF_DELETE_DESCRIPTION), 145 | 'expected not in-sync') 146 | end 147 | 148 | def test_netconf_delete_red_vrf_merge 149 | refute(Cisco::Netconf.insync_for_merge(TARGET_BLUE_RED_VRF, 150 | CURRENT_BLUE_RED_VRF), 151 | 'expected not in-sync') 152 | end 153 | 154 | def test_netconf_delete_red_vrf_replace 155 | refute(Cisco::Netconf.insync_for_replace(TARGET_BLUE_RED_VRF, 156 | CURRENT_BLUE_RED_VRF), 157 | 'expected not in-sync') 158 | end 159 | 160 | def test_netconf_delete_missing_vrf_merge 161 | assert(Cisco::Netconf.insync_for_merge(TARGET_DELETE_DELETED_VRF, 162 | CURRENT_DELETE_DELETED_VRF), 163 | 'expected in-sync') 164 | end 165 | 166 | def test_netconf_delete_missing_vrf_replace 167 | assert(Cisco::Netconf.insync_for_replace(TARGET_DELETE_DELETED_VRF, 168 | CURRENT_DELETE_DELETED_VRF), 169 | 'expected in-sync') 170 | end 171 | 172 | def test_netconf_delete_missing_vrf_2_merge 173 | assert(Cisco::Netconf.insync_for_merge(TARGET_DELETE_DELETED_VRF_2, 174 | CURRENT_DELETE_DELETED_VRF_2), 175 | 'expected in-sync') 176 | end 177 | 178 | def test_netconf_delete_missing_vrf_2_replace 179 | assert(Cisco::Netconf.insync_for_replace(TARGET_DELETE_DELETED_VRF_2, 180 | CURRENT_DELETE_DELETED_VRF_2), 181 | 'expected in-sync') 182 | end 183 | 184 | def test_netconf_delete_combo_merge 185 | assert(Cisco::Netconf.insync_for_merge(TARGET_BLUE_GREEN_VRF, 186 | CURRENT_BLUE_GREEN_VRF), 187 | 'expected in-sync') 188 | end 189 | 190 | def test_netconf_delete_combo_replace 191 | refute(Cisco::Netconf.insync_for_replace(TARGET_BLUE_GREEN_VRF, 192 | CURRENT_BLUE_GREEN_VRF), 193 | 'expected not in-sync') 194 | end 195 | end 196 | --------------------------------------------------------------------------------