Drop os-ken dependency

* Switch to python-docker library to start containers
* Switch to FRR image
* Improve bgp output parsing by using json
* Allow to run tests on multinode environment.
* Allow to run tests from tesmest running in container.
* Move base tests to ipv4 tests.

Related-Prod: PRODX-31417
Change-Id: Ic78a3b5d092aa9a6fd8343a856ef188dafe70b49
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index e5d7eb7..af75385 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -207,6 +207,47 @@
 CONF.register_group(bgpvpn_group)
 CONF.register_opts(BgpvpnGroup, group="bgpvpn")
 
+
+DynamicRoutingGroup = [
+    cfg.StrOpt('frr_docker_image',
+               default='quay.io/frrouting/frr:8.5.0',
+               help=("Docker image with frr.")),
+    cfg.ListOpt('frr_provider_ipv4_ips',
+                default=["10.11.12.71/24", "10.11.12.72/24", "10.11.12.73/24",
+                         "10.11.12.74/24", "10.11.12.75/24", "10.11.12.76/24"],
+                help=('List of ip addresses to bind frr containers. Require '
+                     'at least 3 items per class to run tests simulteniously. '
+                     'The addresses should be assigned on interface with '
+                     'tempest node.')),
+    cfg.ListOpt('frr_provider_ipv6_ips',
+                default=["2001:db8:a000::7001/64", "2001:db8:a000::7002/64",
+                         "2001:db8:a000::7003/64", "2001:db8:a000::7004/64",
+                         "2001:db8:a000::7005/64", "2001:db8:a000::7006/64"],
+                help=('List of ip addresses to bind frr containers. Require '
+                     'at least 3 items per class to run tests simulteniously. '
+                     'The addresses should be assigned on interface with '
+                     'tempest node.')),
+    cfg.BoolOpt('frr_bgp_ipv6_enabled',
+                default=False,
+                help=("Run dynamic routing ipv6 tests.")),
+    cfg.IntOpt('frr_bgp_timeout',
+               default=600,
+               help=('Timeout for bgp operation like setup neighborship '
+                    'or route update.')),
+    cfg.StrOpt('frr_bgp_ipv4_control_cidr',
+               default='10.0.0.0/8',
+               help=("CIDR for bgp control network on gateway nodes. Is "
+                     "used as allowed range for dynamic neighbors.")),
+    cfg.StrOpt('frr_bgp_ipv6_control_cidr',
+               default='2001:db8::/32',
+               help=("CIDR for bgp control network on gateway nodes. Is "
+                     "used as allowed range for dynamic neighbors.")),
+]
+dynamic_routing_group = cfg.OptGroup(name="dynamic_routing",
+                                     title=("Networking-DNR Service Options"))
+CONF.register_group(dynamic_routing_group)
+CONF.register_opts(DynamicRoutingGroup, group="dynamic_routing")
+
 # TODO(slaweq): This config option is added to avoid running fwaas tests twice
 # on stable branches till stable/stein. We need to remove this config option
 # once stable/stein is EOL. Fwaas tempest plugin has been merged into
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/frr/__init__.py
new file mode 100644
index 0000000..8505995
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/__init__.py
@@ -0,0 +1,185 @@
+# Copyright 2023 Mirantis Inc.
+#
+#    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.
+
+import abc
+import json
+import os
+
+import docker
+import jinja2
+from tempest import config
+import tenacity
+
+
+CONF = config.CONF
+
+
+class GenericConnector:
+    @abc.abstractmethod
+    def exec(self, cmd):
+        pass
+
+
+class ContainerConnector(GenericConnector):
+    def __init__(self, container):
+        self.ctn = container
+
+    def exec(self, cmd):
+        res_cmd = f"vtysh -d bgpd  -c '{cmd}'"
+        return self.ctn.exec_run(res_cmd)
+
+
+class BGPClient:
+    def __init__(self, connector):
+        self.connector = connector
+
+    def exec(self, cmd):
+        res = self.connector.exec(cmd)
+        if res.exit_code == 0:
+            return res.output
+        raise Exception(f"Failed to run command {cmd}")
+
+    def show_bgp_neighbors(self, *args):
+        cmd = ["show", "bgp", "neighbors", "json"]
+        cmd.extend(args)
+        return json.loads(self.exec(" ".join(cmd)))
+
+    def show_bgp_ipv4(self):
+        cmd = ["show", "bgp", "ipv4", "json"]
+        return json.loads(self.exec(" ".join(cmd)))
+
+    def show_bgp_ipv6(self):
+        cmd = ["show", "bgp", "ipv6", "json"]
+        return json.loads(self.exec(" ".join(cmd)))
+
+    def show_bgp(self):
+        cmd = ["show", "bgp", "json"]
+        return json.loads(self.exec(" ".join(cmd)))
+
+
+class FrrBGPContainer:
+    conf_dir = "/etc/frr"
+
+    def __init__(self, name, image, bgpd, daemons=None):
+        self.image = image
+        self.name = name
+        self.config_dir = os.path.join(CONF.state_path, f"ctn_base/{name}/")
+        self.volumes = [f"{self.config_dir}:{self.conf_dir}"]
+        self.daemons = daemons or {
+            "bgpd": {"enabled": "yes"},
+            "vtysh": {"enabled": "yes"},
+        }
+        self.bgpd = bgpd
+        self.docker_client = docker.from_env()
+        self._create_config_debian()
+        self.ctn = None
+        self._bgp_client = None
+
+    @property
+    def bgp_client(self):
+        if self._bgp_client is None:
+            self._bgp_client = BGPClient(ContainerConnector(self.ctn))
+        return self._bgp_client
+
+    def _create_config_debian(self):
+        environment = jinja2.Environment(
+            loader=jinja2.FileSystemLoader(
+                os.path.join(os.path.dirname(__file__), "templates")
+            )
+        )
+        template = environment.get_template("bgpd.conf")
+        if not os.path.exists(self.config_dir):
+            os.makedirs(self.config_dir)
+        for cfg_file in ["daemons", "vtysh.conf", "bgpd.conf"]:
+            with open(
+                os.path.join(self.config_dir, cfg_file),
+                mode="w",
+                encoding="utf-8",
+            ) as conf:
+                template = environment.get_template(cfg_file)
+                data = template.render(bgpd=self.bgpd, daemons=self.daemons)
+                conf.write(data)
+
+    def run(self, wait=True):
+        self.docker_client.images.pull(self.image)
+        self.ctn = self.docker_client.containers.create(
+            image=self.image,
+            name=self.name,
+            volumes=self.volumes,
+            privileged=True,
+            network_mode="host",
+        )
+        self.ctn.start()
+        if wait:
+            self._wait_running()
+
+    @tenacity.retry(
+        retry=tenacity.retry_if_result(lambda val: val is not True),
+        wait=tenacity.wait_random(min=5, max=15),
+        stop=tenacity.stop_after_delay(60),
+    )
+    def _wait_running(self):
+        self.ctn.reload()
+        if self.ctn.status == "running":
+            return True
+
+    def exec_on_ctn(self, cmd, capture=True, detach=False):
+        self.ctn.exec_run(cmd, detach=detach)
+
+    @tenacity.retry(
+        retry=tenacity.retry_if_result(lambda val: val is not True),
+        wait=tenacity.wait_random(min=5, max=15),
+        stop=tenacity.stop_after_delay(CONF.dynamic_routing.frr_bgp_timeout),
+    )
+    def bgp_check_neighbor_state(self, nei_ident, expected_state):
+        res = self.bgp_client.show_bgp_neighbors()
+        neighbor_states = [
+            nei.get("bgpState") == expected_state
+            for nei in res.values()
+            if nei.get("peerGroup") == nei_ident
+        ]
+        return len(neighbor_states) > 0 and all(neighbor_states)
+
+    @tenacity.retry(
+        retry=tenacity.retry_if_result(lambda val: val is not True),
+        wait=tenacity.wait_random(min=5, max=15),
+        stop=tenacity.stop_after_delay(CONF.dynamic_routing.frr_bgp_timeout),
+    )
+    def bgp_check_neighbor_absent(self, nei_ident):
+        res = self.bgp_client.show_bgp_neighbors()
+        neighbors = [
+            nei for nei in res.values() if nei.get("peerGroup") == nei_ident
+        ]
+        return len(neighbors) == 0
+
+    @tenacity.retry(
+        retry=tenacity.retry_if_result(lambda val: val is not True),
+        wait=tenacity.wait_random(min=5, max=15),
+        stop=tenacity.stop_after_delay(CONF.dynamic_routing.frr_bgp_timeout),
+    )
+    def bgp_check_rib(self, ip_version, cidr, nexthop=None):
+        res = getattr(self.bgp_client, f"show_bgp_{ip_version}")()
+        should = {"cidr": False}
+        if nexthop:
+            should["nexthop"] = False
+
+        for _cidr, routes in res.get("routes", {}).items():
+            if cidr == _cidr:
+                should["cidr"] = True
+            for route in routes:
+                if nexthop:
+                    for hop_data in route.get("nexthops", []):
+                        if nexthop == hop_data.get("ip"):
+                            should["nexthop"] = True
+        return all(should.values())
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/constants.py b/neutron_tempest_plugin/neutron_dynamic_routing/frr/constants.py
new file mode 100644
index 0000000..f2b96d8
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/constants.py
@@ -0,0 +1,7 @@
+# Various states of bgp state machine.
+BGP_FSM_IDLE = "Idle"
+BGP_FSM_CONNECT = "Connect"
+BGP_FSM_ACTIVE = "Active"
+BGP_FSM_OPEN_SENT = "OpenSent"
+BGP_FSM_OPEN_CONFIRM = "OpenConfirm"
+BGP_FSM_ESTABLISHED = "Established"
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/bgpd.conf b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/bgpd.conf
new file mode 100644
index 0000000..27d8fab
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/bgpd.conf
@@ -0,0 +1,23 @@
+!
+router bgp {{ bgpd.bgp.as_number }}
+ bgp router-id {{ bgpd.bgp.router_id }}
+ {%- for neighbor in bgpd.bgp.neighbors %}
+ {%- if neighbor.peer_group %}
+ neighbor {{ neighbor.address }} peer-group
+ {%- endif %}
+ neighbor {{ neighbor.address }} remote-as {{ neighbor.as_number }}
+ {%- if neighbor.get('passive', false) %}
+ neighbor {{ neighbor.address }} passive
+ {%- endif %}
+ {%- endfor %}
+ {%- if bgpd.bgp.get('listen', {}).get('limit') %}
+ bgp listen limit {{ bgpd.bgp.listen.limit }}
+ {%- endif %}
+ {%- for listen_range in bgpd.bgp.get('listen', {}).get('ranges', []) %}
+ bgp listen range {{ listen_range.cidr }} peer-group {{ listen_range.peer_group }}
+ {%- endfor %}
+ {%- if bgpd.bgp.ebgp_requires_policy is false %}
+ no bgp ebgp-requires-policy
+ {%- endif %}
+ neighbor dnr timers 10 30
+!
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/daemons b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/daemons
new file mode 100644
index 0000000..314e231
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/daemons
@@ -0,0 +1,33 @@
+zebra=no
+bgpd={{ daemons.bgpd.enabled }}
+ospfd=no
+ospf6d=no
+ripd=no
+ripngd=no
+isisd=no
+babeld=no
+
+#
+# If this option is set the /etc/init.d/frr script automatically loads
+# the config via "vtysh -b" when the servers are started.
+# Check /etc/pam.d/frr if you intend to use "vtysh"!
+#
+vtysh_enable={{ daemons.vtysh.enabled }}
+zebra_options="  -A 127.0.0.1 -s 90000000"
+bgpd_options="   -A 127.0.0.1 --listenon {{ daemons.bgpd.listenon }}"
+ospfd_options="  -A 127.0.0.1"
+ospf6d_options=" -A ::1"
+ripd_options="   -A 127.0.0.1"
+ripngd_options=" -A ::1"
+isisd_options="  -A 127.0.0.1"
+pimd_options="   -A 127.0.0.1"
+ldpd_options="   -A 127.0.0.1"
+nhrpd_options="  -A 127.0.0.1"
+eigrpd_options=" -A 127.0.0.1"
+babeld_options=" -A 127.0.0.1"
+sharpd_options=" -A 127.0.0.1"
+pbrd_options="   -A 127.0.0.1"
+staticd_options="-A 127.0.0.1"
+bfdd_options="   -A 127.0.0.1"
+fabricd_options="-A 127.0.0.1"
+vrrpd_options="  -A 127.0.0.1"
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/vtysh.conf b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/vtysh.conf
new file mode 100644
index 0000000..e0ab9cb
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/vtysh.conf
@@ -0,0 +1 @@
+service integrated-vtysh-config
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
index 44990bd..14957d3 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
@@ -14,23 +14,19 @@
        | dragent |           |
        +---------+           |
             |                |
