[Python-modules-commits] [pyeapi] 07/12: Import pyeapi_0.7.0.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Sun Sep 25 15:45:45 UTC 2016


This is an automated email from the git hooks/post-receive script.

bernat pushed a commit to branch master
in repository pyeapi.

commit 32d585d9cdf039bde78ddb31d3f00c2d589cf96c
Author: Vincent Bernat <bernat at debian.org>
Date:   Sun Sep 25 17:41:58 2016 +0200

    Import pyeapi_0.7.0.orig.tar.gz
---
 .coveragerc                       |   3 +
 LICENSE                           |   2 +-
 MANIFEST.in                       |   1 +
 Makefile                          |   3 +-
 PKG-INFO                          |   2 +-
 VERSION                           |   2 +-
 docs/release-notes-0.7.0.rst      |  27 +++
 docs/release-notes.rst            |  29 ++--
 pyeapi.egg-info/PKG-INFO          |   2 +-
 pyeapi.egg-info/SOURCES.txt       |   5 +
 pyeapi/__init__.py                |   2 +-
 pyeapi/api/mlag.py                |   2 +-
 pyeapi/api/ospf.py                | 338 ++++++++++++++++++++++++++++++++++++++
 pyeapi/client.py                  |  40 +++--
 pyeapi/eapilib.py                 |   2 +-
 pyeapi/utils.py                   |  39 ++---
 test/fixtures/running_config.ospf |  29 ++++
 test/system/test_api_mlag.py      |   7 +-
 test/system/test_api_ospf.py      | 185 +++++++++++++++++++++
 test/system/test_client.py        |  42 ++++-
 test/unit/test_api_mlag.py        |   4 +-
 test/unit/test_api_ospf.py        | 137 +++++++++++++++
 test/unit/test_client.py          |  12 ++
 test/unit/test_utils.py           |   2 +-
 24 files changed, 847 insertions(+), 70 deletions(-)

diff --git a/.coveragerc b/.coveragerc
index 8726c6c..deafaca 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,6 +4,7 @@ branch = True
 omit = *mock*
        *test*
        *netaddr*
+       *site-packages*
 
 [report]
 # Regexes for lines to exclude from consideration
@@ -23,6 +24,8 @@ exclude_lines =
     if 0:
     if __name__ == .__main__.:
 
+show_missing = True
+
 ignore_errors = True
 
 [html]
diff --git a/LICENSE b/LICENSE
index 60a2b6a..7370f69 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014, Arista Networks EOS+
+Copyright (c) 2016, Arista Networks EOS+
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/MANIFEST.in b/MANIFEST.in
index df2bb8e..70e6dc3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -13,6 +13,7 @@ recursive-include test *.py
 recursive-include test *.text
 recursive-include test *.vxlan
 recursive-include test *.bgp
+recursive-include test *.ospf
 recursive-include test *.routemaps
 recursive-include test *.varp
 recursive-include test *.varp_null
diff --git a/Makefile b/Makefile
index 9829dcb..7cbd456 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@
 #	make unittest -- runs the unit tests
 #	make systest -- runs the system tests
 #	make clean -- clean distutils
+#	make coverage_report -- code coverage report
 #
 ########################################################
 # variable section
@@ -67,4 +68,4 @@ systest: clean
 	$(COVERAGE) run -m unittest discover test/system -v
 
 coverage_report:
-	$(COVERAGE) report -m
+	$(COVERAGE) report --rcfile=".coveragerc"
diff --git a/PKG-INFO b/PKG-INFO
index aa201b0..cb9b450 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyeapi
-Version: 0.6.1
+Version: 0.7.0
 Summary: Python Client for eAPI
 Home-page: https://github.com/arista-eosplus/pyeapi
 Author: Arista EOS+ CS
