Move neutron-dynamic-routing BGP tests from stadium

This is step 1 of the movement of the tempest plugin for
neutron-dynamic-routing.

Co-Authored-By: Slawek Kaplonski <skaplons@redhat.com>

Change-Id: I35984bb3ad3673b7a54982657c1ac6fdc3ed6de0
diff --git a/.zuul.yaml b/.zuul.yaml
index 011bfe5..70809b3 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -894,6 +894,35 @@
     files:
       - ^neutron_tempest_plugin/fwaas/.*$
 
+- job:
+    name: neutron-tempest-plugin-dynamic-routing
+    parent: neutron-tempest-plugin
+    description: |
+      Perform setup common to all Neutron dynamic routing tempest tests
+    required-projects:
+      - openstack/neutron
+      - openstack/neutron-dynamic-routing
+      - openstack/os-ken
+      - openstack/tempest
+    pre-run: playbooks/dynamic-routing-pre-run.yaml
+    vars:
+      devstack_plugins:
+        neutron-dynamic-routing: https://opendev.org/openstack/neutron-dynamic-routing
+        neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin
+      network_api_extensions_common: *api_extensions_master
+      network_api_extensions_bgp:
+        - bgp
+        - bgp_dragent_scheduler
+        - bgp_4byte_asn
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_bgp) | join(',') }}"
+      devstack_services:
+        neutron-dr: true
+        neutron-dr-agent: true
+        q-l3: true
+      tempest_concurrency: 1
+      tempest_test_regex: ^neutron_tempest_plugin\.neutron_dynamic_routing
+
 - project-template:
     name: neutron-tempest-plugin-jobs
     check:
@@ -967,7 +996,9 @@
         - neutron-tempest-plugin-sfc
         - neutron-tempest-plugin-bgpvpn-bagpipe
         - neutron-tempest-plugin-fwaas
+        - neutron-tempest-plugin-dynamic-routing
     gate:
       jobs:
         - neutron-tempest-plugin-bgpvpn-bagpipe
         - neutron-tempest-plugin-fwaas
