Merge "Add tests for share type availability_zones extra-spec"
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index b49fd32..9d891ec 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -30,7 +30,7 @@
                help="The minimum api microversion is configured to be the "
                     "value of the minimum microversion supported by Manila."),
     cfg.StrOpt("max_api_microversion",
-               default="2.47",
+               default="2.48",
                help="The maximum api microversion is configured to be the "
                     "value of the latest microversion supported by Manila."),
     cfg.StrOpt("region",
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 c91148b..e8a22b6 100644
--- a/manila_tempest_tests/tests/api/admin/test_migration_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_migration_negative.py
@@ -264,6 +264,19 @@
             new_share_type_id=new_type_opposite['share_type']['id'],
             new_share_network_id=new_share_network_id)
 
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @base.skip_if_microversion_lt("2.48")
+    def test_share_type_azs_share_migrate_unsupported_az(self):
+        extra_specs = self.add_extra_specs_to_dict({
+            'availability_zones': 'non-existent az'})
+        new_share_type = self.create_share_type(
+            name=data_utils.rand_name('share_type_specific_az'),
+            extra_specs=extra_specs, cleanup_in_class=False)
+        self.assertRaises(
+            lib_exc.BadRequest, self.shares_v2_client.migrate_share,
+            self.share['id'], self.dest_pool,
+            new_share_type_id=new_share_type['share_type']['id'])
+
     @testtools.skipUnless(CONF.share.run_driver_assisted_migration_tests,
                           "Driver-assisted migration tests are disabled.")
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
diff --git a/manila_tempest_tests/tests/api/test_replication.py b/manila_tempest_tests/tests/api/test_replication.py
index 41ad287..d827c8d 100644
--- a/manila_tempest_tests/tests/api/test_replication.py
+++ b/manila_tempest_tests/tests/api/test_replication.py
@@ -248,6 +248,28 @@
         self.assertEqual(access_to, rules_list[0]["access_to"])
         self.assertEqual('ro', rules_list[0]["access_level"])
 
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @base.skip_if_microversion_not_supported("2.48")
+    def test_share_type_azs_share_replicas(self):
+        az_spec = ', '.join(self.zones)
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type['id'], 'availability_zones', az_spec)
+        self.addCleanup(
+            self.admin_shares_v2_client.delete_share_type_extra_spec,
+            self.share_type['id'], 'availability_zones')
+
+        share = self.create_share(
+            share_type_id=self.share_type['id'], cleanup_in_class=False,
+            availability_zone=self.share_zone)
+        share = self.shares_v2_client.get_share(share['id'])
+        replica = self.create_share_replica(share['id'], self.replica_zone)
+        replica = self.shares_v2_client.get_share_replica(replica['id'])
+
+        self.assertEqual(self.share_zone, share['availability_zone'])
+        self.assertEqual(self.replica_zone, replica['availability_zone'])
+        self.assertIn(share['availability_zone'], self.zones)
+        self.assertIn(replica['availability_zone'], self.zones)
+
     @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
     def test_promote_and_promote_back(self):
         # Test promote back and forth between 2 share replicas
diff --git a/manila_tempest_tests/tests/api/test_replication_negative.py b/manila_tempest_tests/tests/api/test_replication_negative.py
index 30feeb6..8e20483 100644
--- a/manila_tempest_tests/tests/api/test_replication_negative.py
+++ b/manila_tempest_tests/tests/api/test_replication_negative.py
@@ -194,6 +194,19 @@
             lib_exc.Conflict, self.admin_client.migrate_share,
             self.share1['id'], dest_host)
 
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @base.skip_if_microversion_lt("2.48")
+    def test_try_add_replica_share_type_azs_unsupported_az(self):
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type['id'], 'availability_zones', 'non-existent az')
+        self.addCleanup(
+            self.admin_shares_v2_client.delete_share_type_extra_spec,
+            self.share_type['id'], 'availability_zones')
+        self.assertRaises(lib_exc.BadRequest,
+                          self.create_share_replica,
+                          self.share1['id'],
+                          self.replica_zone)
+
 
 @testtools.skipUnless(CONF.share.run_replication_tests,
                       'Replication tests are disabled.')
