Hi Iwase-san, Thank you for review.
On Mon, 24 Oct 2016 11:40:40 +0900 Iwase Yusuke <[email protected]> wrote: > Hi Kakuma-San, > > > On 2016年10月24日 11:12, fumihiko kakuma wrote: > > 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 > > +++++++++++++++++++++++++++++++++ > > Is this file is just a copy of ryu/services/protocols/bgp/application.py? > Can we use the original one? Yes, it is the same except 'from ryu import cfg' line. I thought that this app may need peculiar customization to a test. So I copied it. I know it has some problems and it is troblesome to maintain two files. I will use original one. > > BTW, this file has some problems(e.g. options for this app are not passed to > 'ryu-manager'), > I am re-implementing it as following. > (Currently, this is working in progress, though...) > > https://github.com/osrg/ryu/compare/master...iwaseyusuke:BGPSpeaker-Sort_out_application_for_BGPSpeaker > > > > 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) > > -- fumihiko kakuma <[email protected]> ------------------------------------------------------------------------------ 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