-            | +--------------+
-            | |
-       +--------+
-       | docker |
-       | bridge |
-       +--------+
-            |
-            +-----------+------------+-------
+            |                |
+            |                |
+            |                |
+            +-----------+----+-------+-------
                         |            |
                    +---------+  +---------+
-       docker      | quagga1 |  | quagga2 | ...
+       docker      |   frr1  |  |  frr1   | ...
        container   +---------+  +---------+
 
 
-docker container environment is provided by test tool of os-ken.
+docker container environment is provided by test tools with help of
+python-docker library.
 It has the following functions:
-- build and remove a container image.
 - run, stop and remove a container.
-- some operations to quagga container.
-- get some information from quagga container.
+- some operations to frr container.
+- get some information from frr container.
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
index c7023af..2655804 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
@@ -16,11 +16,10 @@
 
 import collections
 import threading
-import time
 
+import docker
 import netaddr
 
-from os_ken.tests.integrated.common import docker_base as ctn_base
 from tempest.common import utils
 from tempest import config
 
@@ -36,10 +35,6 @@
 SubNet = collections.namedtuple('SubNet', 'name, cidr, mask')
 Router = collections.namedtuple('Router', 'name, gw')
 AS = collections.namedtuple('AS', 'asn, router_id, adv_net')
-CHECKTIME = 180
-CHECKTIME_INFO = 60
-CHECKTIME_INT = 1
-BRIDGE_TYPE = ctn_base.BRIDGE_TYPE_DOCKER
 
 
 def _setup_client_args(auth_provider):
