Extend cleanup CLI to delete regions

RegionService class enables deletion of regions which were
neglected by tempest cleanup before.

Change-Id: I2c978a7b2151b37bb0a224bafd1be490f04950ee
Closes-Bug: #1848672
diff --git a/releasenotes/notes/Extend-cleanup-CLI-to-delete-regions-9f1dbda2c8de12e2.yaml b/releasenotes/notes/Extend-cleanup-CLI-to-delete-regions-9f1dbda2c8de12e2.yaml
new file mode 100644
index 0000000..e2fc5b3
--- /dev/null
+++ b/releasenotes/notes/Extend-cleanup-CLI-to-delete-regions-9f1dbda2c8de12e2.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    tempest cleanup CLI is extended about region deletion. Until now, the
+    regions have been neglected by tempest cleanup. From now on, tempest
+    cleanup is able to delete leftover regions as well.
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 8b625d0..c35cecd 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -730,6 +730,44 @@
 
 
 # begin global services
+class RegionService(BaseService):
+
+    def __init__(self, manager, **kwargs):
+        super(RegionService, self).__init__(kwargs)
+        self.client = manager.regions_client
+
+    def list(self):
+        client = self.client
+        regions = client.list_regions()
+        if not self.is_save_state:
+            regions = [region for region in regions['regions'] if region['id']
+                       not in self.saved_state_json['regions'].keys()]
+            return regions
+        else:
+            return regions['regions']
+
+    def delete(self):
+        client = self.client
+        regions = self.list()
+        for region in regions:
+            try:
+                client.delete_region(region['id'])
+            except Exception:
+                LOG.exception("Delete Region %s exception.", region['id'])
+
+    def dry_run(self):
+        regions = self.list()
+        self.data['regions'] = {}
+        for region in regions:
+            self.data['regions'][region['id']] = region
+
+    def save_state(self):
+        regions = self.list()
+        self.data['regions'] = {}
+        for region in regions:
+            self.data['regions'][region['id']] = region
+
+
 class FlavorService(BaseService):
     def __init__(self, manager, **kwargs):
         super(FlavorService, self).__init__(kwargs)
@@ -1005,4 +1043,5 @@
     global_services.append(ProjectService)
     global_services.append(DomainService)
     global_services.append(RoleService)
+    global_services.append(RegionService)
     return global_services
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index de0dbec..ae08d02 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -175,7 +175,8 @@
         "ports": {u'aa74aa4v-741a': u'saved-port'},
         "security_groups": {u'7q844add-3697': u'saved-sec-group'},
         "subnets": {u'55ttda4a-2584': u'saved-subnet'},
-        "subnetpools": {u'8acf64c1-43fc': u'saved-subnet-pool'}
+        "subnetpools": {u'8acf64c1-43fc': u'saved-subnet-pool'},
+        "regions": {u'RegionOne': {}}
     }
     # Mocked methods
     get_method = 'tempest.lib.common.rest_client.RestClient.get'
@@ -1202,6 +1203,57 @@
 
 
 # begin global services
+class TestRegionService(BaseCmdServiceTests):
+    service_class = 'RegionService'
+    service_name = 'regions'
+    response = {
+        "regions": [{
+            "parent_region_id": None,
+            "id": "RegionOne",
+            "links": {
+                    "self":
+                    "http://10.0.145.61:5000/v3/regions/RegionOne"
+                    },
+            "description": ""
+        },
+            {
+            "parent_region_id": None,
+            "id": "RegionTwo",
+            "links": {
+                "self":
+                    "http://10.0.145.61:5000/v3/regions/RegionTwo"
+                },
+            "description": ""
+            }],
+        "links": {
+            "self":
+                "http://10.0.145.61:5000/v3/regions",
+                "next": None,
+                "previous": None
+        }
+    }
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, "exception", None)]
+        self._test_delete(delete_mock)
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, "exception", None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+
 class TestDomainService(BaseCmdServiceTests):
 
     service_class = 'DomainService'