+        - neutron-tempest-plugin-dynamic-routing
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/__init__.py
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/api/__init__.py
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py
new file mode 100644
index 0000000..f0e576b
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py
@@ -0,0 +1,325 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+#    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 netaddr
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.common import tempest_fixtures as fixtures
+from neutron_tempest_plugin.services.bgp import bgp_client
+
+
+CONF = config.CONF
+
+
+def _setup_client_args(auth_provider):
+    """Set up ServiceClient arguments using config settings. """
+    service = CONF.network.catalog_type or 'network'
+    region = CONF.network.region or 'regionOne'
+    endpoint_type = CONF.network.endpoint_type
+    build_interval = CONF.network.build_interval
+    build_timeout = CONF.network.build_timeout
+
+    # The disable_ssl appears in identity
+    disable_ssl_certificate_validation = (
+        CONF.identity.disable_ssl_certificate_validation)
+    ca_certs = None
+
+    # Trace in debug section
+    trace_requests = CONF.debug.trace_requests
+
+    return [auth_provider, service, region, endpoint_type,
+            build_interval, build_timeout,
+            disable_ssl_certificate_validation, ca_certs,
+            trace_requests]
+
+
+class BgpSpeakerTestJSONBase(base.BaseAdminNetworkTest):
+
+    default_bgp_speaker_args = {'local_as': '1234',
+                                'ip_version': 4,
+                                'name': 'my-bgp-speaker',
+                                'advertise_floating_ip_host_routes': True,
+                                'advertise_tenant_networks': True}
+    default_bgp_peer_args = {'remote_as': '4321',
+                             'name': 'my-bgp-peer',
+                             'peer_ip': '192.168.1.1',
+                             'auth_type': 'md5', 'password': 'my-secret'}
+
+    def setUp(self):
+        self.addCleanup(self.resource_cleanup)
+        super(BgpSpeakerTestJSONBase, self).setUp()
+
+    @classmethod
+    def _setup_bgp_admin_client(cls):
+        mgr = cls.get_client_manager(credential_type='admin')
+        auth_provider = mgr.auth_provider
+        client_args = _setup_client_args(auth_provider)
+        cls.bgp_adm_client = bgp_client.BgpSpeakerClientJSON(*client_args)
+
+    @classmethod
+    def _setup_bgp_non_admin_client(cls):
+        mgr = cls.get_client_manager()
+        auth_provider = mgr.auth_provider
+        client_args = _setup_client_args(auth_provider)
+        cls.bgp_client = bgp_client.BgpSpeakerClientJSON(*client_args)
+
+    @classmethod
+    def resource_setup(cls):
+        super(BgpSpeakerTestJSONBase, cls).resource_setup()
+        if not utils.is_extension_enabled('bgp', 'network'):
+            msg = "BGP Speaker extension is not enabled."
+            raise cls.skipException(msg)
+
+        cls.admin_routerports = []
+        cls.admin_floatingips = []
+        cls.admin_routers = []
+        cls.ext_net_id = CONF.network.public_network_id
+        cls._setup_bgp_admin_client()
+        cls._setup_bgp_non_admin_client()
+
+    @classmethod
+    def resource_cleanup(cls):
+        for floatingip in cls.admin_floatingips:
+            cls._try_delete_resource(cls.admin_client.delete_floatingip,
+                                     floatingip['id'])
+        for routerport in cls.admin_routerports:
+            cls._try_delete_resource(
+                      cls.admin_client.remove_router_interface_with_subnet_id,
+                      routerport['router_id'], routerport['subnet_id'])
+        for router in cls.admin_routers:
+            cls._try_delete_resource(cls.admin_client.delete_router,
+                                     router['id'])
+        super(BgpSpeakerTestJSONBase, cls).resource_cleanup()
+
+    def create_bgp_speaker(self, auto_delete=True, **args):
+        data = {'bgp_speaker': args}
+        bgp_speaker = self.bgp_adm_client.create_bgp_speaker(data)
+        bgp_speaker_id = bgp_speaker['bgp_speaker']['id']
+        if auto_delete:
+            self.addCleanup(self.delete_bgp_speaker, bgp_speaker_id)
+        return bgp_speaker['bgp_speaker']
+
+    def create_bgp_peer(self, **args):
+        bgp_peer = self.bgp_adm_client.create_bgp_peer({'bgp_peer': args})
+        bgp_peer_id = bgp_peer['bgp_peer']['id']
+        self.addCleanup(self.delete_bgp_peer, bgp_peer_id)
+        return bgp_peer['bgp_peer']
+
+    def update_bgp_speaker(self, id, **args):
+        data = {'bgp_speaker': args}
+        return self.bgp_adm_client.update_bgp_speaker(id, data)
+
+    def delete_bgp_speaker(self, id):
+        return self.bgp_adm_client.delete_bgp_speaker(id)
+
+    def get_bgp_speaker(self, id):
+        return self.bgp_adm_client.get_bgp_speaker(id)['bgp_speaker']
+
+    def create_bgp_speaker_and_peer(self):
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args)
+        return (bgp_speaker, bgp_peer)
+
+    def delete_bgp_peer(self, id):
+        return self.bgp_adm_client.delete_bgp_peer(id)
+
+    def add_bgp_peer(self, bgp_speaker_id, bgp_peer_id):
+        return self.bgp_adm_client.add_bgp_peer_with_id(bgp_speaker_id,
+                                                        bgp_peer_id)
+
+    def remove_bgp_peer(self, bgp_speaker_id, bgp_peer_id):
+        return self.bgp_adm_client.remove_bgp_peer_with_id(bgp_speaker_id,
+                                                           bgp_peer_id)
+
+    def delete_address_scope(self, id):
+        return self.admin_client.delete_address_scope(id)
+
+
+class BgpSpeakerTestJSON(BgpSpeakerTestJSONBase):
+
+    """Tests the following operations in the Neutron API using the REST client:
+
+        Create bgp-speaker
+        Delete bgp-speaker
+        Create bgp-peer
+        Update bgp-peer
+        Delete bgp-peer
+    """
+
+    @decorators.idempotent_id('df259771-7104-4ffa-b77f-bd183600d7f9')
+    def test_delete_bgp_speaker(self):
+        bgp_speaker = self.create_bgp_speaker(auto_delete=False,
+                                              **self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.delete_bgp_speaker(bgp_speaker_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.get_bgp_speaker,
+                          bgp_speaker_id)
+
+    @decorators.idempotent_id('81d9dc45-19f8-4c6e-88b8-401d965cd1b0')
+    def test_create_bgp_peer(self):
+        self.create_bgp_peer(**self.default_bgp_peer_args)
+
+    @decorators.idempotent_id('6ade0319-1ee2-493c-ac4b-5eb230ff3a77')
+    def test_add_bgp_peer(self):
+        bgp_speaker, bgp_peer = self.create_bgp_speaker_and_peer()
+        bgp_speaker_id = bgp_speaker['id']
+        bgp_peer_id = bgp_peer['id']
+
+        self.add_bgp_peer(bgp_speaker_id, bgp_peer_id)
+        bgp_speaker = self.get_bgp_speaker(bgp_speaker_id)
+        bgp_peers_list = bgp_speaker['peers']
+        self.assertEqual(1, len(bgp_peers_list))
+        self.assertTrue(bgp_peer_id in bgp_peers_list)
+
+    @decorators.idempotent_id('f9737708-1d79-440b-8350-779f97d882ee')
+    def test_remove_bgp_peer(self):
+        bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args)
+        bgp_peer_id = bgp_peer['id']
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.add_bgp_peer(bgp_speaker_id, bgp_peer_id)
+        bgp_speaker = self.get_bgp_speaker(bgp_speaker_id)
+        bgp_peers_list = bgp_speaker['peers']
+        self.assertTrue(bgp_peer_id in bgp_peers_list)
+
+        bgp_speaker = self.remove_bgp_peer(bgp_speaker_id, bgp_peer_id)
+        bgp_speaker = self.get_bgp_speaker(bgp_speaker_id)
+        bgp_peers_list = bgp_speaker['peers']
+        self.assertTrue(not bgp_peers_list)
+
+    @decorators.idempotent_id('23c8eb37-d10d-4f43-b2e7-6542cb6a4405')
+    def test_add_gateway_network(self):
+        self.useFixture(fixtures.LockFixture('gateway_network_binding'))
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+
+        self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id,
+                                                    self.ext_net_id)
+        bgp_speaker = self.get_bgp_speaker(bgp_speaker_id)
+        network_list = bgp_speaker['networks']
+        self.assertEqual(1, len(network_list))
+        self.assertTrue(self.ext_net_id in network_list)
+
+    @decorators.idempotent_id('6cfc7137-0d99-4a3d-826c-9d1a3a1767b0')
+    def test_remove_gateway_network(self):
+        self.useFixture(fixtures.LockFixture('gateway_network_binding'))
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id,
+                                                    self.ext_net_id)
+        bgp_speaker = self.get_bgp_speaker(bgp_speaker_id)
+        networks = bgp_speaker['networks']
+
+        self.assertTrue(self.ext_net_id in networks)
+        self.bgp_adm_client.remove_bgp_gateway_network(bgp_speaker_id,
+                                                       self.ext_net_id)
+        bgp_speaker = self.get_bgp_speaker(bgp_speaker_id)
+        network_list = bgp_speaker['networks']
+        self.assertTrue(not network_list)
+
+    @decorators.idempotent_id('5bef22ad-5e70-4f7b-937a-dc1944642996')
+    def test_get_advertised_routes_null_address_scope(self):
+        self.useFixture(fixtures.LockFixture('gateway_network_binding'))
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id,
+                                                  self.ext_net_id)
+        routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id)
+        self.assertEqual(0, len(routes['advertised_routes']))
+
+    @decorators.idempotent_id('cae9cdb1-ad65-423c-9604-d4cd0073616e')
+    def test_get_advertised_routes_floating_ips(self):
+        self.useFixture(fixtures.LockFixture('gateway_network_binding'))
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id,
+                                                    self.ext_net_id)
+        tenant_net = self.create_network()
+        tenant_subnet = self.create_subnet(tenant_net)
+        ext_gw_info = {'network_id': self.ext_net_id}
+        router = self.admin_client.create_router(
+                                            'my-router',
+                                            external_gateway_info=ext_gw_info,
+                                            admin_state_up=True,
+                                            distributed=False)
+        self.admin_routers.append(router['router'])
+        self.admin_client.add_router_interface_with_subnet_id(
+                                                       router['router']['id'],
+                                                       tenant_subnet['id'])
+        self.admin_routerports.append({'router_id': router['router']['id'],
+                                       'subnet_id': tenant_subnet['id']})
+        tenant_port = self.create_port(tenant_net)
+        floatingip = self.create_floatingip(self.ext_net_id)
+        self.admin_floatingips.append(floatingip)
+        self.client.update_floatingip(floatingip['id'],
+                                      port_id=tenant_port['id'])
+        routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id)
+        self.assertEqual(1, len(routes['advertised_routes']))
+        self.assertEqual(floatingip['floating_ip_address'] + '/32',
+                         routes['advertised_routes'][0]['destination'])
+
+    @decorators.idempotent_id('c9ad566e-fe8f-4559-8303-bbad9062a30c')
+    def test_get_advertised_routes_tenant_networks(self):
+        self.useFixture(fixtures.LockFixture('gateway_network_binding'))
+        addr_scope = self.create_address_scope('my-scope', ip_version=4)
+        ext_net = self.create_shared_network(**{'router:external': True})
+        tenant_net = self.create_network()
+        ext_subnetpool = self.create_subnetpool(
+                                            'test-pool-ext',
+                                            is_admin=True,
+                                            default_prefixlen=24,
+                                            address_scope_id=addr_scope['id'],
+                                            prefixes=['8.0.0.0/8'])
+        tenant_subnetpool = self.create_subnetpool(
+                                            'tenant-test-pool',
+                                            default_prefixlen=25,
+                                            address_scope_id=addr_scope['id'],
+                                            prefixes=['10.10.0.0/16'])
+        self.create_subnet({'id': ext_net['id']},
+                           cidr=netaddr.IPNetwork('8.0.0.0/24'),
+                           ip_version=4,
+                           client=self.admin_client,
+                           subnetpool_id=ext_subnetpool['id'])
+        tenant_subnet = self.create_subnet(
+                                       {'id': tenant_net['id']},
+                                       cidr=netaddr.IPNetwork('10.10.0.0/24'),
+                                       ip_version=4,
+                                       subnetpool_id=tenant_subnetpool['id'])
+        ext_gw_info = {'network_id': ext_net['id']}
+        router = self.admin_client.create_router(
+                                            'my-router',
+                                            external_gateway_info=ext_gw_info,
+                                            distributed=False)['router']
+        self.admin_routers.append(router)
+        self.admin_client.add_router_interface_with_subnet_id(
+                                                       router['id'],
+                                                       tenant_subnet['id'])
+        self.admin_routerports.append({'router_id': router['id'],
+                                       'subnet_id': tenant_subnet['id']})
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id,
+                                                    ext_net['id'])
+        routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id)
+        self.assertEqual(1, len(routes['advertised_routes']))
+        self.assertEqual(tenant_subnet['cidr'],
+                         routes['advertised_routes'][0]['destination'])
+        fixed_ip = router['external_gateway_info']['external_fixed_ips'][0]
+        self.assertEqual(fixed_ip['ip_address'],
+                         routes['advertised_routes'][0]['next_hop'])
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py
new file mode 100644
index 0000000..9d14536
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py
@@ -0,0 +1,126 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+#    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 netaddr
+
+from tempest.common import utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.neutron_dynamic_routing.api import\
+    test_bgp_speaker_extensions as test_base
+
+
+class BgpSpeakerTestJSONNegative(test_base.BgpSpeakerTestJSONBase):
+
+    """Negative test cases asserting proper behavior of BGP API extension"""
+
+    @decorators.attr(type=['negative', 'smoke'])
+    @decorators.idempotent_id('75e9ee2f-6efd-4320-bff7-ae24741c8b06')
+    def test_create_bgp_speaker_illegal_local_asn(self):
+        wrong_asn = 65537
+        if utils.is_extension_enabled('bgp_4byte_asn', 'network'):
+            wrong_asn = 4294967296
+        self.assertRaises(lib_exc.BadRequest,
+                          self.create_bgp_speaker,
+                          local_as=wrong_asn)
+
+    @decorators.attr(type=['negative', 'smoke'])
+    @decorators.idempotent_id('6742ec2e-382a-4453-8791-13a19b47cd13')
+    def test_create_bgp_speaker_non_admin(self):
+        self.assertRaises(lib_exc.Forbidden,
+                          self.bgp_client.create_bgp_speaker,
+                          {'bgp_speaker': self.default_bgp_speaker_args})
+
+    @decorators.attr(type=['negative', 'smoke'])
+    @decorators.idempotent_id('33f7aaf0-9786-478b-b2d1-a51086a50eb4')
+    def test_create_bgp_peer_non_admin(self):
+        self.assertRaises(lib_exc.Forbidden,
+                          self.bgp_client.create_bgp_peer,
+                          {'bgp_peer': self.default_bgp_peer_args})
+
+    @decorators.attr(type=['negative', 'smoke'])
+    @decorators.idempotent_id('39435932-0266-4358-899b-0e9b1e53c3e9')
+    def test_update_bgp_speaker_local_asn(self):
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+
+        self.assertRaises(lib_exc.BadRequest, self.update_bgp_speaker,
+                          bgp_speaker_id, local_as='4321')
+
+    @decorators.idempotent_id('9cc33701-51e5-421f-a5d5-fd7b330e550f')
+    def test_get_advertised_routes_tenant_networks(self):
+        addr_scope1 = self.create_address_scope('my-scope1', ip_version=4)
+        addr_scope2 = self.create_address_scope('my-scope2', ip_version=4)
+        ext_net = self.create_shared_network(**{'router:external': True})
+        tenant_net1 = self.create_network()
+        tenant_net2 = self.create_network()
+        ext_subnetpool = self.create_subnetpool(
+                                           'test-pool-ext',
+                                           is_admin=True,
+                                           default_prefixlen=24,
+                                           address_scope_id=addr_scope1['id'],
+                                           prefixes=['8.0.0.0/8'])
+        tenant_subnetpool1 = self.create_subnetpool(
+                                           'tenant-test-pool',
+                                           default_prefixlen=25,
+                                           address_scope_id=addr_scope1['id'],
+                                           prefixes=['10.10.0.0/16'])
+        tenant_subnetpool2 = self.create_subnetpool(
+                                           'tenant-test-pool',
+                                           default_prefixlen=25,
+                                           address_scope_id=addr_scope2['id'],
+                                           prefixes=['11.10.0.0/16'])
+        self.create_subnet({'id': ext_net['id']},
+                           cidr=netaddr.IPNetwork('8.0.0.0/24'),
+                           ip_version=4,
+                           client=self.admin_client,
+                           subnetpool_id=ext_subnetpool['id'])
+        tenant_subnet1 = self.create_subnet(
+                                       {'id': tenant_net1['id']},
+                                       cidr=netaddr.IPNetwork('10.10.0.0/24'),
+                                       ip_version=4,
+                                       subnetpool_id=tenant_subnetpool1['id'])
+        tenant_subnet2 = self.create_subnet(
+                                       {'id': tenant_net2['id']},
+                                       cidr=netaddr.IPNetwork('11.10.0.0/24'),
+                                       ip_version=4,
+                                       subnetpool_id=tenant_subnetpool2['id'])
+        ext_gw_info = {'network_id': ext_net['id']}
+        router = self.admin_client.create_router(
+                                  'my-router',
+                                  distributed=False,
+                                  external_gateway_info=ext_gw_info)['router']
+        self.admin_routers.append(router)
+        self.admin_client.add_router_interface_with_subnet_id(
+                                                       router['id'],
+                                                       tenant_subnet1['id'])
+        self.admin_routerports.append({'router_id': router['id'],
+                                       'subnet_id': tenant_subnet1['id']})
+        self.admin_client.add_router_interface_with_subnet_id(
+                                                       router['id'],
+                                                       tenant_subnet2['id'])
+        self.admin_routerports.append({'router_id': router['id'],
+                                       'subnet_id': tenant_subnet2['id']})
+        bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
+        bgp_speaker_id = bgp_speaker['id']
+        self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id,
+                                                    ext_net['id'])
+        routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id)
+        self.assertEqual(1, len(routes['advertised_routes']))
+        self.assertEqual(tenant_subnet1['cidr'],
+                         routes['advertised_routes'][0]['destination'])
+        fixed_ip = router['external_gateway_info']['external_fixed_ips'][0]
+        self.assertEqual(fixed_ip['ip_address'],
+                         routes['advertised_routes'][0]['next_hop'])
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
new file mode 100644
index 0000000..44990bd
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
@@ -0,0 +1,36 @@
+scenario tests use the following environment.
+
+diagram:
+
+       ----------------------+--------------- tenant
+                             |                network
+                        +--------+
+                        | router |
+                        +--------+
+                             |
+       -----+----------------+--------------- provider
+            |                |                network
+       +---------+           |
+       | dragent |           |
+       +---------+           |
+            |                |
+            | +--------------+
+            | |
+       +--------+
+       | docker |
+       | bridge |
+       +--------+
+            |
+            +-----------+------------+-------
+                        |            |
+                   +---------+  +---------+
+       docker      | quagga1 |  | quagga2 | ...
+       container   +---------+  +---------+
+
+
+docker container environment is provided by test tool of os-ken.
+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.
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/__init__.py
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
new file mode 100644
index 0000000..de8677f
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
@@ -0,0 +1,373 @@
+# 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.
+
+import collections
+import threading
+import time
+
+import netaddr
+import six
+
+from os_ken.tests.integrated.common import docker_base as ctn_base
+from tempest.common import utils
+from tempest import config
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.services.bgp import bgp_client
+
+
+CONF = config.CONF
+
+Scope = collections.namedtuple('Scope', 'name')
+Pool = collections.namedtuple('Pool', 'name, prefixlen, prefixes')
+Net = collections.namedtuple('Net', 'name, net, mask, cidr, router')
+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):
+    """Set up ServiceClient arguments using config settings. """
+    service = CONF.network.catalog_type or 'network'
+    region = CONF.network.region or 'regionOne'
+    endpoint_type = CONF.network.endpoint_type
+    build_interval = CONF.network.build_interval
+    build_timeout = CONF.network.build_timeout
+
+    # The disable_ssl appears in identity
+    disable_ssl_certificate_validation = (
+        CONF.identity.disable_ssl_certificate_validation)
+    ca_certs = None
+
+    # Trace in debug section
+    trace_requests = CONF.debug.trace_requests
+
+    return [auth_provider, service, region, endpoint_type,
+            build_interval, build_timeout,
+            disable_ssl_certificate_validation, ca_certs,
+            trace_requests]
+
+
+class BgpSpeakerScenarioTestJSONBase(base.BaseAdminNetworkTest):
+
+    def setUp(self):
+        self.addCleanup(self.net_resource_cleanup)
+        super(BgpSpeakerScenarioTestJSONBase, self).setUp()
+
+    @classmethod
+    def _setup_bgp_non_admin_client(cls):
+        mgr = cls.get_client_manager()
+        auth_provider = mgr.auth_provider
+        client_args = _setup_client_args(auth_provider)
+        cls.bgp_client = bgp_client.BgpSpeakerClientJSON(*client_args)
+
+    @classmethod
+    def _setup_bgp_admin_client(cls):
+        mgr = cls.get_client_manager(credential_type='admin')
+        auth_provider = mgr.auth_provider
+        client_args = _setup_client_args(auth_provider)
+        cls.bgp_adm_client = bgp_client.BgpSpeakerClientJSON(*client_args)
+
+    @classmethod
+    def resource_setup(cls):
+        super(BgpSpeakerScenarioTestJSONBase, cls).resource_setup()
+        if not utils.is_extension_enabled('bgp', 'network'):
+            msg = "BGP Speaker extension is not enabled."
+            raise cls.skipException(msg)
+
+        cls.images = []
+        cls.containers = []
+        cls.r_ass = []
+        cls.r_as_ip = []
+        cls.bridges = []
+        cls.admin_routerports = []
+        cls.admin_floatingips = []
+        cls.admin_routers = []
+        cls.admin_router_ip = []
+        cls.resource_setup_container()
+        cls._setup_bgp_admin_client()
+        cls._setup_bgp_non_admin_client()
+        cls.lock = threading.Lock()
+
+    @classmethod
+    def resource_cleanup(cls):
+        for ctn in cls.containers:
+            try:
+                ctn.stop()
+            except ctn_base.CommandError:
+                pass
+            ctn.remove()
+        for br in cls.bridges:
+            br.delete()
+        super(BgpSpeakerScenarioTestJSONBase, cls).resource_cleanup()
+
+    @classmethod
+    def get_subnet(self, start='10.10.1.0', end='10.10.255.0', step=256):
+        subnet_gen = netaddr.iter_iprange(start, end, step=step)
+        i = 1
+        while True:
+            with self.lock:
+                try:
+                    yield (i, str(six.next(subnet_gen)))
+                except StopIteration:
+                    subnet_gen = netaddr.iter_iprange(start, end, step=step)
+                    yield (i, str(six.next(subnet_gen)))
+                i += 1
+
+    def net_resource_cleanup(self):
+        for floatingip in self.admin_floatingips:
+            self._try_delete_resource(self.admin_client.delete_floatingip,
+                                      floatingip['id'])
+        for routerport in self.admin_routerports:
+            self._try_delete_resource(
+                self.admin_client.remove_router_interface_with_subnet_id,
+                routerport['router_id'], routerport['subnet_id'])
+        for router in self.admin_routers:
+            self._try_delete_resource(self.admin_client.delete_router,
+                                      router['id'])
+
+    def create_bgp_speaker(self, auto_delete=True, **args):
+        data = {'bgp_speaker': args}
+        bgp_speaker = self.bgp_adm_client.create_bgp_speaker(data)
+        bgp_speaker_id = bgp_speaker['bgp_speaker']['id']
+        if auto_delete:
+            self.addCleanup(self.bgp_adm_client.delete_bgp_speaker,
+                            bgp_speaker_id)
+        return bgp_speaker['bgp_speaker']
+
+    def delete_bgp_speaker(self, id):
+        return self.bgp_adm_client.delete_bgp_speaker(id)
+
+    def create_bgp_peer(self, auto_delete=True, **args):
+        bgp_peer = self.bgp_adm_client.create_bgp_peer({'bgp_peer': args})
+        bgp_peer_id = bgp_peer['bgp_peer']['id']
+        if auto_delete:
+            self.addCleanup(self.bgp_adm_client.delete_bgp_peer,
+                            bgp_peer_id)
+        return bgp_peer['bgp_peer']
+
+    def delete_bgp_peer(self, id):
+        return self.bgp_adm_client.delete_bgp_peer(id)
+
+    def get_dragent_id(self):
+        agents = self.admin_client.list_agents(
+            agent_type="BGP dynamic routing agent")
+        self.assertTrue(agents['agents'][0]['alive'])
+        return agents['agents'][0]['id']
+
+    def add_bgp_speaker_to_dragent(self, agent_id, speaker_id):
+        self.bgp_adm_client.add_bgp_speaker_to_dragent(agent_id, speaker_id)
+
+    # tnets[[net1, subnet1, router1], [net2, subnet2, router2], ...]
+    def create_bgp_network(self, ip_version, scope,
+                           exnet, expool, exsubnet,
+                           tpool, tnets):
+        addr_scope = self.create_address_scope(scope.name,
+                                               ip_version=ip_version)
+        # external network
+        ext_net = self.create_shared_network(**{'router:external': True})
+        ext_net_id = ext_net['id']
+        ext_subnetpool = self.create_subnetpool(
+            expool.name,
+            is_admin=True,
+            default_prefixlen=expool.prefixlen,
+            address_scope_id=addr_scope['id'],
+            prefixes=expool.prefixes)
+        self.create_subnet(
+            {'id': ext_net_id},
+            cidr=netaddr.IPNetwork(exsubnet.cidr),
+            mask_bits=exsubnet.mask,
+            ip_version=ip_version,
+            client=self.admin_client,
+            subnetpool_id=ext_subnetpool['id'],
+            reserve_cidr=False)
+        # tenant network
+        tenant_subnetpool = self.create_subnetpool(
+            tpool.name,
+            default_prefixlen=tpool.prefixlen,
+            address_scope_id=addr_scope['id'],
+            prefixes=tpool.prefixes)
+        for tnet, tsubnet, router in tnets:
+            tenant_net = self.create_network()
+            tenant_subnet = self.create_subnet(
+                {'id': tenant_net['id']},
+                cidr=netaddr.IPNetwork(tsubnet.cidr),
+                mask_bits=tsubnet.mask,
+                ip_version=ip_version,
+                subnetpool_id=tenant_subnetpool['id'],
+                reserve_cidr=False)
+            # router
+            ext_gw_info = {'network_id': ext_net_id}
+            router_cr = self.admin_client.create_router(
+                router.name,
+                external_gateway_info=ext_gw_info)['router']
+            self.admin_routers.append(router_cr)
+            self.admin_client.add_router_interface_with_subnet_id(
+                router_cr['id'],
+                tenant_subnet['id'])
+            self.admin_routerports.append({'router_id': router_cr['id'],
+                                           'subnet_id': tenant_subnet['id']})
+            router = self.admin_client.show_router(router_cr['id'])['router']
+            fixed_ips = router['external_gateway_info']['external_fixed_ips']
+            self.admin_router_ip.append(fixed_ips[0]['ip_address'])
+        return ext_net_id
+
+    def create_and_add_peers_to_speaker(self, ext_net_id,
+                                        speaker_info, peer_infos,
+                                        auto_delete=True):
+        speaker = self.create_bgp_speaker(auto_delete=auto_delete,
+                                          **speaker_info)
+        speaker_id = speaker['id']
+        self.bgp_adm_client.add_bgp_gateway_network(speaker_id,
+                                                    ext_net_id)
+        peer_ids = []
+        for peer_args in peer_infos:
+            peer = self.create_bgp_peer(auto_delete=auto_delete,
+                                        **peer_args)
+            peer_id = peer['id']
+            peer_ids.append(peer_id)
+            self.bgp_adm_client.add_bgp_peer_with_id(speaker_id,
+                                                     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 = ''
+        for route in routes['advertised_routes']:
+            if route['destination'] == dest_addr:
+                next_hop = route['next_hop']
+                break
+        return 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
new file mode 100644
index 0000000..1bcf5b1
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
@@ -0,0 +1,146 @@
+# 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.
+
+import six
+from tempest import config
+
+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
+
+
+class BgpSpeakerProtoTestBase(base.BgpSpeakerScenarioTestJSONBase):
+
+    def _test_check_neighbor_established(self, ip_version):
+        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+        num, subnet = six.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(
+            ip_version, 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)
+
+    def _test_check_advertised_tenant_network(self, ip_version):
+        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+        num, subnet = six.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(
+            ip_version, 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)
+        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))
+
+    def _test_check_advertised_multiple_tenant_network(self, ip_version):
+        self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+        tnets = []
+        tnets_cidr = []
+        for i in range(0, 3):
+            num, subnet = six.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='')
+            tnets.append([TNet, TSubNet, MyRouter])
+            tnets_cidr.append(TNet.cidr)
+        ext_net_id = self.create_bgp_network(
+            ip_version, self.MyScope,
+            self.PNet, self.PPool, self.PSubNet,
+            self.TPool, tnets)
+        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)
+        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))
+
+    def _test_check_neighbor_established_with_multiple_peers(
+            self, ip_version):
+        for (bgp_peer_args, r_as_ip) in zip(self.bgp_peer_args,
+                                            self.r_as_ip):
+            bgp_peer_args['peer_ip'] = r_as_ip.split('/')[0]
+        num, subnet = six.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(
+            ip_version, 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)
+        self.check_multi_remote_as_state(self.dr, self.r_ass,
+                                         ctn_base.BGP_FSM_ESTABLISHED)
+
+    def _test_check_advertised_tenant_network_with_multiple_peers(
+            self, ip_version):
+        for (bgp_peer_args, r_as_ip) in zip(self.bgp_peer_args,
+                                            self.r_as_ip):
+            bgp_peer_args['peer_ip'] = r_as_ip.split('/')[0]
+        num, subnet = six.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(
+            ip_version, 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)
+        self.check_multi_remote_as_state(self.dr, self.r_ass,
+                                         ctn_base.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)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py
new file mode 100644
index 0000000..dd170e7
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py
@@ -0,0 +1,105 @@
+# 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
new file mode 100644
index 0000000..f492ede
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py
@@ -0,0 +1,132 @@
+#    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
new file mode 100644
index 0000000..1c680f9
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
@@ -0,0 +1,115 @@
+# 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
+import six
+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 = six.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 = six.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 = six.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/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/__init__.py
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
new file mode 100644
index 0000000..158b7ad
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
@@ -0,0 +1,132 @@
+# 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 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 BgpSpeakerIpv4Test(test_base.BgpSpeakerProtoTestBase):
+
+    RAS_MAX = 3
+    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)
+    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': 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'}
+    ]
+
+    def setUp(self):
+        super(BgpSpeakerIpv4Test, self).setUp()
+
+    @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)
+        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)
+            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('7f2acbc2-ff88-4a63-aa02-a2f9feb3f5b0')
+    def test_check_neighbor_established(self):
+        self._test_check_neighbor_established(self.ip_version)
+
+    @decorators.idempotent_id('f32245fc-aeab-4244-acfa-3af9dd662e8d')
+    def test_check_advertised_tenant_network(self):
+        self._test_check_advertised_tenant_network(self.ip_version)
+
+    @decorators.idempotent_id('a5c238de-b750-499c-aaa2-b44a057e9ed3')
+    def test_check_advertised_multiple_tenant_network(self):
+        self._test_check_advertised_multiple_tenant_network(self.ip_version)
+
+    @decorators.idempotent_id('e4961cc1-0c47-4081-a896-caaa9342ca75')
+    def test_check_neighbor_established_with_multiple_peers(self):
+        self._test_check_neighbor_established_with_multiple_peers(
+            self.ip_version)
+
+    @decorators.idempotent_id('91971dfb-c129-4744-9fbb-ac4f9e8d56c0')
+    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/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/__init__.py
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
new file mode 100644
index 0000000..937b38d
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
@@ -0,0 +1,133 @@
+# 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 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 BgpSpeakerIpv6Test(test_base.BgpSpeakerProtoTestBase):
+
+    RAS_MAX = 3
+    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,
+                      prefixes=[PNet.net + '/8'])
+    PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
+    TPool = base.Pool(name='tenant-test-pool', prefixlen=64,
+                      prefixes=['2001:db8:8000::/48'])
+    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='2001:db8:9002::/48'),
+        base.AS(asn='64523', router_id='192.168.0.13',
+                adv_net='2001:db8:9003::/48'),
+        base.AS(asn='64524', router_id='192.168.0.14',
+                adv_net='2001:db8:9004::/48')
+    ]
+
+    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'}
+    ]
+
+    def setUp(self):
+        super(BgpSpeakerIpv6Test, self).setUp()
+
+    @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)
+        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'})
+            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.tnet_gen = cls.get_subnet(start='2001:db8:8000:1::',
+                                      end='2001:db8:8000:ffff::',
+                                      step=65536 * 65536 * 65536 * 65536)
+
+    @decorators.idempotent_id('5194a8e2-95bd-49f0-872d-1e3e875ede32')
+    def test_check_neighbor_established(self):
+        self._test_check_neighbor_established(self.ip_version)
+
+    @decorators.idempotent_id('6a3483fc-8c8a-4387-bda6-c7061410e04b')
+    def test_check_advertised_tenant_network(self):
+        self._test_check_advertised_tenant_network(self.ip_version)
+
+    @decorators.idempotent_id('aca5d678-c249-4de5-921b-6b6ba621e4f7')
+    def test_check_advertised_multiple_tenant_network(self):
+        self._test_check_advertised_multiple_tenant_network(self.ip_version)
+
+    @decorators.idempotent_id('f81012f3-2f7e-4b3c-8c1d-b1778146d712')
+    def test_check_neighbor_established_with_multiple_peers(self):
+        self._test_check_neighbor_established_with_multiple_peers(
+            self.ip_version)
+
+    @decorators.idempotent_id('be710ec1-a338-44c9-8b89-31c3532aae65')
+    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/scenario/test_bgp.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
similarity index 97%
rename from neutron_tempest_plugin/scenario/test_bgp.py
rename to neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
index 62f433b..85cc810 100644
--- a/neutron_tempest_plugin/scenario/test_bgp.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py
@@ -24,12 +24,7 @@
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import base
 from neutron_tempest_plugin.scenario import constants