@@ -110,7 +105,7 @@
         for ctn in cls.containers:
             try:
                 ctn.stop()
-            except ctn_base.CommandError:
+            except docker.errors.APIError:
                 pass
             ctn.remove()
         for br in cls.bridges:
@@ -249,122 +244,6 @@
                                                      peer_id)
         return (speaker_id, peer_ids)
 
-    def get_remote_as_state(self, l_as, r_as,
-                            expected_state,
-                            init_state=ctn_base.BGP_FSM_IDLE,
-                            checktime=CHECKTIME,
-                            checktime_int=CHECKTIME_INT):
-        neighbor_state = init_state
-        for i in range(0, checktime):
-            neighbor_state = r_as.get_neighbor_state(l_as)
-            if neighbor_state == expected_state:
-                break
-            time.sleep(checktime_int)
-        return neighbor_state
-
-    def check_remote_as_state(self, l_as, r_as,
-                              expected_state,
-                              init_state=ctn_base.BGP_FSM_IDLE,
-                              checktime=CHECKTIME,
-                              checktime_int=CHECKTIME_INT):
-        neighbor_state = self.get_remote_as_state(l_as, r_as,
-                                                  expected_state,
-                                                  init_state=init_state,
-                                                  checktime=checktime,
-                                                  checktime_int=checktime_int)
-        self.assertEqual(neighbor_state, expected_state)
-
-    def get_remote_as_of_state_ok(self, l_as, r_ass,
-                                  expected_state,
-                                  init_state=ctn_base.BGP_FSM_IDLE,
-                                  checktime=CHECKTIME,
-                                  checktime_int=CHECKTIME_INT):
-        neighbor_state = init_state
-        ras_list = []
-        ras_max = len(r_ass)
-        for r_as in r_ass:
-            ras_list.append({'as': r_as, 'check': False})
-        ok_ras = []
-        for i in range(0, checktime):
-            for ras in ras_list:
-                if ras['check']:
-                    continue
-                neighbor_state = ras['as'].get_neighbor_state(l_as)
-                if neighbor_state == expected_state:
-                    ras['check'] = True
-                    ok_ras.append(ras['as'])
-            if len(ok_ras) >= ras_max:
-                break
-            time.sleep(checktime_int)
-        return ok_ras
-
-    def check_multi_remote_as_state(self, l_as, r_ass,
-                                    expected_state,
-                                    init_state=ctn_base.BGP_FSM_IDLE,
-                                    checktime=CHECKTIME,
-                                    checktime_int=CHECKTIME_INT):
-        ok_ras = self.get_remote_as_of_state_ok(
-            l_as, r_ass,
-            expected_state,
-            init_state=init_state,
-            checktime=checktime,
-            checktime_int=checktime_int)
-        self.assertEqual(len(ok_ras), len(r_ass))
-
-    def get_remote_as_rib(self, r_as, prefix, rf, key, expected_item,
-                          checktime=CHECKTIME_INFO,
-                          checktime_int=CHECKTIME_INT):
-        item = None
-        for i in range(0, checktime):
-            rib = r_as.get_global_rib(prefix=prefix, rf=rf)
-            if rib and key in rib[0]:
-                if expected_item == rib[0][key]:
-                    item = rib[0][key]
-                    break
-            time.sleep(checktime_int)
-        return item
-
-    def check_remote_as_rib(self, r_as, prefix, rf, key, expected_item,
-                            checktime=CHECKTIME_INFO,
-                            checktime_int=CHECKTIME_INT):
-        item = self.get_remote_as_rib(r_as=r_as, prefix=prefix, rf=rf,
-                                      key=key, expected_item=expected_item,
-                                      checktime=checktime,
-                                      checktime_int=checktime_int)
-        self.assertEqual(expected_item, item)
-
-    def get_remote_as_of_rib_ok(self, r_ass, prefix, rf, key, expected_item,
-                                checktime=CHECKTIME_INFO,
-                                checktime_int=CHECKTIME_INT):
-        ras_list = []
-        ras_max = len(r_ass)
-        for r_as in r_ass:
-            ras_list.append({'as': r_as, 'check': False})
-        ok_ras = []
-        for i in range(0, checktime):
-            for ras in ras_list:
-                if ras['check']:
-                    continue
-                rib = r_as.get_global_rib(prefix=prefix, rf=rf)
-                if rib and key in rib[0]:
-                    if expected_item == rib[0][key]:
-                        ras['check'] = True
-                        ok_ras.append(ras['as'])
-            if len(ok_ras) >= ras_max:
-                break
-            time.sleep(checktime_int)
-        return ok_ras
-
-    def check_multi_remote_as_rib(self, r_ass, prefix, rf, key, expected_item,
-                                  checktime=CHECKTIME_INFO,
-                                  checktime_int=CHECKTIME_INT):
-        ok_ras = self.get_remote_as_of_rib_ok(
-                   r_ass=r_ass, prefix=prefix, rf=rf,
-                   key=key, expected_item=expected_item,
-                   checktime=checktime,
-                   checktime_int=checktime_int)
-        self.assertEqual(len(ok_ras), len(r_ass))
-
     def get_next_hop(self, speaker_id, dest_addr):
         routes = self.bgp_adm_client.get_bgp_advertised_routes(speaker_id)
         next_hop = ''
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
index 643620f..305ab25 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
@@ -16,10 +16,9 @@
 
 from tempest import config
 
+from neutron_tempest_plugin.neutron_dynamic_routing.frr import constants
 from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
 
-from os_ken.tests.integrated.common import docker_base as ctn_base
-
 CONF = config.CONF
 
 
@@ -41,8 +40,8 @@
             ext_net_id,
             self.bgp_speaker_args,
             [self.bgp_peer_args[0]])
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
+        self.r_ass[0].bgp_check_neighbor_state("dnr",
+                                               constants.BGP_FSM_ESTABLISHED)
 
     def _test_check_advertised_tenant_network(self, ip_version):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
@@ -60,12 +59,12 @@
             ext_net_id,
             self.bgp_speaker_args,
             [self.bgp_peer_args[0]])
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
+
+        self.r_ass[0].bgp_check_neighbor_state("dnr",
+                                               constants.BGP_FSM_ESTABLISHED)
         rf = 'ipv' + str(ip_version)
-        self.check_remote_as_rib(self.r_ass[0], TNet.cidr, rf,
-                                 'nexthop',
-                                 self.get_next_hop(speaker_id, TNet.cidr))
+        self.r_ass[0].bgp_check_rib(rf, TNet.cidr,
+                                    self.get_next_hop(speaker_id, TNet.cidr))
 
     def _test_check_advertised_multiple_tenant_network(self, ip_version):
         self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
