├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── requirements.txt ├── setup.py ├── solenoid ├── __init__.py ├── edit_rib.py ├── grpc_cisco │ ├── __init__.py │ ├── ems_grpc_pb2.py │ └── grpcClient.py ├── logs │ ├── __init__.py │ └── logger.py ├── rest │ ├── __init__.py │ ├── jsonRestClient.py │ └── restClient.py ├── templates │ └── static.json └── tests │ ├── __init__.py │ ├── examples │ ├── __init__.py │ ├── config │ │ ├── grpc │ │ │ ├── grpc_good.config │ │ │ ├── multiple_sections.config │ │ │ ├── no_port.config │ │ │ └── no_section.config │ │ └── restconf │ │ │ ├── multiple_sections.config │ │ │ ├── no_port.config │ │ │ ├── no_section.config │ │ │ └── restconf_good.config │ ├── exa │ │ ├── exa-announce.json │ │ ├── exa-eor.json │ │ ├── exa-raw.json │ │ └── exa-withdraw.json │ ├── filter │ │ ├── filter-all.txt │ │ ├── filter-empty.txt │ │ ├── filter-full.txt │ │ └── filter-invalid.txt │ ├── integration │ │ ├── exa-announce.json │ │ ├── exa-withdraw.json │ │ └── rendered_announce.txt │ ├── rendered_announce.txt │ └── rest │ │ ├── invalid_data.json │ │ ├── patch_data.json │ │ └── put_data.json │ ├── integration │ ├── __init__.py │ └── test_rib.py │ ├── mock │ ├── .test_rest_calls.py │ ├── __init__.py │ ├── test_rib_general.py │ ├── test_rib_grpc.py │ └── test_rib_rest.py │ └── tools.py ├── vagrant ├── README.md ├── SolenoidDiagram.png ├── Vagrantfile ├── devbox │ ├── adv.py │ ├── bootstrap_ubuntu.sh │ └── exabgp-router-conf.ini └── xrv │ ├── bootstrap.sh │ ├── demo.xml │ └── router_config └── website ├── README.md ├── exabgp_website.py ├── static ├── Topology.png ├── jquery-1.12.1.js ├── refresh.js └── style.css └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | led / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # Ipython Notebook 63 | .ipynb_checkpoints 64 | 65 | # pyenv 66 | .python-version 67 | 68 | #Custom 69 | solenoid.config 70 | tester.py 71 | updates.txt 72 | *.DS_Store 73 | 74 | #Vagrantfiles 75 | vagrant/.vagrant 76 | vagrant/.DS_STORE 77 | *.tgz 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributions are welcome! 5 | 6 | **Please carefully read this page to make the code review process go as smoothly as possible and to maximize the likelihood of your contribution being merged.** 7 | 8 | ## License Agreement 9 | 10 | Solenoid is licensed under the [BSD license](https://opensource.org/licenses/BSD-3-Clause). In your commit messages, you must specify that you agree to contribute under the BSD license. Pull requests also must agree to submit under the BSD license. 11 | 12 | ## Bug Reports 13 | 14 | For bug reports or requests [submit an issue](https://github.com/ios-xr/Solenoid/issues). 15 | 16 | ## Pull Requests 17 | 18 | The preferred way to contribute is to fork the [main repository](https://github.com/ios-xr/Solenoid.git) on GitHub. 19 | 20 | 1. Fork the [main repository](https://github.com/ios-xr/Solenoid.git). Click on the 'Fork' button near the top of the page. This creates a copy of the code under your account on the GitHub server. 21 | 22 | 2. Clone this copy to your local disk: 23 | 24 | $ git clone https://github.com/ios-xr/Solenoid.git 25 | $ cd solenoid 26 | 27 | 3. Create a branch to hold your changes and start making changes. Don't work in the `master` branch! If you are adding a feature, use the format 'feature/my-feature', for a bug use 'bug/issue-id'. 28 | 29 | $ git checkout -b feature/my-feature 30 | 31 | 4. Work on this copy on your computer using Git to do the version control. When you're done editing, run the following to record your changes in Git: 32 | 33 | $ git add modified_files 34 | $ git commit 35 | 36 | 5. Push your changes to GitHub with: 37 | 38 | $ git push -u origin my-feature 39 | 40 | 6. Finally, go to the web page of your fork of the `solenoid` repo and click 'Pull Request' to send your changes for review. 41 | 42 | ### GitHub Pull Requests Docs 43 | 44 | If you are not familiar with pull requests, review the [pull request docs](https://help.github.com/articles/using-pull-requests/). 45 | 46 | ### Code Quality 47 | 48 | Ensure your pull request satisfies all of the following, where applicable: 49 | 50 | * Follows [PEP 8](http://legacy.python.org/dev/peps/pep-0008/) code quality standards, as well as [PEP 257](https://www.python.org/dev/peps/pep-0257/) for docstrings. 51 | 52 | * Is thoroughly unit-tested. 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Solenoid is licensed under the BSD 3-Clause License. 2 | 3 | Copyright (c) 2016, Cisco Systems 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ** This project is no longer being maintained, please use at your own discretion! ** 2 | # Solenoid 3 | ### Route Injection Agent 4 | 5 | ##### Author: Lisa Roach & Karthik Kumaravel 6 | ##### Contact: Please use the Issues page to ask questions or open bugs and feature requests. 7 | 8 | 9 | ## Description: 10 | 11 | Solenoid takes a third-party BGP protocol and sends the BGP updates to Cisco's RIB table on the IOS-XR devices, allowing for increased flexibility over the BGP protocols used on the box. 12 | 13 | The changes to the RIB are accomplished by using [gRPC](http://www.grpc.io/) calls to send JSON modeled by YANG. The YANG model currently in use is [Cisco-IOS-XR-ip-static-cfg] (https://github.com/YangModels/yang/blob/master/vendor/cisco/xr/600/Cisco-IOS-XR-ip-static-cfg.yang), see [Current Limitations](#current-limitations) for more information. 14 | 15 | [exaBGP] (https://github.com/Exa-Networks/exabgp) is used for reading BGP updates. exaBGP is a tool for monitoring BGP network announcements, withdrawals, etc. and it triggers the gRPC changes based on these updates. 16 | 17 | Please refer to the [Wiki](https://github.com/ios-xr/Solenoid/wiki) for further documentation. 18 | 19 | #### Current Limitations: 20 | 21 | As of now, the IOS-XR does not support YANG models that can add true BGP or Application routes to the RIB table. As a workaround, static routes are added instead. Be aware of the limits of static routes before using this application. 22 | 23 | 24 | RESTconf is not available on public images of the IOS-XR 6.X. If you are interested in testing RESTconf, please reach out to your Cisco account team or contact Lisa Roach directly. 25 | 26 | ### Vagrant: 27 | 28 | For an easy Solenoid-in-a-box demonstration, please refer to the [vagrant](https://github.com/ios-xr/Solenoid/tree/master/vagrant) directory. Here you will be able to download a fully functional vagrant environment that has Solenoid up and running already. 29 | 30 | ### Usage: 31 | Check the out [XR-Docs](https://xrdocs.github.io/application-hosting/tutorials/2016-09-28-solenoid-inject-routes-into-cisco-s-rib-table-using-grpc/) tutorial for a walkthrough of setting up a Solenoid LXC and playing with the demo. 32 | 33 | This installation and testing process (minus the integration tests) can run in any Linux environment. To fully use the application, it must be connected to a running IOS-XR 6.1+ device. It is recommended that Solenoid be run in an LXC on the IOS-XR you wish to control, see [Creating a Solenoid LXC tarball](https://github.com/ios-xr/Solenoid/wiki/Create-your-own-Solenoid-LXC-tarball) for information about creating your own LXC. 34 | 35 | Step 1: Clone this repo and cd into Solenoid. 36 | 37 | Step 2: It is highly recommended you install and use a [virtualenv](https://virtualenv.pypa.io/en/stable/). 38 | 39 | ``` 40 | pip install virtualenv 41 | 42 | virtualenv venv 43 | 44 | source venv/bin/activate 45 | ``` 46 | 47 | Step 3: Install gRPC (if you are using gRPC). Note: if you experience errors at this step and are not using a virtualenv, we encourage reconsidering using virtualenv. It often makes these problems go away. Otherwise, please consult [grpcio documentation](https://pypi.python.org/pypi/grpcio) for assistance. 48 | 49 | `pip install grpcio` 50 | 51 | Step 4: Install Solenoid. 52 | 53 | ```python setup.py install``` 54 | 55 | Step 5 : Create a solenoid.config file in your top-level solenoid directory and fill in the values in the key:value pair. Please refer to the Config File section of the wiki for more information. 56 | 57 | ``` 58 | [default] 59 | transport: transport # Either gRPC or RESTconf 60 | ip: ip_address # IP address of the destination RIB table (the XR device you intend to control) 61 | port: port number # Depends on what is configured for your gRPC or RESTconf servers 62 | username: username # Username for the router 63 | password: password # Password for the router 64 | ``` 65 | 66 | Example: 67 | 68 | ``` 69 | [IOS-XRv] 70 | transport: gRPC 71 | ip: 192.168.1.2 72 | port: 57777 73 | username: vagrant 74 | password: vagrant 75 | ``` 76 | 77 | Step 6 (optional): Create a filter.txt file to include the ranges of prefixes to be filtered with. This is a whitelist of prefixes, so all of these will be accepted and all others will be dropped. Single prefixes are also acceptable. Example: 78 | 79 | ``` 80 | 1.1.1.0/32-1.1.2.0/32 81 | 10.1.1.0/32-10.1.5.0/32 82 | 10.1.1.6/32 83 | 192.168.1.0/28-192.168.2.0/28 84 | ``` 85 | 86 | Step 7: Set up [exaBGP] (https://github.com/Exa-Networks/exabgp). Form a neighborship with your BGP network. 87 | 88 | Step 8: Change your exaBGP configuration file to run the edit_rib.py script. The important part is the process monitor-neighbors section, the rest is basic exaBGP configuration. 89 | 90 | 91 | Example: 92 | 93 | ``` 94 | group test { 95 | router-id x.x.x.x; 96 | 97 | process monitor-neighbors { 98 | encoder json; 99 | receive { 100 | parsed; 101 | updates; 102 | neighbor-changes; 103 | } 104 | run /your/python/location /path/to/solenoid/solenoid/edit_rib.py; 105 | } 106 | 107 | neighbor y.y.y.y { 108 | local-address x.x.x.x; 109 | local as ####; 110 | peer-as ####; 111 | } 112 | 113 | } 114 | 115 | ``` 116 | 117 | If you chose to add a filter file, you must add the path to the file in the run call with the file flag -f (be sure to include the quotes): 118 | 119 | ``` 120 | run /your/python/location /path/to//solenoid/solenoid/edit_rib.py -f '/path/to/filter/file'; 121 | ``` 122 | 123 | Step 9: Launch your exaBGP instance. You should see the syslog HTTP status codes if it is successful. 124 | 125 | ###Testing 126 | 127 | In order to run the full suite of unit tests, ensure that grpcio is installed. Run the tests with the following command from the Solenoid/ directory: 128 | 129 | ``` 130 | python -m unittest discover solenoid.tests.mock 131 | ``` 132 | 133 | If you only wish to use RESTconf and will not be testing the gRPC code, feel free to run the following individual tests: 134 | 135 | ``` 136 | python -m unittest solenoid.tests.mock.test_rib_rest 137 | 138 | python -m unittest solenoid.tests.mock.test_rib_general 139 | ``` 140 | 141 | The following is expected output from the unit tests: 142 | 143 | ``` 144 | python -m unittest discover solenoid.tests.mock 145 | ............................ 146 | ---------------------------------------------------------------------- 147 | Ran 28 tests in 0.042s 148 | 149 | OK 150 | ``` 151 | 152 | If you recieve the final status of 'OK' you are good to go! 153 | 154 | To run integration testing, run the following command from the Solenoid/ directory. **CAUTION This will make changes to your router's RIB table! Do not run this code in a production environment!** 155 | 156 | For these tests to run, you must provide a properly formatted solenoid.config file, as described in step 3, and ensure that no other instance of Solenoid is currently running. 157 | 158 | ``` 159 | python -m unittest discover solenoid.tests.integration 160 | ``` 161 | 162 | Expected output: 163 | 164 | ``` 165 | python -m unittest discover solenoid.tests.integration 166 | Thu, 01 Sep 2016 22:21:41 | INFO | 19738 | solenoid | ANNOUNCE | OK 167 | .Thu, 01 Sep 2016 22:21:41 | INFO | 19738 | solenoid | WITHDRAW | OK 168 | .Thu, 01 Sep 2016 22:21:42 | INFO | 19738 | solenoid | ANNOUNCE | OK 169 | .Thu, 01 Sep 2016 22:21:42 | INFO | 19738 | solenoid | WITHDRAW | OK 170 | .Thu, 01 Sep 2016 22:21:42 | INFO | 19738 | solenoid | EOR message 171 | . 172 | ---------------------------------------------------------------------- 173 | Ran 5 tests in 0.890s 174 | 175 | OK 176 | 177 | ``` 178 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.11 2 | mock==2.0.0 3 | netaddr==0.7.18 4 | requests==2.20.0 5 | Flask==1.0 6 | 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from codecs import open 4 | from setuptools import setup 5 | from setuptools import find_packages 6 | 7 | here = os.path.abspath(os.path.dirname(__file__)) 8 | 9 | with open(os.path.join(here, 'requirements.txt'), encoding='utf-8') as fp: 10 | requirements = fp.read().splitlines() 11 | 12 | setup( 13 | name='solenoid', 14 | version='1.0', 15 | author='Lisa Roach', 16 | author_email='lisroach@cisco.com', 17 | url='https://github.com/ios-xr/Solenoid.git', 18 | description=''' 19 | Injects routes into Cisco's IOS-XR RIB table, based on some 20 | outside logic. 21 | ''', 22 | license='BSD', 23 | packages=find_packages(), 24 | install_requires=requirements, 25 | data_files=[('solenoid/templates', ['solenoid/templates/static.json'])] 26 | ) 27 | -------------------------------------------------------------------------------- /solenoid/__init__.py: -------------------------------------------------------------------------------- 1 | from rest.jsonRestClient import JSONRestCalls 2 | from logs.logger import Logger 3 | from grpc_cisco.grpcClient import CiscoGRPCClient 4 | -------------------------------------------------------------------------------- /solenoid/edit_rib.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import ConfigParser 4 | import json 5 | import argparse 6 | 7 | from jinja2 import Environment, PackageLoader 8 | from netaddr import IPNetwork, AddrFormatError 9 | 10 | from grpc_cisco.grpcClient import CiscoGRPCClient 11 | from grpc_cisco import ems_grpc_pb2 12 | from rest.jsonRestClient import JSONRestCalls 13 | from logs.logger import Logger 14 | 15 | SOURCE = 'solenoid' 16 | LOGGER = Logger() 17 | 18 | 19 | def create_transport_object(): 20 | """Create a grpc channel object. 21 | Reads in a file containing username, password, and 22 | ip address:port, in that order. 23 | :returns: grpc object 24 | :rtype: grpc class object 25 | """ 26 | location = os.path.dirname(os.path.realpath(__file__)) 27 | config = ConfigParser.ConfigParser() 28 | try: 29 | config.read(os.path.join(location, '../solenoid.config')) 30 | if len(config.sections()) >= 1: 31 | if len(config.sections()) > 1: 32 | LOGGER.warning('Multiple routers not currently supported in the configuration file. Using first router.', SOURCE) 33 | section = config.sections()[0] 34 | arguments = ( 35 | config.get(section, 'ip'), 36 | int(config.get(section, 'port')), 37 | config.get(section, 'username'), 38 | config.get(section, 'password') 39 | ) 40 | if config.get(section, 'transport').lower() == 'grpc': 41 | return CiscoGRPCClient(*arguments) 42 | if config.get(section, 'transport').lower() == 'restconf': 43 | return JSONRestCalls(*arguments) 44 | else: 45 | raise ValueError 46 | except (ConfigParser.Error, ValueError), e: 47 | LOGGER.critical( 48 | 'Something is wrong with your config file: {}'.format( 49 | e.message 50 | ), 51 | SOURCE 52 | ) 53 | sys.exit(1) 54 | 55 | 56 | def get_status(response): 57 | """Get the status of the response object. 58 | :param response: Response object 59 | :type: depends on the type of transport used 60 | """ 61 | if isinstance(response, ems_grpc_pb2.ConfigReply): 62 | return response.errors 63 | elif isinstance(response, JSONRestCalls): 64 | return response.content 65 | 66 | 67 | def rib_announce(rendered_config, transport): 68 | """Add networks to the RIB table using HTTP PATCH over RESTconf. 69 | :param rendered_config: Jinja2 rendered configuration file 70 | :type rendered_config: unicode 71 | """ 72 | response = transport.patch(rendered_config) 73 | status = get_status(response) 74 | if status == '' or status is None: 75 | LOGGER.info('ANNOUNCE | {code} '.format( 76 | code='OK' 77 | ), 78 | SOURCE 79 | ) 80 | else: 81 | LOGGER.warning('ANNOUNCE | {code} | {reason}'.format( 82 | code='FAIL', 83 | reason=status 84 | ), 85 | SOURCE 86 | ) 87 | 88 | 89 | def rib_withdraw(withdrawn_prefixes, transport): 90 | """Remove the withdrawn prefix from the RIB table. 91 | :param new_config: The prefix and prefix-length to be removed 92 | :type new_config: str 93 | """ 94 | # Delete the prefixes in bulk with gRPC. 95 | if isinstance(transport, CiscoGRPCClient): 96 | url = '{{"Cisco-IOS-XR-ip-static-cfg:router-static": {{"default-vrf": {{"address-family": {{"vrfipv4": {{"vrf-unicast": {{"vrf-prefixes": {{"vrf-prefix": [{withdraw}]}}}}}}}}}}}}}}' 97 | prefix_info = '{{"prefix": "{bgp_prefix}","prefix-length": {prefix_length}}}' 98 | prefix_list = [] 99 | for withdrawn_prefix in withdrawn_prefixes: 100 | bgp_prefix, prefix_length = withdrawn_prefix.split('/') 101 | prefix_list += [ 102 | prefix_info.format( 103 | bgp_prefix=bgp_prefix, 104 | prefix_length=prefix_length 105 | ) 106 | ] 107 | prefix_str = ', '.join(prefix_list) 108 | url = url.format(withdraw=prefix_str) 109 | response = transport.delete(url) 110 | status = get_status(response) 111 | else: # Right now there is only gRPC and RESTconf, more elif will be required w/ more options. 112 | # Delete the prefixes one at a time with RESTconf. 113 | for withdrawn_prefix in withdrawn_prefixes: 114 | url = 'Cisco-IOS-XR-ip-static-cfg:router-static/default-vrf/address-family/vrfipv4/vrf-unicast/vrf-prefixes/vrf-prefix={bgp_prefix},{prefix_length}' 115 | bgp_prefix, prefix_length = withdrawn_prefix.split('/') 116 | url = url.format( 117 | bgp_prefix=bgp_prefix, 118 | prefix_length=prefix_length 119 | ) 120 | response = transport.delete(url) 121 | status = get_status(response) 122 | if status is None or status == '': 123 | LOGGER.info('WITHDRAW | {code}'.format( 124 | code='OK' 125 | ), 126 | SOURCE 127 | ) 128 | else: 129 | LOGGER.warning('WITHDRAW | {code} | {reason}'.format( 130 | code='FAIL', 131 | reason=status 132 | ), 133 | SOURCE 134 | ) 135 | 136 | def render_config(json_update, transport): 137 | """Take a BGP command and translate it into yang formatted JSON 138 | :param json_update: JSON dictionary 139 | :type json_update: dict 140 | """ 141 | # Check if any filtering has been applied to the prefixes. 142 | try: 143 | filt = bool(FILEPATH is not None and os.path.getsize(FILEPATH) > 0) 144 | except OSError: 145 | filt = False 146 | 147 | # Render the config. 148 | try: 149 | update_type = json_update['neighbor']['message'] 150 | if 'eor' not in update_type: 151 | update_type = json_update['neighbor']['message']['update'] 152 | # Check if it is an announcement or withdrawal. 153 | if ('announce' in update_type 154 | and 'null' not in update_type['announce']['ipv4 unicast']): 155 | updated_prefixes = update_type['announce']['ipv4 unicast'] 156 | next_hop = updated_prefixes.keys()[0] 157 | prefixes = updated_prefixes.values()[0].keys() 158 | if filt: 159 | prefixes = filter_prefixes(prefixes) 160 | # Set env variable for Jinja2. 161 | if len(prefixes) > 0: 162 | env = Environment(loader=PackageLoader('solenoid', 163 | 'templates') 164 | ) 165 | env.filters['to_json'] = json.dumps 166 | template = env.get_template('static.json') 167 | # Render the template and make the announcement. 168 | rib_announce(template.render(next_hop=next_hop, 169 | prefixes=prefixes), 170 | transport 171 | ) 172 | else: 173 | return 174 | elif 'withdraw' in update_type: 175 | bgp_prefixes = update_type['withdraw']['ipv4 unicast'].keys() 176 | # Filter the prefixes if needed. 177 | if filt: 178 | bgp_prefixes = filter_prefixes(bgp_prefixes) 179 | if len(bgp_prefixes) > 0: 180 | rib_withdraw(bgp_prefixes, transport) 181 | else: 182 | return 183 | else: 184 | LOGGER.info('EOR message', SOURCE) 185 | except KeyError: 186 | LOGGER.error('Not a valid update message type', SOURCE) 187 | raise 188 | 189 | 190 | def filter_prefixes(prefixes): 191 | """Filters out prefixes that do not fall in ranges indicated in filter.txt 192 | 193 | :param prefixes: List of prefixes bgpBGP announced or withdrew 194 | :type prefixes: list of strings 195 | 196 | """ 197 | with open(FILEPATH) as filterf: 198 | final = [] 199 | try: 200 | prefixes = map(IPNetwork, prefixes) 201 | for line in filterf: 202 | if '-' in line: 203 | # Convert it all to IPNetwork for comparison. 204 | ip1, ip2 = map(IPNetwork, line.split('-')) 205 | final += [str(prefix) for prefix in prefixes if ip1 <= prefix <= ip2] 206 | else: 207 | ip = IPNetwork(line) 208 | final += [str(prefix) for prefix in prefixes if prefix == ip] 209 | return final 210 | except AddrFormatError, e: 211 | LOGGER.error('FILTER | {}'.format(e), SOURCE) 212 | raise 213 | 214 | 215 | def update_file(raw_update): 216 | """Update the updates.txt file with the newest exaBGP JSON string. 217 | :param raw_update: The JSON string from exaBGP 218 | :type raw_update: str 219 | """ 220 | # Add the change to the update file. 221 | here = os.path.dirname(os.path.realpath(__file__)) 222 | updates_filepath = os.path.join(here, 'updates.txt') 223 | with open(updates_filepath, 'a') as f: 224 | f.write(str(raw_update) + '\n') 225 | 226 | 227 | def update_validator(raw_update, transport): 228 | """Translate update to JSON and send it to be rendered. 229 | :param raw_update: Raw exaBGP message. 230 | :type raw_update: JSON 231 | """ 232 | try: 233 | json_update = json.loads(raw_update) 234 | # If it is an update, make the RIB changes. 235 | if json_update['type'] == 'update': 236 | render_config(json_update, transport) 237 | # Add the update to a file to keep track. 238 | update_file(raw_update) 239 | except ValueError: 240 | LOGGER.error('Failed JSON conversion for BGP update', SOURCE) 241 | raise 242 | except KeyError: 243 | LOGGER.debug('Not a valid update message type', SOURCE) 244 | raise 245 | 246 | 247 | def update_watcher(): 248 | """Watches for BGP updates and triggers a RIB change when update is heard.""" 249 | # Continuously listen for updates. 250 | transport = create_transport_object() 251 | while 1: 252 | raw_update = sys.stdin.readline().strip() 253 | update_validator(raw_update, transport) 254 | 255 | if __name__ == "__main__": 256 | PARSER = argparse.ArgumentParser() 257 | PARSER.add_argument('-f', type=str) 258 | ARGS = PARSER.parse_args() 259 | global FILEPATH 260 | FILEPATH = ARGS.f 261 | update_watcher() 262 | -------------------------------------------------------------------------------- /solenoid/grpc_cisco/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/grpc_cisco/__init__.py -------------------------------------------------------------------------------- /solenoid/grpc_cisco/ems_grpc_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: ems_grpc.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf.internal import enum_type_wrapper 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf import descriptor_pb2 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | 17 | 18 | 19 | DESCRIPTOR = _descriptor.FileDescriptor( 20 | name='ems_grpc.proto', 21 | package='IOSXRExtensibleManagabilityService', 22 | syntax='proto3', 23 | serialized_pb=_b('\n\x0e\x65ms_grpc.proto\x12\"IOSXRExtensibleManagabilityService\"4\n\rConfigGetArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\x12\x14\n\x0cyangpathjson\x18\x02 \x01(\t\"D\n\x0e\x43onfigGetReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x10\n\x08yangjson\x18\x02 \x01(\t\x12\x0e\n\x06\x65rrors\x18\x03 \x01(\t\"2\n\x0bGetOperArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\x12\x14\n\x0cyangpathjson\x18\x02 \x01(\t\"B\n\x0cGetOperReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x10\n\x08yangjson\x18\x02 \x01(\t\x12\x0e\n\x06\x65rrors\x18\x03 \x01(\t\"-\n\nConfigArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\x12\x10\n\x08yangjson\x18\x02 \x01(\t\"/\n\x0b\x43onfigReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x0e\n\x06\x65rrors\x18\x02 \x01(\t\"+\n\rCliConfigArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\x12\x0b\n\x03\x63li\x18\x02 \x01(\t\"2\n\x0e\x43liConfigReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x0e\n\x06\x65rrors\x18\x02 \x01(\t\"A\n\x11\x43ommitReplaceArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\x12\x0b\n\x03\x63li\x18\x02 \x01(\t\x12\x10\n\x08yangjson\x18\x03 \x01(\t\"6\n\x12\x43ommitReplaceReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x0e\n\x06\x65rrors\x18\x02 \x01(\t\"+\n\tCommitMsg\x12\r\n\x05label\x18\x01 \x01(\t\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\"W\n\nCommitArgs\x12:\n\x03msg\x18\x01 \x01(\x0b\x32-.IOSXRExtensibleManagabilityService.CommitMsg\x12\r\n\x05ReqId\x18\x02 \x01(\x03\"q\n\x0b\x43ommitReply\x12@\n\x06result\x18\x01 \x01(\x0e\x32\x30.IOSXRExtensibleManagabilityService.CommitResult\x12\x10\n\x08ResReqId\x18\x02 \x01(\x03\x12\x0e\n\x06\x65rrors\x18\x03 \x01(\t\"#\n\x12\x44iscardChangesArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\"7\n\x13\x44iscardChangesReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x0e\n\x06\x65rrors\x18\x02 \x01(\t\")\n\x0bShowCmdArgs\x12\r\n\x05ReqId\x18\x01 \x01(\x03\x12\x0b\n\x03\x63li\x18\x02 \x01(\t\"D\n\x10ShowCmdTextReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x0e\n\x06output\x18\x02 \x01(\t\x12\x0e\n\x06\x65rrors\x18\x03 \x01(\t\"H\n\x10ShowCmdJSONReply\x12\x10\n\x08ResReqId\x18\x01 \x01(\x03\x12\x12\n\njsonoutput\x18\x02 \x01(\t\x12\x0e\n\x06\x65rrors\x18\x03 \x01(\t*3\n\x0c\x43ommitResult\x12\n\n\x06\x43HANGE\x10\x00\x12\r\n\tNO_CHANGE\x10\x01\x12\x08\n\x04\x46\x41IL\x10\x02\x32\xcb\x08\n\x0egRPCConfigOper\x12v\n\tGetConfig\x12\x31.IOSXRExtensibleManagabilityService.ConfigGetArgs\x1a\x32.IOSXRExtensibleManagabilityService.ConfigGetReply\"\x00\x30\x01\x12p\n\x0bMergeConfig\x12..IOSXRExtensibleManagabilityService.ConfigArgs\x1a/.IOSXRExtensibleManagabilityService.ConfigReply\"\x00\x12q\n\x0c\x44\x65leteConfig\x12..IOSXRExtensibleManagabilityService.ConfigArgs\x1a/.IOSXRExtensibleManagabilityService.ConfigReply\"\x00\x12r\n\rReplaceConfig\x12..IOSXRExtensibleManagabilityService.ConfigArgs\x1a/.IOSXRExtensibleManagabilityService.ConfigReply\"\x00\x12t\n\tCliConfig\x12\x31.IOSXRExtensibleManagabilityService.CliConfigArgs\x1a\x32.IOSXRExtensibleManagabilityService.CliConfigReply\"\x00\x12\x80\x01\n\rCommitReplace\x12\x35.IOSXRExtensibleManagabilityService.CommitReplaceArgs\x1a\x36.IOSXRExtensibleManagabilityService.CommitReplaceReply\"\x00\x12q\n\x0c\x43ommitConfig\x12..IOSXRExtensibleManagabilityService.CommitArgs\x1a/.IOSXRExtensibleManagabilityService.CommitReply\"\x00\x12\x89\x01\n\x14\x43onfigDiscardChanges\x12\x36.IOSXRExtensibleManagabilityService.DiscardChangesArgs\x1a\x37.IOSXRExtensibleManagabilityService.DiscardChangesReply\"\x00\x12p\n\x07GetOper\x12/.IOSXRExtensibleManagabilityService.GetOperArgs\x1a\x30.IOSXRExtensibleManagabilityService.GetOperReply\"\x00\x30\x01\x32\x8a\x02\n\x08gRPCExec\x12~\n\x11ShowCmdTextOutput\x12/.IOSXRExtensibleManagabilityService.ShowCmdArgs\x1a\x34.IOSXRExtensibleManagabilityService.ShowCmdTextReply\"\x00\x30\x01\x12~\n\x11ShowCmdJSONOutput\x12/.IOSXRExtensibleManagabilityService.ShowCmdArgs\x1a\x34.IOSXRExtensibleManagabilityService.ShowCmdJSONReply\"\x00\x30\x01\x62\x06proto3') 24 | ) 25 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 26 | 27 | _COMMITRESULT = _descriptor.EnumDescriptor( 28 | name='CommitResult', 29 | full_name='IOSXRExtensibleManagabilityService.CommitResult', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | values=[ 33 | _descriptor.EnumValueDescriptor( 34 | name='CHANGE', index=0, number=0, 35 | options=None, 36 | type=None), 37 | _descriptor.EnumValueDescriptor( 38 | name='NO_CHANGE', index=1, number=1, 39 | options=None, 40 | type=None), 41 | _descriptor.EnumValueDescriptor( 42 | name='FAIL', index=2, number=2, 43 | options=None, 44 | type=None), 45 | ], 46 | containing_type=None, 47 | options=None, 48 | serialized_start=1144, 49 | serialized_end=1195, 50 | ) 51 | _sym_db.RegisterEnumDescriptor(_COMMITRESULT) 52 | 53 | CommitResult = enum_type_wrapper.EnumTypeWrapper(_COMMITRESULT) 54 | CHANGE = 0 55 | NO_CHANGE = 1 56 | FAIL = 2 57 | 58 | 59 | 60 | _CONFIGGETARGS = _descriptor.Descriptor( 61 | name='ConfigGetArgs', 62 | full_name='IOSXRExtensibleManagabilityService.ConfigGetArgs', 63 | filename=None, 64 | file=DESCRIPTOR, 65 | containing_type=None, 66 | fields=[ 67 | _descriptor.FieldDescriptor( 68 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.ConfigGetArgs.ReqId', index=0, 69 | number=1, type=3, cpp_type=2, label=1, 70 | has_default_value=False, default_value=0, 71 | message_type=None, enum_type=None, containing_type=None, 72 | is_extension=False, extension_scope=None, 73 | options=None), 74 | _descriptor.FieldDescriptor( 75 | name='yangpathjson', full_name='IOSXRExtensibleManagabilityService.ConfigGetArgs.yangpathjson', index=1, 76 | number=2, type=9, cpp_type=9, label=1, 77 | has_default_value=False, default_value=_b("").decode('utf-8'), 78 | message_type=None, enum_type=None, containing_type=None, 79 | is_extension=False, extension_scope=None, 80 | options=None), 81 | ], 82 | extensions=[ 83 | ], 84 | nested_types=[], 85 | enum_types=[ 86 | ], 87 | options=None, 88 | is_extendable=False, 89 | syntax='proto3', 90 | extension_ranges=[], 91 | oneofs=[ 92 | ], 93 | serialized_start=54, 94 | serialized_end=106, 95 | ) 96 | 97 | 98 | _CONFIGGETREPLY = _descriptor.Descriptor( 99 | name='ConfigGetReply', 100 | full_name='IOSXRExtensibleManagabilityService.ConfigGetReply', 101 | filename=None, 102 | file=DESCRIPTOR, 103 | containing_type=None, 104 | fields=[ 105 | _descriptor.FieldDescriptor( 106 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.ConfigGetReply.ResReqId', index=0, 107 | number=1, type=3, cpp_type=2, label=1, 108 | has_default_value=False, default_value=0, 109 | message_type=None, enum_type=None, containing_type=None, 110 | is_extension=False, extension_scope=None, 111 | options=None), 112 | _descriptor.FieldDescriptor( 113 | name='yangjson', full_name='IOSXRExtensibleManagabilityService.ConfigGetReply.yangjson', index=1, 114 | number=2, type=9, cpp_type=9, label=1, 115 | has_default_value=False, default_value=_b("").decode('utf-8'), 116 | message_type=None, enum_type=None, containing_type=None, 117 | is_extension=False, extension_scope=None, 118 | options=None), 119 | _descriptor.FieldDescriptor( 120 | name='errors', full_name='IOSXRExtensibleManagabilityService.ConfigGetReply.errors', index=2, 121 | number=3, type=9, cpp_type=9, label=1, 122 | has_default_value=False, default_value=_b("").decode('utf-8'), 123 | message_type=None, enum_type=None, containing_type=None, 124 | is_extension=False, extension_scope=None, 125 | options=None), 126 | ], 127 | extensions=[ 128 | ], 129 | nested_types=[], 130 | enum_types=[ 131 | ], 132 | options=None, 133 | is_extendable=False, 134 | syntax='proto3', 135 | extension_ranges=[], 136 | oneofs=[ 137 | ], 138 | serialized_start=108, 139 | serialized_end=176, 140 | ) 141 | 142 | 143 | _GETOPERARGS = _descriptor.Descriptor( 144 | name='GetOperArgs', 145 | full_name='IOSXRExtensibleManagabilityService.GetOperArgs', 146 | filename=None, 147 | file=DESCRIPTOR, 148 | containing_type=None, 149 | fields=[ 150 | _descriptor.FieldDescriptor( 151 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.GetOperArgs.ReqId', index=0, 152 | number=1, type=3, cpp_type=2, label=1, 153 | has_default_value=False, default_value=0, 154 | message_type=None, enum_type=None, containing_type=None, 155 | is_extension=False, extension_scope=None, 156 | options=None), 157 | _descriptor.FieldDescriptor( 158 | name='yangpathjson', full_name='IOSXRExtensibleManagabilityService.GetOperArgs.yangpathjson', index=1, 159 | number=2, type=9, cpp_type=9, label=1, 160 | has_default_value=False, default_value=_b("").decode('utf-8'), 161 | message_type=None, enum_type=None, containing_type=None, 162 | is_extension=False, extension_scope=None, 163 | options=None), 164 | ], 165 | extensions=[ 166 | ], 167 | nested_types=[], 168 | enum_types=[ 169 | ], 170 | options=None, 171 | is_extendable=False, 172 | syntax='proto3', 173 | extension_ranges=[], 174 | oneofs=[ 175 | ], 176 | serialized_start=178, 177 | serialized_end=228, 178 | ) 179 | 180 | 181 | _GETOPERREPLY = _descriptor.Descriptor( 182 | name='GetOperReply', 183 | full_name='IOSXRExtensibleManagabilityService.GetOperReply', 184 | filename=None, 185 | file=DESCRIPTOR, 186 | containing_type=None, 187 | fields=[ 188 | _descriptor.FieldDescriptor( 189 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.GetOperReply.ResReqId', index=0, 190 | number=1, type=3, cpp_type=2, label=1, 191 | has_default_value=False, default_value=0, 192 | message_type=None, enum_type=None, containing_type=None, 193 | is_extension=False, extension_scope=None, 194 | options=None), 195 | _descriptor.FieldDescriptor( 196 | name='yangjson', full_name='IOSXRExtensibleManagabilityService.GetOperReply.yangjson', index=1, 197 | number=2, type=9, cpp_type=9, label=1, 198 | has_default_value=False, default_value=_b("").decode('utf-8'), 199 | message_type=None, enum_type=None, containing_type=None, 200 | is_extension=False, extension_scope=None, 201 | options=None), 202 | _descriptor.FieldDescriptor( 203 | name='errors', full_name='IOSXRExtensibleManagabilityService.GetOperReply.errors', index=2, 204 | number=3, type=9, cpp_type=9, label=1, 205 | has_default_value=False, default_value=_b("").decode('utf-8'), 206 | message_type=None, enum_type=None, containing_type=None, 207 | is_extension=False, extension_scope=None, 208 | options=None), 209 | ], 210 | extensions=[ 211 | ], 212 | nested_types=[], 213 | enum_types=[ 214 | ], 215 | options=None, 216 | is_extendable=False, 217 | syntax='proto3', 218 | extension_ranges=[], 219 | oneofs=[ 220 | ], 221 | serialized_start=230, 222 | serialized_end=296, 223 | ) 224 | 225 | 226 | _CONFIGARGS = _descriptor.Descriptor( 227 | name='ConfigArgs', 228 | full_name='IOSXRExtensibleManagabilityService.ConfigArgs', 229 | filename=None, 230 | file=DESCRIPTOR, 231 | containing_type=None, 232 | fields=[ 233 | _descriptor.FieldDescriptor( 234 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.ConfigArgs.ReqId', index=0, 235 | number=1, type=3, cpp_type=2, label=1, 236 | has_default_value=False, default_value=0, 237 | message_type=None, enum_type=None, containing_type=None, 238 | is_extension=False, extension_scope=None, 239 | options=None), 240 | _descriptor.FieldDescriptor( 241 | name='yangjson', full_name='IOSXRExtensibleManagabilityService.ConfigArgs.yangjson', index=1, 242 | number=2, type=9, cpp_type=9, label=1, 243 | has_default_value=False, default_value=_b("").decode('utf-8'), 244 | message_type=None, enum_type=None, containing_type=None, 245 | is_extension=False, extension_scope=None, 246 | options=None), 247 | ], 248 | extensions=[ 249 | ], 250 | nested_types=[], 251 | enum_types=[ 252 | ], 253 | options=None, 254 | is_extendable=False, 255 | syntax='proto3', 256 | extension_ranges=[], 257 | oneofs=[ 258 | ], 259 | serialized_start=298, 260 | serialized_end=343, 261 | ) 262 | 263 | 264 | _CONFIGREPLY = _descriptor.Descriptor( 265 | name='ConfigReply', 266 | full_name='IOSXRExtensibleManagabilityService.ConfigReply', 267 | filename=None, 268 | file=DESCRIPTOR, 269 | containing_type=None, 270 | fields=[ 271 | _descriptor.FieldDescriptor( 272 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.ConfigReply.ResReqId', index=0, 273 | number=1, type=3, cpp_type=2, label=1, 274 | has_default_value=False, default_value=0, 275 | message_type=None, enum_type=None, containing_type=None, 276 | is_extension=False, extension_scope=None, 277 | options=None), 278 | _descriptor.FieldDescriptor( 279 | name='errors', full_name='IOSXRExtensibleManagabilityService.ConfigReply.errors', index=1, 280 | number=2, type=9, cpp_type=9, label=1, 281 | has_default_value=False, default_value=_b("").decode('utf-8'), 282 | message_type=None, enum_type=None, containing_type=None, 283 | is_extension=False, extension_scope=None, 284 | options=None), 285 | ], 286 | extensions=[ 287 | ], 288 | nested_types=[], 289 | enum_types=[ 290 | ], 291 | options=None, 292 | is_extendable=False, 293 | syntax='proto3', 294 | extension_ranges=[], 295 | oneofs=[ 296 | ], 297 | serialized_start=345, 298 | serialized_end=392, 299 | ) 300 | 301 | 302 | _CLICONFIGARGS = _descriptor.Descriptor( 303 | name='CliConfigArgs', 304 | full_name='IOSXRExtensibleManagabilityService.CliConfigArgs', 305 | filename=None, 306 | file=DESCRIPTOR, 307 | containing_type=None, 308 | fields=[ 309 | _descriptor.FieldDescriptor( 310 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.CliConfigArgs.ReqId', index=0, 311 | number=1, type=3, cpp_type=2, label=1, 312 | has_default_value=False, default_value=0, 313 | message_type=None, enum_type=None, containing_type=None, 314 | is_extension=False, extension_scope=None, 315 | options=None), 316 | _descriptor.FieldDescriptor( 317 | name='cli', full_name='IOSXRExtensibleManagabilityService.CliConfigArgs.cli', index=1, 318 | number=2, type=9, cpp_type=9, label=1, 319 | has_default_value=False, default_value=_b("").decode('utf-8'), 320 | message_type=None, enum_type=None, containing_type=None, 321 | is_extension=False, extension_scope=None, 322 | options=None), 323 | ], 324 | extensions=[ 325 | ], 326 | nested_types=[], 327 | enum_types=[ 328 | ], 329 | options=None, 330 | is_extendable=False, 331 | syntax='proto3', 332 | extension_ranges=[], 333 | oneofs=[ 334 | ], 335 | serialized_start=394, 336 | serialized_end=437, 337 | ) 338 | 339 | 340 | _CLICONFIGREPLY = _descriptor.Descriptor( 341 | name='CliConfigReply', 342 | full_name='IOSXRExtensibleManagabilityService.CliConfigReply', 343 | filename=None, 344 | file=DESCRIPTOR, 345 | containing_type=None, 346 | fields=[ 347 | _descriptor.FieldDescriptor( 348 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.CliConfigReply.ResReqId', index=0, 349 | number=1, type=3, cpp_type=2, label=1, 350 | has_default_value=False, default_value=0, 351 | message_type=None, enum_type=None, containing_type=None, 352 | is_extension=False, extension_scope=None, 353 | options=None), 354 | _descriptor.FieldDescriptor( 355 | name='errors', full_name='IOSXRExtensibleManagabilityService.CliConfigReply.errors', index=1, 356 | number=2, type=9, cpp_type=9, label=1, 357 | has_default_value=False, default_value=_b("").decode('utf-8'), 358 | message_type=None, enum_type=None, containing_type=None, 359 | is_extension=False, extension_scope=None, 360 | options=None), 361 | ], 362 | extensions=[ 363 | ], 364 | nested_types=[], 365 | enum_types=[ 366 | ], 367 | options=None, 368 | is_extendable=False, 369 | syntax='proto3', 370 | extension_ranges=[], 371 | oneofs=[ 372 | ], 373 | serialized_start=439, 374 | serialized_end=489, 375 | ) 376 | 377 | 378 | _COMMITREPLACEARGS = _descriptor.Descriptor( 379 | name='CommitReplaceArgs', 380 | full_name='IOSXRExtensibleManagabilityService.CommitReplaceArgs', 381 | filename=None, 382 | file=DESCRIPTOR, 383 | containing_type=None, 384 | fields=[ 385 | _descriptor.FieldDescriptor( 386 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.CommitReplaceArgs.ReqId', index=0, 387 | number=1, type=3, cpp_type=2, label=1, 388 | has_default_value=False, default_value=0, 389 | message_type=None, enum_type=None, containing_type=None, 390 | is_extension=False, extension_scope=None, 391 | options=None), 392 | _descriptor.FieldDescriptor( 393 | name='cli', full_name='IOSXRExtensibleManagabilityService.CommitReplaceArgs.cli', index=1, 394 | number=2, type=9, cpp_type=9, label=1, 395 | has_default_value=False, default_value=_b("").decode('utf-8'), 396 | message_type=None, enum_type=None, containing_type=None, 397 | is_extension=False, extension_scope=None, 398 | options=None), 399 | _descriptor.FieldDescriptor( 400 | name='yangjson', full_name='IOSXRExtensibleManagabilityService.CommitReplaceArgs.yangjson', index=2, 401 | number=3, type=9, cpp_type=9, label=1, 402 | has_default_value=False, default_value=_b("").decode('utf-8'), 403 | message_type=None, enum_type=None, containing_type=None, 404 | is_extension=False, extension_scope=None, 405 | options=None), 406 | ], 407 | extensions=[ 408 | ], 409 | nested_types=[], 410 | enum_types=[ 411 | ], 412 | options=None, 413 | is_extendable=False, 414 | syntax='proto3', 415 | extension_ranges=[], 416 | oneofs=[ 417 | ], 418 | serialized_start=491, 419 | serialized_end=556, 420 | ) 421 | 422 | 423 | _COMMITREPLACEREPLY = _descriptor.Descriptor( 424 | name='CommitReplaceReply', 425 | full_name='IOSXRExtensibleManagabilityService.CommitReplaceReply', 426 | filename=None, 427 | file=DESCRIPTOR, 428 | containing_type=None, 429 | fields=[ 430 | _descriptor.FieldDescriptor( 431 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.CommitReplaceReply.ResReqId', index=0, 432 | number=1, type=3, cpp_type=2, label=1, 433 | has_default_value=False, default_value=0, 434 | message_type=None, enum_type=None, containing_type=None, 435 | is_extension=False, extension_scope=None, 436 | options=None), 437 | _descriptor.FieldDescriptor( 438 | name='errors', full_name='IOSXRExtensibleManagabilityService.CommitReplaceReply.errors', index=1, 439 | number=2, type=9, cpp_type=9, label=1, 440 | has_default_value=False, default_value=_b("").decode('utf-8'), 441 | message_type=None, enum_type=None, containing_type=None, 442 | is_extension=False, extension_scope=None, 443 | options=None), 444 | ], 445 | extensions=[ 446 | ], 447 | nested_types=[], 448 | enum_types=[ 449 | ], 450 | options=None, 451 | is_extendable=False, 452 | syntax='proto3', 453 | extension_ranges=[], 454 | oneofs=[ 455 | ], 456 | serialized_start=558, 457 | serialized_end=612, 458 | ) 459 | 460 | 461 | _COMMITMSG = _descriptor.Descriptor( 462 | name='CommitMsg', 463 | full_name='IOSXRExtensibleManagabilityService.CommitMsg', 464 | filename=None, 465 | file=DESCRIPTOR, 466 | containing_type=None, 467 | fields=[ 468 | _descriptor.FieldDescriptor( 469 | name='label', full_name='IOSXRExtensibleManagabilityService.CommitMsg.label', index=0, 470 | number=1, type=9, cpp_type=9, label=1, 471 | has_default_value=False, default_value=_b("").decode('utf-8'), 472 | message_type=None, enum_type=None, containing_type=None, 473 | is_extension=False, extension_scope=None, 474 | options=None), 475 | _descriptor.FieldDescriptor( 476 | name='comment', full_name='IOSXRExtensibleManagabilityService.CommitMsg.comment', index=1, 477 | number=2, type=9, cpp_type=9, label=1, 478 | has_default_value=False, default_value=_b("").decode('utf-8'), 479 | message_type=None, enum_type=None, containing_type=None, 480 | is_extension=False, extension_scope=None, 481 | options=None), 482 | ], 483 | extensions=[ 484 | ], 485 | nested_types=[], 486 | enum_types=[ 487 | ], 488 | options=None, 489 | is_extendable=False, 490 | syntax='proto3', 491 | extension_ranges=[], 492 | oneofs=[ 493 | ], 494 | serialized_start=614, 495 | serialized_end=657, 496 | ) 497 | 498 | 499 | _COMMITARGS = _descriptor.Descriptor( 500 | name='CommitArgs', 501 | full_name='IOSXRExtensibleManagabilityService.CommitArgs', 502 | filename=None, 503 | file=DESCRIPTOR, 504 | containing_type=None, 505 | fields=[ 506 | _descriptor.FieldDescriptor( 507 | name='msg', full_name='IOSXRExtensibleManagabilityService.CommitArgs.msg', index=0, 508 | number=1, type=11, cpp_type=10, label=1, 509 | has_default_value=False, default_value=None, 510 | message_type=None, enum_type=None, containing_type=None, 511 | is_extension=False, extension_scope=None, 512 | options=None), 513 | _descriptor.FieldDescriptor( 514 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.CommitArgs.ReqId', index=1, 515 | number=2, type=3, cpp_type=2, label=1, 516 | has_default_value=False, default_value=0, 517 | message_type=None, enum_type=None, containing_type=None, 518 | is_extension=False, extension_scope=None, 519 | options=None), 520 | ], 521 | extensions=[ 522 | ], 523 | nested_types=[], 524 | enum_types=[ 525 | ], 526 | options=None, 527 | is_extendable=False, 528 | syntax='proto3', 529 | extension_ranges=[], 530 | oneofs=[ 531 | ], 532 | serialized_start=659, 533 | serialized_end=746, 534 | ) 535 | 536 | 537 | _COMMITREPLY = _descriptor.Descriptor( 538 | name='CommitReply', 539 | full_name='IOSXRExtensibleManagabilityService.CommitReply', 540 | filename=None, 541 | file=DESCRIPTOR, 542 | containing_type=None, 543 | fields=[ 544 | _descriptor.FieldDescriptor( 545 | name='result', full_name='IOSXRExtensibleManagabilityService.CommitReply.result', index=0, 546 | number=1, type=14, cpp_type=8, label=1, 547 | has_default_value=False, default_value=0, 548 | message_type=None, enum_type=None, containing_type=None, 549 | is_extension=False, extension_scope=None, 550 | options=None), 551 | _descriptor.FieldDescriptor( 552 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.CommitReply.ResReqId', index=1, 553 | number=2, type=3, cpp_type=2, label=1, 554 | has_default_value=False, default_value=0, 555 | message_type=None, enum_type=None, containing_type=None, 556 | is_extension=False, extension_scope=None, 557 | options=None), 558 | _descriptor.FieldDescriptor( 559 | name='errors', full_name='IOSXRExtensibleManagabilityService.CommitReply.errors', index=2, 560 | number=3, type=9, cpp_type=9, label=1, 561 | has_default_value=False, default_value=_b("").decode('utf-8'), 562 | message_type=None, enum_type=None, containing_type=None, 563 | is_extension=False, extension_scope=None, 564 | options=None), 565 | ], 566 | extensions=[ 567 | ], 568 | nested_types=[], 569 | enum_types=[ 570 | ], 571 | options=None, 572 | is_extendable=False, 573 | syntax='proto3', 574 | extension_ranges=[], 575 | oneofs=[ 576 | ], 577 | serialized_start=748, 578 | serialized_end=861, 579 | ) 580 | 581 | 582 | _DISCARDCHANGESARGS = _descriptor.Descriptor( 583 | name='DiscardChangesArgs', 584 | full_name='IOSXRExtensibleManagabilityService.DiscardChangesArgs', 585 | filename=None, 586 | file=DESCRIPTOR, 587 | containing_type=None, 588 | fields=[ 589 | _descriptor.FieldDescriptor( 590 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.DiscardChangesArgs.ReqId', index=0, 591 | number=1, type=3, cpp_type=2, label=1, 592 | has_default_value=False, default_value=0, 593 | message_type=None, enum_type=None, containing_type=None, 594 | is_extension=False, extension_scope=None, 595 | options=None), 596 | ], 597 | extensions=[ 598 | ], 599 | nested_types=[], 600 | enum_types=[ 601 | ], 602 | options=None, 603 | is_extendable=False, 604 | syntax='proto3', 605 | extension_ranges=[], 606 | oneofs=[ 607 | ], 608 | serialized_start=863, 609 | serialized_end=898, 610 | ) 611 | 612 | 613 | _DISCARDCHANGESREPLY = _descriptor.Descriptor( 614 | name='DiscardChangesReply', 615 | full_name='IOSXRExtensibleManagabilityService.DiscardChangesReply', 616 | filename=None, 617 | file=DESCRIPTOR, 618 | containing_type=None, 619 | fields=[ 620 | _descriptor.FieldDescriptor( 621 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.DiscardChangesReply.ResReqId', index=0, 622 | number=1, type=3, cpp_type=2, label=1, 623 | has_default_value=False, default_value=0, 624 | message_type=None, enum_type=None, containing_type=None, 625 | is_extension=False, extension_scope=None, 626 | options=None), 627 | _descriptor.FieldDescriptor( 628 | name='errors', full_name='IOSXRExtensibleManagabilityService.DiscardChangesReply.errors', index=1, 629 | number=2, type=9, cpp_type=9, label=1, 630 | has_default_value=False, default_value=_b("").decode('utf-8'), 631 | message_type=None, enum_type=None, containing_type=None, 632 | is_extension=False, extension_scope=None, 633 | options=None), 634 | ], 635 | extensions=[ 636 | ], 637 | nested_types=[], 638 | enum_types=[ 639 | ], 640 | options=None, 641 | is_extendable=False, 642 | syntax='proto3', 643 | extension_ranges=[], 644 | oneofs=[ 645 | ], 646 | serialized_start=900, 647 | serialized_end=955, 648 | ) 649 | 650 | 651 | _SHOWCMDARGS = _descriptor.Descriptor( 652 | name='ShowCmdArgs', 653 | full_name='IOSXRExtensibleManagabilityService.ShowCmdArgs', 654 | filename=None, 655 | file=DESCRIPTOR, 656 | containing_type=None, 657 | fields=[ 658 | _descriptor.FieldDescriptor( 659 | name='ReqId', full_name='IOSXRExtensibleManagabilityService.ShowCmdArgs.ReqId', index=0, 660 | number=1, type=3, cpp_type=2, label=1, 661 | has_default_value=False, default_value=0, 662 | message_type=None, enum_type=None, containing_type=None, 663 | is_extension=False, extension_scope=None, 664 | options=None), 665 | _descriptor.FieldDescriptor( 666 | name='cli', full_name='IOSXRExtensibleManagabilityService.ShowCmdArgs.cli', index=1, 667 | number=2, type=9, cpp_type=9, label=1, 668 | has_default_value=False, default_value=_b("").decode('utf-8'), 669 | message_type=None, enum_type=None, containing_type=None, 670 | is_extension=False, extension_scope=None, 671 | options=None), 672 | ], 673 | extensions=[ 674 | ], 675 | nested_types=[], 676 | enum_types=[ 677 | ], 678 | options=None, 679 | is_extendable=False, 680 | syntax='proto3', 681 | extension_ranges=[], 682 | oneofs=[ 683 | ], 684 | serialized_start=957, 685 | serialized_end=998, 686 | ) 687 | 688 | 689 | _SHOWCMDTEXTREPLY = _descriptor.Descriptor( 690 | name='ShowCmdTextReply', 691 | full_name='IOSXRExtensibleManagabilityService.ShowCmdTextReply', 692 | filename=None, 693 | file=DESCRIPTOR, 694 | containing_type=None, 695 | fields=[ 696 | _descriptor.FieldDescriptor( 697 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.ShowCmdTextReply.ResReqId', index=0, 698 | number=1, type=3, cpp_type=2, label=1, 699 | has_default_value=False, default_value=0, 700 | message_type=None, enum_type=None, containing_type=None, 701 | is_extension=False, extension_scope=None, 702 | options=None), 703 | _descriptor.FieldDescriptor( 704 | name='output', full_name='IOSXRExtensibleManagabilityService.ShowCmdTextReply.output', index=1, 705 | number=2, type=9, cpp_type=9, label=1, 706 | has_default_value=False, default_value=_b("").decode('utf-8'), 707 | message_type=None, enum_type=None, containing_type=None, 708 | is_extension=False, extension_scope=None, 709 | options=None), 710 | _descriptor.FieldDescriptor( 711 | name='errors', full_name='IOSXRExtensibleManagabilityService.ShowCmdTextReply.errors', index=2, 712 | number=3, type=9, cpp_type=9, label=1, 713 | has_default_value=False, default_value=_b("").decode('utf-8'), 714 | message_type=None, enum_type=None, containing_type=None, 715 | is_extension=False, extension_scope=None, 716 | options=None), 717 | ], 718 | extensions=[ 719 | ], 720 | nested_types=[], 721 | enum_types=[ 722 | ], 723 | options=None, 724 | is_extendable=False, 725 | syntax='proto3', 726 | extension_ranges=[], 727 | oneofs=[ 728 | ], 729 | serialized_start=1000, 730 | serialized_end=1068, 731 | ) 732 | 733 | 734 | _SHOWCMDJSONREPLY = _descriptor.Descriptor( 735 | name='ShowCmdJSONReply', 736 | full_name='IOSXRExtensibleManagabilityService.ShowCmdJSONReply', 737 | filename=None, 738 | file=DESCRIPTOR, 739 | containing_type=None, 740 | fields=[ 741 | _descriptor.FieldDescriptor( 742 | name='ResReqId', full_name='IOSXRExtensibleManagabilityService.ShowCmdJSONReply.ResReqId', index=0, 743 | number=1, type=3, cpp_type=2, label=1, 744 | has_default_value=False, default_value=0, 745 | message_type=None, enum_type=None, containing_type=None, 746 | is_extension=False, extension_scope=None, 747 | options=None), 748 | _descriptor.FieldDescriptor( 749 | name='jsonoutput', full_name='IOSXRExtensibleManagabilityService.ShowCmdJSONReply.jsonoutput', index=1, 750 | number=2, type=9, cpp_type=9, label=1, 751 | has_default_value=False, default_value=_b("").decode('utf-8'), 752 | message_type=None, enum_type=None, containing_type=None, 753 | is_extension=False, extension_scope=None, 754 | options=None), 755 | _descriptor.FieldDescriptor( 756 | name='errors', full_name='IOSXRExtensibleManagabilityService.ShowCmdJSONReply.errors', index=2, 757 | number=3, type=9, cpp_type=9, label=1, 758 | has_default_value=False, default_value=_b("").decode('utf-8'), 759 | message_type=None, enum_type=None, containing_type=None, 760 | is_extension=False, extension_scope=None, 761 | options=None), 762 | ], 763 | extensions=[ 764 | ], 765 | nested_types=[], 766 | enum_types=[ 767 | ], 768 | options=None, 769 | is_extendable=False, 770 | syntax='proto3', 771 | extension_ranges=[], 772 | oneofs=[ 773 | ], 774 | serialized_start=1070, 775 | serialized_end=1142, 776 | ) 777 | 778 | _COMMITARGS.fields_by_name['msg'].message_type = _COMMITMSG 779 | _COMMITREPLY.fields_by_name['result'].enum_type = _COMMITRESULT 780 | DESCRIPTOR.message_types_by_name['ConfigGetArgs'] = _CONFIGGETARGS 781 | DESCRIPTOR.message_types_by_name['ConfigGetReply'] = _CONFIGGETREPLY 782 | DESCRIPTOR.message_types_by_name['GetOperArgs'] = _GETOPERARGS 783 | DESCRIPTOR.message_types_by_name['GetOperReply'] = _GETOPERREPLY 784 | DESCRIPTOR.message_types_by_name['ConfigArgs'] = _CONFIGARGS 785 | DESCRIPTOR.message_types_by_name['ConfigReply'] = _CONFIGREPLY 786 | DESCRIPTOR.message_types_by_name['CliConfigArgs'] = _CLICONFIGARGS 787 | DESCRIPTOR.message_types_by_name['CliConfigReply'] = _CLICONFIGREPLY 788 | DESCRIPTOR.message_types_by_name['CommitReplaceArgs'] = _COMMITREPLACEARGS 789 | DESCRIPTOR.message_types_by_name['CommitReplaceReply'] = _COMMITREPLACEREPLY 790 | DESCRIPTOR.message_types_by_name['CommitMsg'] = _COMMITMSG 791 | DESCRIPTOR.message_types_by_name['CommitArgs'] = _COMMITARGS 792 | DESCRIPTOR.message_types_by_name['CommitReply'] = _COMMITREPLY 793 | DESCRIPTOR.message_types_by_name['DiscardChangesArgs'] = _DISCARDCHANGESARGS 794 | DESCRIPTOR.message_types_by_name['DiscardChangesReply'] = _DISCARDCHANGESREPLY 795 | DESCRIPTOR.message_types_by_name['ShowCmdArgs'] = _SHOWCMDARGS 796 | DESCRIPTOR.message_types_by_name['ShowCmdTextReply'] = _SHOWCMDTEXTREPLY 797 | DESCRIPTOR.message_types_by_name['ShowCmdJSONReply'] = _SHOWCMDJSONREPLY 798 | DESCRIPTOR.enum_types_by_name['CommitResult'] = _COMMITRESULT 799 | 800 | ConfigGetArgs = _reflection.GeneratedProtocolMessageType('ConfigGetArgs', (_message.Message,), dict( 801 | DESCRIPTOR = _CONFIGGETARGS, 802 | __module__ = 'ems_grpc_pb2' 803 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ConfigGetArgs) 804 | )) 805 | _sym_db.RegisterMessage(ConfigGetArgs) 806 | 807 | ConfigGetReply = _reflection.GeneratedProtocolMessageType('ConfigGetReply', (_message.Message,), dict( 808 | DESCRIPTOR = _CONFIGGETREPLY, 809 | __module__ = 'ems_grpc_pb2' 810 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ConfigGetReply) 811 | )) 812 | _sym_db.RegisterMessage(ConfigGetReply) 813 | 814 | GetOperArgs = _reflection.GeneratedProtocolMessageType('GetOperArgs', (_message.Message,), dict( 815 | DESCRIPTOR = _GETOPERARGS, 816 | __module__ = 'ems_grpc_pb2' 817 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.GetOperArgs) 818 | )) 819 | _sym_db.RegisterMessage(GetOperArgs) 820 | 821 | GetOperReply = _reflection.GeneratedProtocolMessageType('GetOperReply', (_message.Message,), dict( 822 | DESCRIPTOR = _GETOPERREPLY, 823 | __module__ = 'ems_grpc_pb2' 824 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.GetOperReply) 825 | )) 826 | _sym_db.RegisterMessage(GetOperReply) 827 | 828 | ConfigArgs = _reflection.GeneratedProtocolMessageType('ConfigArgs', (_message.Message,), dict( 829 | DESCRIPTOR = _CONFIGARGS, 830 | __module__ = 'ems_grpc_pb2' 831 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ConfigArgs) 832 | )) 833 | _sym_db.RegisterMessage(ConfigArgs) 834 | 835 | ConfigReply = _reflection.GeneratedProtocolMessageType('ConfigReply', (_message.Message,), dict( 836 | DESCRIPTOR = _CONFIGREPLY, 837 | __module__ = 'ems_grpc_pb2' 838 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ConfigReply) 839 | )) 840 | _sym_db.RegisterMessage(ConfigReply) 841 | 842 | CliConfigArgs = _reflection.GeneratedProtocolMessageType('CliConfigArgs', (_message.Message,), dict( 843 | DESCRIPTOR = _CLICONFIGARGS, 844 | __module__ = 'ems_grpc_pb2' 845 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CliConfigArgs) 846 | )) 847 | _sym_db.RegisterMessage(CliConfigArgs) 848 | 849 | CliConfigReply = _reflection.GeneratedProtocolMessageType('CliConfigReply', (_message.Message,), dict( 850 | DESCRIPTOR = _CLICONFIGREPLY, 851 | __module__ = 'ems_grpc_pb2' 852 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CliConfigReply) 853 | )) 854 | _sym_db.RegisterMessage(CliConfigReply) 855 | 856 | CommitReplaceArgs = _reflection.GeneratedProtocolMessageType('CommitReplaceArgs', (_message.Message,), dict( 857 | DESCRIPTOR = _COMMITREPLACEARGS, 858 | __module__ = 'ems_grpc_pb2' 859 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CommitReplaceArgs) 860 | )) 861 | _sym_db.RegisterMessage(CommitReplaceArgs) 862 | 863 | CommitReplaceReply = _reflection.GeneratedProtocolMessageType('CommitReplaceReply', (_message.Message,), dict( 864 | DESCRIPTOR = _COMMITREPLACEREPLY, 865 | __module__ = 'ems_grpc_pb2' 866 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CommitReplaceReply) 867 | )) 868 | _sym_db.RegisterMessage(CommitReplaceReply) 869 | 870 | CommitMsg = _reflection.GeneratedProtocolMessageType('CommitMsg', (_message.Message,), dict( 871 | DESCRIPTOR = _COMMITMSG, 872 | __module__ = 'ems_grpc_pb2' 873 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CommitMsg) 874 | )) 875 | _sym_db.RegisterMessage(CommitMsg) 876 | 877 | CommitArgs = _reflection.GeneratedProtocolMessageType('CommitArgs', (_message.Message,), dict( 878 | DESCRIPTOR = _COMMITARGS, 879 | __module__ = 'ems_grpc_pb2' 880 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CommitArgs) 881 | )) 882 | _sym_db.RegisterMessage(CommitArgs) 883 | 884 | CommitReply = _reflection.GeneratedProtocolMessageType('CommitReply', (_message.Message,), dict( 885 | DESCRIPTOR = _COMMITREPLY, 886 | __module__ = 'ems_grpc_pb2' 887 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.CommitReply) 888 | )) 889 | _sym_db.RegisterMessage(CommitReply) 890 | 891 | DiscardChangesArgs = _reflection.GeneratedProtocolMessageType('DiscardChangesArgs', (_message.Message,), dict( 892 | DESCRIPTOR = _DISCARDCHANGESARGS, 893 | __module__ = 'ems_grpc_pb2' 894 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.DiscardChangesArgs) 895 | )) 896 | _sym_db.RegisterMessage(DiscardChangesArgs) 897 | 898 | DiscardChangesReply = _reflection.GeneratedProtocolMessageType('DiscardChangesReply', (_message.Message,), dict( 899 | DESCRIPTOR = _DISCARDCHANGESREPLY, 900 | __module__ = 'ems_grpc_pb2' 901 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.DiscardChangesReply) 902 | )) 903 | _sym_db.RegisterMessage(DiscardChangesReply) 904 | 905 | ShowCmdArgs = _reflection.GeneratedProtocolMessageType('ShowCmdArgs', (_message.Message,), dict( 906 | DESCRIPTOR = _SHOWCMDARGS, 907 | __module__ = 'ems_grpc_pb2' 908 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ShowCmdArgs) 909 | )) 910 | _sym_db.RegisterMessage(ShowCmdArgs) 911 | 912 | ShowCmdTextReply = _reflection.GeneratedProtocolMessageType('ShowCmdTextReply', (_message.Message,), dict( 913 | DESCRIPTOR = _SHOWCMDTEXTREPLY, 914 | __module__ = 'ems_grpc_pb2' 915 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ShowCmdTextReply) 916 | )) 917 | _sym_db.RegisterMessage(ShowCmdTextReply) 918 | 919 | ShowCmdJSONReply = _reflection.GeneratedProtocolMessageType('ShowCmdJSONReply', (_message.Message,), dict( 920 | DESCRIPTOR = _SHOWCMDJSONREPLY, 921 | __module__ = 'ems_grpc_pb2' 922 | # @@protoc_insertion_point(class_scope:IOSXRExtensibleManagabilityService.ShowCmdJSONReply) 923 | )) 924 | _sym_db.RegisterMessage(ShowCmdJSONReply) 925 | 926 | 927 | import abc 928 | import six 929 | from grpc.beta import implementations as beta_implementations 930 | from grpc.beta import interfaces as beta_interfaces 931 | from grpc.framework.common import cardinality 932 | from grpc.framework.interfaces.face import utilities as face_utilities 933 | 934 | class BetagRPCConfigOperServicer(object): 935 | def GetConfig(self, request, context): 936 | """Configuration related commands 937 | 938 | """ 939 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 940 | def MergeConfig(self, request, context): 941 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 942 | def DeleteConfig(self, request, context): 943 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 944 | def ReplaceConfig(self, request, context): 945 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 946 | def CliConfig(self, request, context): 947 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 948 | def CommitReplace(self, request, context): 949 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 950 | def CommitConfig(self, request, context): 951 | """Do we need implicit or explicit commit 952 | 953 | """ 954 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 955 | def ConfigDiscardChanges(self, request, context): 956 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 957 | def GetOper(self, request, context): 958 | """Get only returns oper data 959 | 960 | """ 961 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 962 | 963 | class BetagRPCConfigOperStub(object): 964 | def GetConfig(self, request, timeout): 965 | """Configuration related commands 966 | 967 | """ 968 | raise NotImplementedError() 969 | def MergeConfig(self, request, timeout): 970 | raise NotImplementedError() 971 | MergeConfig.future = None 972 | def DeleteConfig(self, request, timeout): 973 | raise NotImplementedError() 974 | DeleteConfig.future = None 975 | def ReplaceConfig(self, request, timeout): 976 | raise NotImplementedError() 977 | ReplaceConfig.future = None 978 | def CliConfig(self, request, timeout): 979 | raise NotImplementedError() 980 | CliConfig.future = None 981 | def CommitReplace(self, request, timeout): 982 | raise NotImplementedError() 983 | CommitReplace.future = None 984 | def CommitConfig(self, request, timeout): 985 | """Do we need implicit or explicit commit 986 | 987 | """ 988 | raise NotImplementedError() 989 | CommitConfig.future = None 990 | def ConfigDiscardChanges(self, request, timeout): 991 | raise NotImplementedError() 992 | ConfigDiscardChanges.future = None 993 | def GetOper(self, request, timeout): 994 | """Get only returns oper data 995 | 996 | """ 997 | raise NotImplementedError() 998 | 999 | def beta_create_gRPCConfigOper_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 1000 | import ems_grpc_pb2 1001 | import ems_grpc_pb2 1002 | import ems_grpc_pb2 1003 | import ems_grpc_pb2 1004 | import ems_grpc_pb2 1005 | import ems_grpc_pb2 1006 | import ems_grpc_pb2 1007 | import ems_grpc_pb2 1008 | import ems_grpc_pb2 1009 | import ems_grpc_pb2 1010 | import ems_grpc_pb2 1011 | import ems_grpc_pb2 1012 | import ems_grpc_pb2 1013 | import ems_grpc_pb2 1014 | import ems_grpc_pb2 1015 | import ems_grpc_pb2 1016 | import ems_grpc_pb2 1017 | import ems_grpc_pb2 1018 | request_deserializers = { 1019 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CliConfig'): ems_grpc_pb2.CliConfigArgs.FromString, 1020 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitConfig'): ems_grpc_pb2.CommitArgs.FromString, 1021 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitReplace'): ems_grpc_pb2.CommitReplaceArgs.FromString, 1022 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ConfigDiscardChanges'): ems_grpc_pb2.DiscardChangesArgs.FromString, 1023 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'DeleteConfig'): ems_grpc_pb2.ConfigArgs.FromString, 1024 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetConfig'): ems_grpc_pb2.ConfigGetArgs.FromString, 1025 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetOper'): ems_grpc_pb2.GetOperArgs.FromString, 1026 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'MergeConfig'): ems_grpc_pb2.ConfigArgs.FromString, 1027 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ReplaceConfig'): ems_grpc_pb2.ConfigArgs.FromString, 1028 | } 1029 | response_serializers = { 1030 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CliConfig'): ems_grpc_pb2.CliConfigReply.SerializeToString, 1031 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitConfig'): ems_grpc_pb2.CommitReply.SerializeToString, 1032 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitReplace'): ems_grpc_pb2.CommitReplaceReply.SerializeToString, 1033 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ConfigDiscardChanges'): ems_grpc_pb2.DiscardChangesReply.SerializeToString, 1034 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'DeleteConfig'): ems_grpc_pb2.ConfigReply.SerializeToString, 1035 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetConfig'): ems_grpc_pb2.ConfigGetReply.SerializeToString, 1036 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetOper'): ems_grpc_pb2.GetOperReply.SerializeToString, 1037 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'MergeConfig'): ems_grpc_pb2.ConfigReply.SerializeToString, 1038 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ReplaceConfig'): ems_grpc_pb2.ConfigReply.SerializeToString, 1039 | } 1040 | method_implementations = { 1041 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CliConfig'): face_utilities.unary_unary_inline(servicer.CliConfig), 1042 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitConfig'): face_utilities.unary_unary_inline(servicer.CommitConfig), 1043 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitReplace'): face_utilities.unary_unary_inline(servicer.CommitReplace), 1044 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ConfigDiscardChanges'): face_utilities.unary_unary_inline(servicer.ConfigDiscardChanges), 1045 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'DeleteConfig'): face_utilities.unary_unary_inline(servicer.DeleteConfig), 1046 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetConfig'): face_utilities.unary_stream_inline(servicer.GetConfig), 1047 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetOper'): face_utilities.unary_stream_inline(servicer.GetOper), 1048 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'MergeConfig'): face_utilities.unary_unary_inline(servicer.MergeConfig), 1049 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ReplaceConfig'): face_utilities.unary_unary_inline(servicer.ReplaceConfig), 1050 | } 1051 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 1052 | return beta_implementations.server(method_implementations, options=server_options) 1053 | 1054 | def beta_create_gRPCConfigOper_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 1055 | import ems_grpc_pb2 1056 | import ems_grpc_pb2 1057 | import ems_grpc_pb2 1058 | import ems_grpc_pb2 1059 | import ems_grpc_pb2 1060 | import ems_grpc_pb2 1061 | import ems_grpc_pb2 1062 | import ems_grpc_pb2 1063 | import ems_grpc_pb2 1064 | import ems_grpc_pb2 1065 | import ems_grpc_pb2 1066 | import ems_grpc_pb2 1067 | import ems_grpc_pb2 1068 | import ems_grpc_pb2 1069 | import ems_grpc_pb2 1070 | import ems_grpc_pb2 1071 | import ems_grpc_pb2 1072 | import ems_grpc_pb2 1073 | request_serializers = { 1074 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CliConfig'): ems_grpc_pb2.CliConfigArgs.SerializeToString, 1075 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitConfig'): ems_grpc_pb2.CommitArgs.SerializeToString, 1076 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitReplace'): ems_grpc_pb2.CommitReplaceArgs.SerializeToString, 1077 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ConfigDiscardChanges'): ems_grpc_pb2.DiscardChangesArgs.SerializeToString, 1078 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'DeleteConfig'): ems_grpc_pb2.ConfigArgs.SerializeToString, 1079 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetConfig'): ems_grpc_pb2.ConfigGetArgs.SerializeToString, 1080 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetOper'): ems_grpc_pb2.GetOperArgs.SerializeToString, 1081 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'MergeConfig'): ems_grpc_pb2.ConfigArgs.SerializeToString, 1082 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ReplaceConfig'): ems_grpc_pb2.ConfigArgs.SerializeToString, 1083 | } 1084 | response_deserializers = { 1085 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CliConfig'): ems_grpc_pb2.CliConfigReply.FromString, 1086 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitConfig'): ems_grpc_pb2.CommitReply.FromString, 1087 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'CommitReplace'): ems_grpc_pb2.CommitReplaceReply.FromString, 1088 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ConfigDiscardChanges'): ems_grpc_pb2.DiscardChangesReply.FromString, 1089 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'DeleteConfig'): ems_grpc_pb2.ConfigReply.FromString, 1090 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetConfig'): ems_grpc_pb2.ConfigGetReply.FromString, 1091 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'GetOper'): ems_grpc_pb2.GetOperReply.FromString, 1092 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'MergeConfig'): ems_grpc_pb2.ConfigReply.FromString, 1093 | ('IOSXRExtensibleManagabilityService.gRPCConfigOper', 'ReplaceConfig'): ems_grpc_pb2.ConfigReply.FromString, 1094 | } 1095 | cardinalities = { 1096 | 'CliConfig': cardinality.Cardinality.UNARY_UNARY, 1097 | 'CommitConfig': cardinality.Cardinality.UNARY_UNARY, 1098 | 'CommitReplace': cardinality.Cardinality.UNARY_UNARY, 1099 | 'ConfigDiscardChanges': cardinality.Cardinality.UNARY_UNARY, 1100 | 'DeleteConfig': cardinality.Cardinality.UNARY_UNARY, 1101 | 'GetConfig': cardinality.Cardinality.UNARY_STREAM, 1102 | 'GetOper': cardinality.Cardinality.UNARY_STREAM, 1103 | 'MergeConfig': cardinality.Cardinality.UNARY_UNARY, 1104 | 'ReplaceConfig': cardinality.Cardinality.UNARY_UNARY, 1105 | } 1106 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 1107 | return beta_implementations.dynamic_stub(channel, 'IOSXRExtensibleManagabilityService.gRPCConfigOper', cardinalities, options=stub_options) 1108 | 1109 | class BetagRPCExecServicer(object): 1110 | """ 1111 | Should we seperate Exec from Config/Oper? 1112 | 1113 | 1114 | """ 1115 | def ShowCmdTextOutput(self, request, context): 1116 | """Exec commands 1117 | """ 1118 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 1119 | def ShowCmdJSONOutput(self, request, context): 1120 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 1121 | 1122 | class BetagRPCExecStub(object): 1123 | """ 1124 | Should we seperate Exec from Config/Oper? 1125 | 1126 | 1127 | """ 1128 | def ShowCmdTextOutput(self, request, timeout): 1129 | """Exec commands 1130 | """ 1131 | raise NotImplementedError() 1132 | def ShowCmdJSONOutput(self, request, timeout): 1133 | raise NotImplementedError() 1134 | 1135 | def beta_create_gRPCExec_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): 1136 | import ems_grpc_pb2 1137 | import ems_grpc_pb2 1138 | import ems_grpc_pb2 1139 | import ems_grpc_pb2 1140 | request_deserializers = { 1141 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdJSONOutput'): ems_grpc_pb2.ShowCmdArgs.FromString, 1142 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdTextOutput'): ems_grpc_pb2.ShowCmdArgs.FromString, 1143 | } 1144 | response_serializers = { 1145 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdJSONOutput'): ems_grpc_pb2.ShowCmdJSONReply.SerializeToString, 1146 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdTextOutput'): ems_grpc_pb2.ShowCmdTextReply.SerializeToString, 1147 | } 1148 | method_implementations = { 1149 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdJSONOutput'): face_utilities.unary_stream_inline(servicer.ShowCmdJSONOutput), 1150 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdTextOutput'): face_utilities.unary_stream_inline(servicer.ShowCmdTextOutput), 1151 | } 1152 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) 1153 | return beta_implementations.server(method_implementations, options=server_options) 1154 | 1155 | def beta_create_gRPCExec_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 1156 | import ems_grpc_pb2 1157 | import ems_grpc_pb2 1158 | import ems_grpc_pb2 1159 | import ems_grpc_pb2 1160 | request_serializers = { 1161 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdJSONOutput'): ems_grpc_pb2.ShowCmdArgs.SerializeToString, 1162 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdTextOutput'): ems_grpc_pb2.ShowCmdArgs.SerializeToString, 1163 | } 1164 | response_deserializers = { 1165 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdJSONOutput'): ems_grpc_pb2.ShowCmdJSONReply.FromString, 1166 | ('IOSXRExtensibleManagabilityService.gRPCExec', 'ShowCmdTextOutput'): ems_grpc_pb2.ShowCmdTextReply.FromString, 1167 | } 1168 | cardinalities = { 1169 | 'ShowCmdJSONOutput': cardinality.Cardinality.UNARY_STREAM, 1170 | 'ShowCmdTextOutput': cardinality.Cardinality.UNARY_STREAM, 1171 | } 1172 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) 1173 | return beta_implementations.dynamic_stub(channel, 'IOSXRExtensibleManagabilityService.gRPCExec', cardinalities, options=stub_options) 1174 | # @@protoc_insertion_point(module_scope) 1175 | 1176 | -------------------------------------------------------------------------------- /solenoid/grpc_cisco/grpcClient.py: -------------------------------------------------------------------------------- 1 | import ems_grpc_pb2 2 | 3 | from grpc.beta import implementations 4 | 5 | 6 | class CiscoGRPCClient(object): 7 | def __init__(self, server, port, user, password, timeout=10): 8 | """This class creates grpc calls using python. 9 | 10 | :param username: Username for device login 11 | :param password: Password for device login 12 | :param server: The ip address for the device 13 | :param port: The port for the device 14 | :type password: str 15 | :type username: str 16 | :type server: str 17 | :type port: int 18 | """ 19 | self._server = server 20 | self._port = port 21 | self._channel = implementations.insecure_channel(self._server, self._port) 22 | self._stub = ems_grpc_pb2.beta_create_gRPCConfigOper_stub(self._channel) 23 | self._timeout = int(timeout) 24 | self._metadata = [('username', user), ('password', password)] 25 | 26 | def __repr__(self): 27 | return '%s(Server = %s, Port = %s, User = %s, Password = %s, Timeout = %s)' % ( 28 | self.__class__.__name__, 29 | self._server, 30 | self._port, 31 | self._metadata[0][1], 32 | self._metadata[1][1], 33 | self._timeout 34 | ) 35 | 36 | def get(self, path): 37 | """Get grpc call 38 | :param data: JSON 39 | :type data: str 40 | :return: Return the response object 41 | :rtype: Response stream object 42 | """ 43 | message = ems_grpc_pb2.ConfigGetArgs(yangpathjson=path) 44 | responses = self._stub.GetConfig(message, self._timeout, metadata=self._metadata) 45 | objects = '' 46 | for response in responses: 47 | objects += response.yangjson 48 | return objects 49 | 50 | def patch(self, yangjson): 51 | """Merge grpc call equivalent of PATCH RESTconf call 52 | :param data: JSON 53 | :type data: str 54 | :return: Return the response object 55 | :rtype: Response object 56 | """ 57 | message = ems_grpc_pb2.ConfigArgs(yangjson=yangjson) 58 | response = self._stub.MergeConfig(message, self._timeout, metadata=self._metadata) 59 | return response 60 | 61 | def delete(self, yangjson): 62 | """delete grpc call 63 | :param data: JSON 64 | :type data: str 65 | :return: Return the response object 66 | :rtype: Response object 67 | """ 68 | message = ems_grpc_pb2.ConfigArgs(yangjson=yangjson) 69 | response = self._stub.DeleteConfig(message, self._timeout, metadata=self._metadata) 70 | return response 71 | 72 | def put(self, yangjson): 73 | """Replace grpc call equivalent of PUT in restconf 74 | :param data: JSON 75 | :type data: str 76 | :return: Return the response object 77 | :rtype: Response object 78 | """ 79 | message = ems_grpc_pb2.ConfigArgs(yangjson=yangjson) 80 | response = self._stub.ReplaceConfig(message, self._timeout, metadata=self._metadata) 81 | return response 82 | -------------------------------------------------------------------------------- /solenoid/logs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/logs/__init__.py -------------------------------------------------------------------------------- /solenoid/logs/logger.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sys 4 | import logging 5 | import logging.handlers 6 | 7 | 8 | class PermissiveRotatingFileHandler(logging.handlers.RotatingFileHandler): 9 | def _open(self): 10 | prevumask = os.umask(0002) 11 | rfh_open = logging.handlers.RotatingFileHandler._open(self) 12 | os.umask(prevumask) 13 | return rfh_open 14 | 15 | 16 | class Logger(object): 17 | 18 | _pid = os.getpid() 19 | _location = os.path.dirname(os.path.realpath(__file__)) 20 | logging.handlers.PermissiveRotatingFileHandler = PermissiveRotatingFileHandler 21 | 22 | def __init__(self): 23 | 24 | self._logger = logging.getLogger() 25 | self._logger.setLevel(logging.DEBUG) 26 | # initialize messages to stream 27 | self._streamhandler = logging.StreamHandler(sys.stderr) 28 | self._streamhandler.setLevel(logging.INFO) 29 | self._logger.addHandler(self._streamhandler) 30 | # initialize errors to file 31 | err_filepath = os.path.join(self._location, 'errors.log') 32 | self._errorhandler = logging.handlers.PermissiveRotatingFileHandler( 33 | err_filepath 34 | ) 35 | self._errorhandler.setLevel(logging.ERROR) 36 | self._logger.addHandler(self._errorhandler) 37 | # initialize debug messages to file 38 | deb_filepath = os.path.join(self._location, 'debug.log') 39 | self._debughandler = logging.handlers.PermissiveRotatingFileHandler( 40 | deb_filepath 41 | ) 42 | self._debughandler.setLevel(logging.DEBUG) 43 | self._logger.addHandler(self._debughandler) 44 | 45 | def _format(self, timestamp, level, source, message): 46 | now = time.strftime('%a, %d %b %Y %H:%M:%S', timestamp) 47 | return "%s | %-8s | %-6d | %-13s | %s" % (now, level, self._pid, source, message) 48 | 49 | def report(self, message, source='', level=''): 50 | timestamp = time.localtime() 51 | if level == 'DEBUG': 52 | self._logger.debug(self._format(timestamp, 53 | level, 54 | source, 55 | message) 56 | ) 57 | elif level == 'INFO': 58 | self._logger.info(self._format(timestamp, 59 | level, 60 | source, 61 | message) 62 | ) 63 | elif level == 'WARNING': 64 | self._logger.warn(self._format(timestamp, 65 | level, 66 | source, 67 | message) 68 | ) 69 | elif level == "ERROR": 70 | self._logger.error(self._format(timestamp, 71 | level, 72 | source, 73 | message), 74 | exc_info=True 75 | ) 76 | elif level == "CRITICAL": 77 | self._logger.critical(self._format(timestamp, 78 | level, 79 | source, 80 | message), 81 | exc_info=True 82 | ) 83 | 84 | def debug(self, message, source='', level='DEBUG'): 85 | self.report(message, source, level) 86 | 87 | def info(self, message, source='', level='INFO'): 88 | self.report(message, source, level) 89 | 90 | def warning(self, message, source='', level='WARNING'): 91 | self.report(message, source, level) 92 | 93 | def error(self, message, source='', level='ERROR'): 94 | self.report(message, source, level) 95 | 96 | def critical(self, message, source='', level='CRITICAL'): 97 | self.report(message, source, level) 98 | -------------------------------------------------------------------------------- /solenoid/rest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/rest/__init__.py -------------------------------------------------------------------------------- /solenoid/rest/jsonRestClient.py: -------------------------------------------------------------------------------- 1 | from restClient import RestCalls 2 | 3 | 4 | class JSONRestCalls(RestCalls): 5 | Format = 'json' 6 | 7 | def __repr__(self): 8 | return '%s(Session Object%s, Host = %s, Format = %s)' % ( 9 | self.__class__.__name__, 10 | self._session.headers.items(), 11 | self._host, 12 | self.Format 13 | ) 14 | -------------------------------------------------------------------------------- /solenoid/rest/restClient.py: -------------------------------------------------------------------------------- 1 | """This module contains a class for making RESTcalls with Python""" 2 | 3 | import requests 4 | import abc 5 | import logging 6 | 7 | 8 | class RestCalls(object): 9 | """This class creates RESTconf calls using python. 10 | 11 | :param username: Username for device login 12 | :param password: Password for device login 13 | :param ip_address_port: The ip address and port number for the device 14 | :type password: str 15 | :type username: str 16 | :type ip_address_port: str 17 | """ 18 | #Prevent logging messages for anything below warning showing up. 19 | logging.getLogger("requests").setLevel(logging.WARNING) 20 | logging.getLogger("urllib3").setLevel(logging.WARNING) 21 | 22 | __metaclass__ = abc.ABCMeta 23 | BasePath = '/restconf/data' 24 | Accept = ( 25 | 'application/yang.data+{fmt}', 26 | 'application/yang.errors+{fmt}', 27 | ) 28 | ContentType = 'application/yang.data+{fmt}' 29 | 30 | def __init__(self, ip_address, port=80, username=None, password=None): 31 | session = requests.Session() 32 | if username is not None and password is not None: 33 | session.auth = (username, password) 34 | session.headers.update({ 35 | 'Accept': ','.join([ 36 | accept.format(fmt=self.Format) for accept in self.Accept 37 | ]), 38 | 'Content-Type': self.ContentType.format(fmt=self.Format), 39 | }) 40 | self._session = session 41 | self._host = '{scheme}://{ip}:{port}{basePath}/'.format( 42 | scheme='http', 43 | ip=ip_address, 44 | port=port, 45 | basePath=self.BasePath 46 | ) 47 | 48 | def put(self, data, endpoint): 49 | """PUT RESTconf call 50 | :param data: JSON or XML with config changes 51 | :type data: str 52 | :return: Return the response object 53 | :rtype: Response object 54 | """ 55 | url = self._host + endpoint 56 | res = self._session.put(url, data=data) 57 | return res 58 | 59 | def post(self, data, endpoint): 60 | """POST RESTconf call 61 | :param data: JSON or XML file with config changes 62 | :type data: str 63 | :return: Return the response object 64 | :rtype: Response object 65 | """ 66 | url = self._host + endpoint 67 | res = self._session.post(url, data=data) 68 | return res 69 | 70 | def patch(self, data, endpoint='Cisco-IOS-XR-ip-static-cfg:router-static'): 71 | """PATCH RESTconf call 72 | :param data: JSON or XML with config changes 73 | :type data: str 74 | :return: Return the response object 75 | :rtype: Response object 76 | """ 77 | url = self._host + endpoint 78 | res = self._session.patch(url, data=data) 79 | return res 80 | 81 | def get(self, endpoint='', **kwargs): 82 | """GET RESTconf call 83 | :param endpoint: String selection of YANG model and container 84 | :type endpoint: str 85 | :return: Return the response object 86 | :rtype: Response object 87 | 88 | kwargs would be the type of content. See the ietf's restconf 89 | draft section 4.8.1 (expires October 2016). Options: 90 | 91 | config -- Return only configuration descendant data nodes 92 | nonconfig -- Return only non-configuration descendant data nodes 93 | all -- Return all descendant data nodes 94 | """ 95 | url = self._host + endpoint 96 | if 'content' not in kwargs: 97 | kwargs = {'content': 'config'} 98 | res = self._session.get(url, params=kwargs) 99 | return res 100 | 101 | def delete(self, endpoint): 102 | """GET RESTconf call 103 | :param endpoint: String selection of YANG model and container 104 | :type endpoint: str 105 | :return: Return the response object 106 | :rtype: Response object 107 | """ 108 | url = self._host + endpoint 109 | res = self._session.delete(url) 110 | return res 111 | -------------------------------------------------------------------------------- /solenoid/templates/static.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-ip-static-cfg:router-static": { 3 | "default-vrf": { 4 | "address-family": { 5 | "vrfipv4": { 6 | "vrf-unicast": { 7 | "vrf-prefixes": { 8 | "vrf-prefix": [ 9 | {% for prefix in prefixes -%} 10 | {% set prefix, length = prefix.split('/') %} 11 | { 12 | "prefix": {{ prefix | to_json }}, 13 | "prefix-length": {{ length | to_json}}, 14 | "vrf-route": { 15 | "vrf-next-hop-table": { 16 | "vrf-next-hop-next-hop-address": [ 17 | { 18 | "next-hop-address": {{ next_hop | to_json }} 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | {% if not loop.last %} 25 | , 26 | {%endif %} 27 | {%- endfor %} 28 | ] 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solenoid/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/tests/__init__.py -------------------------------------------------------------------------------- /solenoid/tests/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/tests/examples/__init__.py -------------------------------------------------------------------------------- /solenoid/tests/examples/config/grpc/grpc_good.config: -------------------------------------------------------------------------------- 1 | [default] 2 | transport: grpc 3 | ip: 127.0.0.1 4 | port: 8080 5 | username: user 6 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/grpc/multiple_sections.config: -------------------------------------------------------------------------------- 1 | [router1] 2 | transport: grpc 3 | ip: 127.0.0.1 4 | port: 8080 5 | username: user 6 | password: admin 7 | 8 | [router2] 9 | transport: grpc 10 | ip: 127.0.0.1 11 | port: 8080 12 | username: user 13 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/grpc/no_port.config: -------------------------------------------------------------------------------- 1 | [default] 2 | transport: grpc # capitalization shouldn't matter 3 | ip: 127.0.0.1 4 | username: admin 5 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/grpc/no_section.config: -------------------------------------------------------------------------------- 1 | transport: GRPC 2 | ip: 127.0.0.1 3 | port: 8080 4 | username: user 5 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/restconf/multiple_sections.config: -------------------------------------------------------------------------------- 1 | [router1] 2 | transport: Restconf 3 | ip: 127.0.0.1 4 | port: 8080 5 | username: user 6 | password: admin 7 | 8 | [router2] 9 | transport: Restconf 10 | ip: 127.0.0.1 11 | port: 8080 12 | username: user 13 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/restconf/no_port.config: -------------------------------------------------------------------------------- 1 | [default] 2 | transport: restCONF # capitalization shouldn't matter 3 | ip: 127.0.0.1 4 | username: admin 5 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/restconf/no_section.config: -------------------------------------------------------------------------------- 1 | transport: Restconf 2 | ip: 127.0.0.1 3 | port: 8080 4 | username: user 5 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/config/restconf/restconf_good.config: -------------------------------------------------------------------------------- 1 | [default] 2 | transport: Restconf 3 | ip: 127.0.0.1 4 | port: 8080 5 | username: user 6 | password: admin -------------------------------------------------------------------------------- /solenoid/tests/examples/exa/exa-announce.json: -------------------------------------------------------------------------------- 1 | { 2 | "exabgp": "3.4.8", 3 | "time": 1455836455, 4 | "host": "LXC_NAME", 5 | "pid": "15830", 6 | "ppid": "1", 7 | "counter": 1, 8 | "type": "update", 9 | "neighbor": { 10 | "ip": "10.25.0.32", 11 | "address": { 12 | "local": "10.25.0.6", 13 | "peer": "10.25.0.32" 14 | }, 15 | "asn": { 16 | "local": "65000", 17 | "peer": "65000" 18 | }, 19 | "message": { 20 | "update": { 21 | "attribute": { 22 | "origin": "igp", 23 | "as-path": [ 24 | 60222, 25 | 50126, 26 | 45814, 27 | 7939, 28 | 44943, 29 | 25155, 30 | 50742, 31 | 61802, 32 | 60048 33 | ], 34 | "confederation-path": [ 35 | 36 | ], 37 | "local-preference": 100, 38 | "community": [ 39 | [ 40 | 1, 41 | 13340 42 | ], 43 | [ 44 | 2, 45 | 52012 46 | ], 47 | [ 48 | 3, 49 | 35916 50 | ], 51 | [ 52 | 4, 53 | 19483 54 | ], 55 | [ 56 | 5, 57 | 59304 58 | ], 59 | [ 60 | 6, 61 | 59613 62 | ], 63 | [ 64 | 7, 65 | 57276 66 | ], 67 | [ 68 | 8, 69 | 32646 70 | ] 71 | ] 72 | }, 73 | "announce": { 74 | "ipv4 unicast": { 75 | "10.25.0.49": { 76 | "1.1.1.1\/32": { 77 | 78 | }, 79 | "1.1.1.2\/32": { 80 | 81 | }, 82 | "1.1.1.9\/32": { 83 | 84 | }, 85 | "1.1.1.10\/32": { 86 | 87 | }, 88 | "10.1.1.1\/32": { 89 | 90 | }, 91 | "10.1.6.1\/24": { 92 | 93 | }, 94 | "192.168.3.1\/28": { 95 | 96 | } 97 | 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /solenoid/tests/examples/exa/exa-eor.json: -------------------------------------------------------------------------------- 1 | { "exabgp": "3.4.8", "time": 1464368446, "host" : "LXC_NAME", "pid" : "18518", "ppid" : "1", "counter": 15, "type": "update", "neighbor": { "ip": "10.25.0.51", "address": { "local": "10.25.0.6", "peer": "10.25.0.51"}, "asn": { "local": "65000", "peer": "65000"}, "message": { "eor": { "afi" : "l2vpn", "safi" : "vpls" }}} } -------------------------------------------------------------------------------- /solenoid/tests/examples/exa/exa-raw.json: -------------------------------------------------------------------------------- 1 | { "exabgp": "3.4.8", "time": 1455836455, "host": "LXC_NAME", "pid": "15830", "ppid": "1", "counter": 1, "type": "update", "neighbor": { "ip": "10.25.0.32", "address": { "local": "10.25.0.6", "peer": "10.25.0.32" }, "asn": { "local": "65000", "peer": "65000" }, "message": { "update": { "attribute": { "origin": "igp", "as-path": [ 60222, 50126, 45814, 7939, 44943, 25155, 50742, 61802, 60048 ], "confederation-path": [ ], "local-preference": 100, "community": [ [ 1, 13340 ], [ 2, 52012 ], [ 3, 35916 ], [ 4, 19483 ], [ 5, 59304 ], [ 6, 59613 ], [ 7, 57276 ], [ 8, 32646 ] ] }, "announce": { "ipv4 unicast": { "10.25.0.49": { "1.1.1.1\/32": { }, "1.1.1.2\/32": { }, "1.1.1.9\/32": { }, "1.1.1.10\/32": { }, "10.1.1.1\/32": { }, "10.1.6.1\/24": {}, "192.168.3.1\/28": {} } } } } } } } 2 | { "exabgp": "3.4.8", "time": 1455909283, "host": "LXC_NAME", "pid": "1167", "ppid": "1", "counter": 31, "type": "update", "neighbor": { "ip": "10.25.0.32", "address": { "local": "10.25.0.6", "peer": "10.25.0.32" }, "asn": { "local": "65000", "peer": "65000" }, "message": { "update": { "withdraw": { "ipv4 unicast": { "1.1.1.1\/32": { }, "1.1.1.2\/32": { }, "1.1.1.3\/32": { }, "1.1.1.4\/32": { }, "1.1.1.5\/32": { }, "1.1.1.6\/32": { }, "1.1.1.7\/32": { }, "1.1.1.8\/32": { }, "1.1.1.9\/32": { }, "1.1.1.10\/32": { } } } } } } } 3 | { "exabgp": "3.4.8", "time": 1455909283, "host": "LXC_NAME", "pid": "1167", "ppid": "1", "counter": 31, "type": "update", "neighbor": { "ip": "10.25.0.32", "address": { "local": "10.25.0.6", "peer": "10.25.0.32" }, "asn": { "local": "65000", "peer": "65000" }, "message": { "update": { "withdraw": { "ipv4 unicast": { "1.1.1.1\/32": { }, "1.1.1.2\/32": { }, "1.1.1.3\/32": { }, "1.1.1.4\/32": { }, "1.1.1.5\/32": { }, "1.1.1.6\/32": { }, "1.1.1.7\/32": { }, "1.1.1.8\/32": { }, "1.1.1.9\/32": { }, "1.1.1.\/32": { } } } } } } } 4 | { "exabgp": "3.4.8", "time": 1455909283, "host": "LXC_NAME", "pid": "1167", "ppid": "1", "counter": 31, "type": "update", "neighbor": { "ip": "10.25.0.32", "address": { "local": "10.25.0.6", "peer": "10.25.0.32" }, "asn": { "local": "65000", "peer": "65000" }, "message": { "update": { "withdraw": { "ipv7 unicast": { "1.1.1.1\/32": { }, "1.1.1.2\/32": { }, "1.1.1.3\/32": { }, "1.1.1.4\/32": { }, "1.1.1.5\/32": { }, "1.1.1.6\/32": { }, "1.1.1.7\/32": { }, "1.1.1.8\/32": { }, "1.1.1.9\/32": { }, "1.1.1.10\/32": { } } } } } } } } } 5 | { "exabgp": "3.4.8", "time": 1455836455, "host": "LXC_NAME", "pid": "15830", "ppid": "1", "counter": 1, "type": "update", "neighbor": { "ip": "10.25.0.32", "address": { "local": "10.25.0.6", "peer": "10.25.0.32" }, "asn": { "local": "65000", "peer": "65000" }, "message": { "update": { "attribute": { "origin": "igp", "as-path": [ 60222, 50126, 45814, 7939, 44943, 25155, 50742, 61802, 60048 ], "confederation-path": [ ], "local-preference": 100, "community": [ [ 1, 13340 ], [ 2, 52012 ], [ 3, 35916 ], [ 4, 19483 ], [ 5, 59304 ], [ 6, 59613 ], [ 7, 57276 ], [ 8, 32646 ] ] }, "announce": { "10.25.0.49": { "1.1.1.1\/32": { }, "1.1.1.2\/32": { }, "1.1.1.9\/32": { }, "1.1.1.10\/32": { }, "10.1.1.1\/32": { }, "10.1.6.1\/24": {}, "192.168.3.1\/28": {} } } } } } } 6 | { "glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}} 7 | { "exabgp": "3.4.8", "time": 1464368446, "host" : "LXC_NAME", "pid" : "18518", "ppid" : "1", "counter": 15, "type": "update", "neighbor": { "ip": "10.25.0.51", "address": { "local": "10.25.0.6", "peer": "10.25.0.51"}, "asn": { "local": "65000", "peer": "65000"}, "message": { "eor": { "afi" : "l2vpn", "safi" : "vpls" }}} } 8 | -------------------------------------------------------------------------------- /solenoid/tests/examples/exa/exa-withdraw.json: -------------------------------------------------------------------------------- 1 | { 2 | "exabgp": "3.4.8", 3 | "time": 1455909283, 4 | "host": "LXC_NAME", 5 | "pid": "1167", 6 | "ppid": "1", 7 | "counter": 31, 8 | "type": "update", 9 | "neighbor": { 10 | "ip": "10.25.0.32", 11 | "address": { 12 | "local": "10.25.0.6", 13 | "peer": "10.25.0.32" 14 | }, 15 | "asn": { 16 | "local": "65000", 17 | "peer": "65000" 18 | }, 19 | "message": { 20 | "update": { 21 | "withdraw": { 22 | "ipv4 unicast": { 23 | "1.1.1.1\/32": { 24 | 25 | }, 26 | "1.1.1.2\/32": { 27 | 28 | }, 29 | "1.1.1.3\/32": { 30 | 31 | }, 32 | "1.1.1.4\/32": { 33 | 34 | }, 35 | "1.1.1.5\/32": { 36 | 37 | }, 38 | "1.1.1.6\/32": { 39 | 40 | }, 41 | "1.1.1.7\/32": { 42 | 43 | }, 44 | "1.1.1.8\/32": { 45 | 46 | }, 47 | "1.1.1.9\/32": { 48 | 49 | }, 50 | "1.1.1.10\/32": { 51 | 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /solenoid/tests/examples/filter/filter-all.txt: -------------------------------------------------------------------------------- 1 | 9.9.9.9/32 -------------------------------------------------------------------------------- /solenoid/tests/examples/filter/filter-empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/tests/examples/filter/filter-empty.txt -------------------------------------------------------------------------------- /solenoid/tests/examples/filter/filter-full.txt: -------------------------------------------------------------------------------- 1 | 1.1.1.0/32-1.1.2.0/32 2 | 10.1.1.0/32-10.1.5.0/32 3 | 10.1.6.1/24 4 | 192.168.1.0/28-192.168.2.0/28 -------------------------------------------------------------------------------- /solenoid/tests/examples/filter/filter-invalid.txt: -------------------------------------------------------------------------------- 1 | 1.1.1.0/43-1.1.2.0/32 2 | 10.1.1.0/32-10.1.5.0/32 3 | 192.168.1.0/28-192.168.2.0/28 -------------------------------------------------------------------------------- /solenoid/tests/examples/integration/exa-announce.json: -------------------------------------------------------------------------------- 1 | { 2 | "exabgp": "3.4.8", 3 | "time": 1455836455, 4 | "host": "LXC_NAME", 5 | "pid": "15830", 6 | "ppid": "1", 7 | "counter": 1, 8 | "type": "update", 9 | "neighbor": { 10 | "ip": "192.168.1.3", 11 | "address": { 12 | "local": "192.168.1.2", 13 | "peer": "192.168.1.3" 14 | }, 15 | "asn": { 16 | "local": "65000", 17 | "peer": "65000" 18 | }, 19 | "message": { 20 | "update": { 21 | "attribute": { 22 | "origin": "igp", 23 | "as-path": [ 24 | 60222, 25 | 50126, 26 | 45814, 27 | 7939, 28 | 44943, 29 | 25155, 30 | 50742, 31 | 61802, 32 | 60048 33 | ], 34 | "confederation-path": [ 35 | 36 | ], 37 | "local-preference": 100, 38 | "community": [ 39 | [ 40 | 1, 41 | 13340 42 | ], 43 | [ 44 | 2, 45 | 52012 46 | ], 47 | [ 48 | 3, 49 | 35916 50 | ], 51 | [ 52 | 4, 53 | 19483 54 | ], 55 | [ 56 | 5, 57 | 59304 58 | ], 59 | [ 60 | 6, 61 | 59613 62 | ], 63 | [ 64 | 7, 65 | 57276 66 | ], 67 | [ 68 | 8, 69 | 32646 70 | ] 71 | ] 72 | }, 73 | "announce": { 74 | "ipv4 unicast": { 75 | "192.168.1.3": { 76 | "1.1.1.1\/32": { 77 | 78 | }, 79 | "1.1.1.2\/32": { 80 | 81 | }, 82 | "2.2.2.2\/32": { 83 | 84 | }, 85 | "3.3.3.3\/32": { 86 | 87 | } 88 | 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /solenoid/tests/examples/integration/exa-withdraw.json: -------------------------------------------------------------------------------- 1 | { 2 | "exabgp": "3.4.8", 3 | "time": 1455909283, 4 | "host": "LXC_NAME", 5 | "pid": "1167", 6 | "ppid": "1", 7 | "counter": 31, 8 | "type": "update", 9 | "neighbor": { 10 | "ip": "10.25.0.32", 11 | "address": { 12 | "local": "10.25.0.6", 13 | "peer": "10.25.0.32" 14 | }, 15 | "asn": { 16 | "local": "65000", 17 | "peer": "65000" 18 | }, 19 | "message": { 20 | "update": { 21 | "withdraw": { 22 | "ipv4 unicast": { 23 | "1.1.1.1\/32": { 24 | 25 | }, 26 | "1.1.1.2\/32": { 27 | 28 | }, 29 | "2.2.2.2\/32": { 30 | 31 | }, 32 | "3.3.3.3\/32": { 33 | 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /solenoid/tests/examples/integration/rendered_announce.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-ip-static-cfg:router-static": { 3 | "default-vrf": { 4 | "address-family": { 5 | "vrfipv4": { 6 | "vrf-unicast": { 7 | "vrf-prefixes": { 8 | "vrf-prefix": [ 9 | 10 | { 11 | "prefix": "1.1.1.1", 12 | "prefix-length": "32", 13 | "vrf-route": { 14 | "vrf-next-hop-table": { 15 | "vrf-next-hop-next-hop-address": [ 16 | { 17 | "next-hop-address": "192.168.1.3" 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | , 24 | { 25 | "prefix": "1.1.1.2", 26 | "prefix-length": "32", 27 | "vrf-route": { 28 | "vrf-next-hop-table": { 29 | "vrf-next-hop-next-hop-address": [ 30 | { 31 | "next-hop-address": "192.168.1.3" 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | , 38 | { 39 | "prefix": "2.2.2.2", 40 | "prefix-length": "32", 41 | "vrf-route": { 42 | "vrf-next-hop-table": { 43 | "vrf-next-hop-next-hop-address": [ 44 | { 45 | "next-hop-address": "192.168.1.3" 46 | } 47 | ] 48 | } 49 | } 50 | } 51 | , 52 | { 53 | "prefix": "3.3.3.3", 54 | "prefix-length": "32", 55 | "vrf-route": { 56 | "vrf-next-hop-table": { 57 | "vrf-next-hop-next-hop-address": [ 58 | { 59 | "next-hop-address": "192.168.1.3" 60 | } 61 | ] 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /solenoid/tests/examples/rendered_announce.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-ip-static-cfg:router-static": { 3 | "default-vrf": { 4 | "address-family": { 5 | "vrfipv4": { 6 | "vrf-unicast": { 7 | "vrf-prefixes": { 8 | "vrf-prefix": [ 9 | 10 | { 11 | "prefix": "1.1.1.9", 12 | "prefix-length": "32", 13 | "vrf-route": { 14 | "vrf-next-hop-table": { 15 | "vrf-next-hop-next-hop-address": [ 16 | { 17 | "next-hop-address": "10.25.0.49" 18 | } 19 | ] 20 | } 21 | } 22 | } 23 | 24 | , 25 | 26 | { 27 | "prefix": "192.168.3.1", 28 | "prefix-length": "28", 29 | "vrf-route": { 30 | "vrf-next-hop-table": { 31 | "vrf-next-hop-next-hop-address": [ 32 | { 33 | "next-hop-address": "10.25.0.49" 34 | } 35 | ] 36 | } 37 | } 38 | } 39 | 40 | , 41 | 42 | { 43 | "prefix": "1.1.1.2", 44 | "prefix-length": "32", 45 | "vrf-route": { 46 | "vrf-next-hop-table": { 47 | "vrf-next-hop-next-hop-address": [ 48 | { 49 | "next-hop-address": "10.25.0.49" 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | 56 | , 57 | 58 | { 59 | "prefix": "1.1.1.1", 60 | "prefix-length": "32", 61 | "vrf-route": { 62 | "vrf-next-hop-table": { 63 | "vrf-next-hop-next-hop-address": [ 64 | { 65 | "next-hop-address": "10.25.0.49" 66 | } 67 | ] 68 | } 69 | } 70 | } 71 | 72 | , 73 | 74 | { 75 | "prefix": "10.1.1.1", 76 | "prefix-length": "32", 77 | "vrf-route": { 78 | "vrf-next-hop-table": { 79 | "vrf-next-hop-next-hop-address": [ 80 | { 81 | "next-hop-address": "10.25.0.49" 82 | } 83 | ] 84 | } 85 | } 86 | } 87 | 88 | , 89 | 90 | { 91 | "prefix": "1.1.1.10", 92 | "prefix-length": "32", 93 | "vrf-route": { 94 | "vrf-next-hop-table": { 95 | "vrf-next-hop-next-hop-address": [ 96 | { 97 | "next-hop-address": "10.25.0.49" 98 | } 99 | ] 100 | } 101 | } 102 | } 103 | 104 | , 105 | 106 | { 107 | "prefix": "10.1.6.1", 108 | "prefix-length": "24", 109 | "vrf-route": { 110 | "vrf-next-hop-table": { 111 | "vrf-next-hop-next-hop-address": [ 112 | { 113 | "next-hop-address": "10.25.0.49" 114 | } 115 | ] 116 | } 117 | } 118 | } 119 | 120 | ] 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /solenoid/tests/examples/rest/invalid_data.json: -------------------------------------------------------------------------------- 1 | "bgp:bgp": { 2 | "neighbors": { 3 | "neighbor": { 4 | "neighbor-address": "10.99.0.5", 5 | "config": { 6 | "neighbor-address": "10.99.0.5", 7 | "peer-as": "3005" 8 | }, 9 | "afi-safis": { 10 | "afi-safi": { 11 | "afi-safi-name": "ipv4-unicast", 12 | "config": { 13 | "afi-safi-name": "ipv4-unicast", 14 | "enabled": "true" 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | }- -------------------------------------------------------------------------------- /solenoid/tests/examples/rest/patch_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-ip-static-cfg:router-static": { 3 | "default-vrf": { 4 | "address-family": { 5 | "vrfipv4": { 6 | "vrf-unicast": { 7 | "vrf-prefixes": { 8 | "vrf-prefix": [ 9 | { 10 | "prefix": "10.1.1.0", 11 | "prefix-length": "24", 12 | "vrf-route": { 13 | "segment-route-next-hop-table": { 14 | "vrf-next-hop-next-hop-address": [ 15 | { 16 | "next-hop-address": "10.10.10.1" 17 | } 18 | ] 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /solenoid/tests/examples/rest/put_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cisco-IOS-XR-ipv4-ospf-cfg:ospf": { 3 | "processes": { 4 | "process": [ 5 | { 6 | "process-name": "testOspf123", 7 | "snmp": { 8 | "trap-rate-limit": { 9 | "window-size": 2, 10 | "max-window-traps": 2 11 | } 12 | }, 13 | "protocol-shutdown": "full", 14 | "default-vrf": { 15 | "monitor-convergence": { 16 | "enable": [ 17 | null 18 | ], 19 | "track-ip-frr": [ 20 | null 21 | ], 22 | "prefix-list": "ospf_monitor", 23 | "track-external-routes": [ 24 | null 25 | ], 26 | "track-summary-routes": [ 27 | null 28 | ] 29 | }, 30 | "router-id": "100.1.1.1", 31 | "maximum-redistribute-prefix": { 32 | "number-of-prefixes": 1, 33 | "threshold": 1, 34 | "warning-only": true 35 | }, 36 | "nsf": { 37 | "ietf-support-role": "never", 38 | "ietf-strict-lsa-checking": [ 39 | null 40 | ], 41 | "lifetime": 90, 42 | "flush-delay-time": 100 43 | }, 44 | "queue": { 45 | "dispatch-rate-limited-flush": 100, 46 | "dispatch-incoming": 700, 47 | "limit-low": 2000, 48 | "limit-high": 1000, 49 | "dispatch-rate-limited": 500, 50 | "limit-medium": 3000, 51 | "dispatch-spf-lsa-limit": 300 52 | }, 53 | "microloop": { 54 | "avoidance": { 55 | "rib-update-delay": 1, 56 | "enable": "protected" 57 | } 58 | }, 59 | "ignore-mospf": [ 60 | null 61 | ], 62 | "default-information": { 63 | "always-advertise": true, 64 | "metric": 100, 65 | "metric-type": "type1" 66 | }, 67 | "type7": [ 68 | null 69 | ], 70 | "max-metric": { 71 | "max-metric-on-proc-restart": { 72 | "startup-max": 10, 73 | "include-stub": true, 74 | "summary-lsa": true, 75 | "summary-lsa-maximum-metric": 10, 76 | "external-lsa": true, 77 | "external-lsa-maximum-metric": 10 78 | } 79 | } 80 | }, 81 | "start": [ 82 | null 83 | ] 84 | } 85 | ] 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /solenoid/tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/tests/integration/__init__.py -------------------------------------------------------------------------------- /solenoid/tests/integration/test_rib.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | from solenoid import edit_rib 4 | from solenoid.tests import tools 5 | 6 | 7 | 8 | class RestRibTestCase(unittest.TestCase, object): 9 | def setUp(self): 10 | #Set global variable 11 | edit_rib.FILEPATH = tools.add_location('examples/filter-empty.txt') 12 | edit_rib.transport = edit_rib.create_transport_object() 13 | #Clear out logging files. 14 | open(tools.add_location('../updates.txt'), 'w').close() 15 | open(tools.add_location('../logs/debug.log'), 'w').close() 16 | open(tools.add_location('../logs/errors.log'), 'w').close() 17 | 18 | def test_rib_1announce(self): 19 | with open(tools.add_location('examples/integration/rendered_announce.txt')) as f: 20 | rendered_announce = f.read() 21 | edit_rib.rib_announce(rendered_announce, edit_rib.transport) 22 | self.assertIn('| ANNOUNCE | OK', tools.check_debuglog()[0]) 23 | 24 | def test_rib_2withdraw(self): 25 | withdraw_prefixes = ['1.1.1.1/32', 26 | '1.1.1.2/32', 27 | '2.2.2.2/32', 28 | '3.3.3.3/32'] 29 | edit_rib.rib_withdraw(withdraw_prefixes, edit_rib.transport) 30 | self.assertIn('| WITHDRAW | OK', tools.check_debuglog()[0]) 31 | 32 | def test_rib_3announce_json(self): 33 | with open(tools.add_location('examples/integration/exa-announce.json')) as f: 34 | exa_announce = f.read() 35 | edit_rib.render_config(json.loads(exa_announce), edit_rib.transport) 36 | self.assertIn('| ANNOUNCE | OK', tools.check_debuglog()[0]) 37 | 38 | def test_rib_4withdraw_json(self): 39 | with open(tools.add_location('examples/integration/exa-withdraw.json')) as f: 40 | exa_withdraw = f.read() 41 | edit_rib.render_config(json.loads(exa_withdraw), edit_rib.transport) 42 | self.assertIn('| WITHDRAW | OK', tools.check_debuglog()[0]) 43 | 44 | def test_rib_5announce_EOR(self): 45 | with open(tools.add_location('examples/exa/exa-eor.json')) as f: 46 | exa_announce_eor = f.read() 47 | edit_rib.render_config(json.loads(exa_announce_eor), edit_rib.transport) 48 | self.assertIn('EOR message\n', tools.check_debuglog()[0]) 49 | if __name__ == '__main__': 50 | unittest.main(failfast=True) 51 | -------------------------------------------------------------------------------- /solenoid/tests/mock/.test_rest_calls.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import ConfigParser 4 | 5 | import mock 6 | from requests import Response 7 | 8 | from solenoid.rest.jsonRestClient import JSONRestCalls 9 | 10 | 11 | class JSONRestCallsCase(unittest.TestCase): 12 | def setUp(self): 13 | #This needs to be rewritten 14 | config = ConfigParser.ConfigParser() 15 | config.readfp(open(os.path.expanduser(obj))) 16 | self.ip = config.get('default', 'ip'), 17 | self.port = config.get('default', 'port'), 18 | self.username = config.get('default', 'username'), 19 | self.password = config.get('default', 'password') 20 | self.classObject = JSONRestCalls(self.ip, self.port, 21 | self.username, self.password) 22 | 23 | def test__init__(self): 24 | """Does constructor create a proper object""" 25 | headers = { 26 | 'Accept': ','.join([ 27 | 'application/yang.data+json', 28 | 'application/yang.errors+json', 29 | ]), 30 | 'Content-Type': 'application/yang.data+json', 31 | 'Accept-Encoding': 'gzip, deflate, compress', 32 | 'User-Agent': 'python-requests/2.2.1 CPython/2.7.6 Linux/3.13.0-70-generic' 33 | } 34 | url = '{scheme}://{ip}:{port}{basePath}/'.format( 35 | scheme='http', 36 | ip=self.ip_address, 37 | port=self.port, 38 | basePath='/restconf/data' 39 | ) 40 | self.assertEqual(self.classObject._session.headers, 41 | headers) 42 | self.assertEqual(self.classObject._host, url) 43 | 44 | @mock.patch('rest.jsonRestClient.JSONRestCalls.get') 45 | def test_get(self, mock_get): 46 | mock_get.return_value = mock.MagicMock(spec=Response, 47 | status_code=200) 48 | get_res = self.classObject.get('bgp:bgp') 49 | 50 | mock_get.assert_called_once_with('bgp:bgp') 51 | self.assertEqual(get_res.status_code, 52 | mock_get.return_value.status_code) 53 | 54 | @mock.patch('rest.jsonRestClient.JSONRestCalls.put') 55 | def test_put(self, mock_put): 56 | # Tests it with good data. 57 | location = os.path.dirname(os.path.realpath(__file__)) 58 | with open(os.path.join(location, 'examples/put_data.json'), 59 | 'rb') as f: 60 | contents = f.read() 61 | mock_put.return_value = mock.MagicMock(spec=Response, 62 | status_code=204) 63 | put_res = self.classObject.put( 64 | 'Cisco-IOS-XR-ip-static-cfg:router-static', 65 | contents 66 | ) 67 | mock_put.assert_called_once_with( 68 | 'Cisco-IOS-XR-ip-static-cfg:router-static', 69 | contents 70 | ) 71 | self.assertEqual(put_res.status_code, 72 | mock_put.return_value.status_code) 73 | 74 | # Test it with bad data. 75 | location = os.path.dirname(os.path.realpath(__file__)) 76 | with open(os.path.join(location, 'examples/invalid_data.json'), 77 | 'rb') as f: 78 | contents = f.read() 79 | mock_put.return_value = mock.MagicMock(spec=Response, 80 | status_code=400) 81 | put_res = self.classObject.put('bgp:bgp', contents) 82 | self.assertEqual(put_res.status_code, 83 | mock_put.return_value.status_code) 84 | 85 | @mock.patch('rest.jsonRestClient.JSONRestCalls.patch') 86 | def test_patch_good_data(self, mock_patch): 87 | location = os.path.dirname(os.path.realpath(__file__)) 88 | with open(os.path.join(location, 'examples/patch_data.json'), 89 | 'rb') as f: 90 | contents = f.read() 91 | mock_patch.return_value = mock.MagicMock(spec=Response, 92 | status_code=204) 93 | patch_res = self.classObject.patch( 94 | 'Cisco-IOS-XR-ip-static-cfg:router-static', 95 | contents 96 | ) 97 | mock_patch.assert_called_once_with( 98 | 'Cisco-IOS-XR-ip-static-cfg:router-static', 99 | contents 100 | ) 101 | self.assertEqual(patch_res.status_code, 102 | mock_patch.return_value.status_code) 103 | 104 | @mock.patch('rest.jsonRestClient.JSONRestCalls.patch') 105 | def test_patch_bad_data(self, mock_patch): 106 | location = os.path.dirname(os.path.realpath(__file__)) 107 | with open(os.path.join(location, 'examples/invalid_data.json'), 108 | 'rb') as f: 109 | contents = f.read() 110 | mock_patch.return_value = mock.MagicMock(spec=Response, 111 | status_code=400) 112 | patch_res = self.classObject.put('bgp:bgp', contents) 113 | self.assertEqual(patch_res.status_code, 114 | mock_patch.return_value.status_code) 115 | 116 | @mock.patch('rest.jsonRestClient.JSONRestCalls.post') 117 | def test_post(self, mock_post): 118 | location = os.path.dirname(os.path.realpath(__file__)) 119 | with open(os.path.join(location, 'examples/put_data.json'), 120 | 'rb') as f: 121 | contents = f.read() 122 | mock_post.return_value = mock.MagicMock(spec=Response, 123 | status_code=204) 124 | post_res = self.classObject.post( 125 | 'Cisco-IOS-XR-ip-static-cfg:router-static', 126 | contents 127 | ) 128 | mock_post.assert_called_once_with( 129 | 'Cisco-IOS-XR-ip-static-cfg:router-static', 130 | contents 131 | ) 132 | self.assertEqual(post_res.status_code, 133 | mock_post.return_value.status_code) 134 | 135 | # Test it with bad data 136 | location = os.path.dirname(os.path.realpath(__file__)) 137 | with open(os.path.join(location, 'examples/invalid_data.json'), 138 | 'rb') as f: 139 | contents = f.read() 140 | mock_post.return_value = mock.MagicMock(spec=Response, 141 | status_code=400) 142 | put_res = self.classObject.put('bgp:bgp', contents) 143 | self.assertEqual(put_res.status_code, 144 | mock_post.return_value.status_code) 145 | 146 | 147 | if __name__ == "__main__": 148 | unittest.main() 149 | -------------------------------------------------------------------------------- /solenoid/tests/mock/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/solenoid/tests/mock/__init__.py -------------------------------------------------------------------------------- /solenoid/tests/mock/test_rib_general.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | import time 4 | import sys 5 | import os 6 | import shutil 7 | 8 | from StringIO import StringIO 9 | from mock import patch 10 | from solenoid import edit_rib 11 | from solenoid.tests import tools 12 | 13 | 14 | class GeneralRibTestCase(tools.TestBookends, object): 15 | 16 | def setUp(self): 17 | shutil.copy( 18 | tools.add_location('examples/config/restconf/restconf_good.config'), 19 | tools.add_location('../../solenoid.config') 20 | ) 21 | self.transport = edit_rib.create_transport_object() 22 | #Set global variable. 23 | edit_rib.FILEPATH = tools.add_location('examples/filter/filter-empty.txt') 24 | #Clear out logging files. 25 | open(tools.add_location('../updates.txt'), 'w').close() 26 | open(tools.add_location('../logs/debug.log'), 'w').close() 27 | open(tools.add_location('../logs/errors.log'), 'w').close() 28 | 29 | @patch('sys.stdin', StringIO(tools.exa_raw('announce_g'))) 30 | @patch('solenoid.edit_rib.update_validator') 31 | def test_update_watcher_call(self, mock_validator): 32 | # Monkey patching to avoid infinite loop. 33 | def mock_watcher(): 34 | raw_update = sys.stdin.readline().strip() 35 | edit_rib.update_validator(raw_update, self.transport) 36 | args = tools.exa_raw('announce_g') 37 | edit_rib.update_watcher = mock_watcher 38 | mock_watcher() 39 | mock_validator.assert_called_with(args, self.transport) 40 | 41 | @patch('solenoid.edit_rib.render_config') 42 | def test_update_validator_good_json_conversion(self, mock_render): 43 | raw_g_json = tools.exa_raw('announce_g') 44 | args = json.loads(tools.exa_raw('announce_g')) 45 | edit_rib.update_validator(raw_g_json, self.transport) 46 | mock_render.assert_called_with(args, self.transport) 47 | 48 | @patch('solenoid.edit_rib.render_config') 49 | def test_update_validator_bad_json_conversion(self, mock_render): 50 | raw_b_json = tools.exa_raw('invalid_json') 51 | with self.assertRaises(ValueError): 52 | edit_rib.update_validator(raw_b_json, self.transport) 53 | # Check the logs. 54 | self.assertIn('Failed JSON conversion for BGP update\n', 55 | tools.check_errorlog()[0]) 56 | self.assertFalse(mock_render.called) 57 | 58 | @patch('solenoid.edit_rib.render_config') 59 | def test_update_validator_incorrect_model(self, mock_render): 60 | raw_b_json = tools.exa_raw('invalid_i_model') 61 | with self.assertRaises(KeyError): 62 | edit_rib.update_validator(raw_b_json, self.transport) 63 | # Check the logs. 64 | self.assertTrue('Not a valid update message type\n', 65 | tools.check_debuglog()[0]) 66 | self.assertFalse(mock_render.called) 67 | 68 | def test_update_file(self): 69 | edit_rib.update_file( 70 | { 71 | 'Test': time.ctime() 72 | } 73 | ) 74 | with open(tools.add_location('../updates.txt')) as f: 75 | self.assertTrue(len(f.readlines()) == 1) 76 | 77 | @patch('solenoid.edit_rib.rib_announce') 78 | def test_render_config_normal_model_missing_value(self, mock_announce): 79 | formatted_json = json.loads(tools.exa_raw('invalid_n_model')) 80 | edit_rib.rib_announce = mock_announce 81 | with self.assertRaises(KeyError): 82 | edit_rib.render_config(formatted_json, self.transport) 83 | self.assertIn('Not a valid update message type\n', 84 | tools.check_errorlog()[0]) 85 | self.assertFalse(mock_announce.called) 86 | 87 | @patch('solenoid.edit_rib.rib_announce') 88 | def test_render_config_normal_model_eor(self, mock_announce): 89 | formatted_json = json.loads(tools.exa_raw('announce_eor')) 90 | #edit_rib.rib_announce = mock_announce 91 | edit_rib.render_config(formatted_json, self.transport) 92 | self.assertIn('EOR message\n', tools.check_debuglog()[0]) 93 | self.assertFalse(mock_announce.called) 94 | 95 | @patch('solenoid.edit_rib.rib_announce') 96 | def test_render_config_announce_good(self, mock_announce): 97 | formatted_json = json.loads(tools.exa_raw('announce_g')) 98 | edit_rib.render_config(formatted_json, self.transport) 99 | with open(tools.add_location('examples/rendered_announce.txt'), 'U') as f: 100 | rendered_announce = f.read() 101 | mock_announce.assert_called_with(rendered_announce, self.transport) 102 | 103 | @patch('solenoid.edit_rib.rib_withdraw') 104 | def test_render_config_withdraw_good(self, mock_withdraw): 105 | withdraw_prefixes = ['1.1.1.8/32', 106 | '1.1.1.5/32', 107 | '1.1.1.7/32', 108 | '1.1.1.9/32', 109 | '1.1.1.2/32', 110 | '1.1.1.1/32', 111 | '1.1.1.6/32', 112 | '1.1.1.3/32', 113 | '1.1.1.10/32', 114 | '1.1.1.4/32'] 115 | formatted_json = json.loads(tools.exa_raw('withdraw_g')) 116 | edit_rib.render_config(formatted_json, self.transport) 117 | mock_withdraw.assert_called_with(withdraw_prefixes, self.transport) 118 | 119 | def test_filter_prefix_good(self): 120 | edit_rib.FILEPATH = tools.add_location('examples/filter/filter-full.txt') 121 | start_prefixes = ['1.1.1.9/32', 122 | '192.168.3.1/28', 123 | '1.1.1.2/32', 124 | '1.1.1.1/32', 125 | '10.1.1.1/32', 126 | '1.1.1.10/32', 127 | '10.1.6.1/24'] 128 | filtered_list = edit_rib.filter_prefixes(start_prefixes) 129 | end_prefixes = ['1.1.1.9/32', 130 | '1.1.1.2/32', 131 | '1.1.1.1/32', 132 | '1.1.1.10/32', 133 | '10.1.1.1/32', 134 | '10.1.6.1/24'] 135 | self.assertEqual(filtered_list, end_prefixes) 136 | 137 | @patch('solenoid.edit_rib.filter_prefixes') 138 | @patch('solenoid.edit_rib.rib_withdraw') 139 | def test_filter_empty(self, mock_withdraw, mock_filter): 140 | # The Setup configures us to have an empty filter file 141 | formatted_json = json.loads(tools.exa_raw('withdraw_g')) 142 | edit_rib.render_config(formatted_json, self.transport) 143 | mock_filter.assert_not_called() 144 | 145 | def test_filter_all_prefixes(self): 146 | edit_rib.FILEPATH = tools.add_location('examples/filter/filter-all.txt') 147 | start_prefixes = ['2.2.2.0/32', 148 | '10.2.1.1/24'] 149 | filtered_list = edit_rib.filter_prefixes(start_prefixes) 150 | end_prefixes = [] 151 | self.assertEqual(filtered_list, end_prefixes) 152 | 153 | @patch('solenoid.edit_rib.rib_withdraw') 154 | def test_render_config_prefixes_all_filtered_withdraw(self, mock_withdraw): 155 | edit_rib.FILEPATH = tools.add_location('examples/filter/filter-all.txt') 156 | formatted_json = json.loads(tools.exa_raw('withdraw_g')) 157 | edit_rib.render_config(formatted_json, self.transport) 158 | mock_withdraw.assert_not_called() 159 | 160 | @patch('solenoid.edit_rib.rib_announce') 161 | def test_render_config_prefixes_all_filtered_announce(self, mock_announce): 162 | edit_rib.FILEPATH = tools.add_location('examples/filter/filter-all.txt') 163 | formatted_json = json.loads(tools.exa_raw('announce_g')) 164 | edit_rib.render_config(formatted_json, self.transport) 165 | mock_announce.assert_not_called() 166 | 167 | def test_filter_prefix_invalid(self): 168 | edit_rib.FILEPATH = tools.add_location('examples/filter/filter-invalid.txt') 169 | start_prefixes = ['1.1.1.9/32', 170 | '192.168.3.1/28', 171 | '1.1.1.2/32', 172 | '1.1.1.1/32', 173 | '10.1.1.1/32', 174 | '1.1.1.10/32', 175 | '10.1.6.1/24'] 176 | from netaddr import AddrFormatError 177 | with self.assertRaises(AddrFormatError): 178 | edit_rib.filter_prefixes(start_prefixes) 179 | 180 | def test_create_transport_object_no_config_file(self): 181 | #Remove the config file 182 | if os.path.isfile(tools.add_location('../../solenoid.config')): 183 | os.remove(tools.add_location('../../solenoid.config')) 184 | with self.assertRaises(SystemExit): 185 | edit_rib.create_transport_object() 186 | self.assertIn('Something is wrong with your config file:', 187 | tools.check_debuglog()[0]) 188 | 189 | if __name__ == '__main__': 190 | unittest.main() 191 | 192 | -------------------------------------------------------------------------------- /solenoid/tests/mock/test_rib_grpc.py: -------------------------------------------------------------------------------- 1 | """Unit tests for checking rest calls. The tests are all mocked so no actual 2 | calls are made to the router. 3 | """ 4 | 5 | import unittest 6 | import shutil 7 | 8 | from mock import patch 9 | from solenoid import edit_rib 10 | from solenoid.tests import tools 11 | 12 | 13 | class GRPCRibTestCase(tools.TestBookends, object): 14 | 15 | def test_create_transport_object_correct_class_created(self): 16 | shutil.copy( 17 | tools.add_location('examples/config/grpc/grpc_good.config'), 18 | tools.add_location('../../solenoid.config') 19 | ) 20 | transport_object = edit_rib.create_transport_object() 21 | self.assertIsInstance(transport_object, edit_rib.CiscoGRPCClient) 22 | 23 | 24 | def test_create_transport_object_missing_object(self): 25 | shutil.copy( 26 | tools.add_location('examples/config/grpc/no_port.config'), 27 | tools.add_location('../../solenoid.config') 28 | ) 29 | with self.assertRaises(SystemExit): 30 | edit_rib.create_transport_object() 31 | self.assertIn('Something is wrong with your config file:', 32 | tools.check_errorlog()[0]) 33 | 34 | def test_create_transport_object_missing_section(self): 35 | shutil.copy( 36 | tools.add_location('examples/config/grpc/no_section.config'), 37 | tools.add_location('../../solenoid.config') 38 | ) 39 | with self.assertRaises(SystemExit): 40 | edit_rib.create_transport_object() 41 | self.assertIn('Something is wrong with your config file:', 42 | tools.check_errorlog()[0]) 43 | 44 | def test_create_transport_object_multiple_sections(self): 45 | shutil.copy( 46 | tools.add_location('examples/config/grpc/multiple_sections.config'), 47 | tools.add_location('../../solenoid.config') 48 | ) 49 | transport_object = edit_rib.create_transport_object() 50 | self.assertIsInstance(transport_object, edit_rib.CiscoGRPCClient) 51 | self.assertIn('Multiple routers not currently supported in the configuration file', 52 | tools.check_debuglog()[0]) 53 | 54 | @patch('solenoid.edit_rib.CiscoGRPCClient.patch') 55 | def test_rib_announce(self, mock_patch): 56 | shutil.copy( 57 | tools.add_location('examples/config/grpc/grpc_good.config'), 58 | tools.add_location('../../solenoid.config') 59 | ) 60 | with open(tools.add_location('examples/rendered_announce.txt')) as f: 61 | rendered_announce = f.read() 62 | edit_rib.transport = edit_rib.create_transport_object() 63 | edit_rib.rib_announce(rendered_announce, edit_rib.transport) 64 | mock_patch.assert_called_with(rendered_announce) 65 | self.assertIn('| ANNOUNCE | ', tools.check_debuglog()[0]) 66 | 67 | @patch('solenoid.edit_rib.CiscoGRPCClient.delete') 68 | def test_rib_withdraw(self, mock_delete): 69 | withdraw_prefixes = ['1.1.1.8/32', 70 | '1.1.1.5/32', 71 | '1.1.1.7/32', 72 | '1.1.1.9/32', 73 | '1.1.1.2/32', 74 | '1.1.1.1/32', 75 | '1.1.1.6/32', 76 | '1.1.1.3/32', 77 | '1.1.1.10/32', 78 | '1.1.1.4/32'] 79 | shutil.copy( 80 | tools.add_location('examples/config/grpc/grpc_good.config'), 81 | tools.add_location('../../solenoid.config') 82 | ) 83 | edit_rib.transport = edit_rib.create_transport_object() 84 | edit_rib.rib_withdraw(withdraw_prefixes, edit_rib.transport) 85 | url = '{{"Cisco-IOS-XR-ip-static-cfg:router-static": {{"default-vrf": {{"address-family": {{"vrfipv4": {{"vrf-unicast": {{"vrf-prefixes": {{"vrf-prefix": [{withdraw}]}}}}}}}}}}}}}}' 86 | prefix_info = '{{"prefix": "{bgp_prefix}","prefix-length": {prefix_length}}}' 87 | prefix_list = [] 88 | for withdrawn_prefix in withdraw_prefixes: 89 | bgp_prefix, prefix_length = withdrawn_prefix.split('/') 90 | prefix_list += [ 91 | prefix_info.format( 92 | bgp_prefix=bgp_prefix, 93 | prefix_length=prefix_length 94 | ) 95 | ] 96 | prefix_str = ', '.join(prefix_list) 97 | url = url.format(withdraw=prefix_str) 98 | mock_delete.assert_called_once_with(url) 99 | self.assertIn('| WITHDRAW | ', tools.check_debuglog()[0]) 100 | 101 | if __name__ == '__main__': 102 | unittest.main() 103 | -------------------------------------------------------------------------------- /solenoid/tests/mock/test_rib_rest.py: -------------------------------------------------------------------------------- 1 | """Unit tests for checking rest calls. The tests are all mocked so no actual 2 | calls are made to the router. 3 | """ 4 | import shutil 5 | import unittest 6 | from mock import patch, call 7 | from solenoid import edit_rib 8 | from solenoid.tests import tools 9 | 10 | 11 | class RestRibTestCase(tools.TestBookends, object): 12 | 13 | def test_create_transport_object_correct_class_created(self): 14 | shutil.copy( 15 | tools.add_location('examples/config/restconf/restconf_good.config'), 16 | tools.add_location('../../solenoid.config') 17 | ) 18 | transport_object = edit_rib.create_transport_object() 19 | self.assertIsInstance(transport_object, edit_rib.JSONRestCalls) 20 | 21 | def test_create_transport_object_missing_object(self): 22 | shutil.copy( 23 | tools.add_location('examples/config/restconf/no_port.config'), 24 | tools.add_location('../../solenoid.config') 25 | ) 26 | with self.assertRaises(SystemExit) as cm: 27 | edit_rib.create_transport_object() 28 | 29 | def test_create_transport_object_missing_section(self): 30 | shutil.copy( 31 | tools.add_location('examples/config/restconf/no_section.config'), 32 | tools.add_location('../../solenoid.config') 33 | ) 34 | with self.assertRaises(SystemExit): 35 | edit_rib.create_transport_object() 36 | self.assertIn('Something is wrong with your config file:', 37 | tools.check_debuglog()[0]) 38 | 39 | def test_create_transport_object_multiple_sections(self): 40 | shutil.copy( 41 | tools.add_location('examples/config/restconf/multiple_sections.config'), 42 | tools.add_location('../../solenoid.config') 43 | ) 44 | transport_object = edit_rib.create_transport_object() 45 | self.assertIsInstance(transport_object, edit_rib.JSONRestCalls) 46 | self.assertIn('Multiple routers not currently supported in the configuration file', 47 | tools.check_debuglog()[0]) 48 | 49 | @patch('solenoid.edit_rib.JSONRestCalls.patch') 50 | def test_rib_announce(self, mock_patch): 51 | shutil.copy( 52 | tools.add_location('examples/config/restconf/restconf_good.config'), 53 | tools.add_location('../../solenoid.config') 54 | ) 55 | with open(tools.add_location('examples/rendered_announce.txt')) as f: 56 | rendered_announce = f.read() 57 | transport_object = edit_rib.create_transport_object() 58 | edit_rib.rib_announce(rendered_announce, transport_object) 59 | mock_patch.assert_called_with(rendered_announce) 60 | self.assertIn('| ANNOUNCE | ', tools.check_debuglog()[0]) 61 | 62 | @patch('solenoid.edit_rib.JSONRestCalls.delete') 63 | def test_rib_withdraw(self, mock_delete): 64 | withdraw_prefixes = ['1.1.1.8/32', 65 | '1.1.1.5/32', 66 | '1.1.1.7/32', 67 | '1.1.1.9/32', 68 | '1.1.1.2/32', 69 | '1.1.1.1/32', 70 | '1.1.1.6/32', 71 | '1.1.1.3/32', 72 | '1.1.1.10/32', 73 | '1.1.1.4/32'] 74 | shutil.copy( 75 | tools.add_location('examples/config/restconf/restconf_good.config'), 76 | tools.add_location('../../solenoid.config') 77 | ) 78 | transport_object = edit_rib.create_transport_object() 79 | edit_rib.rib_withdraw(withdraw_prefixes, transport_object) 80 | url = 'Cisco-IOS-XR-ip-static-cfg:router-static/default-vrf/address-family/vrfipv4/vrf-unicast/vrf-prefixes/vrf-prefix=' 81 | comma_list = [prefix.replace('/', ',') for prefix in withdraw_prefixes] 82 | calls = map(call, [url+x for x in comma_list]) 83 | mock_delete.assert_has_calls(calls, any_order=True) 84 | self.assertIn('| WITHDRAW | ', tools.check_debuglog()[0]) 85 | 86 | if __name__ == '__main__': 87 | unittest.main() 88 | -------------------------------------------------------------------------------- /solenoid/tests/tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from solenoid import edit_rib 4 | 5 | class TestBookends(unittest.TestCase, object): 6 | @classmethod 7 | def setUpClass(cls): 8 | #Silence stream logger. 9 | streamhandler = edit_rib.LOGGER._streamhandler 10 | streamhandler.close() 11 | edit_rib.LOGGER._logger.removeHandler(streamhandler) 12 | # Move the config file so it doesn't get edited 13 | if os.path.isfile(add_location('../../solenoid.config')): 14 | os.rename( 15 | add_location('../../solenoid.config'), 16 | add_location('../../solenoidtest.config') 17 | ) 18 | 19 | def setUp(self): 20 | #Set global variable. 21 | edit_rib.FILEPATH = add_location('examples/filter/filter-empty.txt') 22 | #Clear out logging files. 23 | open(add_location('../updates.txt'), 'w').close() 24 | open(add_location('../logs/debug.log'), 'w').close() 25 | open(add_location('../logs/errors.log'), 'w').close() 26 | 27 | @classmethod 28 | def tearDownClass(cls): 29 | # If a new config file was created, delete it 30 | if (os.path.isfile(add_location('../../solenoid.config')) 31 | and os.path.isfile(add_location('../../solenoidtest.config')) 32 | ): 33 | os.remove(add_location('../../solenoid.config')) 34 | # If the config file was moved, move it back 35 | if os.path.isfile(add_location('../../solenoidtest.config')): 36 | os.rename( 37 | add_location('../../solenoidtest.config'), 38 | add_location('../../solenoid.config') 39 | ) 40 | 41 | def exa_raw(test): 42 | with open(add_location('examples/exa/exa-raw.json')) as f: 43 | lines = f.readlines() 44 | if test == 'announce_g': 45 | exa_line = lines[0].strip() 46 | elif test == 'withdraw_g': 47 | exa_line = lines[1].strip() 48 | elif test == 'withdraw_b': 49 | exa_line = lines[2].strip() 50 | elif test == 'invalid_json': 51 | exa_line = lines[3].strip() 52 | elif test == 'invalid_n_model': 53 | exa_line = lines[4].strip() 54 | elif test == 'invalid_i_model': 55 | exa_line = lines[5].strip() 56 | elif test == 'announce_eor': 57 | exa_line = lines[6].strip() 58 | return exa_line 59 | 60 | def check_errorlog(): 61 | with open(add_location('../logs/errors.log')) as err_log: 62 | return err_log.readlines() 63 | 64 | def check_debuglog(): 65 | with open(add_location('../logs/debug.log')) as debug_log: 66 | return debug_log.readlines() 67 | 68 | def add_location(filepath): 69 | location = os.path.dirname(os.path.realpath(__file__)) 70 | new_filepath = os.path.join(location, filepath) 71 | return new_filepath 72 | -------------------------------------------------------------------------------- /vagrant/README.md: -------------------------------------------------------------------------------- 1 | # Solenoid-Vagrant 2 | #####Vagrant for Solenoid application demo 3 | #####Author: Karthik Kumaravel 4 | ##### Contact: Please use the issues page to ask questions or open bugs and feature requests. 5 | 6 | ### Overview 7 | This is a Vagrant box set up to demo the Solenoid application. This Vagrant uses two virtualbox VMs, an IOS-XRv image and an Ubuntu/trusty64 image. The plumbing and demo functions are brought up through bash scripts to allow you to use the Solenoid application without hassle, and demo it to others. 8 | 9 | ![Solenoid Vagrant Diagram](SolenoidDiagram.png) 10 | 11 | ### Set Up 12 | 13 | To install the application. 14 | 15 | Step 1. Set up Virtualbox and Vagrant on your device 16 | 17 | Step 2. Clone this repo 18 | 19 | Step 3. Download the IOS-XRv vagrant box through the following link: 20 | 21 |     Download: https://xrdocs.github.io/getting-started/steps-download-iosxr-vagrant 22 | 23 | And follow the instructions to add the base box: 24 | 25 |     Instructions: https://xrdocs.github.io/application-hosting/tutorials/iosxr-vagrant-quickstart 26 | 27 | 28 | 29 | Step 4 (optional). The Solenoid LXC tarball is downloaded for you (see vagrant/xrv/bootstrap.sh), but you can also create your own tarball if you want use the latest code. Follow the instructions [here](https://github.com/ios-xr/Solenoid/wiki/Create-your-own-Solenoid-LXC-tarball). 30 | 31 | 32 | Step 5. In a terminal screen change directory into the vagrant directory of the repository. The vagrant file is located here.
33 | 34 | cd Solenoid/vagrant 35 | 36 | Step 6. ```vagrant up``` 37 | 38 | This is all you need to get Solenoid working! It will take a few minutes, and you will see a number of ugly looking messages like these: 39 | 40 | ``` 41 | ==> xrv: tar: dev/audio2: Cannot mknod: Operation not permitted 42 | ==> xrv: tar: dev/sequencer: Cannot mknod: Operation not permitted 43 | ==> xrv: tar: dev/midi3: Cannot mknod: Operation not permitted 44 | ==> xrv: tar: dev/mixer3: Cannot mknod: Operation not permitted 45 | ==> xrv: tar: dev/smpte3: Cannot mknod: Operation not permitted 46 | ==> xrv: tar: dev/mpu401data: Cannot mknod: Operation not permitted 47 | ``` 48 | 49 | But don't worry, your vagrant boxes are working perfectly. Once you see the following message you wil know you are done: 50 | 51 | ``` 52 | ==> xrv: Machine 'xrv' has a post `vagrant up` message. This is a message 53 | ==> xrv: from the creator of the Vagrantfile, and not from Vagrant itself: 54 | ==> xrv: 55 | ==> xrv: 56 | ==> xrv: Welcome to the IOS XRv (64-bit) VirtualBox. 57 | ... 58 | ``` 59 | 60 | 61 | ### How to use this demo. 62 | 63 | After completing the initial ```vagrant up```, the application is already up and running. If you navigate to: 64 | 65 | localhost:57780 66 | 67 | on your browser, you will see the routes being added and withdrawn from the IOS-XRv's RIB table. To view the application running on the box, reference the instructions below on how to navigate the vagrant environment. 68 | 69 | 70 | ### Navigating the Vagrant environment 71 | 72 | This Vagrant environment has 4 locations: 73 | 74 | 1. IOS-XRv bash 75 | 76 | 2. IOS-XRv cli 77 | 78 | 3. Ubuntu container on IOS-XRv (running Solenoid) 79 | 80 | 4. Ubuntu devbox (running exaBGP) 81 | 82 | 83 | This is how to access each of these components. 84 | 85 | ####XR bash 86 | From the vagrant folder on your laptop: 87 | 88 | vagrant ssh xrv 89 | 90 | XR CLI and Solenoid is accessed in from XR bash 91 | 92 | ####XR CLI 93 | From XR Bash (see instructions above): 94 | 95 | ssh 10.1.1.5 96 | 97 | password: vagrant 98 | 99 | 100 | ####Container running Solenoid 101 | From your laptop's vagrant folder: 102 | 103 | ssh -p 58822 ubuntu@localhost 104 | 105 | Password: ubuntu 106 | 107 | 108 | To see the actual Solenoid application running, enter the following from the container (see instructions above) in order to enter the running [screen](https://www.gnu.org/software/screen/manual/screen.html): 109 | 110 | screen -r exabgp 111 | 112 | To see the website running, enter the following from the container (see instructions above) in order to enter the running [screen](https://www.gnu.org/software/screen/manual/screen.html): 113 | 114 | screen -r website 115 | 116 | 117 | ####Ubuntu Devbox 118 | From the vagrant folder: 119 | 120 | vagrant ssh devbox 121 | 122 | From this vagrant box, to access exaBGP enter the [screen](https://www.gnu.org/software/screen/manual/screen.html) that is currently running: 123 | 124 | sudo screen -r 125 | 126 | note: This will be changed to not use sudo. 127 | 128 | 129 | -------------------------------------------------------------------------------- /vagrant/SolenoidDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/vagrant/SolenoidDiagram.png -------------------------------------------------------------------------------- /vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | Vagrant.configure(2) do |config| 4 | 5 | config.vm.define "xrv", primary: true do |xrv| 6 | xrv.vm.box = "IOS-XRv" 7 | xrv.vm.network :private_network, virtualbox__intnet: "connection", auto_config: false 8 | xrv.vm.network "forwarded_port", guest: 57780, host: 57780 9 | xrv.vm.network "forwarded_port", guest: 58822, host: 58822 10 | xrv.vm.provider "virtualbox" do |v| 11 | v.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] 12 | v.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"] 13 | end 14 | 15 | xrv.vm.provision "file", source: "xrv/demo.xml", destination: "/home/vagrant/demo.xml" 16 | xrv.vm.provision "file", source: "xrv/router_config", destination: "/home/vagrant/router_config" 17 | if File.exist? File.expand_path "solenoid.tgz" 18 | xrv.vm.provision "file", source: "solenoid.tgz", destination: "/home/vagrant/solenoid.tgz" 19 | else 20 | xrv.vm.provision :shell, inline: "curl -L 'https://cisco.box.com/shared/static/9no4xqjtm8q05ofmsa5dhe52hv3tmof7.tgz' -o 'solenoid.tgz'" 21 | end 22 | xrv.vm.provision :shell, path: "xrv/bootstrap.sh" 23 | end 24 | 25 | config.vm.define "devbox", primary: true do |ubuntu| 26 | ubuntu.vm.box = "ubuntu/trusty64" 27 | ubuntu.vm.network :private_network, virtualbox__intnet: "connection", ip: "11.1.1.20" 28 | ubuntu.vm.provider "virtualbox" do |v| 29 | v.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] 30 | v.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"] 31 | end 32 | ubuntu.vm.provision :shell, path: "devbox/bootstrap_ubuntu.sh" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /vagrant/devbox/adv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import time 5 | 6 | while True: 7 | messages_an = [ 8 | 'announce route 1.1.1.0/24 next-hop self', 9 | 'announce route 2.2.2.0/24 next-hop self', 10 | 'announce route 3.3.3.0/24 next-hop self', 11 | ] 12 | 13 | messages_with = [ 14 | 'withdraw route 1.1.1.0/24 next-hop self', 15 | 'withdraw route 2.2.2.0/24 next-hop self', 16 | 'withdraw route 3.3.3.0/24 next-hop self', 17 | ] 18 | 19 | 20 | while messages_an: 21 | message = messages_an.pop(0) 22 | sys.stdout.write( message + '\n') 23 | sys.stdout.flush() 24 | time.sleep(2) 25 | 26 | while messages_with: 27 | message = messages_with.pop(0) 28 | sys.stdout.write( message + '\n') 29 | sys.stdout.flush() 30 | time.sleep(2) 31 | 32 | time.sleep(2) 33 | -------------------------------------------------------------------------------- /vagrant/devbox/bootstrap_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl -L https://github.com/Exa-Networks/exabgp/archive/3.4.16.tar.gz | sudo tar zx -C /opt/ > /dev/null 4 | 5 | cp /vagrant/devbox/exabgp-router-conf.ini /usr/local/etc/exabgp-router-conf.ini 6 | cp /vagrant/devbox/adv.py /usr/local/bin/adv.py 7 | chmod 777 /usr/local/etc/exabgp-router-conf.ini /usr/local/bin/adv.py 8 | 9 | screen -S exabgp -dm bash -c 'sudo env exabgp.tcp.bind="11.1.1.20" exabgp.tcp.port=179 /opt/exabgp-3.4.16/sbin/exabgp /usr/local/etc/exabgp-router-conf.ini' 10 | -------------------------------------------------------------------------------- /vagrant/devbox/exabgp-router-conf.ini: -------------------------------------------------------------------------------- 1 | group demo { 2 | router-id 11.1.1.20; 3 | 4 | neighbor 11.1.1.10 { 5 | local-address 11.1.1.20; 6 | local-as 65000; 7 | peer-as 65000; 8 | } 9 | 10 | process add-routes { 11 | run /usr/bin/python /usr/local/bin/adv.py; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vagrant/xrv/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ### Configs ### 4 | 5 | #curl -L 'https://cisco.box.com/shared/static/9no4xqjtm8q05ofmsa5dhe52hv3tmof7.tgz' -o 'solenoid.tgz' 6 | 7 | ## Install Deploy Container ## 8 | 9 | cd /misc/app_host/ 10 | sudo mkdir solenoid 11 | sudo mv /home/vagrant/solenoid.tgz . 12 | tar -zvxf solenoid.tgz -C solenoid/ > /dev/null 13 | sudo -i virsh create /home/vagrant/demo.xml 14 | 15 | ## Apply a blind config ## 16 | 17 | source /pkg/bin/ztp_helper.sh 18 | 19 | xrapply /home/vagrant/router_config 20 | if [ $? -ne 0 ]; then 21 | echo "xrapply failed to run" 22 | fi 23 | xrcmd "show config failed" > /home/vagrant/config_failed_check 24 | 25 | config_file=/home/vagrant/router_config 26 | 27 | 28 | cat /home/vagrant/config_failed_check 29 | grep -q "ERROR" /home/vagrant/config_failed_check 30 | 31 | if [ $? -ne 0 ]; then 32 | echo "Configuration was successful!" 33 | echo "Last applied configuration was:" 34 | xrcmd "show configuration commit changes last 1" 35 | else 36 | echo "Configuration Failed. Check /home/vagrant/config_failed on the router for logs" 37 | xrcmd "show configuration failed" > /home/vagrant/config_failed 38 | exit 1 39 | fi 40 | 41 | sleep 2m 42 | 43 | sshpass -p "ubuntu" ssh -p 58822 -o StrictHostKeyChecking=no -T ubuntu@11.1.1.10 "bash -s" << EOF 44 | screen -S exabgp -dm bash -c 'cd Solenoid ; source venv/bin/activate; cd .. ; exabgp router.ini' 45 | screen -S website -dm bash -c 'cd Solenoid ; source venv/bin/activate; cd website; python exabgp_website.py' 46 | EOF 47 | -------------------------------------------------------------------------------- /vagrant/xrv/demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | solenoid 4 | 327680 5 | 6 | exe 7 | /sbin/init 8 | 9 | 10 | 11 | 12 | 1 13 | 14 | destroy 15 | restart 16 | destroy 17 | 18 | /usr/lib64/libvirt/libvirt_lxc 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /vagrant/xrv/router_config: -------------------------------------------------------------------------------- 1 | 2 | interface GigabitEthernet 0/0/0/0 3 | ipv4 address 11.1.1.10 255.255.255.0 4 | no shut 5 | ! 6 | interface Loopback1 7 | ipv4 address 10.1.1.5 255.255.255.255 8 | ! 9 | router static 10 | address-family ipv4 unicast 11 | no 0.0.0.0/0 MgmtEth0/RP0/CPU0/0 10.0.2.2 12 | 0.0.0.0/0 10.0.2.2 13 | ! 14 | ! 15 | grpc 16 | port 57777 17 | ! 18 | end 19 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | ##Solenoid-Website 2 | ######Author: Karthik Kumaravel 3 | 4 | ##Description 5 | 6 | A Basic website created to show off Solenoid injecting and withdrawing routes that it hears about. 7 | The website has three components, a route filtering component, Exa-bgp announcement table, and a RIB table. 8 | The route filtering component is still under construction and is yet not finished. 9 | The Exa-bgp announcement table, is a table of announcements and withdraws that it learns about from a speaker. 10 | The RIB table is a constant restcall to the RIB table of the IOS-XRv instance. This shows the routes programmed into the RIB table and reflect's Solenoid injections and withdraw. 11 | 12 | ##Running the website 13 | 14 | The website is a simple flask application all you would need to do is cd into the website directory and use python to run the application. 15 | 16 | ``` 17 | cd website 18 | python exabgp_website.py 19 | ``` 20 | 21 | The website should be up and running. It is currently on the 57780 port. You would need to NAT this port on Vagrant for you to see the website. You can also change which port in the exabgp_website.py file. 22 | -------------------------------------------------------------------------------- /website/exabgp_website.py: -------------------------------------------------------------------------------- 1 | 2 | ''''This module is for starting a Flask website for exaBGP demo 3 | Author: Karthik Kumaravel 4 | Contact: kkumara3@cisco.com 5 | ''' 6 | 7 | from flask import Flask, render_template, request, jsonify 8 | import requests 9 | import json 10 | import sys 11 | import os 12 | import ConfigParser 13 | from solenoid import CiscoGRPCClient 14 | from solenoid import JSONRestCalls 15 | app = Flask(__name__) 16 | 17 | class InvalidUsage(Exception): 18 | status_code = 400 19 | 20 | def __init__(self, message, status_code=None, payload=None): 21 | Exception.__init__(self) 22 | self.message = message 23 | if status_code is not None: 24 | self.status_code = status_code 25 | self.payload = payload 26 | 27 | def to_dict(self): 28 | rv = dict(self.payload or ()) 29 | rv['message'] = self.message 30 | return rv 31 | 32 | @app.errorhandler(InvalidUsage) 33 | def handle_invalid_usage(error): 34 | response = jsonify(error.to_dict()) 35 | response.status_code = error.status_code 36 | return response 37 | 38 | @app.route("/", methods=['GET', 'POST']) 39 | def template_test(): 40 | rib = get_rib() 41 | return render_template('index.html', 42 | Title = 'Solenoid Demo on IOS-XRv', 43 | content2 = rib, 44 | ) 45 | @app.route("/get_rib_json", methods=['GET']) 46 | #Used for refreshing of object 47 | def get_rib_json(): 48 | return jsonify(get_rib()) 49 | 50 | @app.route("/get_exa_json", methods=['GET']) 51 | #Used for refreshing of object 52 | def get_exa_json(): 53 | return get_exa() 54 | @app.route("/prefix_change", methods=['GET']) 55 | def prefix_change(): 56 | prefix = request.args.get('ip_address') 57 | method = request.args.get('network') 58 | #Push Network to Second ExaBGP instance, change URL to appropriate http api 59 | here = os.path.dirname(os.path.realpath(__file__)) 60 | filepath = os.path.join(here, '../filter.txt') 61 | if method == 'add': 62 | with open(filepath, 'a+') as filterf: 63 | found = False 64 | for line in filterf: 65 | if prefix in line: 66 | raise InvalidUsage('Error cannot add: This prefix is already in the filter file.') 67 | if not found: 68 | filterf.write(prefix + '\n') 69 | filter_list = filterf.read() 70 | return filter_list 71 | elif method == 'remove': 72 | with open(filepath, 'r+') as filterf: 73 | found = False 74 | prefix_list = [] 75 | for line in filterf: 76 | prefix_list.append(line) 77 | filterf.seek(0) 78 | for line in prefix_list: 79 | if line != prefix + '\n': 80 | filterf.write(line) 81 | else: 82 | found = True 83 | filterf.truncate() 84 | filter_list = filterf.read() 85 | if not found: 86 | raise InvalidUsage('Error cannot remove: The prefix was not in the prefix list') 87 | return filter_list 88 | else: 89 | with open(filepath) as filterf: 90 | filter_list = filterf.read() 91 | return filter_list 92 | 93 | 94 | 95 | def get_rib(): 96 | """Grab the current RIB config off of the box 97 | :return: return the json HTTP response object 98 | :rtype: dict 99 | """ 100 | transport_object = create_transport_object() 101 | if isinstance(transport_object, CiscoGRPCClient): 102 | response = transport_object.get('{"Cisco-IOS-XR-ip-static-cfg:router-static": [null]}') 103 | return json.loads(response) 104 | else: 105 | response = transport_object.get('Cisco-IOS-XR-ip-static-cfg:router-static') 106 | return response.json() 107 | def create_transport_object(): 108 | """Create a grpc channel object. 109 | Reads in a file containing username, password, and 110 | ip address:port, in that order. 111 | :returns: grpc object 112 | :rtype: grpc class object 113 | """ 114 | location = os.path.dirname(os.path.realpath(__file__)) 115 | config = ConfigParser.ConfigParser() 116 | try: 117 | config.read(os.path.join(location, '../solenoid.config')) 118 | if len(config.sections()) >= 1: 119 | section = config.sections()[0] 120 | args = ( 121 | config.get(section, 'ip'), 122 | int(config.get(section, 'port')), 123 | config.get(section, 'username'), 124 | config.get(section, 'password') 125 | ) 126 | if config.get(section, 'transport').lower() == 'grpc': 127 | return CiscoGRPCClient(*args) 128 | if config.get(section, 'transport').lower() == 'restconf': 129 | return JSONRestCalls(*args) 130 | else: 131 | raise ValueError 132 | except (ConfigParser.Error, ValueError), e: 133 | sys.exit(1) 134 | 135 | def get_exa(): 136 | #Opens file that announcements are stored and returns the last line 137 | with open('../solenoid/updates.txt', 'rb') as f: # change this to where the history.txt. file will be 138 | f.seek(-2,2) 139 | while f.read(1) != b"\n": # Until EOL is found... 140 | f.seek(-2, 1) # ...jump back the read byte plus one more. 141 | last = f.readline() # Read last line. 142 | return last 143 | 144 | 145 | 146 | if __name__ == '__main__': 147 | app.run(debug=True, host='0.0.0.0', port=57780) 148 | 149 | 150 | -------------------------------------------------------------------------------- /website/static/Topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ios-xr/Solenoid/6295b5374fb569d1dce33d6c694f20c1a2b5faea/website/static/Topology.png -------------------------------------------------------------------------------- /website/static/refresh.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | function getInfo(){ 4 | var xhttp = new XMLHttpRequest(); 5 | xhttp.onreadystatechange = function() { 6 | if (xhttp.readyState == 4 && xhttp.status == 200) { 7 | var newData = "" 8 | var json = JSON.parse(xhttp.responseText); 9 | var keys = Object.keys(json['Cisco-IOS-XR-ip-static-cfg:router-static']['default-vrf']['address-family']['vrfipv4']['vrf-unicast']['vrf-prefixes']['vrf-prefix']).length 10 | for (var i = 0; i < keys; i++){ 11 | newData += "" + json['Cisco-IOS-XR-ip-static-cfg:router-static']['default-vrf']['address-family']['vrfipv4']['vrf-unicast']['vrf-prefixes']['vrf-prefix'][i]['prefix'] + ""; 12 | newData += "" + json['Cisco-IOS-XR-ip-static-cfg:router-static']['default-vrf']['address-family']['vrfipv4']['vrf-unicast']['vrf-prefixes']['vrf-prefix'][i]['vrf-route']['vrf-next-hop-table']['vrf-next-hop-next-hop-address'][0]['next-hop-address']; 13 | newData += "\n" 14 | } 15 | 16 | $('#rib tbody').html(newData); 17 | 18 | } 19 | }; 20 | xhttp.open('GET', '/get_rib_json', true); 21 | xhttp.send(); 22 | } 23 | function getInfo1(){ 24 | var Table = document.getElementById("exa"); 25 | var rows_l = Table.rows.length 26 | var xhttp = new XMLHttpRequest(); 27 | xhttp.onreadystatechange = function() { 28 | if (xhttp.readyState == 4 && xhttp.status == 200) { 29 | var exa = JSON.parse(xhttp.responseText); 30 | console.log(exa); 31 | var time = timeConverter(exa['time']); 32 | var old_time = Table.rows[1].cells[4].innerHTML; 33 | if (time != old_time){ 34 | var row = Table.insertRow(1) 35 | row.insertCell(0).innerHTML = exa['neighbor']['ip']; 36 | var counter = Object.keys(exa['neighbor']['message']['update']).length - 1 37 | var update = Object.keys(exa['neighbor']['message']['update'])[counter]; 38 | row.insertCell(1).innerHTML = update; 39 | var nexthop = Object.keys(exa['neighbor']['message']['update'][update]['ipv4 unicast']); 40 | if (counter == 0){ 41 | row.insertCell(2).innerHTML = " "; 42 | var network = Object.keys(exa['neighbor']['message']['update'][update]['ipv4 unicast'][nexthop]); 43 | row.insertCell(3).innerHTML = nexthop; 44 | row.insertCell(4).innerHTML = time; 45 | } else { 46 | row.insertCell(2).innerHTML = nexthop; 47 | var network = Object.keys(exa['neighbor']['message']['update'][update]['ipv4 unicast'][nexthop]); 48 | row.insertCell(3).innerHTML = network; 49 | row.insertCell(4).innerHTML = time; 50 | } 51 | } 52 | } 53 | }; 54 | xhttp.open('GET', '/get_exa_json', true); 55 | xhttp.send(); 56 | } 57 | function timeConverter(UNIX_timestamp){ 58 | var a = new Date(UNIX_timestamp * 1000); 59 | var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; 60 | var year = a.getFullYear(); 61 | var month = months[a.getMonth()]; 62 | var date = a.getDate(); 63 | var hour = a.getHours(); 64 | var min = a.getMinutes(); 65 | var sec = a.getSeconds(); 66 | var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ; 67 | return time; 68 | } 69 | setInterval(getInfo1, 1000); 70 | setInterval(getInfo, 2000); 71 | }()); 72 | function getprefix(){ 73 | var xhttp = new XMLHttpRequest(); 74 | xhttp.onreadystatechange = function() { 75 | if (xhttp.readyState == 4 && xhttp.status == 200) { 76 | var newData = "" 77 | var lines = xhttp.responseText.split('\n'); 78 | for (var line in lines){ 79 | newData += "" + lines[line] + ""; 80 | } 81 | 82 | $('#prefix tbody').html(newData); 83 | 84 | } 85 | }; 86 | xhttp.open('GET', '/prefix_change', true); 87 | xhttp.send(); 88 | } 89 | $(document).ready(getprefix) 90 | -------------------------------------------------------------------------------- /website/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; 3 | padding:0; 4 | line-height: 1.5em; 5 | background: #46A040; /* For browsers that do not support gradients */ 6 | background: -webkit-linear-gradient(left top, #46A040, #0096D6); /* For Safari 5.1 to 6.0 */ 7 | background: -o-linear-gradient(bottom right, #46A040, #0096D6); /* For Opera 11.1 to 12.0 */ 8 | background: -moz-linear-gradient(bottom right, #46A040, #0096D6); /* For Firefox 3.6 to 15 */ 9 | background: linear-gradient(to bottom right, #46A040, #0096D6); /* Standard syntax */ 10 | } 11 | 12 | h1 { 13 | font-family: Arial; 14 | font-size: 26px; 15 | font-style: normal; 16 | font-variant: normal; 17 | font-weight: 400; 18 | font-color: #444444; 19 | line-height: 28.6px; 20 | } 21 | 22 | h3 { 23 | font-family: Arial; 24 | font-size: 18px; 25 | font-style: normal; 26 | font-variant: normal; 27 | font-weight: 400; 28 | font-color: #444444; 29 | line-height: 28.6px; 30 | } 31 | 32 | p { 33 | font-family: Arial; 34 | font-size: 12px; 35 | font-style: normal; 36 | font-variant: normal; 37 | font-weight: 400; 38 | font-color: #525252; 39 | line-height: 20px; 40 | } 41 | 42 | 43 | label{ 44 | padding-left: 10px; 45 | } 46 | #wrap { 47 | width: 960px; 48 | margin: 5px auto; 49 | background: white; 50 | border: 2px solid #ccc; 51 | border-radius: 5px; 52 | } 53 | 54 | #header { 55 | font-family: Arial; 56 | font-size: 26px; 57 | font-style: normal; 58 | font-variant: normal; 59 | font-weight: 400; 60 | font-color: #444444; 61 | line-height: 28.6px; 62 | background: #ccc; 63 | height: 75px; 64 | padding-top:15px; 65 | } 66 | 67 | .box{ 68 | padding: 1em; 69 | border:1px solid #ccc; 70 | border-radius: 3px; 71 | width: 1335px; 72 | } 73 | 74 | .box * { 75 | box-sizing: border-box; 76 | } 77 | 78 | #header h1 { 79 | margin: 0; 80 | padding-top: 15px; 81 | } 82 | 83 | main { 84 | padding-bottom: 10010px; 85 | margin-bottom: -10000px; 86 | float: left; 87 | width: 100%; 88 | } 89 | 90 | #nav { 91 | padding-bottom: 10010px; 92 | margin-bottom: -10000px; 93 | float: left; 94 | width: 295px; 95 | margin-left: -100%; 96 | background: #eee; 97 | } 98 | 99 | #footer { 100 | clear: left; 101 | width: 100%; 102 | background: #ccc; 103 | text-align: center; 104 | padding: 4px 0; 105 | } 106 | 107 | #wrapper { 108 | overflow: hidden; 109 | } 110 | 111 | #content { 112 | margin-left: 295px; /* Same as 'nav' width */ 113 | margin-right: 50px; 114 | } 115 | 116 | .innertube { 117 | margin: 15px; /* Padding for content */ 118 | margin-top: 0; 119 | } 120 | 121 | p { 122 | color: #555; 123 | } 124 | 125 | nav ul { 126 | list-style-type: none; 127 | margin: 0; 128 | padding: 0; 129 | } 130 | 131 | nav ul a { 132 | color: darkgreen; 133 | text-decoration: none; 134 | } 135 | .myForm { 136 | font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; 137 | font-size: 0.8em; 138 | padding: 5px 5px 15px 5px; 139 | border: 1px solid #ccc; 140 | border-radius: 3px; 141 | box-shadow: -5px -5px 5px #888; 142 | width: 255px; 143 | background: white; 144 | } 145 | 146 | .myForm * { 147 | box-sizing: border-box; 148 | } 149 | 150 | .myForm button { 151 | padding: 0.7em; 152 | border-radius: 0.5em; 153 | background: #eee; 154 | border: none; 155 | font-weight: bold; 156 | } 157 | 158 | .myForm button:hover { 159 | background: #ccc; 160 | cursor: pointer; 161 | } 162 | 163 | table { 164 | border-collapse: collapse; 165 | width: 100%; 166 | border: 1px solid #ccc; 167 | box-shadow: -5px -5px 5px #888; 168 | border-radius: 3px; 169 | } 170 | 171 | th { 172 | border: 1px solid #ccc; 173 | text-align: center; 174 | padding: 15px; 175 | } 176 | 177 | td { 178 | border: 1px solid #ccc; 179 | vertical-align: center; 180 | text-align:center; 181 | padding: 15px; 182 | } 183 | 184 | #table-scroll{ 185 | overflow:auto; 186 | margin-top:20px; 187 | height: 475px; 188 | border: 1px solid #ccc; 189 | border-radius: 3px; 190 | box-shadow: -5px -5px 5px #888; 191 | } 192 | 193 | #table-prefix{ 194 | background: white; 195 | margin-top: 20px; 196 | width: 265px; 197 | border: 1px solid #ccc; 198 | border-radius: 3px; 199 | box-shadow: -5px -5px 5px #888 200 | } 201 | 202 | #table-prefix td{ 203 | padding:5px; 204 | } 205 | 206 | input:required { 207 | box-shadow: none; 208 | } 209 | -------------------------------------------------------------------------------- /website/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Solenoid Demo on IOS-XR 7 | 8 | 9 | 10 | 11 |
12 | 17 | 18 |
19 | 20 |
21 |
22 |
23 |

Latest Update on ExaBGP

24 |

Solenoid is currently listening to the updates that Exabgp recieves. It will then inject the announce and withdraw request into the rib table. A controller of any sort can be in place of exabgp.

25 |

26 |

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Peer IPUpdate TypeNext HopNetworkTime
47 |
48 |

49 |
50 |

RIB table on Router

51 |

The RIB table is shown here through restconf. The RIB table is continously updated, reflecting what Solenoid has injected.

52 |

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {% for prefix in content2['Cisco-IOS-XR-ip-static-cfg:router-static']['default-vrf']['address-family']['vrfipv4']['vrf-unicast']['vrf-prefixes']['vrf-prefix'] %} 62 | 63 | 64 | 65 | 66 | {% endfor %} 67 | 68 |
PrefixNext Hop
{{prefix['prefix']}} {{prefix['vrf-route']['vrf-next-hop-table']['vrf-next-hop-next-hop-address'][0]['next-hop-address'] }}
69 |

70 |
71 |
72 |
73 | 74 | 113 | 114 | 115 |
116 | 117 | 123 |
124 | 125 | 126 | 150 | 151 | 152 | 153 | --------------------------------------------------------------------------------