Import pyeapi_0.7.0.orig.tar.gz
Vincent Bernat
7 years ago
3 | 3 | omit = *mock* |
4 | 4 | *test* |
5 | 5 | *netaddr* |
6 | *site-packages* | |
6 | 7 | |
7 | 8 | [report] |
8 | 9 | # Regexes for lines to exclude from consideration |
22 | 23 | if 0: |
23 | 24 | if __name__ == .__main__.: |
24 | 25 | |
26 | show_missing = True | |
27 | ||
25 | 28 | ignore_errors = True |
26 | 29 | |
27 | 30 | [html] |
0 | Copyright (c) 2014, Arista Networks EOS+ | |
0 | Copyright (c) 2016, Arista Networks EOS+ | |
1 | 1 | All rights reserved. |
2 | 2 | |
3 | 3 | Redistribution and use in source and binary forms, with or without |
12 | 12 | recursive-include test *.text |
13 | 13 | recursive-include test *.vxlan |
14 | 14 | recursive-include test *.bgp |
15 | recursive-include test *.ospf | |
15 | 16 | recursive-include test *.routemaps |
16 | 17 | recursive-include test *.varp |
17 | 18 | recursive-include test *.varp_null |
12 | 12 | # make unittest -- runs the unit tests |
13 | 13 | # make systest -- runs the system tests |
14 | 14 | # make clean -- clean distutils |
15 | # make coverage_report -- code coverage report | |
15 | 16 | # |
16 | 17 | ######################################################## |
17 | 18 | # variable section |
66 | 67 | $(COVERAGE) run -m unittest discover test/system -v |
67 | 68 | |
68 | 69 | coverage_report: |
69 | $(COVERAGE) report -m | |
70 | $(COVERAGE) report --rcfile=".coveragerc" |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: pyeapi |
2 | Version: 0.6.1 | |
2 | Version: 0.7.0 | |
3 | 3 | Summary: Python Client for eAPI |
4 | 4 | Home-page: https://github.com/arista-eosplus/pyeapi |
5 | 5 | Author: Arista EOS+ CS |
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. |
5 | 5 | :maxdepth: 2 |
6 | 6 | :titlesonly: |
7 | 7 | |
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 | |
8 | 23 | 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 |
28 | 28 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
29 | 29 | # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | 30 | # |
31 | __version__ = '0.6.1' | |
31 | __version__ = '0.7.0' | |
32 | 32 | __author__ = 'Arista EOS+' |
33 | 33 | |
34 | 34 |
115 | 115 | dict: A dict object that is intended to be merged into the |
116 | 116 | resource dict |
117 | 117 | """ |
118 | match = re.search(r'domain-id (\w+)', config) | |
118 | match = re.search(r'domain-id (.+)$', config) | |
119 | 119 | value = match.group(1) if match else None |
120 | 120 | return dict(domain_id=value) |
121 | 121 |
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) |
90 | 90 | |
91 | 91 | """ |
92 | 92 | import os |
93 | import sys | |
93 | 94 | import logging |
94 | 95 | import re |
95 | 96 | |
103 | 104 | from ConfigParser import SafeConfigParser |
104 | 105 | from ConfigParser import Error as SafeConfigParserError |
105 | 106 | |
106 | from pyeapi.utils import load_module, make_iterable, syslog_warning | |
107 | from pyeapi.utils import load_module, make_iterable, debug | |
107 | 108 | |
108 | 109 | from pyeapi.eapilib import HttpEapiConnection, HttpsEapiConnection |
109 | 110 | from pyeapi.eapilib import SocketEapiConnection, HttpLocalEapiConnection |
110 | 111 | from pyeapi.eapilib import CommandError |
111 | ||
112 | LOGGER = logging.getLogger(__name__) | |
113 | 112 | |
114 | 113 | CONFIG_SEARCH_PATH = ['~/.eapi.conf', '/mnt/flash/eapi.conf'] |
115 | 114 | |
192 | 191 | Args: |
193 | 192 | filename (str): The full path to the file to load |
194 | 193 | """ |
194 | ||
195 | 195 | try: |
196 | 196 | SafeConfigParser.read(self, filename) |
197 | 197 | except SafeConfigParserError as exc: |
198 | 198 | # 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) | |
201 | 202 | |
202 | 203 | self._add_default_connection() |
203 | 204 | |
558 | 559 | block_end = line_end + block_end |
559 | 560 | return config[block_start:block_end] |
560 | 561 | |
561 | def enable(self, commands, encoding='json', strict=False): | |
562 | def enable(self, commands, encoding='json', strict=False, | |
563 | send_enable=True): | |
562 | 564 | """Sends the array of commands to the node in enable mode |
563 | 565 | |
564 | 566 | This method will send the commands to the node and evaluate |
574 | 576 | |
575 | 577 | strict (bool): If False, this method will attempt to run a |
576 | 578 | 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. | |
577 | 581 | |
578 | 582 | Returns: |
579 | 583 | A dict object that includes the response for each command along |
602 | 606 | # there in error and both are now present to avoid breaking |
603 | 607 | # existing scripts. 'response' will be removed in a future release. |
604 | 608 | if strict: |
605 | responses = self.run_commands(commands, encoding) | |
609 | responses = self.run_commands(commands, encoding, send_enable) | |
606 | 610 | for index, response in enumerate(responses): |
607 | 611 | results.append(dict(command=commands[index], |
608 | 612 | result=response, |
611 | 615 | else: |
612 | 616 | for command in commands: |
613 | 617 | try: |
614 | resp = self.run_commands(command, encoding) | |
618 | resp = self.run_commands(command, encoding, send_enable) | |
615 | 619 | results.append(dict(command=command, |
616 | 620 | result=resp[0], |
617 | 621 | encoding=encoding)) |
618 | 622 | except CommandError as exc: |
619 | 623 | if exc.error_code == 1003: |
620 | resp = self.run_commands(command, 'text') | |
624 | resp = self.run_commands(command, 'text', send_enable) | |
621 | 625 | results.append(dict(command=command, |
622 | 626 | result=resp[0], |
623 | 627 | encoding='text')) |
625 | 629 | raise |
626 | 630 | return results |
627 | 631 | |
628 | def run_commands(self, commands, encoding='json'): | |
632 | def run_commands(self, commands, encoding='json', send_enable=True): | |
629 | 633 | """Sends the commands over the transport to the device |
630 | 634 | |
631 | 635 | This method sends the commands to the device using the nodes |
637 | 641 | device using the transport |
638 | 642 | encoding (str): The encoding method to use for the request and |
639 | 643 | excpected response. |
644 | send_enable (bool): If True the enable command will be | |
645 | prepended to the command list automatically. | |
640 | 646 | |
641 | 647 | Returns: |
642 | 648 | This method will return the raw response from the connection |
656 | 662 | 'input': '%s\n' % (c.split('MULTILINE:')[1].strip())} |
657 | 663 | if 'MULTILINE:' in c else c for c in commands] |
658 | 664 | |
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') | |
663 | 670 | |
664 | 671 | response = self._connection.execute(commands, encoding) |
665 | 672 | |
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) | |
668 | 676 | |
669 | 677 | return response['result'] |
670 | 678 |
388 | 388 | # For Python 3.x - decode bytes into string |
389 | 389 | response_content = response_content.decode() |
390 | 390 | decoded = json.loads(response_content) |
391 | debug('eapi_response: %s' % decoded) | |
391 | _LOGGER.debug('eapi_response: %s' % decoded) | |
392 | 392 | |
393 | 393 | if 'error' in decoded: |
394 | 394 | (code, msg, err, out) = self._parse_error_message(decoded) |
31 | 31 | import os |
32 | 32 | import sys |
33 | 33 | import imp |
34 | import inspect | |
34 | 35 | import logging |
35 | 36 | import logging.handlers |
36 | import syslog | |
37 | 37 | import collections |
38 | 38 | |
39 | 39 | from itertools import tee |
46 | 46 | from itertools import izip_longest as zip_longest |
47 | 47 | |
48 | 48 | _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() | |
51 | 56 | _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) | |
53 | 63 | |
54 | 64 | def import_module(name): |
55 | 65 | """ Imports a module into the current runtime environment |
139 | 149 | return os.path.exists('/etc/Eos-release') |
140 | 150 | |
141 | 151 | 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) | |
162 | 163 | |
163 | 164 | def make_iterable(value): |
164 | 165 | """Converts the supplied value to a list object |
0 | 0 | Metadata-Version: 1.1 |
1 | 1 | Name: pyeapi |
2 | Version: 0.6.1 | |
2 | Version: 0.7.0 | |
3 | 3 | Summary: Python Client for eAPI |
4 | 4 | Home-page: https://github.com/arista-eosplus/pyeapi |
5 | 5 | Author: Arista EOS+ CS |
33 | 33 | docs/release-notes-0.5.0.rst |
34 | 34 | docs/release-notes-0.6.0.rst |
35 | 35 | docs/release-notes-0.6.1.rst |
36 | docs/release-notes-0.7.0.rst | |
36 | 37 | docs/release-notes.rst |
37 | 38 | docs/requirements.rst |
38 | 39 | docs/support.rst |
58 | 59 | pyeapi/api/ipinterfaces.py |
59 | 60 | pyeapi/api/mlag.py |
60 | 61 | pyeapi/api/ntp.py |
62 | pyeapi/api/ospf.py | |
61 | 63 | pyeapi/api/routemaps.py |
62 | 64 | pyeapi/api/spanningtree.py |
63 | 65 | pyeapi/api/staticroute.py |
76 | 78 | test/fixtures/ipinterfaces.json |
77 | 79 | test/fixtures/nohost.conf |
78 | 80 | test/fixtures/running_config.bgp |
81 | test/fixtures/running_config.ospf | |
79 | 82 | test/fixtures/running_config.portchannel |
80 | 83 | test/fixtures/running_config.routemaps |
81 | 84 | test/fixtures/running_config.text |
96 | 99 | test/system/test_api_ipinterfaces.py |
97 | 100 | test/system/test_api_mlag.py |
98 | 101 | test/system/test_api_ntp.py |
102 | test/system/test_api_ospf.py | |
99 | 103 | test/system/test_api_routemaps.py |
100 | 104 | test/system/test_api_staticroute.py |
101 | 105 | test/system/test_api_stp.py |
112 | 116 | test/unit/test_api_ipinterfaces.py |
113 | 117 | test/unit/test_api_mlag.py |
114 | 118 | test/unit/test_api_ntp.py |
119 | test/unit/test_api_ospf.py | |
115 | 120 | test/unit/test_api_routemaps.py |
116 | 121 | test/unit/test_api_staticroute.py |
117 | 122 | 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 | ! |
54 | 54 | dut.config('default mlag configuration') |
55 | 55 | api = dut.api('mlag') |
56 | 56 | 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')) | |
60 | 61 | |
61 | 62 | def test_set_domain_id_with_no_value(self): |
62 | 63 | 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 |
37 | 37 | from testlib import random_int, random_string, get_fixture |
38 | 38 | |
39 | 39 | import pyeapi.client |
40 | ||
40 | import pyeapi.eapilib | |
41 | 41 | |
42 | 42 | class TestClient(unittest.TestCase): |
43 | 43 | |
61 | 61 | result = dut.run_commands('show version') |
62 | 62 | self.assertIsInstance(result, list, 'dut=%s' % dut) |
63 | 63 | 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) | |
64 | 76 | |
65 | 77 | def test_enable_multiple_commands(self): |
66 | 78 | for dut in self.duts: |
163 | 175 | % (1002, 'invalid command', |
164 | 176 | 'Invalid input \(at token \d+: \'.*\'\)'))) |
165 | 177 | # 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 | |
166 | 183 | cases.append(('reload', rfmt |
167 | 184 | % (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..*'))) | |
171 | 186 | # Send a continuous command that requires a break |
172 | 187 | cases.append(('watch 10 show int e1 count rates', rfmt |
173 | 188 | % (1000, 'could not run command', |
174 | 189 | '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 | ||
175 | 195 | |
176 | 196 | for dut in self.duts: |
177 | 197 | for (cmd, regex) in cases: |
178 | 198 | try: |
179 | 199 | # 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 | ||
182 | 207 | self.fail('A CommandError should have been raised') |
183 | 208 | except pyeapi.eapilib.CommandError as exc: |
184 | 209 | # 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) | |
186 | 214 | self.assertIsNotNone(exc.command_error) |
187 | 215 | self.assertIsNotNone(exc.output) |
188 | 216 | self.assertIsNotNone(exc.commands) |
65 | 65 | for state in ['config', 'negate', 'default']: |
66 | 66 | cmds = ['mlag configuration'] |
67 | 67 | 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') | |
70 | 70 | elif state == 'negate': |
71 | 71 | cmds.append('no domain-id') |
72 | 72 | 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 |
59 | 59 | |
60 | 60 | self.connection.execute.assert_called_once_with(response, 'json') |
61 | 61 | 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 | ||
62 | 74 | |
63 | 75 | def test_enable_with_multiple_commands(self): |
64 | 76 | commands = list() |