Codebase list pyeapi / 32d585d
Import pyeapi_0.7.0.orig.tar.gz Vincent Bernat 7 years ago
24 changed file(s) with 852 addition(s) and 75 deletion(s). Raw diff Collapse all Expand all
33 omit = *mock*
44 *test*
55 *netaddr*
6 *site-packages*
67
78 [report]
89 # Regexes for lines to exclude from consideration
2223 if 0:
2324 if __name__ == .__main__.:
2425
26 show_missing = True
27
2528 ignore_errors = True
2629
2730 [html]
0 Copyright (c) 2014, Arista Networks EOS+
0 Copyright (c) 2016, Arista Networks EOS+
11 All rights reserved.
22
33 Redistribution and use in source and binary forms, with or without
1212 recursive-include test *.text
1313 recursive-include test *.vxlan
1414 recursive-include test *.bgp
15 recursive-include test *.ospf
1516 recursive-include test *.routemaps
1617 recursive-include test *.varp
1718 recursive-include test *.varp_null
1212 # make unittest -- runs the unit tests
1313 # make systest -- runs the system tests
1414 # make clean -- clean distutils
15 # make coverage_report -- code coverage report
1516 #
1617 ########################################################
1718 # variable section
6667 $(COVERAGE) run -m unittest discover test/system -v
6768
6869 coverage_report:
69 $(COVERAGE) report -m
70 $(COVERAGE) report --rcfile=".coveragerc"
00 Metadata-Version: 1.1
11 Name: pyeapi
2 Version: 0.6.1
2 Version: 0.7.0
33 Summary: Python Client for eAPI
44 Home-page: https://github.com/arista-eosplus/pyeapi
55 Author: Arista EOS+ CS
0 0.6.1
0 0.7.0
0 ######
1 v0.7.0
2 ######
3
4 2016-09-08
5
6 New Modules
7 ^^^^^^^^^^^
8
9 * Add OSPF API (`95 <https://github.com/arista-eosplus/pyeapi/pull/95>`_) [`brigoldberg <https://github.com/brigoldberg>`_]
10 Big thanks for the community support!
11
12 Enhancements
13 ^^^^^^^^^^^^
14
15 * Enhance Node enable() method (`100 <https://github.com/arista-eosplus/pyeapi/pull/100>`_) [`dathelen <https://github.com/dathelen>`_]
16 This enhancement adds a send_enable flag to the enable and run_commands Node methods. By default the enable command will be sent, however you can now run commands without prepending the enable.
17 * Finish OSPF API (`99 <https://github.com/arista-eosplus/pyeapi/pull/99>`_) [`dathelen <https://github.com/dathelen>`_]
18 Create system tests and add unit tests to increase code coverage.
19 * Add Cross-Platform Support for pyeapi (`94 <https://github.com/arista-eosplus/pyeapi/pull/94>`_) [`grybak-arista <https://github.com/grybak-arista>`_]
20 Use logging instead of syslog for better cross-platform compatibility. This enhancement provides support for Windows users.
21
22 Fixed
23 ^^^^^
24
25 * Allow dot and hyphen in mlag domain-id (`91 <https://github.com/arista-eosplus/pyeapi/issues/91>`_)
26 Include handling any character in domain-id string, including dot, hyphen, and space.
55 :maxdepth: 2
66 :titlesonly:
77
8 release-notes-0.7.0.rst
9 release-notes-0.6.1.rst
10 release-notes-0.6.0.rst
11 release-notes-0.5.0.rst
12 release-notes-0.4.0.rst
13 release-notes-0.3.3.rst
14 release-notes-0.3.2.rst
15 release-notes-0.3.1.rst
16 release-notes-0.3.0.rst
17 release-notes-0.2.4.rst
18 release-notes-0.2.3.rst
19 release-notes-0.2.2.rst
20 release-notes-0.2.1.rst
21 release-notes-0.2.0.rst
22 release-notes-0.1.1.rst
823 release-notes-0.1.0.rst
9 release-notes-0.1.1.rst
10 release-notes-0.2.0.rst
11 release-notes-0.2.1.rst
12 release-notes-0.2.2.rst
13 release-notes-0.2.3.rst
14 release-notes-0.2.4.rst
15 release-notes-0.3.0.rst
16 release-notes-0.3.1.rst
17 release-notes-0.3.2.rst
18 release-notes-0.3.3.rst
19 release-notes-0.4.0.rst
20 release-notes-0.5.0.rst
21 release-notes-0.6.0.rst
22 release-notes-0.6.1.rst
2828 # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
2929 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030 #
31 __version__ = '0.6.1'
31 __version__ = '0.7.0'
3232 __author__ = 'Arista EOS+'
3333
3434
115115 dict: A dict object that is intended to be merged into the
116116 resource dict
117117 """
118 match = re.search(r'domain-id (\w+)', config)
118 match = re.search(r'domain-id (.+)$', config)
119119 value = match.group(1) if match else None
120120 return dict(domain_id=value)
121121
0 #
1 # Copyright (c) 2016, Arista Networks, Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 # Redistributions of source code must retain the above copyright notice,
9 # this list of conditions and the following disclaimer.
10 #
11 # Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 #
15 # Neither the name of Arista Networks nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
23 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26 # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28 # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
29 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #
31 """Module for working with OSPF configuration in EOS
32
33 This module provides an API for creating/modifying/deleting
34 OSPF configurations
35
36 """
37
38 import re
39 from pyeapi.api import Entity
40 from pyeapi.utils import make_iterable
41
42 class Ospf(Entity):
43 """ The Ospf class implements global Ospf router configuration
44 """
45
46 def __init__(self, *args, **kwargs):
47 super(Ospf, self).__init__(*args, **kwargs)
48 pass
49
50 def get(self):
51 """Returns the OSPF routing configuration
52
53 Args:
54 None
55 Returns:
56 dict:
57 keys: router_id (int): OSPF router-id
58 networks (dict): All networks that
59 are advertised in OSPF
60 ospf_process_id (int): OSPF proc id
61 redistribution (dict): All protocols that
62 are configured to be
63 redistributed in OSPF
64 shutdown (bool): Gives the current shutdown
65 off the process
66 """
67
68 config = self.get_block('^router ospf .*')
69 if not config:
70 return None
71
72 response = dict()
73 response.update(self._parse_router_id(config))
74 response.update(self._parse_networks(config))
75 response.update(self._parse_ospf_process_id(config))
76 response.update(self._parse_redistribution(config))
77 response.update(self._parse_shutdown(config))
78
79 return response
80
81 def _parse_ospf_process_id(self, config):
82 """Parses config file for the OSPF proc ID
83
84 Args:
85 config(str): Running configuration
86 Returns:
87 dict: key: ospf_process_id (int)
88 """
89 match = re.search(r'^router ospf (\d+)', config)
90 return dict(ospf_process_id=int(match.group(1)))
91
92 def _parse_router_id(self, config):
93 """Parses config file for the OSPF router ID
94
95 Args:
96 config(str): Running configuration
97 Returns:
98 dict: key: router_id (str)
99 """
100 match = re.search(r'router-id ([^\s]+)', config)
101 value = match.group(1) if match else None
102 return dict(router_id=value)
103
104 def _parse_networks(self, config):
105 """Parses config file for the networks advertised
106 by the OSPF process
107
108 Args:
109 config(str): Running configuration
110 Returns:
111 list: dict:
112 keys: network (str)
113 netmask (str)
114 area (str)
115 """
116
117 networks = list()
118 regexp = r'network (.+)/(\d+) area (\d+\.\d+\.\d+\.\d+)'
119 matches = re.findall(regexp, config)
120 for (network, netmask, area) in matches:
121 networks.append(dict(network=network, netmask=netmask, area=area))
122 return dict(networks=networks)
123
124 def _parse_redistribution(self, config):
125 """Parses config file for the OSPF router ID
126
127 Args:
128 config (str): Running configuration
129 Returns:
130 list: dict:
131 keys: protocol (str)
132 route-map (optional) (str)
133 """
134 redistributions = list()
135 regexp = r'redistribute .*'
136 matches = re.findall(regexp, config)
137 for line in matches:
138 ospf_redist = line.split()
139 if len(ospf_redist) == 2:
140 # simple redist: eg 'redistribute bgp'
141 protocol = ospf_redist[1]
142 redistributions.append(dict(protocol=protocol))
143 if len(ospf_redist) == 4:
144 # complex redist eg 'redistribute bgp route-map NYSE-RP-MAP'
145 protocol = ospf_redist[1]
146 route_map_name = ospf_redist[3]
147 redistributions.append(dict(protocol=protocol,
148 route_map=route_map_name))
149 return dict(redistributions=redistributions)
150
151 def _parse_shutdown(self, config):
152 """Parses config file for the OSPF router ID
153
154 Args:
155 config(str): Running configuration
156 Returns:
157 dict: key: shutdown (bool)
158 """
159
160 value = 'no shutdown' in config
161 return dict(shutdown=not value)
162
163 def set_shutdown(self):
164 """Shutdowns the OSPF process
165
166 Args:
167 None
168 Returns:
169 bool: True if the commands are completed successfully
170 """
171
172 cmd = 'shutdown'
173 return self.configure_ospf(cmd)
174
175 def set_no_shutdown(self):
176 """Removes the shutdown property from the OSPF process
177
178 Args:
179 None
180 Returns:
181 bool: True if the commands are completed successfully
182 """
183
184
185 cmd = 'no shutdown'
186 return self.configure_ospf(cmd)
187
188 def delete(self):
189 """Removes the entire ospf process from the running configuration
190
191 Args:
192 None
193 Returns:
194 bool: True if the command completed succssfully
195 """
196 config = self.get()
197 if not config:
198 return True
199 command = 'no router ospf {}'.format(config['ospf_process_id'])
200 return self.configure(command)
201
202 def create(self, ospf_process_id):
203 """Creates a OSPF process in the default VRF
204
205 Args:
206 ospf_process_id (str): The OSPF proccess Id value
207 Returns:
208 bool: True if the command completed successfully
209 Exception:
210 ValueError: If the ospf_process_id passed in less
211 than 0 or greater than 65536
212 """
213 value = int(ospf_process_id)
214 if not 0 < value < 65536:
215 raise ValueError('ospf as must be between 1 and 65535')
216 command = 'router ospf {}'.format(ospf_process_id)
217 return self.configure(command)
218
219 def configure_ospf(self, cmd):
220 """Allows for a list of OSPF subcommands to be configured"
221
222 Args:
223 cmd: (list or str): Subcommand to be entered
224 Returns:
225 bool: True if all the commands completed successfully
226 """
227 config = self.get()
228 cmds = ['router ospf {}'.format(config['ospf_process_id'])]
229 cmds.extend(make_iterable(cmd))
230 return super(Ospf, self).configure(cmds)
231
232 def set_router_id(self, value=None, default=False, disable=False):
233 """Controls the router id property for the OSPF Proccess
234
235 Args:
236 value (str): The router-id value
237 default (bool): Controls the use of the default keyword
238 disable (bool): Controls the use of the no keyword
239 Returns:
240 bool: True if the commands are completed successfully
241 """
242 cmd = self.command_builder('router-id', value=value,
243 default=default, disable=disable)
244 return self.configure_ospf(cmd)
245
246 def add_network(self, network, netmask, area=0):
247 """Adds a network to be advertised by OSPF
248
249 Args:
250 network (str): The network to be advertised in dotted decimal
251 notation
252 netmask (str): The netmask to configure
253 area (str): The area the network belongs to.
254 By default this value is 0
255 Returns:
256 bool: True if the command completes successfully
257 Exception:
258 ValueError: This will get raised if network or netmask
259 are not passed to the method
260 """
261 if network == '' or netmask == '':
262 raise ValueError('network and mask values '
263 'may not be empty')
264 cmd = 'network {}/{} area {}'.format(network, netmask, area)
265 return self.configure_ospf(cmd)
266
267 def remove_network(self, network, netmask, area=0):
268 """Removes a network advertisment by OSPF
269
270 Args:
271 network (str): The network to be removed in dotted decimal
272 notation
273 netmask (str): The netmask to configure
274 area (str): The area the network belongs to.
275 By default this value is 0
276 Returns:
277 bool: True if the command completes successfully
278 Exception:
279 ValueError: This will get raised if network or netmask
280 are not passed to the method
281 """
282
283 if network == '' or netmask == '':
284 raise ValueError('network and mask values '
285 'may not be empty')
286 cmd = 'no network {}/{} area {}'.format(network, netmask, area)
287 return self.configure_ospf(cmd)
288
289 def add_redistribution(self, protocol, route_map_name=None):
290 """Adds a protocol redistribution to OSPF
291
292 Args:
293 protocol (str): protocol to redistribute
294 route_map_name (str): route-map to be used to
295 filter the protocols
296 Returns:
297 bool: True if the command completes successfully
298 Exception:
299 ValueError: This will be raised if the protocol pass is not one
300 of the following: [rip, bgp, static, connected]
301 """
302 protocols = ['bgp', 'rip', 'static', 'connected']
303 if protocol not in protocols:
304 raise ValueError('redistributed protocol must be'
305 'bgp, connected, rip or static')
306 if route_map_name is None:
307 cmd = 'redistribute {}'.format(protocol)
308 else:
309 cmd = 'redistribute {} route-map {}'.format(protocol,
310 route_map_name)
311 return self.configure_ospf(cmd)
312
313 def remove_redistribution(self, protocol):
314 """Removes a protocol redistribution to OSPF
315
316 Args:
317 protocol (str): protocol to redistribute
318 route_map_name (str): route-map to be used to
319 filter the protocols
320 Returns:
321 bool: True if the command completes successfully
322 Exception:
323 ValueError: This will be raised if the protocol pass is not one
324 of the following: [rip, bgp, static, connected]
325 """
326
327 protocols = ['bgp', 'rip', 'static', 'connected']
328 if protocol not in protocols:
329 raise ValueError('redistributed protocol must be'
330 'bgp, connected, rip or static')
331 cmd = 'no redistribute {}'.format(protocol)
332 return self.configure_ospf(cmd)
333
334 def instance(api):
335 """Returns an instance of Ospf
336 """
337 return Ospf(api)
9090
9191 """
9292 import os
93 import sys
9394 import logging
9495 import re
9596
103104 from ConfigParser import SafeConfigParser
104105 from ConfigParser import Error as SafeConfigParserError
105106
106 from pyeapi.utils import load_module, make_iterable, syslog_warning
107 from pyeapi.utils import load_module, make_iterable, debug
107108
108109 from pyeapi.eapilib import HttpEapiConnection, HttpsEapiConnection
109110 from pyeapi.eapilib import SocketEapiConnection, HttpLocalEapiConnection
110111 from pyeapi.eapilib import CommandError
111
112 LOGGER = logging.getLogger(__name__)
113112
114113 CONFIG_SEARCH_PATH = ['~/.eapi.conf', '/mnt/flash/eapi.conf']
115114
192191 Args:
193192 filename (str): The full path to the file to load
194193 """
194
195195 try:
196196 SafeConfigParser.read(self, filename)
197197 except SafeConfigParserError as exc:
198198 # Ignore file and syslog a message on SafeConfigParser errors
199 syslog_warning("%s: parsing error in eapi conf file: %s" %
200 (type(exc).__name__, filename))
199 msg = ("%s: parsing error in eapi conf file: %s" %
200 (type(exc).__name__, filename))
201 debug(msg)
201202
202203 self._add_default_connection()
203204
558559 block_end = line_end + block_end
559560 return config[block_start:block_end]
560561
561 def enable(self, commands, encoding='json', strict=False):
562 def enable(self, commands, encoding='json', strict=False,
563 send_enable=True):
562564 """Sends the array of commands to the node in enable mode
563565
564566 This method will send the commands to the node and evaluate
574576
575577 strict (bool): If False, this method will attempt to run a
576578 command with text encoding if JSON encoding fails
579 send_enable (bool): If True the enable command will be
580 prepended to the command list automatically.
577581
578582 Returns:
579583 A dict object that includes the response for each command along
602606 # there in error and both are now present to avoid breaking
603607 # existing scripts. 'response' will be removed in a future release.
604608 if strict:
605 responses = self.run_commands(commands, encoding)
609 responses = self.run_commands(commands, encoding, send_enable)
606610 for index, response in enumerate(responses):
607611 results.append(dict(command=commands[index],
608612 result=response,
611615 else:
612616 for command in commands:
613617 try:
614 resp = self.run_commands(command, encoding)
618 resp = self.run_commands(command, encoding, send_enable)
615619 results.append(dict(command=command,
616620 result=resp[0],
617621 encoding=encoding))
618622 except CommandError as exc:
619623 if exc.error_code == 1003:
620 resp = self.run_commands(command, 'text')
624 resp = self.run_commands(command, 'text', send_enable)
621625 results.append(dict(command=command,
622626 result=resp[0],
623627 encoding='text'))
625629 raise
626630 return results
627631
628 def run_commands(self, commands, encoding='json'):
632 def run_commands(self, commands, encoding='json', send_enable=True):
629633 """Sends the commands over the transport to the device
630634
631635 This method sends the commands to the device using the nodes
637641 device using the transport
638642 encoding (str): The encoding method to use for the request and
639643 excpected response.
644 send_enable (bool): If True the enable command will be
645 prepended to the command list automatically.
640646
641647 Returns:
642648 This method will return the raw response from the connection
656662 'input': '%s\n' % (c.split('MULTILINE:')[1].strip())}
657663 if 'MULTILINE:' in c else c for c in commands]
658664
659 if self._enablepwd:
660 commands.insert(0, {'cmd': 'enable', 'input': self._enablepwd})
661 else:
662 commands.insert(0, 'enable')
665 if send_enable:
666 if self._enablepwd:
667 commands.insert(0, {'cmd': 'enable', 'input': self._enablepwd})
668 else:
669 commands.insert(0, 'enable')
663670
664671 response = self._connection.execute(commands, encoding)
665672
666 # pop enable command from the response
667 response['result'].pop(0)
673 # pop enable command from the response only if we sent enable
674 if send_enable:
675 response['result'].pop(0)
668676
669677 return response['result']
670678
388388 # For Python 3.x - decode bytes into string
389389 response_content = response_content.decode()
390390 decoded = json.loads(response_content)
391 debug('eapi_response: %s' % decoded)
391 _LOGGER.debug('eapi_response: %s' % decoded)
392392
393393 if 'error' in decoded:
394394 (code, msg, err, out) = self._parse_error_message(decoded)
3131 import os
3232 import sys
3333 import imp
34 import inspect
3435 import logging
3536 import logging.handlers
36 import syslog
3737 import collections
3838
3939 from itertools import tee
4646 from itertools import izip_longest as zip_longest
4747
4848 _LOGGER = logging.getLogger(__name__)
49
50 _syslog_handler = logging.handlers.SysLogHandler()
49 _LOGGER.setLevel(logging.DEBUG)
50
51 # Create a handler to log messages to syslog
52 if sys.platform == "darwin":
53 _syslog_handler = logging.handlers.SysLogHandler(address='/var/run/syslog')
54 else:
55 _syslog_handler = logging.handlers.SysLogHandler()
5156 _LOGGER.addHandler(_syslog_handler)
52 _LOGGER.setLevel(logging.INFO)
57
58 # Create a handler to log messages to stderr
59 _stderr_formatter = logging.Formatter('\n\n******** LOG NOTE ********\n%(message)s\n')
60 _stderr_handler = logging.StreamHandler()
61 _stderr_handler.setFormatter(_stderr_formatter)
62 _LOGGER.addHandler(_stderr_handler)
5363
5464 def import_module(name):
5565 """ Imports a module into the current runtime environment
139149 return os.path.exists('/etc/Eos-release')
140150
141151 def debug(text):
142 """Prints text to syslog when on a local connection
143
144 Args:
145 text (str): The string object to print to syslog
146
147 """
148
149 if islocalconnection():
150 _LOGGER.debug(text)
151
152 def syslog_warning(text):
153 """Print text to syslog at warning level
154
155 Args:
156 text (str): The string object to print to syslog
157
158 """
159
160 syslog.openlog("pyeapi")
161 syslog.syslog(syslog.LOG_WARNING, text)
152 """Log a message to syslog and stderr
153
154 Args:
155 text (str): The string object to print
156
157 """
158 frame = inspect.currentframe().f_back
159 module = frame.f_globals['__name__']
160 func = frame.f_code.co_name
161 msg = "%s.%s: %s" % (module, func, text)
162 _LOGGER.debug(msg)
162163
163164 def make_iterable(value):
164165 """Converts the supplied value to a list object
00 Metadata-Version: 1.1
11 Name: pyeapi
2 Version: 0.6.1
2 Version: 0.7.0
33 Summary: Python Client for eAPI
44 Home-page: https://github.com/arista-eosplus/pyeapi
55 Author: Arista EOS+ CS
3333 docs/release-notes-0.5.0.rst
3434 docs/release-notes-0.6.0.rst
3535 docs/release-notes-0.6.1.rst
36 docs/release-notes-0.7.0.rst
3637 docs/release-notes.rst
3738 docs/requirements.rst
3839 docs/support.rst
5859 pyeapi/api/ipinterfaces.py
5960 pyeapi/api/mlag.py
6061 pyeapi/api/ntp.py
62 pyeapi/api/ospf.py
6163 pyeapi/api/routemaps.py
6264 pyeapi/api/spanningtree.py
6365 pyeapi/api/staticroute.py
7678 test/fixtures/ipinterfaces.json
7779 test/fixtures/nohost.conf
7880 test/fixtures/running_config.bgp
81 test/fixtures/running_config.ospf
7982 test/fixtures/running_config.portchannel
8083 test/fixtures/running_config.routemaps
8184 test/fixtures/running_config.text
9699 test/system/test_api_ipinterfaces.py
97100 test/system/test_api_mlag.py
98101 test/system/test_api_ntp.py
102 test/system/test_api_ospf.py
99103 test/system/test_api_routemaps.py
100104 test/system/test_api_staticroute.py
101105 test/system/test_api_stp.py
112116 test/unit/test_api_ipinterfaces.py
113117 test/unit/test_api_mlag.py
114118 test/unit/test_api_ntp.py
119 test/unit/test_api_ospf.py
115120 test/unit/test_api_routemaps.py
116121 test/unit/test_api_staticroute.py
117122 test/unit/test_api_stp.py
0 !
1 ip routing
2 !
3 router ospf 65000
4 router-id 1.1.1.1
5 no bfd all-interfaces
6 distance ospf intra-area 110
7 distance ospf external 110
8 distance ospf inter-area 110
9 redistribute bgp route-map RM-IN
10 redistribute bgp route-map RM-OUT
11 redistribute static
12 area 0.0.0.0 default-cost 10
13 network 172.16.10.0/24 area 0.0.0.0
14 network 172.17.0.0/16 area 0.0.0.0
15 max-lsa 12000 75 ignore-time 5 ignore-count 5 reset-time 5
16 adjacency exchange-start threshold 20
17 log-adjacency-changes
18 timers throttle spf 0 5000 5000
19 timers lsa arrival 1000
20 timers throttle lsa all 1000 5000 5000
21 no timers out-delay
22 maximum-paths 128
23 no timers pacing flood
24 no max-metric router-lsa
25 point-to-point routes
26 no graceful-restart
27 !
28 !
5454 dut.config('default mlag configuration')
5555 api = dut.api('mlag')
5656 self.assertIn('no domain-id', api.get_block('mlag configuration'))
57 result = dut.api('mlag').set_domain_id('test')
58 self.assertTrue(result)
59 self.assertIn('domain-id test', api.get_block('mlag configuration'))
57 for domid in ['test_domain_id', 'test.dom-id', 'test domain id']:
58 result = dut.api('mlag').set_domain_id(domid)
59 self.assertTrue(result)
60 self.assertIn('domain-id %s' % domid, api.get_block('mlag configuration'))
6061
6162 def test_set_domain_id_with_no_value(self):
6263 for dut in self.duts:
0 #
1 # Copyright (c) 2016, Arista Networks, Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 # Redistributions of source code must retain the above copyright notice,
9 # this list of conditions and the following disclaimer.
10 #
11 # Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 #
15 # Neither the name of Arista Networks nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
23 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26 # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
28 # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
29 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #
31 import os
32 import sys
33 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))
34
35 from random import randint
36 from systestlib import DutSystemTest
37
38 def clear_ospf_config(dut, id=None):
39 if id is None:
40 try:
41 id = int(dut.get_config(params="section ospf")[0].split()[2])
42 dut.config(['no router ospf %d' % id])
43 except IndexError:
44 '''No OSPF configured'''
45 pass
46
47 class TestApiOspf(DutSystemTest):
48 def test_get(self):
49 for dut in self.duts:
50 clear_ospf_config(dut)
51 dut.config(["router ospf 1", "router-id 1.1.1.1", "network 2.2.2.0/24 area 0",
52 "redistribute bgp"])
53 ospf_response = dut.api('ospf').get()
54 config = dict(router_id="1.1.1.1", ospf_process_id=1,
55 networks=[dict(netmask='24', network="2.2.2.0", area="0.0.0.0")],
56 redistributions=[dict(protocol="bgp")], shutdown=False)
57 self.assertEqual(ospf_response, config)
58
59 def test_shutdown(self):
60 for dut in self.duts:
61 clear_ospf_config(dut)
62 dut.config(["router ospf 1", "network 1.1.1.1/32 area 0"])
63 ospf = dut.api('ospf')
64 response = ospf.set_shutdown()
65 self.assertTrue(response)
66 self.assertIn('shutdown', ospf.get_block("router ospf 1"))
67
68 def test_no_shutown(self):
69 for dut in self.duts:
70 clear_ospf_config(dut)
71 dut.config(["router ospf 10", "network 1.1.1.0/24 area 0",
72 "shutdown"])
73 ospf = dut.api('ospf')
74 response = ospf.set_no_shutdown()
75 self.assertTrue(response)
76 self.assertIn('no shutdown', ospf.get_block("router ospf 10"))
77
78 def test_delete(self):
79 for dut in self.duts:
80 clear_ospf_config(dut)
81 dut.config(["router ospf 10"])
82 ospf = dut.api("ospf")
83 response = ospf.delete()
84 self.assertTrue(response)
85 self.assertEqual(None, ospf.get_block("router ospf"))
86
87 def test_create_valid_id(self):
88 for dut in self.duts:
89 clear_ospf_config(dut)
90 id = randint(1, 65536)
91 ospf = dut.api("ospf")
92 response = ospf.create(id)
93 self.assertTrue(response)
94 self.assertIn("router ospf {}".format(id), dut.get_config())
95
96 def test_create_invalid_id(self):
97 for dut in self.duts:
98 clear_ospf_config(dut)
99 id = randint(70000, 100000)
100 with self.assertRaises(ValueError):
101 dut.api("ospf").create(id)
102
103 def test_configure_ospf(self):
104 for dut in self.duts:
105 clear_ospf_config(dut)
106 dut.config(["router ospf 1"])
107 ospf = dut.api("ospf")
108 response = ospf.configure_ospf("router-id 1.1.1.1")
109 self.assertTrue(response)
110 self.assertIn("router-id 1.1.1.1", ospf.get_block("router ospf 1"))
111
112 def test_set_router_id(self):
113 for dut in self.duts:
114 clear_ospf_config(dut)
115 dut.config(["router ospf 1"])
116 ospf = dut.api("ospf")
117 response = ospf.set_router_id(randint(1, 65536))
118 self.assertFalse(response)
119 response = ospf.set_router_id("2.2.2.2")
120 self.assertTrue(response)
121 self.assertIn("router-id 2.2.2.2", ospf.get_block("router ospf 1"))
122 response = ospf.set_router_id(default=True)
123 self.assertTrue(response)
124 self.assertIn("no router-id", ospf.get_block("router ospf 1"))
125 response = ospf.set_router_id(disable=True)
126 self.assertTrue(response)
127 self.assertIn("no router-id", ospf.get_block("router ospf 1"))
128
129 def test_add_network(self):
130 for dut in self.duts:
131 clear_ospf_config(dut)
132 dut.config(["router ospf 1"])
133 ospf = dut.api("ospf")
134 response = ospf.add_network("2.2.2.0", "24", 1234)
135 self.assertTrue(response)
136 self.assertIn("network 2.2.2.0/24 area 0.0.4.210", ospf.get_block("router ospf 1"))
137 response = ospf.add_network("10.10.10.0", "24")
138 self.assertTrue(response)
139 self.assertIn("network 10.10.10.0/24 area 0.0.0.0", ospf.get_block("router ospf 1"))
140
141 def test_remove_network(self):
142 for dut in self.duts:
143 clear_ospf_config(dut)
144 ospf_config = ["router ospf 1", "network 2.2.2.0/24 area 0.0.0.0",
145 "network 3.3.3.1/32 area 1.1.1.1"]
146 dut.config(ospf_config)
147 ospf = dut.api("ospf")
148 response = ospf.remove_network("2.2.2.0", "24")
149 self.assertTrue(response)
150 response = ospf.remove_network("3.3.3.1", "32", "1.1.1.1")
151 self.assertTrue(response)
152 for config in ospf_config:
153 if "router ospf" not in config:
154 self.assertNotIn(config, ospf.get_block("router ospf 1"))
155
156 def test_add_redistribution(self):
157 for dut in self.duts:
158 clear_ospf_config(dut)
159 dut.config(["router ospf 1"])
160 ospf = dut.api("ospf")
161 protos = ['bgp', 'rip', 'static', 'connected']
162 for proto in protos:
163 if randint(1, 10) % 2 == 0:
164 response = ospf.add_redistribution(proto, 'test')
165 else:
166 response = ospf.add_redistribution(proto)
167 self.assertTrue(response)
168 for proto in protos:
169 self.assertIn("redistribute {}".format(proto), ospf.get_block("router ospf 1"))
170 with self.assertRaises(ValueError):
171 ospf.add_redistribution("NOT VALID")
172
173 def test_remove_redistribution(self):
174 for dut in self.duts:
175 clear_ospf_config(dut)
176 dut.config(["router ospf 1", "redistribute bgp", "redistribute static route-map test"])
177 ospf = dut.api("ospf")
178 response = ospf.remove_redistribution('bgp')
179 self.assertTrue(response)
180 response = ospf.remove_redistribution('static')
181 self.assertTrue(response)
182 self.assertNotIn("redistribute", ospf.get_block("router ospf 1"))
183
184
3737 from testlib import random_int, random_string, get_fixture
3838
3939 import pyeapi.client
40
40 import pyeapi.eapilib
4141
4242 class TestClient(unittest.TestCase):
4343
6161 result = dut.run_commands('show version')
6262 self.assertIsInstance(result, list, 'dut=%s' % dut)
6363 self.assertEqual(len(result), 1, 'dut=%s' % dut)
64
65 def test_no_enable_single_command(self):
66 for dut in self.duts:
67 result = dut.run_commands('show version', 'json', send_enable=False)
68 self.assertIsInstance(result, list, 'dut=%s' % dut)
69 self.assertEqual(len(result), 1, 'dut=%s' % dut)
70
71 def test_no_enable_single_command_no_auth(self):
72 for dut in self.duts:
73 dut.run_commands('disable')
74 with self.assertRaises(pyeapi.eapilib.CommandError):
75 dut.run_commands('show running-config', 'json', send_enable=False)
6476
6577 def test_enable_multiple_commands(self):
6678 for dut in self.duts:
163175 % (1002, 'invalid command',
164176 'Invalid input \(at token \d+: \'.*\'\)')))
165177 # Send a command that cannot be run through the api
178 # note the command for reload looks to change in new EOS
179 # in 4.15 the reload now is replaced with 'force' if you are
180 # testing some DUT running older code and this test fails
181 # change the error message to the following:
182 # To reload the machine over the API, please use 'reload now' instead
166183 cases.append(('reload', rfmt
167184 % (1004, 'incompatible command',
168 'Command not permitted via API access. To reload '
169 'the machine over the API, please use \'reload '
170 'now\' instead.')))
185 'Command not permitted via API access..*')))
171186 # Send a continuous command that requires a break
172187 cases.append(('watch 10 show int e1 count rates', rfmt
173188 % (1000, 'could not run command',
174189 'init error \(cbreak\(\) returned ERR\)')))
190 # Send a command that has insufficient priv
191 cases.append(('show running-config', rfmt
192 % (1002, 'invalid command',
193 'Invalid input \(privileged mode required\)')))
194
175195
176196 for dut in self.duts:
177197 for (cmd, regex) in cases:
178198 try:
179199 # Insert the error in list of valid commands
180 dut.enable(['show version', cmd, 'show hostname'],
181 strict=True)
200 if cmd != "show running-config":
201 dut.enable(['show version', cmd, 'show hostname'],
202 strict=True)
203 else:
204 dut.enable(['disable', 'show version', cmd],
205 strict=True, send_enable=False)
206
182207 self.fail('A CommandError should have been raised')
183208 except pyeapi.eapilib.CommandError as exc:
184209 # Validate the properties of the exception
185 self.assertEqual(len(exc.trace), 4)
210 if cmd != 'show running-config':
211 self.assertEqual(len(exc.trace), 4)
212 else:
213 self.assertEqual(len(exc.trace), 3)
186214 self.assertIsNotNone(exc.command_error)
187215 self.assertIsNotNone(exc.output)
188216 self.assertIsNotNone(exc.commands)
6565 for state in ['config', 'negate', 'default']:
6666 cmds = ['mlag configuration']
6767 if state == 'config':
68 cmds.append('domain-id test')
69 func = function('set_domain_id', 'test')
68 cmds.append('domain-id test.dom-id string')
69 func = function('set_domain_id', 'test.dom-id string')
7070 elif state == 'negate':
7171 cmds.append('no domain-id')
7272 func = function('set_domain_id', value='test', disable=True)
0 import sys
1 import os
2 import unittest
3
4 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))
5
6 from testlib import get_fixture, function
7 from testlib import EapiConfigUnitTest
8
9 import pyeapi.api.ospf
10
11
12 class TestApiOspf(EapiConfigUnitTest):
13
14 def __init__(self, *args, **kwargs):
15 super(TestApiOspf, self).__init__(*args, **kwargs)
16 self.instance = pyeapi.api.ospf.instance(None)
17 self.config = open(get_fixture('running_config.ospf')).read()
18
19 def test_get(self):
20 result = self.instance.get()
21 keys = ['networks', 'ospf_process_id', 'redistributions', 'router_id', 'shutdown']
22 self.assertEqual(sorted(keys), sorted(result.keys()))
23
24 def test_create(self):
25 for ospf_id in ['65000', 65000]:
26 func = function('create', ospf_id)
27 cmds = 'router ospf {}'.format(ospf_id)
28 self.eapi_positive_config_test(func, cmds)
29
30 def test_create_invalid_id(self):
31 for ospf_id in ['66000', 66000]:
32 with self.assertRaises(ValueError):
33 self.instance.create(ospf_id)
34
35 def test_delete(self):
36 func = function('delete')
37 cmds = 'no router ospf 65000'
38 self.eapi_positive_config_test(func, cmds)
39
40 def test_add_network(self):
41 func = function('add_network', '172.16.10.0', '24', '0')
42 cmds = ['router ospf 65000', 'network 172.16.10.0/24 area 0']
43 self.eapi_positive_config_test(func, cmds)
44
45 func = function('add_network', '', '24', '0')
46 self.eapi_exception_config_test(func, ValueError)
47
48 func = function('add_network', '172.16.10.0', '', '0')
49 self.eapi_exception_config_test(func, ValueError)
50
51 def test_remove_network(self):
52 func = function('remove_network', '172.16.10.0', '24', '0')
53 cmds = ['router ospf 65000', 'no network 172.16.10.0/24 area 0']
54 self.eapi_positive_config_test(func, cmds)
55
56 func = function('remove_network', '', '24', '0')
57 self.eapi_exception_config_test(func, ValueError)
58
59 func = function('remove_network', '172.16.10.0', '', '0')
60 self.eapi_exception_config_test(func, ValueError)
61
62 def test_set_router_id(self):
63 for state in ['config', 'negate', 'default']:
64 rid = '1.1.1.1'
65 if state == 'config':
66 cmds = ['router ospf 65000', 'router-id 1.1.1.1']
67 func = function('set_router_id', rid)
68 elif state == 'negate':
69 cmds = ['router ospf 65000', 'no router-id']
70 func = function('set_router_id')
71 elif state == 'default':
72 cmds = ['router ospf 65000', 'default router-id']
73 func = function('set_router_id', rid, True)
74 self.eapi_positive_config_test(func, cmds)
75
76 cmds = ['router ospf 65000', 'no router-id']
77 func = function('set_router_id')
78 self.eapi_positive_config_test(func, cmds)
79
80 def test_set_shutdown(self):
81 for state in ['config', 'negate', 'default']:
82 if state == 'config':
83 cmds = ['router ospf 65000', 'shutdown']
84 func = function('set_shutdown')
85 elif state == 'negate':
86 cmds = ['router ospf 65000', 'no shutdown']
87 func = function('set_no_shutdown')
88 self.eapi_positive_config_test(func, cmds)
89
90 def test_add_redistribution_no_route_map(self):
91 for protocol in ['bgp', 'rip', 'static', 'connected', 'no-proto']:
92 cmds = ['router ospf 65000', 'redistribute {}'.format(protocol)]
93 func = function('add_redistribution', protocol)
94 if protocol != 'no-proto':
95 self.eapi_positive_config_test(func, cmds)
96 else:
97 self.eapi_exception_config_test(func, ValueError)
98
99 def test_add_redistribution_with_route_map(self):
100 for protocol in ['bgp', 'rip', 'static', 'connected']:
101 cmds = ['router ospf 65000', 'redistribute {} route-map test'.format(protocol)]
102 func = function('add_redistribution', protocol, 'test')
103 if protocol != 'no-proto':
104 self.eapi_positive_config_test(func, cmds)
105 else:
106 self.eapi_exception_config_test(func, ValueError)
107
108
109 def test_delete_redistribution_no_route_map(self):
110 for protocol in ['bgp', 'rip', 'static', 'connected', 'no-proto']:
111 cmds = ['router ospf 65000', 'no redistribute {}'.format(protocol)]
112 func = function('remove_redistribution', protocol)
113 if protocol != 'no-proto':
114 self.eapi_positive_config_test(func, cmds)
115 else:
116 self.eapi_exception_config_test(func, ValueError)
117
118
119 class TestApiNegOspf(EapiConfigUnitTest):
120
121 def __init__(self, *args, **kwargs):
122 super(TestApiNegOspf, self).__init__(*args, **kwargs)
123 self.instance = pyeapi.api.ospf.instance(None)
124 self.config = open(get_fixture('running_config.bgp')).read()
125
126 def test_no_get(self):
127 result = self.instance.get()
128 self.assertEqual(None, result)
129
130 def test_no_delete(self):
131 result = self.instance.delete()
132 self.assertTrue(result)
133
134 if __name__ == '__main__':
135 unittest.main()
136
5959
6060 self.connection.execute.assert_called_once_with(response, 'json')
6161 self.assertEqual(command, result[0]['result'])
62
63 def test_no_enable_with_single_command(self):
64 command = random_string()
65 response = [command]
66
67 self.connection.execute.return_value = {'result': list(response)}
68 result = self.node.enable(command, send_enable=False)
69
70 self.connection.execute.assert_called_once_with(response, 'json')
71 self.assertEqual(command, result[0]['result'])
72
73
6274
6375 def test_enable_with_multiple_commands(self):
6476 commands = list()
7575 def test_debug(self, mock_logger):
7676 pyeapi.utils.islocalconnection = Mock(return_value=True)
7777 pyeapi.utils.debug('test')
78 mock_logger.debug.assert_called_with('test')
78 mock_logger.debug.assert_called_with('test_utils.test_debug: test')