├── .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 | 
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 |
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 | Prefix
57 | Next Hop
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 | {{prefix['prefix']}}
64 | {{prefix['vrf-route']['vrf-next-hop-table']['vrf-next-hop-next-hop-address'][0]['next-hop-address'] }}
65 |
66 | {% endfor %}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
Topology
77 |
78 |
A BGP network is shown above. Networks are automatically announced and withdrawn by another bgp speaker at a fixed interval.
79 |
Solenoid is running in an Ubuntu container, that is sitting on an IOS-XRv image.
80 |
Prefix Filtering
81 |
Solenoid can use filtering to only pass designated prefixes that fall in a prefix range or indivdual prefix
82 |
In the website you can add or remove prefixes or a prefix range using the toggle key and input box
83 |
84 |
96 |
97 |
98 |
99 |
100 | Prefix List
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
123 |
124 |
125 |
126 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------