-
-try:
-    # TODO(yamamoto): Remove this hack after bgp tests are rehomed
-    from neutron_dynamic_routing.tests.tempest import bgp_client
-except ImportError:
-    bgp_client = None
+from neutron_tempest_plugin.services.bgp import bgp_client
 
 
 CONF = config.CONF
diff --git a/neutron_tempest_plugin/services/bgp/__init__.py b/neutron_tempest_plugin/services/bgp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/services/bgp/__init__.py
diff --git a/neutron_tempest_plugin/services/bgp/bgp_client.py b/neutron_tempest_plugin/services/bgp/bgp_client.py
new file mode 100644
index 0000000..ae51427
--- /dev/null
+++ b/neutron_tempest_plugin/services/bgp/bgp_client.py
@@ -0,0 +1,143 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+#    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 oslo_serialization import jsonutils
+from tempest.lib.common import rest_client
+
+
+class BgpSpeakerClientJSON(rest_client.RestClient):
+
+    def create_bgp_speaker(self, post_data):
+        post_body = jsonutils.dumps(post_data)
+        resp, body = self.post('v2.0/bgp-speakers', post_body)
+        body = jsonutils.loads(body)
+        self.expected_success(201, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_bgp_speaker(self, id):
+        uri = 'v2.0/bgp-speakers/{0}'.format(id)
+        resp, body = self.get(uri)
+        body = jsonutils.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_bgp_speakers(self):
+        uri = self.get_uri("bgp-speakers")
+        resp, body = self.get(uri)
+        body = jsonutils.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBodyList(resp, body)
+
+    def update_bgp_speaker(self, id, put_data):
+        uri = 'v2.0/bgp-speakers/{0}'.format(id)
+        update_body = {'bgp_speaker': put_data}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        body = jsonutils.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_bgp_speaker(self, id):
+        uri = 'v2.0/bgp-speakers/{0}'.format(id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_bgp_peer(self, post_data):
+        post_body = jsonutils.dumps(post_data)
+        resp, body = self.post('v2.0/bgp-peers', post_body)
+        body = jsonutils.loads(body)
+        self.expected_success(201, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_bgp_peer(self, id):
+        uri = 'v2.0/bgp-peers/{0}'.format(id)
+        resp, body = self.get(uri)
+        body = jsonutils.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_bgp_peer(self, id):
+        uri = 'v2.0/bgp-peers/{0}'.format(id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id):
+        uri = 'v2.0/bgp-speakers/%s/add_bgp_peer' % bgp_speaker_id
+        update_body = {"bgp_peer_id": bgp_peer_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def remove_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id):
+        uri = 'v2.0/bgp-speakers/%s/remove_bgp_peer' % bgp_speaker_id
+        update_body = {"bgp_peer_id": bgp_peer_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_bgp_gateway_network(self, bgp_speaker_id, network_id):
+        uri = 'v2.0/bgp-speakers/%s/add_gateway_network' % bgp_speaker_id
+        update_body = {"network_id": network_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def remove_bgp_gateway_network(self, bgp_speaker_id, network_id):
+        uri = 'v2.0/bgp-speakers/%s/remove_gateway_network' % bgp_speaker_id
+        update_body = {"network_id": network_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_bgp_advertised_routes(self, bgp_speaker_id):
+        base_uri = 'v2.0/bgp-speakers/%s/get_advertised_routes'
+        uri = base_uri % bgp_speaker_id
+        resp, body = self.get(uri)
+        body = jsonutils.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_dragents_for_bgp_speaker(self, bgp_speaker_id):
+        uri = 'v2.0/bgp-speakers/%s/bgp-dragents' % bgp_speaker_id
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_bgp_speaker_to_dragent(self, agent_id, bgp_speaker_id,
+                                   ignore_errors=False):
+        uri = 'v2.0/agents/%s/bgp-drinstances' % agent_id
+        update_body = {"bgp_speaker_id": bgp_speaker_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.post(uri, update_body)
+        if not ignore_errors:
+            self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def remove_bgp_speaker_from_dragent(self, agent_id, bgp_speaker_id):
+        uri = 'v2.0/agents/%s/bgp-drinstances/%s' % (agent_id, bgp_speaker_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/playbooks/dynamic-routing-pre-run.yaml b/playbooks/dynamic-routing-pre-run.yaml
new file mode 100644
index 0000000..925223e
--- /dev/null
+++ b/playbooks/dynamic-routing-pre-run.yaml
@@ -0,0 +1,3 @@
+- hosts: all
+  roles:
+    - docker-setup
diff --git a/requirements.txt b/requirements.txt
index 2febb7e..9a5e99f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,6 +7,7 @@
 oslo.config>=5.2.0 # Apache-2.0
 ipaddress>=1.0.17;python_version<'3.3' # PSF
 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
diff --git a/roles/docker-setup/files/52_docker_for_tempest b/roles/docker-setup/files/52_docker_for_tempest
new file mode 100644
index 0000000..a61c897
--- /dev/null
+++ b/roles/docker-setup/files/52_docker_for_tempest
@@ -0,0 +1 @@
+tempest ALL=(ALL) NOPASSWD: ALL
diff --git a/roles/docker-setup/files/docker_apparmor b/roles/docker-setup/files/docker_apparmor
new file mode 100644
index 0000000..198eeb7
--- /dev/null
+++ b/roles/docker-setup/files/docker_apparmor
@@ -0,0 +1,34 @@
+#include <tunables/global>
+
+profile docker-default flags=(attach_disconnected,mediate_deleted) {
+
+  #include <abstractions/base>
+
+  network,
+  capability,
+  file,
+  umount,
+
+  deny @{PROC}/* w,   # deny write for all files directly in /proc (not in a subdir)
+  # deny write to files not in /proc/<number>/** or /proc/sys/**
+  deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
+  deny @{PROC}/sys/[^k]** w,  # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
+  deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w,  # deny everything except shm* in /proc/sys/kernel/
+  deny @{PROC}/sysrq-trigger rwklx,
+  deny @{PROC}/mem rwklx,
+  deny @{PROC}/kmem rwklx,
+  deny @{PROC}/kcore rwklx,
+
+  deny mount,
+
+  deny /sys/[^f]*/** wklx,
+  deny /sys/f[^s]*/** wklx,
+  deny /sys/fs/[^c]*/** wklx,
+  deny /sys/fs/c[^g]*/** wklx,
+  deny /sys/fs/cg[^r]*/** wklx,
+  deny /sys/firmware/efi/efivars/** rwklx,
+  deny /sys/kernel/security/** rwklx,
+
+  # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
+  ptrace (trace,read) peer=docker-default,
+}
diff --git a/roles/docker-setup/tasks/main.yml b/roles/docker-setup/tasks/main.yml
new file mode 100644
index 0000000..19d8bfb
--- /dev/null
+++ b/roles/docker-setup/tasks/main.yml
@@ -0,0 +1,36 @@
+- name: Install and configure docker
+  become: yes
+  package:
+    name: docker.io
+    state: present
+
+- name: Copy 52_docker_for_tempest to /etc/sudoers.d
+  copy:
+    src: 52_docker_for_tempest
+    dest: /etc/sudoers.d
+    owner: root
+    group: root
+    mode: 0440
+  become: yes
+
+- name: Copy docker_apparmor to /etc/apparmor.d
+  copy:
+    src: docker_apparmor
+    dest: /etc/apparmor.d
+    owner: root
+    group: root
+    mode: 0640
+  become: yes
+
+- name: Ensure apparmor is restarted
+  become: yes
+  service:
+    name: apparmor
+    state: restarted
+  ignore_errors: yes
+
+- name: Ensure docker engine is restarted
+  become: yes
+  service:
+    name: docker
+    state: restarted