diff --git a/VERSION b/VERSION
index ee6cdce..faef31a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.6.1
+0.7.0
diff --git a/docs/release-notes-0.7.0.rst b/docs/release-notes-0.7.0.rst
new file mode 100644
index 0000000..6970888
--- /dev/null
+++ b/docs/release-notes-0.7.0.rst
@@ -0,0 +1,27 @@
+######
+v0.7.0
+######
+
+2016-09-08
+
+New Modules
+^^^^^^^^^^^
+
+* Add OSPF API (`95 <https://github.com/arista-eosplus/pyeapi/pull/95>`_) [`brigoldberg <https://github.com/brigoldberg>`_]
+    Big thanks for the community support!
+
+Enhancements
+^^^^^^^^^^^^
+
+* Enhance Node enable() method (`100 <https://github.com/arista-eosplus/pyeapi/pull/100>`_) [`dathelen <https://github.com/dathelen>`_]
+    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.
+* Finish OSPF API (`99 <https://github.com/arista-eosplus/pyeapi/pull/99>`_) [`dathelen <https://github.com/dathelen>`_]
+    Create system tests and add unit tests to increase code coverage.
+* Add Cross-Platform Support for pyeapi (`94 <https://github.com/arista-eosplus/pyeapi/pull/94>`_) [`grybak-arista <https://github.com/grybak-arista>`_]
+    Use logging instead of syslog for better cross-platform compatibility.  This enhancement provides support for Windows users.
+
+Fixed
+^^^^^
+
+* Allow dot and hyphen in mlag domain-id (`91 <https://github.com/arista-eosplus/pyeapi/issues/91>`_)
+    Include handling any character in domain-id string, including dot, hyphen, and space.
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index b406f3a..c9a806c 100644
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -6,18 +6,19 @@ Release Notes
     :maxdepth: 2
     :titlesonly:
 
-    release-notes-0.1.0.rst
-    release-notes-0.1.1.rst
-    release-notes-0.2.0.rst
-    release-notes-0.2.1.rst
-    release-notes-0.2.2.rst
-    release-notes-0.2.3.rst
-    release-notes-0.2.4.rst
-    release-notes-0.3.0.rst
-    release-notes-0.3.1.rst
-    release-notes-0.3.2.rst
-    release-notes-0.3.3.rst
-    release-notes-0.4.0.rst
-    release-notes-0.5.0.rst
-    release-notes-0.6.0.rst
+    release-notes-0.7.0.rst
     release-notes-0.6.1.rst
+    release-notes-0.6.0.rst
+    release-notes-0.5.0.rst
+    release-notes-0.4.0.rst
+    release-notes-0.3.3.rst
+    release-notes-0.3.2.rst
+    release-notes-0.3.1.rst
+    release-notes-0.3.0.rst
+    release-notes-0.2.4.rst
+    release-notes-0.2.3.rst
+    release-notes-0.2.2.rst
+    release-notes-0.2.1.rst
+    release-notes-0.2.0.rst
+    release-notes-0.1.1.rst
+    release-notes-0.1.0.rst
diff --git a/pyeapi.egg-info/PKG-INFO b/pyeapi.egg-info/PKG-INFO
index aa201b0..cb9b450 100644
--- a/pyeapi.egg-info/PKG-INFO
+++ b/pyeapi.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pyeapi
-Version: 0.6.1
+Version: 0.7.0
 Summary: Python Client for eAPI
 Home-page: https://github.com/arista-eosplus/pyeapi
 Author: Arista EOS+ CS
diff --git a/pyeapi.egg-info/SOURCES.txt b/pyeapi.egg-info/SOURCES.txt
index 0a1f5c0..ec12dd7 100644
--- a/pyeapi.egg-info/SOURCES.txt
+++ b/pyeapi.egg-info/SOURCES.txt
@@ -34,6 +34,7 @@ docs/release-notes-0.4.0.rst
 docs/release-notes-0.5.0.rst
 docs/release-notes-0.6.0.rst
 docs/release-notes-0.6.1.rst
+docs/release-notes-0.7.0.rst
 docs/release-notes.rst
 docs/requirements.rst
 docs/support.rst
@@ -59,6 +60,7 @@ pyeapi/api/interfaces.py
 pyeapi/api/ipinterfaces.py
 pyeapi/api/mlag.py
 pyeapi/api/ntp.py
+pyeapi/api/ospf.py
 pyeapi/api/routemaps.py
 pyeapi/api/spanningtree.py
 pyeapi/api/staticroute.py
