Add share type change to Share Migration

This patch adds a 'new_share_type_id' parameter to Share Migration,
where the destination share can be provisioned under a different
share type of choice.

Host-assisted migration handles it by creating a totally new share,
as before.

Driver-assisted migration handles by creating the destination
instance model with the new share type, the driver is responsible
for making the necessary changes to satisfy the provided model.

In order to accomplish this, a database change was required,
transferring the 'share_type_id' field from the 'shares' table
to the 'share_instances' table.

APIImpact

Partially implements: blueprint newton-migration-improvements
Change-Id: I3200eaaa5b66d9b8ce1cbd16c1658db8516c70fb
diff --git a/manila_tempest_tests/services/share/v2/json/shares_client.py b/manila_tempest_tests/services/share/v2/json/shares_client.py
index b5a745e..0ccc18b 100755
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -1021,13 +1021,14 @@
                       force_host_assisted_migration=False,
                       new_share_network_id=None, writable=False,
                       preserve_metadata=False, nondisruptive=False,
-                      version=LATEST_MICROVERSION):
+                      new_share_type_id=None, version=LATEST_MICROVERSION):
 
         body = {
             'migration_start': {
                 'host': host,
                 'force_host_assisted_migration': force_host_assisted_migration,
                 'new_share_network_id': new_share_network_id,
+                'new_share_type_id': new_share_type_id,
                 'writable': writable,
                 'preserve_metadata': preserve_metadata,
                 'nondisruptive': nondisruptive,
diff --git a/manila_tempest_tests/tests/api/admin/test_migration.py b/manila_tempest_tests/tests/api/admin/test_migration.py
index ed643ff..0f55598 100644
--- a/manila_tempest_tests/tests/api/admin/test_migration.py
+++ b/manila_tempest_tests/tests/api/admin/test_migration.py
@@ -13,8 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import six
+
 import ddt
 from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest import test
 
 from manila_tempest_tests.common import constants
@@ -59,6 +62,18 @@
                 CONF.share.run_driver_assisted_migration_tests):
             raise cls.skipException("Share migration tests are disabled.")
 
+        extra_specs = {
+            'storage_protocol': CONF.share.capability_storage_protocol,
+            'driver_handles_share_servers': (
+                CONF.share.multitenancy_enabled),
+            'snapshot_support': six.text_type(
+                CONF.share.capability_snapshot_support),
+        }
+        cls.new_type = cls.create_share_type(
+            name=data_utils.rand_name('new_share_type_for_migration'),
+            cleanup_in_class=True,
+            extra_specs=extra_specs)
+
     @test.attr(type=[base.TAG_POSITIVE, base.TAG_BACKEND])
     @base.skip_if_microversion_lt("2.22")
     @ddt.data(True, False)
@@ -115,16 +130,19 @@
         old_share_network_id = share['share_network_id']
         new_share_network_id = self._create_secondary_share_network(
             old_share_network_id)
+        old_share_type_id = share['share_type']
+        new_share_type_id = self.new_type['share_type']['id']
 
         share = self.migrate_share(
             share['id'], dest_pool,
             force_host_assisted_migration=force_host_assisted,
-            wait_for_status=task_state,
+            wait_for_status=task_state, new_share_type_id=new_share_type_id,
             new_share_network_id=new_share_network_id)
 
         self._validate_migration_successful(
-            dest_pool, share, task_state,
-            complete=False, share_network_id=old_share_network_id)
+            dest_pool, share, task_state, complete=False,
+            share_network_id=old_share_network_id,
+            share_type_id=old_share_type_id)
 
         progress = self.shares_v2_client.migration_get_progress(share['id'])
 
@@ -135,7 +153,8 @@
 
         self._validate_migration_successful(
             dest_pool, share, constants.TASK_STATE_MIGRATION_SUCCESS,
-            complete=True, share_network_id=new_share_network_id)
+            complete=True, share_network_id=new_share_network_id,
+            share_type_id=new_share_type_id)
 
     def _setup_migration(self):
 
@@ -146,7 +165,7 @@
                                      "needed to run share migration tests.")
 
         share = self.create_share(self.protocol)
-        share = self.shares_client.get_share(share['id'])
+        share = self.shares_v2_client.get_share(share['id'])
 
         self.shares_v2_client.create_access_rule(
             share['id'], access_to="50.50.50.50", access_level="rw")
