This adds some scenario tests using a scenario test tool.

Signed-off-by: Fumihiko Kakuma <[email protected]>
---
 ryu/tests/integrated/bgp/__init__.py   |   0
 ryu/tests/integrated/bgp/base.py       |  81 +++++++++++
 ryu/tests/integrated/bgp/ryubgp_app.py | 238 +++++++++++++++++++++++++++++++++
 ryu/tests/integrated/bgp/test_basic.py |  47 +++++++
 ryu/tests/integrated/run_test.py       |  50 +++++++
 5 files changed, 416 insertions(+)
 create mode 100644 ryu/tests/integrated/bgp/__init__.py
 create mode 100644 ryu/tests/integrated/bgp/base.py
 create mode 100644 ryu/tests/integrated/bgp/ryubgp_app.py
 create mode 100644 ryu/tests/integrated/bgp/test_basic.py
 create mode 100644 ryu/tests/integrated/run_test.py

diff --git a/ryu/tests/integrated/bgp/__init__.py 
b/ryu/tests/integrated/bgp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ryu/tests/integrated/bgp/base.py b/ryu/tests/integrated/bgp/base.py
new file mode 100644
index 0000000..d1e4331
--- /dev/null
+++ b/ryu/tests/integrated/bgp/base.py
@@ -0,0 +1,81 @@
+# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import unittest
+
+from ryu.tests.integrated.common import docker_base as ctn_base
+from ryu.tests.integrated.common import ryubgp
+from ryu.tests.integrated.common import quagga
+
+
+class BgpSpeakerTestBase(unittest.TestCase):
+
+    checktime = 120
+
+    @classmethod
+    def setUpClass(cls):
+        cls.images = []
+        cls.containers = []
+        cls.bridges = []
+
+        cls.brdc1 = ctn_base.Bridge(name='brdc1',
+                                    subnet='192.168.10.0/24')
+        cls.bridges.append(cls.brdc1)
+
+        cls.dockerimg = ctn_base.DockerImage()
+        cls.r_img = cls.dockerimg.create_ryu()
+        cls.images.append(cls.r_img)
+        cls.q_img = cls.dockerimg.create_quagga()
+        cls.images.append(cls.q_img)
+
+        cls.r1 = ryubgp.RyuBGPContainer(name='r1', asn=64512,
+                                        router_id='192.168.0.1',
+                                        ctn_image_name=cls.r_img)
+        cls.containers.append(cls.r1)
+        cls.r1.add_route('10.10.0.0/28')
+        cls.r1.run(wait=True)
+        cls.r1_ip_cidr = cls.brdc1.addif(cls.r1)
+        cls.r1_ip = cls.r1_ip_cidr.split('/')[0]
+
+        cls.q1 = quagga.QuaggaBGPContainer(name='q1', asn=64522,
+                                           router_id='192.168.0.2',
+                                           ctn_image_name=cls.q_img)
+        cls.containers.append(cls.q1)
+        cls.q1.add_route('192.168.160.0/24')
+        cls.q1.run(wait=True)
+        cls.q1_ip_cidr = cls.brdc1.addif(cls.q1)
+        cls.q1_ip = cls.q1_ip_cidr.split('/')[0]
+
+        cls.r1.add_peer(cls.q1, bridge=cls.brdc1.name)
+        cls.q1.add_peer(cls.r1, bridge=cls.brdc1.name)
+
+        super(BgpSpeakerTestBase, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        for ctn in cls.containers:
+            try:
+                ctn.stop()
+            except ctn_base.CommandError as e:
+                pass
+            ctn.remove()
+        for br in cls.bridges:
+            br.delete()
+        for img in cls.images:
+            cls.dockerimg.remove(img)
+        super(BgpSpeakerTestBase, cls).tearDownClass()
diff --git a/ryu/tests/integrated/bgp/ryubgp_app.py 
b/ryu/tests/integrated/bgp/ryubgp_app.py
new file mode 100644
index 0000000..7e364b1
--- /dev/null
+++ b/ryu/tests/integrated/bgp/ryubgp_app.py
@@ -0,0 +1,238 @@
+# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+  Defines bases classes to create a BGP application.
+"""
+from __future__ import absolute_import
+
+import imp
+import logging
+import traceback
+from ryu import cfg
+
+from ryu.lib import hub
+from ryu.base.app_manager import RyuApp
+
+from ryu.services.protocols.bgp.api.base import call
+from ryu.services.protocols.bgp.base import add_bgp_error_metadata
+from ryu.services.protocols.bgp.base import BGPSException
+from ryu.services.protocols.bgp.base import BIN_ERROR
+from ryu.services.protocols.bgp.core_manager import CORE_MANAGER
+from ryu.services.protocols.bgp import net_ctrl
+from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
+from ryu.services.protocols.bgp.rtconf.common import BGP_SERVER_PORT
+from ryu.services.protocols.bgp.rtconf.common import DEFAULT_BGP_SERVER_PORT
+from ryu.services.protocols.bgp.rtconf.common import \
+    DEFAULT_REFRESH_MAX_EOR_TIME
+from ryu.services.protocols.bgp.rtconf.common import \
+    DEFAULT_REFRESH_STALEPATH_TIME
+from ryu.services.protocols.bgp.rtconf.common import DEFAULT_LABEL_RANGE
+from ryu.services.protocols.bgp.rtconf.common import LABEL_RANGE
+from ryu.services.protocols.bgp.rtconf.common import LOCAL_AS
+from ryu.services.protocols.bgp.rtconf.common import REFRESH_MAX_EOR_TIME
+from ryu.services.protocols.bgp.rtconf.common import REFRESH_STALEPATH_TIME
+from ryu.services.protocols.bgp.rtconf.common import ROUTER_ID
+from ryu.services.protocols.bgp.rtconf import neighbors
+from ryu.services.protocols.bgp.rtconf import vrfs
+from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4
+from ryu.services.protocols.bgp.operator import ssh
+
+try:
+    from logging.config import dictConfig
+except Exception:
+    from ryu.services.protocols.bgp.utils.dictconfig import dictConfig
+
+
+LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+
+CONF.register_opts([
+    cfg.IntOpt('bind-port', default=50002, help='rpc-port'),
+    cfg.StrOpt('bind-ip', default='0.0.0.0', help='rpc-bind-ip'),
+    cfg.StrOpt('bgp-config-file', default=None,
+               help='bgp-config-file')
+])
+
+
+@add_bgp_error_metadata(code=BIN_ERROR,
+                        sub_code=1,
+                        def_desc='Unknown bootstrap exception.')
+class ApplicationException(BGPSException):
+    """Specific Base exception related to `BSPSpeaker`."""
+    pass
+
+
+class RyuBGPSpeaker(RyuApp):
+    def __init__(self, *args, **kwargs):
+        self.bind_ip = RyuBGPSpeaker.validate_rpc_ip(CONF.bind_ip)
+        self.bind_port = RyuBGPSpeaker.validate_rpc_port(CONF.bind_port)
+        self.config_file = CONF.bgp_config_file
+        super(RyuBGPSpeaker, self).__init__(*args, **kwargs)
+
+    def start(self):
+        # Only two main green threads are required for APGW bgp-agent.
+        # One for NetworkController, another for BGPS core.
+
+        # If configuration file was provided and loaded successfully. We start
+        # BGPS core using these settings. If no configuration file is provided
+        # or if configuration file is missing minimum required settings BGPS
+        # core is not started.
+        if self.config_file:
+            LOG.debug('Loading config. from settings file.')
+            settings = self.load_config(self.config_file)
+            # Configure log settings, if available.
+            if getattr(settings, 'LOGGING', None):
+                dictConfig(settings.LOGGING)
+
+            if getattr(settings, 'BGP', None):
+                self._start_core(settings)
+
+            if getattr(settings, 'SSH', None) is not None:
+                hub.spawn(ssh.SSH_CLI_CONTROLLER.start, None, **settings.SSH)
+        # Start Network Controller to server RPC peers.
+        t = hub.spawn(net_ctrl.NET_CONTROLLER.start, *[],
+                      **{net_ctrl.NC_RPC_BIND_IP: self.bind_ip,
+                         net_ctrl.NC_RPC_BIND_PORT: self.bind_port})
+        LOG.debug('Started Network Controller')
+
+        super(RyuBGPSpeaker, self).start()
+
+        return t
+
+    @classmethod
+    def validate_rpc_ip(cls, ip):
+        """Validates given ip for use as rpc host bind address.
+        """
+        if not is_valid_ipv4(ip):
+            raise ApplicationException(desc='Invalid rpc ip address.')
+        return ip
+
+    @classmethod
+    def validate_rpc_port(cls, port):
+        """Validates give port for use as rpc server port.
+        """
+        if not port:
+            raise ApplicationException(desc='Invalid rpc port number.')
+        if isinstance(port, str):
+            port = int(port)
+
+        return port
+
+    def load_config(self, config_file):
+        """Validates give file as settings file for BGPSpeaker.
+
+        Load the configuration from file as settings module.
+        """
+        if not config_file or not isinstance(config_file, str):
+            raise ApplicationException('Invalid configuration file.')
+
+        # Check if file can be read
+        try:
+            return imp.load_source('settings', config_file)
+        except Exception as e:
+            raise ApplicationException(desc=str(e))
+
+    def _start_core(self, settings):
+        """Starts BGPS core using setting and given pool.
+        """
+        # Get common settings
+        routing_settings = settings.BGP.get('routing')
+        common_settings = {}
+
+        # Get required common settings.
+        try:
+            common_settings[LOCAL_AS] = routing_settings.pop(LOCAL_AS)
+            common_settings[ROUTER_ID] = routing_settings.pop(ROUTER_ID)
+        except KeyError as e:
+            raise ApplicationException(
+                desc='Required minimum configuration missing %s' %
+                     e)
+
+        # Get optional common settings
+        common_settings[BGP_SERVER_PORT] = \
+            routing_settings.get(BGP_SERVER_PORT, DEFAULT_BGP_SERVER_PORT)
+        common_settings[REFRESH_STALEPATH_TIME] = \
+            routing_settings.get(REFRESH_STALEPATH_TIME,
+                                 DEFAULT_REFRESH_STALEPATH_TIME)
+        common_settings[REFRESH_MAX_EOR_TIME] = \
+            routing_settings.get(REFRESH_MAX_EOR_TIME,
+                                 DEFAULT_REFRESH_MAX_EOR_TIME)
+        common_settings[LABEL_RANGE] = \
+            routing_settings.get(LABEL_RANGE, DEFAULT_LABEL_RANGE)
+
+        # Start BGPS core service
+        waiter = hub.Event()
+        call('core.start', waiter=waiter, **common_settings)
+        waiter.wait()
+
+        LOG.debug('Core started %s', CORE_MANAGER.started)
+        # Core manager started add configured neighbor and vrfs
+        if CORE_MANAGER.started:
+            # Add neighbors.
+            self._add_neighbors(routing_settings)
+
+            # Add Vrfs.
+            self._add_vrfs(routing_settings)
+
+            # Add Networks
+            self._add_networks(routing_settings)
+
+    def _add_neighbors(self, routing_settings):
+        """Add bgp peers/neighbors from given settings to BGPS runtime.
+
+        All valid neighbors are loaded. Miss-configured neighbors are ignored
+        and error is logged.
+        """
+        bgp_neighbors = routing_settings.setdefault('bgp_neighbors', {})
+        for ip, bgp_neighbor in bgp_neighbors.items():
+            try:
+                bgp_neighbor[neighbors.IP_ADDRESS] = ip
+                call('neighbor.create', **bgp_neighbor)
+                LOG.debug('Added neighbor %s', ip)
+            except RuntimeConfigError as re:
+                LOG.error(re)
+                LOG.error(traceback.format_exc())
+                continue
+
+    def _add_vrfs(self, routing_settings):
+        """Add VRFs from given settings to BGPS runtime.
+
+        If any of the VRFs are miss-configured errors are logged.
+        All valid VRFs are loaded.
+        """
+        vpns_conf = routing_settings.setdefault('vpns', {})
+        for vrfname, vrf in vpns_conf.items():
+            try:
+                vrf[vrfs.VRF_NAME] = vrfname
+                call('vrf.create', **vrf)
+                LOG.debug('Added vrf  %s', vrf)
+            except RuntimeConfigError as e:
+                LOG.error(e)
+                continue
+
+    def _add_networks(self, routing_settings):
+        """Add networks from given settings to BGPS runtime.
+
+        If any of the networks are miss-configured errors are logged.
+        All valid networks are loaded.
+        """
+        networks = routing_settings.setdefault('networks', [])
+        for prefix in networks:
+            try:
+                call('network.add', prefix=prefix)
+                LOG.debug('Added network %s', prefix)
+            except RuntimeConfigError as e:
+                LOG.error(e)
+                continue
diff --git a/ryu/tests/integrated/bgp/test_basic.py 
b/ryu/tests/integrated/bgp/test_basic.py
new file mode 100644
index 0000000..6321191
--- /dev/null
+++ b/ryu/tests/integrated/bgp/test_basic.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import time
+
+from . import base
+from ryu.tests.integrated.common import docker_base as ctn_base
+
+
+class BgpSpeakerBasicTest(base.BgpSpeakerTestBase):
+    def setUp(self):
+        super(BgpSpeakerBasicTest, self).setUp()
+        self.r1.stop_ryubgp(retry=True)
+        self.r1.start_ryubgp(retry=True)
+
+    def test_check_neighbor_established(self):
+        for i in range(0, self.checktime):
+            neighbor_state = self.q1.get_neighbor_state(self.r1)
+            if neighbor_state == ctn_base.BGP_FSM_ESTABLISHED:
+                break
+            time.sleep(1)
+        self.assertEqual(neighbor_state, ctn_base.BGP_FSM_ESTABLISHED)
+
+    def test_check_rib_nexthop(self):
+        for i in range(0, self.checktime):
+            neighbor_state = self.q1.get_neighbor_state(self.r1)
+            if neighbor_state == ctn_base.BGP_FSM_ESTABLISHED:
+                break
+            time.sleep(1)
+        self.assertEqual(neighbor_state, ctn_base.BGP_FSM_ESTABLISHED)
+        rib = self.q1.get_global_rib(prefix='10.10.0.0/28')
+        self.assertEqual(self.r1_ip, rib[0]['nexthop'])
diff --git a/ryu/tests/integrated/run_test.py b/ryu/tests/integrated/run_test.py
new file mode 100644
index 0000000..c7001d0
--- /dev/null
+++ b/ryu/tests/integrated/run_test.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import logging
+import os
+import sys
+import unittest
+
+from ryu import log
+
+
+def load_tests(loader, tests, pattern):
+    dirname = os.path.dirname(os.path.abspath(__file__))
+    base_path = os.path.abspath(dirname + '/../../..')
+    suite = unittest.TestSuite()
+    for test_dir in ['ryu/tests/integrated/bgp']:
+        if not pattern:
+            suite.addTests(loader.discover(test_dir,
+                                           top_level_dir=base_path))
+        else:
+            suite.addTests(loader.discover(test_dir, pattern=pattern,
+                                           top_level_dir=base_path))
+    return suite
+
+
+if __name__ == '__main__':
+    log.early_init_log(logging.DEBUG)
+    log.init_log()
+    LOG = logging.getLogger(__name__)
+    pattern = None
+    if len(sys.argv) == 2:
+        pattern = sys.argv[1]
+    loader = unittest.defaultTestLoader
+    suite = load_tests(loader, None, pattern)
+    unittest.TextTestRunner(verbosity=2).run(suite)
-- 
1.9.1


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most 
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to