@@ -77,6 +79,7 @@ test/fixtures/env_path.conf
 test/fixtures/ipinterfaces.json
 test/fixtures/nohost.conf
 test/fixtures/running_config.bgp
+test/fixtures/running_config.ospf
 test/fixtures/running_config.portchannel
 test/fixtures/running_config.routemaps
 test/fixtures/running_config.text
@@ -97,6 +100,7 @@ test/system/test_api_interfaces.py
 test/system/test_api_ipinterfaces.py
 test/system/test_api_mlag.py
 test/system/test_api_ntp.py
+test/system/test_api_ospf.py
 test/system/test_api_routemaps.py
 test/system/test_api_staticroute.py
 test/system/test_api_stp.py
@@ -113,6 +117,7 @@ test/unit/test_api_interfaces.py
 test/unit/test_api_ipinterfaces.py
 test/unit/test_api_mlag.py
 test/unit/test_api_ntp.py
+test/unit/test_api_ospf.py
 test/unit/test_api_routemaps.py
 test/unit/test_api_staticroute.py
 test/unit/test_api_stp.py
diff --git a/pyeapi/__init__.py b/pyeapi/__init__.py
index 3d330ab..2d0d25f 100644
--- a/pyeapi/__init__.py
+++ b/pyeapi/__init__.py
@@ -29,7 +29,7 @@
 # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
-__version__ = '0.6.1'
+__version__ = '0.7.0'
 __author__ = 'Arista EOS+'
 
 