@@ -174,7 +193,8 @@
 
     def _validate_migration_successful(self, dest_pool, share, status_to_wait,
                                        version=CONF.share.max_api_microversion,
-                                       complete=True, share_network_id=None):
+                                       complete=True, share_network_id=None,
+                                       share_type_id=None):
 
         statuses = ((status_to_wait,)
                     if not isinstance(status_to_wait, (tuple, list, set))
@@ -190,6 +210,8 @@
         self.assertIn(share['task_state'], statuses)
         if share_network_id:
             self.assertEqual(share_network_id, share['share_network_id'])
+        if share_type_id:
+            self.assertEqual(share_type_id, share['share_type'])
 
         # Share migrated
         if complete:
diff --git a/manila_tempest_tests/tests/api/admin/test_migration_negative.py b/manila_tempest_tests/tests/api/admin/test_migration_negative.py
index 94450d2..5d7a578 100644
--- a/manila_tempest_tests/tests/api/admin/test_migration_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_migration_negative.py
@@ -13,7 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import six
+
 from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
 from tempest import test
 import testtools
@@ -65,6 +68,18 @@
 
         cls.dest_pool = dest_pool['name']
 
+        extra_specs = {
+            'storage_protocol': CONF.share.capability_storage_protocol,
+            'driver_handles_share_servers': CONF.share.multitenancy_enabled,
+            'snapshot_support': six.text_type(
+                not CONF.share.capability_snapshot_support),
+        }
+        cls.new_type = cls.create_share_type(
+            name=data_utils.rand_name(
+                'new_invalid_share_type_for_migration'),
+            cleanup_in_class=True,
+            extra_specs=extra_specs)
+
     @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
     @base.skip_if_microversion_lt("2.22")
     def test_migration_cancel_invalid(self):
@@ -144,7 +159,18 @@
             force_host_assisted_migration=True, writable=True,
             preserve_metadata=True)
         self.shares_v2_client.wait_for_migration_status(
-            self.share['id'], self.dest_pool, 'migration_error')
+            self.share['id'], self.dest_pool,
+            constants.TASK_STATE_MIGRATION_ERROR)
+
+    @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @base.skip_if_microversion_lt("2.22")
+    def test_migrate_share_change_type_no_valid_host(self):
+        self.shares_v2_client.migrate_share(
+            self.share['id'], self.dest_pool,
+            new_share_type_id=self.new_type['share_type']['id'])
+        self.shares_v2_client.wait_for_migration_status(
+            self.share['id'], self.dest_pool,
+            constants.TASK_STATE_MIGRATION_ERROR)
 
     @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
     @base.skip_if_microversion_lt("2.22")
@@ -172,6 +198,14 @@
     @base.skip_if_microversion_lt("2.22")
     def test_migrate_share_invalid_share_network(self):
         self.assertRaises(
-            lib_exc.NotFound, self.shares_v2_client.migrate_share,
+            lib_exc.BadRequest, self.shares_v2_client.migrate_share,
             self.share['id'], self.dest_pool,
             new_share_network_id='invalid_net_id')
+
+    @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @base.skip_if_microversion_lt("2.22")
+    def test_migrate_share_invalid_share_type(self):
+        self.assertRaises(
+            lib_exc.BadRequest, self.shares_v2_client.migrate_share,
+            self.share['id'], self.dest_pool, True,
+            new_share_type_id='invalid_type_id')
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index 6d0eb25..768a115 100644
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -404,14 +404,14 @@
     def migrate_share(
             cls, share_id, dest_host, wait_for_status, client=None,
             force_host_assisted_migration=False, new_share_network_id=None,
-            **kwargs):
+            new_share_type_id=None, **kwargs):
         client = client or cls.shares_v2_client
         client.migrate_share(
             share_id, dest_host,
             force_host_assisted_migration=force_host_assisted_migration,
             new_share_network_id=new_share_network_id,
             writable=False, preserve_metadata=False, nondisruptive=False,
-            **kwargs)
+            new_share_type_id=new_share_type_id, **kwargs)
         share = client.wait_for_migration_status(
             share_id, dest_host, wait_for_status, **kwargs)
         return share