@@ -88,13 +87,14 @@
             ext_net_id,
             self.bgp_speaker_args,
             [self.bgp_peer_args[0]])
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
+
+        self.r_ass[0].bgp_check_neighbor_state("dnr",
+                                               constants.BGP_FSM_ESTABLISHED)
+
         rf = 'ipv' + str(ip_version)
         for cidr in tnets_cidr:
-            self.check_remote_as_rib(self.r_ass[0], cidr, rf,
-                                     'nexthop',
-                                     self.get_next_hop(speaker_id, cidr))
+            self.r_ass[0].bgp_check_rib(rf, cidr,
+                                        self.get_next_hop(speaker_id, cidr))
 
     def _test_check_neighbor_established_with_multiple_peers(
             self, ip_version):
@@ -115,8 +115,8 @@
             ext_net_id,
             self.bgp_speaker_args,
             self.bgp_peer_args)
-        self.check_multi_remote_as_state(self.dr, self.r_ass,
-                                         ctn_base.BGP_FSM_ESTABLISHED)
+        for r_as in self.r_ass:
+            r_as.bgp_check_neighbor_state("dnr", constants.BGP_FSM_ESTABLISHED)
 
     def _test_check_advertised_tenant_network_with_multiple_peers(
             self, ip_version):
@@ -137,9 +137,10 @@
             ext_net_id,
             self.bgp_speaker_args,
             self.bgp_peer_args)
-        self.check_multi_remote_as_state(self.dr, self.r_ass,
-                                         ctn_base.BGP_FSM_ESTABLISHED)
+        for r_as in self.r_ass:
+            r_as.bgp_check_neighbor_state("dnr", constants.BGP_FSM_ESTABLISHED)
+
         rf = 'ipv' + str(ip_version)