diff --git a/pyeapi/api/mlag.py b/pyeapi/api/mlag.py
index f958f01..6c523d8 100644
--- a/pyeapi/api/mlag.py
+++ b/pyeapi/api/mlag.py
@@ -116,7 +116,7 @@ class Mlag(Entity):
             dict: A dict object that is intended to be merged into the
                 resource dict
         """
-        match = re.search(r'domain-id (\w+)', config)
+        match = re.search(r'domain-id (.+)$', config)
         value = match.group(1) if match else None
         return dict(domain_id=value)
 
diff --git a/pyeapi/api/ospf.py b/pyeapi/api/ospf.py
new file mode 100644
index 0000000..28c1325
--- /dev/null
+++ b/pyeapi/api/ospf.py
@@ -0,0 +1,338 @@
+#
+# Copyright (c) 2016, Arista Networks, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#   Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+#
+#   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.
+#
+#   Neither the name of Arista Networks nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+#
+# 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 ARISTA NETWORKS
+# 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.
+#
+"""Module for working with OSPF configuration in EOS
+
+This module provides an API for creating/modifying/deleting
+OSPF configurations
+
+"""
+
+import re
+from pyeapi.api import Entity
+from pyeapi.utils import make_iterable
+
+class Ospf(Entity):
+    """ The Ospf class implements global Ospf router configuration
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(Ospf, self).__init__(*args, **kwargs)
+        pass
+
+    def get(self):
+        """Returns the OSPF routing configuration
+
+           Args:
+                None
+           Returns:
+               dict:
+                    keys: router_id (int): OSPF router-id
+                          networks (dict): All networks that
+                                           are advertised in OSPF
+                          ospf_process_id (int): OSPF proc id
+                          redistribution (dict): All protocols that
+                                                 are configured to be
+                                                 redistributed in OSPF
+                          shutdown (bool): Gives the current shutdown
+                                           off the process
+        """
+
+        config = self.get_block('^router ospf .*')
+        if not config:
+            return None
+
+        response = dict()
+        response.update(self._parse_router_id(config))
+        response.update(self._parse_networks(config))
+        response.update(self._parse_ospf_process_id(config))
+        response.update(self._parse_redistribution(config))
+        response.update(self._parse_shutdown(config))
+
+        return response
+
+    def _parse_ospf_process_id(self, config):
+        """Parses config file for the OSPF proc ID
+
+           Args:
+               config(str):  Running configuration
+           Returns:
+               dict: key: ospf_process_id (int)
+        """
+        match = re.search(r'^router ospf (\d+)', config)
+        return dict(ospf_process_id=int(match.group(1)))
+
+    def _parse_router_id(self, config):
+        """Parses config file for the OSPF router ID
+
+           Args:
+               config(str):  Running configuration
+           Returns:
+               dict: key: router_id (str)
+        """
+        match = re.search(r'router-id ([^\s]+)', config)
+        value = match.group(1) if match else None
+        return dict(router_id=value)
+
+    def _parse_networks(self, config):
+        """Parses config file for the networks advertised
+           by the OSPF process
+
+           Args:
+               config(str):  Running configuration
+           Returns:
+               list: dict:
+                         keys: network (str)
+                               netmask (str)
+                               area (str)
+        """
+
+        networks = list()
+        regexp = r'network (.+)/(\d+) area (\d+\.\d+\.\d+\.\d+)'
+        matches = re.findall(regexp, config)
+        for (network, netmask, area) in matches:
+            networks.append(dict(network=network, netmask=netmask, area=area))
+        return dict(networks=networks)
+
+    def _parse_redistribution(self, config):
+        """Parses config file for the OSPF router ID
+
+           Args:
+               config (str):  Running configuration
+           Returns:
+               list: dict:
+                         keys: protocol (str)
+                               route-map (optional) (str)
+        """
+        redistributions = list()
+        regexp = r'redistribute .*'
+        matches = re.findall(regexp, config)
+        for line in matches:
+            ospf_redist = line.split()
+            if len(ospf_redist) == 2:
+                # simple redist: eg 'redistribute bgp'
+                protocol = ospf_redist[1]
+                redistributions.append(dict(protocol=protocol))
+            if len(ospf_redist) == 4:
+                # complex redist eg 'redistribute bgp route-map NYSE-RP-MAP'
+                protocol = ospf_redist[1]
+                route_map_name = ospf_redist[3]
+                redistributions.append(dict(protocol=protocol,
+                                       route_map=route_map_name))
+        return dict(redistributions=redistributions)
+
+    def _parse_shutdown(self, config):
+        """Parses config file for the OSPF router ID
+
+           Args:
+               config(str):  Running configuration
+           Returns:
+               dict: key: shutdown (bool)
+        """
+
+        value = 'no shutdown' in config
+        return dict(shutdown=not value)
+
+    def set_shutdown(self):
+        """Shutdowns the OSPF process
+
+           Args:
+               None
+           Returns:
+              bool: True if the commands are completed successfully
+        """
+
+        cmd = 'shutdown'
+        return self.configure_ospf(cmd)
+
+    def set_no_shutdown(self):
+        """Removes the shutdown property from the OSPF process
+
+           Args:
+               None
+           Returns:
+              bool: True if the commands are completed successfully
+        """
+
+
+        cmd = 'no shutdown'
+        return self.configure_ospf(cmd)
+
+    def delete(self):
+        """Removes the entire ospf process from the running configuration
+
+           Args:
+               None
+           Returns:
+               bool: True if the command completed succssfully
+        """
+        config = self.get()
+        if not config:
+            return True
+        command = 'no router ospf {}'.format(config['ospf_process_id'])
+        return self.configure(command)
+
+    def create(self, ospf_process_id):
+        """Creates a OSPF process in the default VRF
+
+           Args:
+                ospf_process_id (str): The OSPF proccess Id value
+           Returns:
+                bool: True if the command completed successfully
+           Exception:
+                ValueError: If the ospf_process_id passed in less
+                            than 0 or greater than 65536
+        """
+        value = int(ospf_process_id)
+        if not 0 < value < 65536:
+            raise ValueError('ospf as must be between 1 and 65535')
+        command = 'router ospf {}'.format(ospf_process_id)
+        return self.configure(command)
+
+    def configure_ospf(self, cmd):
+        """Allows for a list of OSPF subcommands to be configured"
+
+           Args:
+               cmd: (list or str): Subcommand to be entered
+           Returns:
+               bool: True if all the commands completed successfully
+        """
+        config = self.get()
+        cmds = ['router ospf {}'.format(config['ospf_process_id'])]
+        cmds.extend(make_iterable(cmd))
+        return super(Ospf, self).configure(cmds)
+
+    def set_router_id(self, value=None, default=False, disable=False):
+        """Controls the router id property for the OSPF Proccess
+
+           Args:
+               value (str): The router-id value
+               default (bool): Controls the use of the default keyword
+               disable (bool): Controls the use of the no keyword
+           Returns:
+               bool: True if the commands are completed successfully
+        """
+        cmd = self.command_builder('router-id', value=value,
+                                   default=default, disable=disable)
+        return self.configure_ospf(cmd)
+
+    def add_network(self, network, netmask, area=0):
+        """Adds a network to be advertised by OSPF
+
+           Args:
+               network (str):  The network to be advertised in dotted decimal
+                               notation
+               netmask (str):  The netmask to configure
+               area (str):  The area the network belongs to.
+                            By default this value is 0
+           Returns:
+               bool: True if the command completes successfully
+           Exception:
+               ValueError: This will get raised if network or netmask
+                           are not passed to the method
+        """
+        if network == '' or netmask == '':
+            raise ValueError('network and mask values '
+                             'may not be empty')
+        cmd = 'network {}/{} area {}'.format(network, netmask, area)
+        return self.configure_ospf(cmd)
+
+    def remove_network(self, network, netmask, area=0):
+        """Removes a network advertisment by OSPF
+
+           Args:
+               network (str):  The network to be removed in dotted decimal
+                               notation
+               netmask (str):  The netmask to configure
+               area (str):  The area the network belongs to.
+                            By default this value is 0
+           Returns:
+               bool: True if the command completes successfully
+           Exception:
+               ValueError: This will get raised if network or netmask
+                           are not passed to the method
+        """
+
+        if network == '' or netmask == '':
+            raise ValueError('network and mask values '
+                             'may not be empty')
+        cmd = 'no network {}/{} area {}'.format(network, netmask, area)
+        return self.configure_ospf(cmd)
+
+    def add_redistribution(self, protocol, route_map_name=None):
+        """Adds a protocol redistribution to OSPF
+
+           Args:
+               protocol (str):  protocol to redistribute
+               route_map_name (str): route-map to be used to
+                                     filter the protocols
+           Returns:
+               bool: True if the command completes successfully
+           Exception:
+               ValueError:  This will be raised if the protocol pass is not one
+                            of the following: [rip, bgp, static, connected]
+        """
+        protocols = ['bgp', 'rip', 'static', 'connected']
+        if protocol not in protocols:
+            raise ValueError('redistributed protocol must be'
+                             'bgp, connected, rip or static')
+        if route_map_name is None:
+            cmd = 'redistribute {}'.format(protocol)
+        else:
+            cmd = 'redistribute {} route-map {}'.format(protocol,
+                                                        route_map_name)
+        return self.configure_ospf(cmd)
+
+    def remove_redistribution(self, protocol):
+        """Removes a protocol redistribution to OSPF
+
+           Args:
+               protocol (str):  protocol to redistribute
+               route_map_name (str): route-map to be used to
+                                     filter the protocols
+           Returns:
+               bool: True if the command completes successfully
+           Exception:
+               ValueError:  This will be raised if the protocol pass is not one
+                            of the following: [rip, bgp, static, connected]
+        """
+
+        protocols = ['bgp', 'rip', 'static', 'connected']
+        if protocol not in protocols:
+            raise ValueError('redistributed protocol must be'
+                             'bgp, connected, rip or static')
+        cmd = 'no redistribute {}'.format(protocol)
+        return self.configure_ospf(cmd)
+
+def instance(api):
+    """Returns an instance of Ospf
+    """
+    return Ospf(api)
diff --git a/pyeapi/client.py b/pyeapi/client.py
index 6223197..ab6fb0f 100644
--- a/pyeapi/client.py
+++ b/pyeapi/client.py
@@ -91,6 +91,7 @@ contains the settings for nodes used by the connect_to function.
 
 """
 import os
+import sys
 import logging
 import re
 
@@ -104,14 +105,12 @@ except ImportError:
     from ConfigParser import SafeConfigParser
     from ConfigParser import Error as SafeConfigParserError
 
-from pyeapi.utils import load_module, make_iterable, syslog_warning
+from pyeapi.utils import load_module, make_iterable, debug
 
 from pyeapi.eapilib import HttpEapiConnection, HttpsEapiConnection
 from pyeapi.eapilib import SocketEapiConnection, HttpLocalEapiConnection
 from pyeapi.eapilib import CommandError
 
-LOGGER = logging.getLogger(__name__)
-
 CONFIG_SEARCH_PATH = ['~/.eapi.conf', '/mnt/flash/eapi.conf']
 
 TRANSPORTS = {
@@ -193,12 +192,14 @@ class Config(SafeConfigParser):
         Args:
             filename (str): The full path to the file to load
         """
