Add tempest tests for DVR router state management

Add positive and negative tempest tests that assert correct operation of
a DVR router when the extension 'router-admin-state-down-before-update'
is enabled.
Added extension to neutron-tempest-plugin job.

Depends-On: https://review.opendev.org/#/c/625134/
Change-Id: Iaf24afa3d0fc28f2bec7be1b705a8d8b5ff886f8
Signed-off-by: Matt Welch <matt.welch@intel.com>
diff --git a/.zuul.yaml b/.zuul.yaml
index 4e31ba8..f5343d2 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -61,6 +61,7 @@
         - rbac-policies
         - rbac-security-groups
         - router
+        - router-admin-state-down-before-update
         - router_availability_zone
         - security-group
         - segment
diff --git a/neutron_tempest_plugin/api/test_routers.py b/neutron_tempest_plugin/api/test_routers.py
index 5c98c8b..ab6b0f6 100644
--- a/neutron_tempest_plugin/api/test_routers.py
+++ b/neutron_tempest_plugin/api/test_routers.py
@@ -280,6 +280,62 @@
         self.assertNotIn('ha', show_body['router'])
 
 
+class DvrRoutersTestUpdateDistributedExtended(base_routers.BaseRouterTest):
+
+    required_extensions = ['dvr', 'l3-ha',
+                           'router-admin-state-down-before-update']
+
+    @decorators.idempotent_id('0ffb9973-0c1a-4b76-a1f2-060178057661')
+    def test_convert_centralized_router_to_distributed_extended(self):
+        router_args = {'tenant_id': self.client.tenant_id,
+                       'distributed': False, 'ha': False}
+        router = self.admin_client.create_router(
+            data_utils.rand_name('router'), admin_state_up=True,
+            **router_args)['router']
+        self.addCleanup(self.admin_client.delete_router,
+                        router['id'])
+        self.assertTrue(router['admin_state_up'])
+        self.assertFalse(router['distributed'])
+        # take router down to allow setting the router to distributed
+        update_body = self.admin_client.update_router(router['id'],
+                                                      admin_state_up=False)
+        self.assertFalse(update_body['router']['admin_state_up'])
+        # set the router to distributed
+        update_body = self.admin_client.update_router(router['id'],
+                                                      distributed=True)
+        self.assertTrue(update_body['router']['distributed'])
+        # bring the router back up
+        update_body = self.admin_client.update_router(router['id'],
+                                                      admin_state_up=True)
+        self.assertTrue(update_body['router']['admin_state_up'])
+        self.assertTrue(update_body['router']['distributed'])
+
+    @decorators.idempotent_id('e9a8f55b-c535-44b7-8b0a-20af6a7c2921')
+    def test_convert_distributed_router_to_centralized_extended(self):
+        router_args = {'tenant_id': self.client.tenant_id,
+                       'distributed': True, 'ha': False}
+        router = self.admin_client.create_router(
+            data_utils.rand_name('router'), admin_state_up=True,
+            **router_args)['router']
+        self.addCleanup(self.admin_client.delete_router,
+                        router['id'])
+        self.assertTrue(router['admin_state_up'])
+        self.assertTrue(router['distributed'])
+        # take router down to allow setting the router to centralized
+        update_body = self.admin_client.update_router(router['id'],
+                                                      admin_state_up=False)
+        self.assertFalse(update_body['router']['admin_state_up'])
+        # set router to centralized
+        update_body = self.admin_client.update_router(router['id'],
+                                                      distributed=False)
+        self.assertFalse(update_body['router']['distributed'])
+        # bring router back up
+        update_body = self.admin_client.update_router(router['id'],
+                                                      admin_state_up=True)
+        self.assertTrue(update_body['router']['admin_state_up'])
+        self.assertFalse(update_body['router']['distributed'])
+
+
 class HaRoutersTest(base_routers.BaseRouterTest):
 
     required_extensions = ['l3-ha']
diff --git a/neutron_tempest_plugin/api/test_routers_negative.py b/neutron_tempest_plugin/api/test_routers_negative.py
index bbd6c5d..f085fc9 100644
--- a/neutron_tempest_plugin/api/test_routers_negative.py
+++ b/neutron_tempest_plugin/api/test_routers_negative.py
@@ -80,6 +80,56 @@
                 data_utils.rand_name('router'), distributed=True)
 
 
+class DvrRoutersNegativeTestExtended(RoutersNegativeTestBase):
+
+    required_extensions = ['dvr', 'router-admin-state-down-before-update']
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('5379fe06-e45e-4a4f-8b4a-9e28a924b451')
+    def test_router_update_distributed_returns_exception(self):
+        # create a centralized router
+        router_args = {'tenant_id': self.client.tenant_id,
+                       'distributed': False}
+        router = self.admin_client.create_router(
+            data_utils.rand_name('router'), admin_state_up=True,
+            **router_args)['router']
+        self.assertTrue(router['admin_state_up'])
+        self.assertFalse(router['distributed'])
+        # attempt to set the router to distributed, catch BadRequest exception
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_client.update_router,
+                          router['id'],
+                          distributed=True)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('c277e945-3b39-442d-b149-e2e8cc6a2b40')
+    def test_router_update_centralized_returns_exception(self):
+        # create a centralized router
+        router_args = {'tenant_id': self.client.tenant_id,
+                       'distributed': False}
+        router = self.admin_client.create_router(
+            data_utils.rand_name('router'), admin_state_up=True,
+            **router_args)['router']
+        self.assertTrue(router['admin_state_up'])
+        self.assertFalse(router['distributed'])
+        # take the router down to modify distributed->True
+        update_body = self.admin_client.update_router(router['id'],
+                                                      admin_state_up=False)
+        self.assertFalse(update_body['router']['admin_state_up'])
+        update_body = self.admin_client.update_router(router['id'],
+                                                      distributed=True)
+        self.assertTrue(update_body['router']['distributed'])
+        # set admin_state_up=True
+        update_body = self.admin_client.update_router(router['id'],
+                                                      admin_state_up=True)
+        self.assertTrue(update_body['router']['admin_state_up'])
+        # attempt to set the router to centralized, catch BadRequest exception
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_client.update_router,
+                          router['id'],
+                          distributed=False)
+
+
 class HaRoutersNegativeTest(RoutersNegativeTestBase):
 
     required_extensions = ['l3-ha']