-        next_hop = self.get_next_hop(speaker_id, TNet.cidr)
-        self.check_multi_remote_as_rib(self.r_ass, TNet.cidr, rf,
-                                       'nexthop', next_hop)
+        for r_as in self.r_ass:
+            r_as.bgp_check_rib(rf, TNet.cidr,
+                               self.get_next_hop(speaker_id, TNet.cidr))
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py
+++ /dev/null
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py
deleted file mode 100644
index dd170e7..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (C) 2016 VA Linux Systems Japan K.K.
-# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
-# All Rights Reserved.
-#
-#    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 os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
-
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
-
-
-class BgpSpeakerBasicTestJSONBase(base.BgpSpeakerScenarioTestJSONBase):
-
-    RAS_MAX = 3
-    public_gw = '192.168.20.1'
-    MyScope = base.Scope(name='my-scope')
-    PNet = base.Net(name='', net='172.24.6.0', mask=24,
-                    cidr='172.24.6.0/24', router=None)
-    PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
-                      prefixes=[PNet.net + '/8'])
-    PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
-    TPool = base.Pool(name='tenant-test-pool', prefixlen=28,
-                      prefixes=['10.10.0.0/16'])
-    L_AS = base.AS(asn='64512', router_id='192.168.0.2', adv_net='')
-    ras_l = [
-        base.AS(asn='64522', router_id='192.168.0.12',
-                adv_net='192.168.162.0/24'),
-        base.AS(asn='64523', router_id='192.168.0.13',
-                adv_net='192.168.163.0/24'),
-        base.AS(asn='64524', router_id='192.168.0.14',
-                adv_net='192.168.164.0/24')
-    ]
-
-    bgp_speaker_args = {
-        'local_as': L_AS.asn,
-        'ip_version': 4,
-        'name': 'my-bgp-speaker1',
-        'advertise_floating_ip_host_routes': True,
-        'advertise_tenant_networks': True
-    }
-    bgp_peer_args = [
-        {'remote_as': ras_l[0].asn,
-         'name': 'my-bgp-peer1',
-         'peer_ip': None,
-         'auth_type': 'none'},
-        {'remote_as': ras_l[1].asn,
-         'name': 'my-bgp-peer2',
-         'peer_ip': None,
-         'auth_type': 'none'},
-        {'remote_as': ras_l[2].asn,
-         'name': 'my-bgp-peer3',
-         'peer_ip': None,
-         'auth_type': 'none'}
-    ]
-
-    def setUp(self):
-        super(BgpSpeakerBasicTestJSONBase, self).setUp()
-
-    @classmethod
-    def resource_setup_container(cls):
-        cls.brdc = ctn_base.Bridge(name='br-docker-basic',
-                                   subnet='192.168.20.0/24',
-                                   start_ip='192.168.20.128',
-                                   end_ip='192.168.20.254',
-                                   self_ip=True,
-                                   fixed_ip=cls.public_gw + '/24',
-                                   br_type=base.BRIDGE_TYPE)
-        cls.bridges.append(cls.brdc)
-        # This is dummy container object for a dr service.
-        # This keeps data which passes to a quagga container.
-        cls.dr = ctn_base.BGPContainer(name='dummy-dr-basic',
-                                       asn=int(cls.L_AS.asn),
-                                       router_id=cls.L_AS.router_id)
-        cls.dr.set_addr_info(bridge='br-docker-basic', ipv4=cls.public_gw)
-        # quagga container
-        cls.dockerimg = ctn_base.DockerImage()
-        cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
-        cls.images.append(cls.q_img)
-        for i in range(cls.RAS_MAX):
-            qg = quagga.QuaggaBGPContainer(name='q-basic-' + str(i + 1),
-                                           asn=int(cls.ras_l[i].asn),
-                                           router_id=cls.ras_l[i].router_id,
-                                           ctn_image_name=cls.q_img)
-            cls.containers.append(qg)
-            cls.r_ass.append(qg)
-            qg.add_route(cls.ras_l[i].adv_net)
-            qg.run(wait=True)
-            cls.r_as_ip.append(cls.brdc.addif(qg))
-            qg.add_peer(cls.dr, bridge=cls.brdc.name,
-                        peer_info={'passive': True})
-
-        cls.tnet_gen = cls.get_subnet(start='10.10.1.0',
-                                      end='10.10.255.0',
-                                      step=256)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py
deleted file mode 100644
index f492ede..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py
+++ /dev/null
@@ -1,132 +0,0 @@
-#    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 tempest.common import utils
-from tempest import config
-from tempest.lib import decorators
-
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
-    import base_test_proto as test_base
-
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
-
-CONF = config.CONF
-
-
-class BgpSpeaker4byteASNTest(test_base.BgpSpeakerProtoTestBase):
-
-    RAS_MAX = 3
-    ip_version = 4
-    public_gw = '192.168.10.1'
-    MyScope = base.Scope(name='my-scope')
-    PNet = base.Net(name='', net='172.24.6.0', mask=24,
-                    cidr='172.24.6.0/24', router=None)
-    PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
-                      prefixes=[PNet.net + '/8'])
-    PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
-    TPool = base.Pool(name='tenant-test-pool', prefixlen=28,
-                      prefixes=['10.10.0.0/16'])
-    L_AS = base.AS(asn='4200000000', router_id='192.168.0.3', adv_net='')
-    ras_l = [
-        base.AS(asn='4210000000', router_id='192.168.0.12',
-                adv_net='192.168.162.0/24'),
-        base.AS(asn='64522', router_id='192.168.0.13',
-                adv_net='192.168.163.0/24'),
-        base.AS(asn='4230000000', router_id='192.168.0.14',
-                adv_net='192.168.164.0/24')
-    ]
-
-    bgp_speaker_args = {
-        'local_as': L_AS.asn,
-        'ip_version': ip_version,
-        'name': 'my-bgp-speaker1',
-        'advertise_floating_ip_host_routes': True,
-        'advertise_tenant_networks': True
-    }
-    bgp_peer_args = [
-        {'remote_as': ras_l[0].asn,
-         'name': 'my-bgp-peer1',
-         'peer_ip': None,
-         'auth_type': 'none'},
-        {'remote_as': ras_l[1].asn,
-         'name': 'my-bgp-peer2',
-         'peer_ip': None,
-         'auth_type': 'none'},
-        {'remote_as': ras_l[2].asn,
-         'name': 'my-bgp-peer3',
-         'peer_ip': None,
-         'auth_type': 'none'}
-    ]
-
-    @classmethod
-    @utils.requires_ext(extension="bgp_4byte_asn", service="network")
-    def resource_setup(cls):
-        super(BgpSpeaker4byteASNTest, cls).resource_setup()
-
-    @classmethod
-    def resource_setup_container(cls):
-        cls.brdc = ctn_base.Bridge(name='br-docker-4byte-asn',
-                                   subnet='192.168.10.0/24',
-                                   start_ip='192.168.10.128',
-                                   end_ip='192.168.10.254',
-                                   self_ip=True,
-                                   fixed_ip=cls.public_gw + '/24',
-                                   br_type=base.BRIDGE_TYPE)
-        cls.bridges.append(cls.brdc)
-        # This is dummy container object for a dr service.
-        # This keeps data which passes to a quagga container.
-        cls.dr = ctn_base.BGPContainer(name='dummy-dr-4byte-asn',
-                                       asn=int(cls.L_AS.asn),
-                                       router_id=cls.L_AS.router_id)
-        cls.dr.set_addr_info(bridge='br-docker-4byte-asn', ipv4=cls.public_gw)
-        # quagga container
-        cls.dockerimg = ctn_base.DockerImage()
-        cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
-        cls.images.append(cls.q_img)
-        for i in range(cls.RAS_MAX):
-            qg = quagga.QuaggaBGPContainer(name='q-4byte-asn-' + str(i + 1),
-                                           asn=int(cls.ras_l[i].asn),
-                                           router_id=cls.ras_l[i].router_id,
-                                           ctn_image_name=cls.q_img)
-            cls.containers.append(qg)
-            cls.r_ass.append(qg)
-            qg.add_route(cls.ras_l[i].adv_net)
-            qg.run(wait=True)
-            cls.r_as_ip.append(cls.brdc.addif(qg))
-            qg.add_peer(cls.dr, bridge=cls.brdc.name,
-                        peer_info={'passive': True})
-        cls.tnet_gen = cls.get_subnet(start='10.10.1.0', end='10.10.255.0',
-                                      step=256)
-
-    @decorators.idempotent_id('9f18c931-a59e-4a27-939b-21124995ffe2')
-    def test_check_neighbor_established(self):
-        self._test_check_neighbor_established(self.ip_version)
-
-    @decorators.idempotent_id('73466aa5-7d1d-4f9f-8fb4-4100fad2dffe')
-    def test_check_advertised_tenant_network(self):
-        self._test_check_advertised_tenant_network(self.ip_version)
-
-    @decorators.idempotent_id('c3158328-2f69-4aa2-b1b7-5a06ab58afaf')
-    def test_check_advertised_multiple_tenant_network(self):
-        self._test_check_advertised_multiple_tenant_network(self.ip_version)
-
-    @decorators.idempotent_id('212a3d82-ac50-43dc-b657-030b1133643e')
-    def test_check_neighbor_established_with_multiple_peers(self):
-        self._test_check_neighbor_established_with_multiple_peers(
-            self.ip_version)
-
-    @decorators.idempotent_id('c72411c8-ea79-495d-bdbd-a10159642676')
-    def test_check_advertised_tenant_network_with_multiple_peers(self):
-        self._test_check_advertised_tenant_network_with_multiple_peers(
-            self.ip_version)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
deleted file mode 100644
index 90a6815..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# Copyright (C) 2016 VA Linux Systems Japan K.K.
-# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
-# All Rights Reserved.
-#
-#    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 os_ken.tests.integrated.common import docker_base as ctn_base
-from tempest import config
-from tempest.lib import decorators
-
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
-    import base as s_base
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario.basic import base
-
-CONF = config.CONF
-
-
-class BgpSpeakerBasicTest(base.BgpSpeakerBasicTestJSONBase):
-
-    @decorators.idempotent_id('cc615252-c6cb-4d75-a70e-608fb2c3736a')
-    def test_schedule_added_speaker(self):
-        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = next(self.tnet_gen)
-        mask = '/' + str(self.TPool.prefixlen)
-        TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
-                          cidr=subnet + mask, router=None)
-        TSubNet = s_base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
-        MyRouter = s_base.Router(name='my-router' + str(num), gw='')
-        ext_net_id = self.create_bgp_network(
-            4, self.MyScope,
-            self.PNet, self.PPool, self.PSubNet,
-            self.TPool, [[TNet, TSubNet, MyRouter]])
-        speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
-            ext_net_id,
-            self.bgp_speaker_args,
-            [self.bgp_peer_args[0]])
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
-
-    @decorators.idempotent_id('ce98c33c-0ffa-49ae-b365-da836406793b')
-    def test_unschedule_deleted_speaker(self):
-        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = next(self.tnet_gen)
-        mask = '/' + str(self.TPool.prefixlen)
-        TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
-                          cidr=subnet + mask, router=None)
-        TSubNet = s_base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
-        MyRouter = s_base.Router(name='my-router' + str(num), gw='')
-        ext_net_id = self.create_bgp_network(
-            4, self.MyScope,
-            self.PNet, self.PPool, self.PSubNet,
-            self.TPool, [[TNet, TSubNet, MyRouter]])
-        speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
-            ext_net_id,
-            self.bgp_speaker_args,
-            [self.bgp_peer_args[0]],
-            auto_delete=False)
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
-        self.delete_bgp_speaker(speaker_id)
-        self.delete_bgp_peer(peers_ids[0])
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ACTIVE,
-                                   init_state=ctn_base.BGP_FSM_ESTABLISHED)
-
-    @decorators.idempotent_id('aa6c565c-ded3-413b-8dc9-3928b3b0e38f')
-    def test_remove_add_speaker_agent(self):
-        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
-        num, subnet = next(self.tnet_gen)
-        mask = '/' + str(self.TPool.prefixlen)
-        TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
-                          cidr=subnet + mask, router=None)
-        TSubNet = s_base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
-        MyRouter = s_base.Router(name='my-router' + str(num), gw='')
-        ext_net_id = self.create_bgp_network(
-            4, self.MyScope,
-            self.PNet, self.PPool, self.PSubNet,
-            self.TPool, [[TNet, TSubNet, MyRouter]])
-        speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
-            ext_net_id,
-            self.bgp_speaker_args,
-            [self.bgp_peer_args[0]])
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
-        agent_list = self.bgp_client.list_dragents_for_bgp_speaker(
-            speaker_id)['agents']
-        self.assertEqual(1, len(agent_list))
-        agent_id = agent_list[0]['id']
-        self.bgp_client.remove_bgp_speaker_from_dragent(agent_id, speaker_id)
-        # NOTE(tidwellr) This assertion can fail due to the fact that BGP
-        # speakers are auto-scheduled. The BGP process can quickly return to
-        # ACTIVE status before this gets asserted. Let's see how it goes with
-        # this commented out.
-        # self.check_remote_as_state(self.dr, self.r_ass[0],
-        #                           ctn_base.BGP_FSM_ACTIVE,
-        #                           init_state=ctn_base.BGP_FSM_ESTABLISHED)
-
-        # Ignore errors re-associating the BGP speaker, auto-scheduling may
-        # have already added it to an agent. The next assertion is what
-        # matters.
-        self.bgp_client.add_bgp_speaker_to_dragent(agent_id, speaker_id,
-                                                   ignore_errors=True)
-        self.check_remote_as_state(self.dr, self.r_ass[0],
-                                   ctn_base.BGP_FSM_ESTABLISHED)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
index 158b7ad..5a9deae 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
@@ -14,24 +14,24 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.common import utils
 from tempest import config
 from tempest.lib import decorators
 