+
         try:
             SafeConfigParser.read(self, filename)
         except SafeConfigParserError as exc:
             # Ignore file and syslog a message on SafeConfigParser errors
-            syslog_warning("%s: parsing error in eapi conf file: %s" %
-                           (type(exc).__name__, filename))
+            msg = ("%s: parsing error in eapi conf file: %s" %
+                   (type(exc).__name__, filename))
+            debug(msg)
 
         self._add_default_connection()
 
@@ -559,7 +560,8 @@ class Node(object):
         block_end = line_end + block_end
         return config[block_start:block_end]
 
-    def enable(self, commands, encoding='json', strict=False):
+    def enable(self, commands, encoding='json', strict=False, 
+              send_enable=True):
         """Sends the array of commands to the node in enable mode
 
         This method will send the commands to the node and evaluate
@@ -575,6 +577,8 @@ class Node(object):
 
             strict (bool): If False, this method will attempt to run a
                 command with text encoding if JSON encoding fails
+            send_enable (bool): If True the enable command will be
+                               prepended to the command list automatically.
 
         Returns:
             A dict object that includes the response for each command along
@@ -603,7 +607,7 @@ class Node(object):
         # there in error and both are now present to avoid breaking
         # existing scripts. 'response' will be removed in a future release.
         if strict:
-            responses = self.run_commands(commands, encoding)
+            responses = self.run_commands(commands, encoding, send_enable)
             for index, response in enumerate(responses):
                 results.append(dict(command=commands[index],
                                     result=response,
@@ -612,13 +616,13 @@ class Node(object):
         else:
             for command in commands:
                 try:
-                    resp = self.run_commands(command, encoding)
+                    resp = self.run_commands(command, encoding, send_enable)
                     results.append(dict(command=command,
                                         result=resp[0],
                                         encoding=encoding))
                 except CommandError as exc:
                     if exc.error_code == 1003:
-                        resp = self.run_commands(command, 'text')
+                        resp = self.run_commands(command, 'text', send_enable)
                         results.append(dict(command=command,
                                             result=resp[0],
                                             encoding='text'))
@@ -626,7 +630,7 @@ class Node(object):
                         raise
         return results
 
-    def run_commands(self, commands, encoding='json'):
+    def run_commands(self, commands, encoding='json', send_enable=True):
         """Sends the commands over the transport to the device
 
         This method sends the commands to the device using the nodes