diff --git a/manila_tempest_tests/tests/api/test_share_type_availability_zones.py b/manila_tempest_tests/tests/api/test_share_type_availability_zones.py
new file mode 100644
index 0000000..79678ca
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_share_type_availability_zones.py
@@ -0,0 +1,143 @@
+#    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 ddt
+from tempest import config
+from tempest.lib.common.utils import data_utils
+import testtools
+
+from manila_tempest_tests.tests.api import base
+
+tc = testtools.testcase
+CONF = config.CONF
+
+
+@base.skip_if_microversion_not_supported("2.48")
+@ddt.ddt
+class ShareTypeAvailabilityZonesTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(ShareTypeAvailabilityZonesTest, cls).resource_setup()
+        cls.share_type = cls._create_share_type()
+        cls.share_type_id = cls.share_type['id']
+        cls.share_group_type = cls._create_share_group_type()
+        cls.share_group_type_id = cls.share_group_type['id']
+        all_azs = cls.get_availability_zones()
+        cls.valid_azs = cls.get_availability_zones_matching_share_type(
+            cls.share_type)
+        cls.invalid_azs = ((set(all_azs) - set(cls.valid_azs))
+                           or ['az_that_doesnt_exist'])
+        cls.az_spec = 'availability_zones'
+        cls.valid_azs_spec = ', '.join(cls.valid_azs)
+        cls.invalid_azs_spec = ', '.join(cls.invalid_azs)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data('az1,     az2, az 3  ', 'az1,az2,az 3', 'az1   ,az2,   az 3')
+    def test_share_type_azs_create_and_get_share_type(self, spec):
+        az_spec = {'availability_zones': spec}
+        extra_specs = self.add_extra_specs_to_dict(az_spec)
+        share_type = self.create_share_type(
+            data_utils.rand_name('az_share_type'),
+            cleanup_in_class=False,
+            extra_specs=extra_specs,
+            client=self.admin_shares_v2_client)['share_type']
+        self.assertEqual(
+            'az1,az2,az 3', share_type['extra_specs']['availability_zones'])
+
+        share_type = self.admin_shares_v2_client.get_share_type(
+            share_type['id'])['share_type']
+        self.assertEqual(
+            'az1,az2,az 3', share_type['extra_specs']['availability_zones'])
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data('az1', 'az2', 'az 3', 'az1,  az 3', 'az 3, az1',
+              'az2, az 3, az1')
+    def test_share_type_azs_filter_by_availability_zones(self, filter):
+        az_spec = {'availability_zones': 'az1, az2, az 3'}
+        extra_specs = self.add_extra_specs_to_dict(az_spec)
+        share_type_in_specific_azs = self.create_share_type(
+            data_utils.rand_name('support_some_azs_share_type'),
+            cleanup_in_class=False,
+            extra_specs=extra_specs,
+            client=self.admin_shares_v2_client)['share_type']
+
+        extra_specs = self.add_extra_specs_to_dict()
+        share_type_no_az_spec = self.create_share_type(
+            data_utils.rand_name('support_any_az_share_type'),
+            cleanup_in_class=False,
+            extra_specs=extra_specs,
+            client=self.admin_shares_v2_client)['share_type']
+
+        share_types = self.admin_shares_v2_client.list_share_types(
+            params={'extra_specs': {'availability_zones': filter}}
+        )['share_types']
+        share_type_ids = [s['id'] for s in share_types]
+        self.assertIn(share_type_in_specific_azs['id'], share_type_ids)
+        self.assertIn(share_type_no_az_spec['id'], share_type_ids)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_share_type_azs_old_version_api_ignores_spec(self):
+        """< v2.48, configuring share type AZs shouldn't fail share creation"""
+        # Use valid AZs as share_type's availability_zones
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.valid_azs_spec)
+        self.create_share(share_type_id=self.share_type_id,
+                          cleanup_in_class=False, version='2.47')
+
+        # Use invalid AZs as share_type's availability_zones
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.invalid_azs_spec)
+        share = self.create_share(share_type_id=self.share_type_id,
+                                  cleanup_in_class=False, version='2.47')
+        share = self.shares_v2_client.get_share(share['id'])
+        # Test default scheduler behavior: the share type capabilities should
+        # have ensured the share landed in an AZ that is supported
+        # regardless of the 'availability_zones' extra-spec
+        self.assertIn(share['availability_zone'], self.valid_azs)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data(True, False)
+    def test_share_type_azs_shares_az_in_create_req(self, specify_az):
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.valid_azs_spec)
+        kwargs = {
+            'share_type_id': self.share_type_id,
+            'cleanup_in_class': False,
+            'availability_zone': self.valid_azs[0] if specify_az else None,
+        }
+        share = self.create_share(**kwargs)
+        share = self.shares_v2_client.get_share(share['id'])
+        if specify_az:
+            self.assertEqual(self.valid_azs[0], share['availability_zone'])
+        else:
+            self.assertIn(share['availability_zone'], self.valid_azs)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data(True, False)
+    def test_share_type_azs_share_groups_az_in_create_req(self, specify_az):
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.valid_azs_spec)
+        kwargs = {
+            'share_group_type_id': self.share_group_type_id,
+            'share_type_ids': [self.share_type_id],
+            'cleanup_in_class': False,
+            'availability_zone': self.valid_azs[0] if specify_az else None,
+        }
+        # Create share group
+        share_group = self.create_share_group(**kwargs)
+        share_group = self.shares_v2_client.get_share_group(share_group['id'])
+        if specify_az:
+            self.assertEqual(self.valid_azs[0],
+                             share_group['availability_zone'])
+        else:
+            self.assertIn(share_group['availability_zone'], self.valid_azs)
diff --git a/manila_tempest_tests/tests/api/test_share_type_availability_zones_negative.py b/manila_tempest_tests/tests/api/test_share_type_availability_zones_negative.py
new file mode 100644
index 0000000..0e2d9c7
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_share_type_availability_zones_negative.py
@@ -0,0 +1,92 @@
+#    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 ddt
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
+from testtools import testcase as tc
+
+from manila_tempest_tests.tests.api import base
+
+
+@base.skip_if_microversion_not_supported("2.48")
+@ddt.ddt
+class ShareTypeAvailabilityZonesNegativeTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(ShareTypeAvailabilityZonesNegativeTest, cls).resource_setup()
+        cls.share_type = cls._create_share_type()
+        cls.share_type_id = cls.share_type['id']
+        cls.share_group_type = cls._create_share_group_type()
+        cls.share_group_type_id = cls.share_group_type['id']
+        all_azs = cls.get_availability_zones()
+        cls.valid_azs = cls.get_availability_zones_matching_share_type(
+            cls.share_type)
+        cls.invalid_azs = ((set(all_azs) - set(cls.valid_azs))
+                           or ['az_that_doesnt_exist'])
+        cls.az_spec = 'availability_zones'
+        cls.valid_azs_spec = ', '.join(cls.valid_azs)
+        cls.invalid_azs_spec = ', '.join(cls.invalid_azs)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data('az1,     az2, az 3,  ', 'az1,,az 3', ',az2,  az 3')
+    def test_share_type_azs_create_with_invalid_az_spec(self, spec):
+        az_spec = {'availability_zones': spec}
+        extra_specs = self.add_extra_specs_to_dict(az_spec)
+
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.create_share_type,
+            data_utils.rand_name('share_type_invalid_az_spec'),
+            cleanup_in_class=False,
+            extra_specs=extra_specs,
+            client=self.admin_shares_v2_client)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_not_supported("2.48")
+    def test_share_type_azs_filter_by_invalid_azs_extra_spec(self):
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.valid_azs_spec)
+        share_type_no_az_spec = self.create_share_type(
+            data_utils.rand_name('support_any_az_share_type'),
+            cleanup_in_class=False,
+            extra_specs=self.add_extra_specs_to_dict(),
+            client=self.admin_shares_v2_client)['share_type']
+
+        share_types = self.admin_shares_v2_client.list_share_types(params={
+            'extra_specs': {'availability_zones': self.invalid_azs_spec}}
+        )['share_types']
+        share_type_ids = [s['id'] for s in share_types]
+        self.assertNotIn(self.share_type_id, share_type_ids)
+        self.assertIn(share_type_no_az_spec['id'], share_type_ids)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    def test_share_type_azs_shares_unsupported_az(self):
+        """Test using an AZ not supported by the share type."""
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.invalid_azs_spec)
+        self.assertRaises(
+            lib_exc.BadRequest, self.create_share,
+            share_type_id=self.share_type_id,
+            availability_zone=self.valid_azs[0],
+            cleanup_in_class=False)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    def test_share_type_azs_share_groups_unsupported(self):
+        self.admin_shares_v2_client.update_share_type_extra_spec(
+            self.share_type_id, self.az_spec, self.invalid_azs_spec)
+        self.assertRaises(
+            lib_exc.BadRequest, self.create_share_group,
+            share_group_type_id=self.share_group_type_id,
+            share_type_ids=[self.share_type_id],
+            availability_zone=self.valid_azs[0],
+            cleanup_in_class=False)