+from neutron_tempest_plugin.neutron_dynamic_routing import frr
+from neutron_tempest_plugin.neutron_dynamic_routing.frr import constants
 from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
 from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
     import base_test_proto as test_base
 
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
-
 CONF = config.CONF
 
 
 class BgpSpeakerIpv4Test(test_base.BgpSpeakerProtoTestBase):
 
     RAS_MAX = 3
+    IP_ALLOCATION_MULTIPLIER = 0
     ip_version = 4
-    public_gw = '192.168.11.1'
     MyScope = base.Scope(name='my-scope')
     PNet = base.Net(name='', net='172.24.6.0', mask=24,
                     cidr='172.24.6.0/24', router=None)
@@ -73,42 +73,71 @@
     ]
 
     def setUp(self):
+        for ctn in self.containers:
+            ctn.restart()
         super(BgpSpeakerIpv4Test, self).setUp()
 
     @classmethod
+    def skip_checks(cls):
+        super().skip_checks()
+        if CONF.production:
+            raise cls.skipException('Skip on production environment.')
+
+    @classmethod
     def resource_setup_container(cls):
-        cls.brdc = ctn_base.Bridge(name='br-docker-ipv4',
-                                   subnet='192.168.11.0/24',
-                                   start_ip='192.168.11.128',
-                                   end_ip='192.168.11.254',
-                                   self_ip=True,
-                                   fixed_ip=cls.public_gw + '/24',
-                                   br_type=base.BRIDGE_TYPE)
-        cls.bridges.append(cls.brdc)
-        # This is dummy container object for a dr service.
-        # This keeps data which passes to a quagga container.
-        cls.dr = ctn_base.BGPContainer(name='dr', asn=int(cls.L_AS.asn),
-                                       router_id=cls.L_AS.router_id)
-        cls.dr.set_addr_info(bridge='br-docker-ipv4', ipv4=cls.public_gw)
-        # quagga container
-        cls.dockerimg = ctn_base.DockerImage()
-        cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
-        cls.images.append(cls.q_img)
+        # frr container
         for i in range(cls.RAS_MAX):
-            qg = quagga.QuaggaBGPContainer(name='q' + str(i + 1),
-                                           asn=int(cls.ras_l[i].asn),
-                                           router_id=cls.ras_l[i].router_id,
-                                           ctn_image_name=cls.q_img)
-            cls.containers.append(qg)
-            cls.r_ass.append(qg)
-            qg.add_route(cls.ras_l[i].adv_net)
+            container_number = i + 1
+            bgp_listenon = CONF.dynamic_routing.frr_provider_ipv4_ips[
+                cls.IP_ALLOCATION_MULTIPLIER + container_number - 1
+            ].split('/')[0]
+            bgpd = {
+                'bgp': {
+                    'as_number': int(cls.ras_l[i].asn),
+                    'router_id': bgp_listenon,
+                    'neighbors': [
+                        {
+                            'peer_group': True,
+                            'address': 'dnr',
+                            'as_number': cls.L_AS.asn,
+                            'passive': True,
+                        }
+                    ],
+                    'listen': {
+                        'ranges': [
+                            {'cidr': CONF.dynamic_routing.
+                             frr_bgp_ipv4_control_cidr,
+                             'peer_group': 'dnr'}
+                        ],
+                        'limit': 10,
+                    },
+                    'ebgp_requires_policy': False,
+                }
+            }
+            daemons = {
+                "bgpd": {
+                    "enabled": "yes",
+                    "listenon": bgp_listenon,
+                },
+                "vtysh": {"enabled": "yes"},
+            }
+            qg = frr.FrrBGPContainer(
+                "q" + str(container_number),
+                CONF.dynamic_routing.frr_docker_image,
+                bgpd=bgpd,
+                daemons=daemons,
+            )
             qg.run(wait=True)
-            cls.r_as_ip.append(cls.brdc.addif(qg))
-            qg.add_peer(cls.dr, bridge=cls.brdc.name,
-                        peer_info={'passive': True})
+            cls.containers.append(qg.ctn)
+            cls.r_ass.append(qg)
+            cls.r_as_ip.append(
+                CONF.dynamic_routing.frr_provider_ipv4_ips[
+                    cls.IP_ALLOCATION_MULTIPLIER + container_number - 1]
+            )
         cls.tnet_gen = cls.get_subnet(start='10.10.1.0', end='10.10.255.0',
                                       step=256)
 
+    @decorators.attr(type='smoke')
     @decorators.idempotent_id('7f2acbc2-ff88-4a63-aa02-a2f9feb3f5b0')
     def test_check_neighbor_established(self):
         self._test_check_neighbor_established(self.ip_version)
@@ -130,3 +159,246 @@
     def test_check_advertised_tenant_network_with_multiple_peers(self):
         self._test_check_advertised_tenant_network_with_multiple_peers(
             self.ip_version)