@@ -638,6 +642,8 @@ class Node(object):
                 device using the transport
             encoding (str): The encoding method to use for the request and
                 excpected response.
+            send_enable (bool): If True the enable command will be
+                               prepended to the command list automatically.
 
         Returns:
             This method will return the raw response from the connection
@@ -657,15 +663,17 @@ class Node(object):
                      'input': '%s\n' % (c.split('MULTILINE:')[1].strip())}
                     if 'MULTILINE:' in c else c for c in commands]
 
-        if self._enablepwd:
-            commands.insert(0, {'cmd': 'enable', 'input': self._enablepwd})
-        else:
-            commands.insert(0, 'enable')
+        if send_enable:
+            if self._enablepwd:
+                commands.insert(0, {'cmd': 'enable', 'input': self._enablepwd})
+            else:
+                commands.insert(0, 'enable')
 
         response = self._connection.execute(commands, encoding)
 
-        # pop enable command from the response
-        response['result'].pop(0)
+        # pop enable command from the response only if we sent enable
+        if send_enable:
+            response['result'].pop(0)
 
         return response['result']
 
diff --git a/pyeapi/eapilib.py b/pyeapi/eapilib.py
index 267ecfa..26b867e 100644
--- a/pyeapi/eapilib.py
+++ b/pyeapi/eapilib.py
@@ -389,7 +389,7 @@ class EapiConnection(object):
                 # For Python 3.x - decode bytes into string
                 response_content = response_content.decode()
             decoded = json.loads(response_content)