+
+    @decorators.idempotent_id('cc615252-c6cb-4d75-a70e-608fb2c3736a')
+    def test_schedule_added_speaker(self):
+        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+        num, subnet = next(self.tnet_gen)
+        mask = '/' + str(self.TPool.prefixlen)
+        TNet = base.Net(
+            name='',
+            net=subnet,
+            mask=self.TPool.prefixlen,
+            cidr=subnet + mask,
+            router=None,
+        )
+        TSubNet = base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
+        MyRouter = base.Router(name='my-router' + str(num), gw='')
+        ext_net_id = self.create_bgp_network(
+            4,
+            self.MyScope,
+            self.PNet,
+            self.PPool,
+            self.PSubNet,
+            self.TPool,
+            [[TNet, TSubNet, MyRouter]],
+        )
+        speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
+            ext_net_id, self.bgp_speaker_args, [self.bgp_peer_args[0]]
+        )
+        self.r_ass[0].bgp_check_neighbor_state(
+            'dnr', constants.BGP_FSM_ESTABLISHED
+        )
+
+    @decorators.idempotent_id('ce98c33c-0ffa-49ae-b365-da836406793b')
+    def test_unschedule_deleted_speaker(self):
+        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+        num, subnet = next(self.tnet_gen)
+        mask = '/' + str(self.TPool.prefixlen)
+        TNet = base.Net(
+            name='',
+            net=subnet,
+            mask=self.TPool.prefixlen,
+            cidr=subnet + mask,
+            router=None,
+        )
+        TSubNet = base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
+        MyRouter = base.Router(name='my-router' + str(num), gw='')
+        ext_net_id = self.create_bgp_network(
+            4,
+            self.MyScope,
+            self.PNet,
+            self.PPool,
+            self.PSubNet,
+            self.TPool,
+            [[TNet, TSubNet, MyRouter]],
+        )
+        speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
+            ext_net_id,
+            self.bgp_speaker_args,
+            [self.bgp_peer_args[0]],
+            auto_delete=False,
+        )
+        self.r_ass[0].bgp_check_neighbor_state(
+            'dnr', constants.BGP_FSM_ESTABLISHED
+        )
+        self.delete_bgp_speaker(speaker_id)
+        self.delete_bgp_peer(peers_ids[0])
+        self.r_ass[0].bgp_check_neighbor_absent('dnr')
+
+    @decorators.idempotent_id('aa6c565c-ded3-413b-8dc9-3928b3b0e38f')
+    def test_remove_add_speaker_agent(self):
+        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+        num, subnet = next(self.tnet_gen)
+        mask = '/' + str(self.TPool.prefixlen)
+        TNet = base.Net(
+            name='',
+            net=subnet,
+            mask=self.TPool.prefixlen,
+            cidr=subnet + mask,
+            router=None,
+        )
+        TSubNet = base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
+        MyRouter = base.Router(name='my-router' + str(num), gw='')
+        ext_net_id = self.create_bgp_network(
+            4,
+            self.MyScope,
+            self.PNet,
+            self.PPool,
+            self.PSubNet,
+            self.TPool,
+            [[TNet, TSubNet, MyRouter]],
+        )
+        speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
+            ext_net_id, self.bgp_speaker_args, [self.bgp_peer_args[0]]
+        )
+        self.r_ass[0].bgp_check_neighbor_state(
+            'dnr', constants.BGP_FSM_ESTABLISHED
+        )
+        agent_list = self.bgp_client.list_dragents_for_bgp_speaker(speaker_id)[
+            'agents'
+        ]
+        self.assertEqual(1, len(agent_list))
+        agent_id = agent_list[0]['id']
+        self.bgp_client.remove_bgp_speaker_from_dragent(agent_id, speaker_id)
+        self.r_ass[0].bgp_check_neighbor_absent('dnr')
+        self.bgp_client.add_bgp_speaker_to_dragent(
+            agent_id, speaker_id, ignore_errors=True
+        )
+        self.r_ass[0].bgp_check_neighbor_state(
+            'dnr', constants.BGP_FSM_ESTABLISHED
+        )
+
+
+class BgpSpeaker4byteASNTest(test_base.BgpSpeakerProtoTestBase):
+
+    RAS_MAX = 3
+    IP_ALLOCATION_MULTIPLIER = 3
+    ip_version = 4
+    MyScope = base.Scope(name='my-scope')
+    PNet = base.Net(name='', net='172.24.6.0', mask=24, cidr='172.24.6.0/24',
+                    router=None)
+    PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
+                      prefixes=[PNet.net + '/8'])
+    PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
+    TPool = base.Pool(name='tenant-test-pool', prefixlen=28,
+                      prefixes=['10.10.0.0/16'])
+    L_AS = base.AS(asn='4200000000', router_id='192.168.0.3', adv_net='')
+    ras_l = [
+        base.AS(asn='4210000000', router_id='192.168.0.12',
+                adv_net='192.168.162.0/24'),
+        base.AS(asn='64522', router_id='192.168.0.13',
+                adv_net='192.168.163.0/24'),
+        base.AS(asn='4230000000', router_id='192.168.0.14',
+                adv_net='192.168.164.0/24'),
+    ]
+
+    bgp_speaker_args = {
+        'local_as': L_AS.asn,
+        'ip_version': ip_version,
+        'name': 'my-bgp-speaker1',
+        'advertise_floating_ip_host_routes': True,
+        'advertise_tenant_networks': True,
+    }
+    bgp_peer_args = [
+        {'remote_as': ras_l[0].asn,
+         'name': 'my-bgp-peer1',
+         'peer_ip': None,
+         'auth_type': 'none'},
+        {'remote_as': ras_l[1].asn,
+         'name': 'my-bgp-peer2',
+         'peer_ip': None,
+         'auth_type': 'none'},
+        {'remote_as': ras_l[2].asn,
+         'name': 'my-bgp-peer3',
+         'peer_ip': None,
+         'auth_type': 'none'},
+    ]
+
+    @classmethod
+    @utils.requires_ext(extension='bgp_4byte_asn', service='network')
+    def resource_setup(cls):
+        super(BgpSpeaker4byteASNTest, cls).resource_setup()
+
+    @classmethod
+    def skip_checks(cls):
+        super().skip_checks()
+        if CONF.production:
+            raise cls.skipException('Skip on production environment.')
+
+    @classmethod
+    def resource_setup_container(cls):
+        # frr container
+        for i in range(cls.RAS_MAX):
+            container_number = i + 1
+            bgp_listenon = CONF.dynamic_routing.frr_provider_ipv4_ips[
+                cls.IP_ALLOCATION_MULTIPLIER + container_number - 1
+            ].split('/')[0]
+            bgpd = {
+                'bgp': {
+                    'as_number': int(cls.ras_l[i].asn),
+                    'router_id': bgp_listenon,
+                    'neighbors': [
+                        {
+                            'peer_group': True,
+                            'address': 'dnr',
+                            'as_number': cls.L_AS.asn,
+                            'passive': True,
+                        }
+                    ],
+                    'listen': {
+                        'ranges': [
+                            {'cidr': CONF.dynamic_routing.
+                             frr_bgp_ipv4_control_cidr,
+                             'peer_group': 'dnr'}
+                        ],
+                        'limit': 10,
+                    },
+                    'ebgp_requires_policy': False,
+                }
+            }
+            daemons = {
+                "bgpd": {
+                    "enabled": "yes",
+                    "listenon": bgp_listenon,
+                },
+                "vtysh": {"enabled": "yes"},
+            }
+            qg = frr.FrrBGPContainer(
+                'q-4byte-asn-' + str(container_number),
+                CONF.dynamic_routing.frr_docker_image,
+                bgpd=bgpd,
+                daemons=daemons,
+            )
+            qg.run(wait=True)
+            cls.containers.append(qg.ctn)
+            cls.r_ass.append(qg)
+            cls.r_as_ip.append(
+                CONF.dynamic_routing.frr_provider_ipv4_ips[
+                    cls.IP_ALLOCATION_MULTIPLIER + container_number - 1]
+            )
+        cls.tnet_gen = cls.get_subnet(
+            start='10.10.1.0', end='10.10.255.0', step=256
+        )
+
+    @decorators.idempotent_id('9f18c931-a59e-4a27-939b-21124995ffe2')
+    def test_check_neighbor_established(self):
+        self._test_check_neighbor_established(self.ip_version)
+
+    @decorators.idempotent_id('73466aa5-7d1d-4f9f-8fb4-4100fad2dffe')
+    def test_check_advertised_tenant_network(self):
+        self._test_check_advertised_tenant_network(self.ip_version)
+
+    @decorators.idempotent_id('c3158328-2f69-4aa2-b1b7-5a06ab58afaf')
+    def test_check_advertised_multiple_tenant_network(self):
+        self._test_check_advertised_multiple_tenant_network(self.ip_version)
+
+    @decorators.idempotent_id('212a3d82-ac50-43dc-b657-030b1133643e')
+    def test_check_neighbor_established_with_multiple_peers(self):
+        self._test_check_neighbor_established_with_multiple_peers(
+            self.ip_version)
+
+    @decorators.idempotent_id('c72411c8-ea79-495d-bdbd-a10159642676')
+    def test_check_advertised_tenant_network_with_multiple_peers(self):
+        self._test_check_advertised_tenant_network_with_multiple_peers(
+            self.ip_version)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
index 937b38d..d83fb7c 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
@@ -17,12 +17,11 @@
 from tempest import config
 from tempest.lib import decorators
 