-            debug('eapi_response: %s' % decoded)
+            _LOGGER.debug('eapi_response: %s' % decoded)
 
             if 'error' in decoded:
                 (code, msg, err, out) = self._parse_error_message(decoded)
diff --git a/pyeapi/utils.py b/pyeapi/utils.py
index 9af4841..82fd140 100644
--- a/pyeapi/utils.py
+++ b/pyeapi/utils.py
@@ -32,9 +32,9 @@
 import os
 import sys
 import imp
+import inspect
 import logging
 import logging.handlers
-import syslog
 import collections
 
 from itertools import tee
@@ -47,10 +47,20 @@ except ImportError:
     from itertools import izip_longest as zip_longest
 
 _LOGGER = logging.getLogger(__name__)
+_LOGGER.setLevel(logging.DEBUG)
 
-_syslog_handler = logging.handlers.SysLogHandler()
+# Create a handler to log messages to syslog
+if sys.platform == "darwin":
+    _syslog_handler = logging.handlers.SysLogHandler(address='/var/run/syslog')
+else:
+    _syslog_handler = logging.handlers.SysLogHandler()
 _LOGGER.addHandler(_syslog_handler)
-_LOGGER.setLevel(logging.INFO)
+
+# Create a handler to log messages to stderr
+_stderr_formatter = logging.Formatter('\n\n******** LOG NOTE ********\n%(message)s\n')
+_stderr_handler = logging.StreamHandler()
+_stderr_handler.setFormatter(_stderr_formatter)
+_LOGGER.addHandler(_stderr_handler)
 
 def import_module(name):
     """ Imports a module into the current runtime environment
@@ -140,26 +150,17 @@ def islocalconnection():
     return os.path.exists('/etc/Eos-release')
 
 def debug(text):
-    """Prints text to syslog when on a local connection
-
-    Args:
-        text (str): The string object to print to syslog
-
-    """
-
-    if islocalconnection():
-        _LOGGER.debug(text)
-
-def syslog_warning(text):
-    """Print text to syslog at warning level
+    """Log a message to syslog and stderr
 
     Args:
-        text (str): The string object to print to syslog
+        text (str): The string object to print
 
     """