+from neutron_tempest_plugin.neutron_dynamic_routing import frr
 from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
 from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
     import base_test_proto as test_base
 
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
 
 CONF = config.CONF
 
@@ -30,9 +29,8 @@
 class BgpSpeakerIpv6Test(test_base.BgpSpeakerProtoTestBase):
 
     RAS_MAX = 3
+    IP_ALLOCATION_MULTIPLIER = 0
     ip_version = 6
-    public_gw = '2001:db8:a000::1'
-    MyScope = base.Scope(name='my-scope')
     PNet = base.Net(name='', net='2001:db8::', mask=64,
                     cidr='2001:db8::/64', router=None)
     PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
@@ -76,36 +74,53 @@
         super(BgpSpeakerIpv6Test, self).setUp()
 
     @classmethod
+    def skip_checks(cls):
+        super().skip_checks()
+        if not CONF.dynamic_routing.frr_bgp_ipv6_enabled:
+            raise cls.skipException("Dynamic routing IPv6 tests not enabled.")
+        if CONF.production:
+            raise cls.skipException("Skip on production environment.")
+
+    @classmethod
     def resource_setup_container(cls):
-        cls.brdc = ctn_base.Bridge(name='br-docker-ipv6',
-                                   subnet='2001:db8:a000::/64',
-                                   start_ip='2001:db8:a000::8000',
-                                   end_ip='2001:db8:a000::fffe',
-                                   self_ip=True,
-                                   fixed_ip=cls.public_gw + '/64',
-                                   br_type=base.BRIDGE_TYPE)
-        cls.bridges.append(cls.brdc)
-        # This is dummy container object for a dr service.
-        # This keeps data which passes to a quagga container.
-        cls.dr = ctn_base.BGPContainer(name='dr', asn=int(cls.L_AS.asn),
-                                       router_id=cls.L_AS.router_id)
-        cls.dr.set_addr_info(bridge='br-docker-ipv6', ipv6=cls.public_gw)
-        # quagga container
-        cls.dockerimg = ctn_base.DockerImage()
-        cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
-        cls.images.append(cls.q_img)
+        # frr container
         for i in range(cls.RAS_MAX):
-            qg = quagga.QuaggaBGPContainer(name='q' + str(i + 1),
-                                           asn=int(cls.ras_l[i].asn),
-                                           router_id=cls.ras_l[i].router_id,
-                                           ctn_image_name=cls.q_img)
-            cls.containers.append(qg)
-            cls.r_ass.append(qg)
-            qg.add_route(cls.ras_l[i].adv_net, route_info={'rf': 'ipv6'})
+            container_number = i + 1
+            bgpd = {
+                'bgp': {
+                    'as_number': int(cls.ras_l[i].asn),
+                    'router_id': cls.ras_l[i].router_id,
+                    'neighbors': [
+                        {
+                            'peer_group': True,
+                            'address': 'dnr',
+                            'as_number': cls.L_AS.asn,
+                            'passive': True,
+                        }
+                    ],
+                    'listen': {
+                        'ranges': [
+                            {'cidr': CONF.dynamic_routing.
+                             frr_bgp_ipv6_control_cidr,
+                             'peer_group': 'dnr'}
+                        ],
+                        'limit': 10,
+                    },
+                    'ebgp_requires_policy': False,
+                }
+            }
+            qg = frr.FrrBGPContainer(
+                "qv6" + str(container_number),
+                CONF.dynamic_routing.frr_docker_image,
+                bgpd=bgpd,
+            )
             qg.run(wait=True)
-            cls.r_as_ip.append(cls.brdc.addif(qg))
-            qg.add_peer(cls.dr, bridge=cls.brdc.name, v6=True,
-                        peer_info={'passive': True})
+            cls.containers.append(qg.ctn)
+            cls.r_ass.append(qg)
+            cls.r_as_ip.append(
+                CONF.dynamic_routing.frr_provider_ips[
+                    cls.IP_ALLOCATION_MULTIPLIER + container_number - 1]
+            )
         cls.tnet_gen = cls.get_subnet(start='2001:db8:8000:1::',
                                       end='2001:db8:8000:ffff::',
                                       step=65536 * 65536 * 65536 * 65536)
diff --git a/requirements.txt b/requirements.txt
index 34531e9..8580ddf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,6 @@
 neutron-lib>=1.25.0 # Apache-2.0
 oslo.config>=5.2.0 # Apache-2.0
 netaddr>=0.7.18 # BSD
-os-ken>=0.3.0 # Apache-2.0
 oslo.log>=3.36.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
 oslo.utils>=3.33.0 # Apache-2.0
@@ -19,3 +18,4 @@
 testscenarios>=0.4 # Apache-2.0/BSD
 eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
 debtcollector>=1.2.0 # Apache-2.0
+docker  # Apache-2.0