-
-    syslog.openlog("pyeapi")
-    syslog.syslog(syslog.LOG_WARNING, text)
+    frame = inspect.currentframe().f_back
+    module = frame.f_globals['__name__']
+    func = frame.f_code.co_name
+    msg = "%s.%s: %s" % (module, func, text)
+    _LOGGER.debug(msg)
 
 def make_iterable(value):
     """Converts the supplied value to a list object
diff --git a/test/fixtures/running_config.ospf b/test/fixtures/running_config.ospf
new file mode 100644
index 0000000..92f03b6
--- /dev/null
+++ b/test/fixtures/running_config.ospf
@@ -0,0 +1,29 @@
+!
+ip routing
+!
+router ospf 65000
+   router-id 1.1.1.1
+   no bfd all-interfaces
+   distance ospf intra-area 110
+   distance ospf external 110
+   distance ospf inter-area 110
+   redistribute bgp route-map RM-IN
+   redistribute bgp route-map RM-OUT
+   redistribute static
+   area 0.0.0.0 default-cost 10
+   network 172.16.10.0/24 area 0.0.0.0
+   network 172.17.0.0/16 area 0.0.0.0
+   max-lsa 12000 75 ignore-time 5 ignore-count 5 reset-time 5
+   adjacency exchange-start threshold 20
+   log-adjacency-changes
+   timers throttle spf 0 5000 5000
+   timers lsa arrival 1000
+   timers throttle lsa all 1000 5000 5000
+   no timers out-delay
+   maximum-paths 128
+   no timers pacing flood
+   no max-metric router-lsa
+   point-to-point routes
+   no graceful-restart
+!
+!
diff --git a/test/system/test_api_mlag.py b/test/system/test_api_mlag.py
index e7a75c7..7c395c7 100644
--- a/test/system/test_api_mlag.py
+++ b/test/system/test_api_mlag.py
@@ -55,9 +55,10 @@ class TestApiMlag(DutSystemTest):
             dut.config('default mlag configuration')
             api = dut.api('mlag')
             self.assertIn('no domain-id', api.get_block('mlag configuration'))
-            result = dut.api('mlag').set_domain_id('test')
-            self.assertTrue(result)
-            self.assertIn('domain-id test', api.get_block('mlag configuration'))
+            for domid in ['test_domain_id', 'test.dom-id', 'test domain id']:
+                result = dut.api('mlag').set_domain_id(domid)
+                self.assertTrue(result)
+                self.assertIn('domain-id %s' % domid, api.get_block('mlag configuration'))
 
     def test_set_domain_id_with_no_value(self):
         for dut in self.duts:
diff --git a/test/system/test_api_ospf.py b/test/system/test_api_ospf.py
new file mode 100644
index 0000000..bed21cd
--- /dev/null
+++ b/test/system/test_api_ospf.py
@@ -0,0 +1,185 @@
+#
+# Copyright (c) 2016, Arista Networks, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#   Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+#
+#   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.
+#
+#   Neither the name of Arista Networks nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+#
+# 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 ARISTA NETWORKS
+# 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.
+#
+import os
+import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))
+
+from random import randint
+from systestlib import DutSystemTest
+
+def clear_ospf_config(dut, id=None):
+    if id is None:
+        try:
+            id = int(dut.get_config(params="section ospf")[0].split()[2])
+            dut.config(['no router ospf %d' % id])
+        except IndexError:
+            '''No OSPF configured'''
+            pass
+
+class TestApiOspf(DutSystemTest):
+    def test_get(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            dut.config(["router ospf 1", "router-id 1.1.1.1", "network 2.2.2.0/24 area 0",
+                        "redistribute bgp"])
+            ospf_response = dut.api('ospf').get()
+            config = dict(router_id="1.1.1.1", ospf_process_id=1,
+                          networks=[dict(netmask='24', network="2.2.2.0", area="0.0.0.0")],
+                          redistributions=[dict(protocol="bgp")], shutdown=False)
+            self.assertEqual(ospf_response, config)
+
+    def test_shutdown(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            dut.config(["router ospf 1", "network 1.1.1.1/32 area 0"])
+            ospf = dut.api('ospf')
+            response = ospf.set_shutdown()
+            self.assertTrue(response)
+            self.assertIn('shutdown', ospf.get_block("router ospf 1"))
+
+    def test_no_shutown(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            dut.config(["router ospf 10", "network 1.1.1.0/24 area 0",
+                       "shutdown"])
+            ospf = dut.api('ospf')
+            response = ospf.set_no_shutdown()
+            self.assertTrue(response)
+            self.assertIn('no shutdown', ospf.get_block("router ospf 10"))
+
+    def test_delete(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            dut.config(["router ospf 10"])
+            ospf = dut.api("ospf")
+            response = ospf.delete()
+            self.assertTrue(response)
+            self.assertEqual(None, ospf.get_block("router ospf"))
+
+    def test_create_valid_id(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            id = randint(1, 65536)
+            ospf = dut.api("ospf")
+            response = ospf.create(id)
+            self.assertTrue(response)
+            self.assertIn("router ospf {}".format(id), dut.get_config())
+
+    def test_create_invalid_id(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            id = randint(70000, 100000)
+            with self.assertRaises(ValueError):
+                dut.api("ospf").create(id)
+
+    def test_configure_ospf(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            dut.config(["router ospf 1"])
+            ospf = dut.api("ospf")
+            response = ospf.configure_ospf("router-id 1.1.1.1")
+            self.assertTrue(response)
+            self.assertIn("router-id 1.1.1.1", ospf.get_block("router ospf 1"))
+
+    def test_set_router_id(self):
+        for dut in self.duts:
+            clear_ospf_config(dut)
+            dut.config(["router ospf 1"])
... 341 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/pyeapi.git



More information about the Python-modules-commits mailing list