Merge "Remove duplicate keys from dictionary"
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 2282510..a965a36 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -17,12 +17,13 @@
 import time
 import urllib
 
+from tempest import config
 from tempest_lib.common.utils import data_utils
 from tempest_lib import exceptions
 
-from manila_tempest_tests.services.share.json import shares_client  # noqa
+from manila_tempest_tests.services.share.json import shares_client
 from manila_tempest_tests import share_exceptions
-from tempest import config  # noqa
+from manila_tempest_tests import utils
 
 CONF = config.CONF
 LATEST_MICROVERSION = CONF.share.max_api_microversion
@@ -105,7 +106,7 @@
             cgsnapshots.
         """
         if action_name is None:
-            if float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
                 action_name = 'reset_status'
             else:
                 action_name = 'os-reset_status'
@@ -124,7 +125,7 @@
         s_type: shares, snapshots
         """
         if action_name is None:
-            if float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
                 action_name = 'force_delete'
             else:
                 action_name = 'os-force_delete'
@@ -292,7 +293,10 @@
     def extend_share(self, share_id, new_size, version=LATEST_MICROVERSION,
                      action_name=None):
         if action_name is None:
-            action_name = 'extend' if float(version) > 2.6 else 'os-extend'
+            if utils.is_microversion_gt(version, "2.6"):
+                action_name = 'extend'
+            else:
+                action_name = 'os-extend'
         post_body = {
             action_name: {
                 "new_size": new_size,
@@ -307,7 +311,10 @@
     def shrink_share(self, share_id, new_size, version=LATEST_MICROVERSION,
                      action_name=None):
         if action_name is None:
-            action_name = 'shrink' if float(version) > 2.6 else 'os-shrnk'
+            if utils.is_microversion_gt(version, "2.6"):
+                action_name = 'shrink'
+            else:
+                action_name = 'os-shrink'
         post_body = {
             action_name: {
                 "new_size": new_size,
@@ -337,7 +344,7 @@
             }
         }
         if url is None:
-            if float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
                 url = 'shares/manage'
             else:
                 url = 'os-share-manage'
@@ -349,10 +356,16 @@
     def unmanage_share(self, share_id, version=LATEST_MICROVERSION, url=None,
                        action_name=None, body=None):
         if url is None:
-            url = 'shares' if float(version) > 2.6 else 'os-share-unmanage'
+            if utils.is_microversion_gt(version, "2.6"):
+                url = 'shares'
+            else:
+                url = 'os-share-unmanage'
         if action_name is None:
-            action_name = 'action' if float(version) > 2.6 else 'unmanage'
-        if body is None and float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
+                action_name = 'action'
+            else:
+                action_name = 'unmanage'
+        if body is None and utils.is_microversion_gt(version, "2.6"):
             body = json.dumps({'unmanage': {}})
         resp, body = self.post(
             "%(url)s/%(share_id)s/%(action_name)s" % {
@@ -365,7 +378,7 @@
 ###############
 
     def _get_access_action_name(self, version):
-        if float(version) > 2.6:
+        if utils.is_microversion_gt(version, "2.6"):
             return 'allow_access'
         return 'os-allow_access'
 
@@ -412,7 +425,7 @@
                                 version=LATEST_MICROVERSION):
         """Get list of availability zones."""
         if url is None:
-            if float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
                 url = 'availability-zones'
             else:
                 url = 'os-availability-zone'
@@ -426,7 +439,10 @@
                       version=LATEST_MICROVERSION):
         """List services."""
         if url is None:
-            url = 'services' if float(version) > 2.6 else 'os-services'
+            if utils.is_microversion_gt(version, "2.6"):
+                url = 'services'
+            else:
+                url = 'os-services'
         if params:
             url += '?%s' % urllib.urlencode(params)
         resp, body = self.get(url, version=version)
@@ -445,7 +461,7 @@
 
     def create_share_type(self, name, is_public=True,
                           version=LATEST_MICROVERSION, **kwargs):
-        if float(version) > 2.6:
+        if utils.is_microversion_gt(version, "2.6"):
             is_public_keyname = 'share_type_access:is_public'
         else:
             is_public_keyname = 'os-share-type-access:is_public'
@@ -473,7 +489,7 @@
                                   version=LATEST_MICROVERSION,
                                   action_name=None):
         if action_name is None:
-            if float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
                 action_name = 'share_type_access'
             else:
                 action_name = 'os-share-type-access'
@@ -487,7 +503,7 @@
 ###############
 
     def _get_quotas_url(self, version):
-        if float(version) > 2.6:
+        if utils.is_microversion_gt(version, "2.6"):
             return 'quota-sets'
         return 'os-quota-sets'
 
@@ -758,7 +774,7 @@
     def migrate_share(self, share_id, host, version=LATEST_MICROVERSION,
                       action_name=None):
         if action_name is None:
-            if float(version) > 2.6:
+            if utils.is_microversion_gt(version, "2.6"):
                 action_name = 'migrate_share'
             else:
                 action_name = 'os-migrate_share'
diff --git a/manila_tempest_tests/tests/api/admin/test_share_manage.py b/manila_tempest_tests/tests/api/admin/test_share_manage.py
index 78d28ee..c7efa01 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_manage.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_manage.py
@@ -14,13 +14,14 @@
 #    under the License.
 
 import six
-from tempest import config  # noqa
-from tempest import test  # noqa
-from tempest_lib.common.utils import data_utils  # noqa
-from tempest_lib import exceptions as lib_exc  # noqa
-import testtools  # noqa
+from tempest import config
+from tempest import test
+from tempest_lib.common.utils import data_utils
+from tempest_lib import exceptions as lib_exc
+import testtools
 
 from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
 
 CONF = config.CONF
 
@@ -78,7 +79,7 @@
 
         # Data for creating shares in parallel
         data = [creation_data, creation_data]
-        if float(CONF.share.max_api_microversion) >= 2.8:
+        if utils.is_microversion_ge(CONF.share.max_api_microversion, "2.8"):
             data.append(creation_data)
         shares_created = cls.create_shares(data)
 
@@ -127,7 +128,7 @@
         share = self.shares_v2_client.get_share(share['id'], version="2.6")
         self.assertEqual(self.st['share_type']['id'], share['share_type'])
 
-        if float(version) >= 2.8:
+        if utils.is_microversion_ge(version, "2.8"):
             self.assertEqual(is_public, share['is_public'])
         else:
             self.assertFalse(share['is_public'])
@@ -139,9 +140,7 @@
                           self.shares_v2_client.get_share,
                           share['id'])
 
-    @testtools.skipIf(
-        float(CONF.share.max_api_microversion) < 2.8,
-        "Only for API Microversion >= 2.8")
+    @base.skip_if_microversion_not_supported("2.8")
     @test.attr(type=["gate", "smoke"])
     def test_manage_with_is_public_True(self):
         self._test_manage(share=self.shares[2], is_public=True)
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types.py b/manila_tempest_tests/tests/api/admin/test_share_types.py
index d58c61c..a521aad 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types.py
@@ -20,7 +20,7 @@
 from tempest_lib import exceptions as lib_exc  # noqa
 
 from manila_tempest_tests.tests.api import base
-
+from manila_tempest_tests import utils
 
 CONF = config.CONF
 
@@ -51,7 +51,7 @@
     def _verify_is_public_key_name(self, share_type, version):
         old_key_name = 'os-share-type-access:is_public'
         new_key_name = 'share_type_access:is_public'
-        if float(version) > 2.6:
+        if utils.is_microversion_gt(version, "2.6"):
             self.assertIn(new_key_name, share_type)
             self.assertNotIn(old_key_name, share_type)
         else:
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_negative.py b/manila_tempest_tests/tests/api/admin/test_share_types_negative.py
index 62e4523..32c9620 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types_negative.py
@@ -68,3 +68,33 @@
                           self.create_share_type,
                           st["share_type"]["name"],
                           extra_specs=self.add_required_extra_specs_to_dict())
+
+    @test.attr(type=["gate", "smoke", ])
+    def test_add_share_type_allowed_for_public(self):
+        st = self._create_share_type()
+        self.assertRaises(lib_exc.Conflict,
+                          self.shares_client.add_access_to_share_type,
+                          st["share_type"]["id"],
+                          self.shares_client.tenant_id)
+
+    @test.attr(type=["gate", "smoke", ])
+    def test_remove_share_type_allowed_for_public(self):
+        st = self._create_share_type()
+        self.assertRaises(lib_exc.Conflict,
+                          self.shares_client.remove_access_from_share_type,
+                          st["share_type"]["id"],
+                          self.shares_client.tenant_id)
+
+    @test.attr(type=["gate", "smoke", ])
+    def test_add_share_type_by_nonexistent_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.shares_client.add_access_to_share_type,
+                          data_utils.rand_name("fake"),
+                          self.shares_client.tenant_id)
+
+    @test.attr(type=["gate", "smoke", ])
+    def test_remove_share_type_by_nonexistent_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.shares_client.remove_access_from_share_type,
+                          data_utils.rand_name("fake"),
+                          self.shares_client.tenant_id)
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index be56719..d57b493 100644
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -15,7 +15,6 @@
 
 import copy
 import inspect
-import random
 import traceback
 
 from oslo_concurrency import lockutils
@@ -27,27 +26,15 @@
 from tempest import test
 from tempest_lib.common.utils import data_utils
 from tempest_lib import exceptions
-import testtools
 
 from manila_tempest_tests import clients_share as clients
 from manila_tempest_tests import share_exceptions
+from manila_tempest_tests import utils
 
 CONF = config.CONF
 LOG = log.getLogger(__name__)
 
 
-def rand_ip():
-    """This uses the TEST-NET-3 range of reserved IP addresses.
-
-    Using this range, which are reserved solely for use in
-    documentation and example source code, should avoid any potential
-    conflicts in real-world testing.
-    """
-    TEST_NET_3 = '203.0.113.'
-    final_octet = six.text_type(random.randint(0, 255))
-    return TEST_NET_3 + final_octet
-
-
 class handle_cleanup_exceptions(object):
     """Handle exceptions raised with cleanup operations.
 
@@ -91,19 +78,7 @@
     return wrapped_func
 
 
-def is_microversion_supported(microversion):
-    if (float(microversion) > float(CONF.share.max_api_microversion) or
-            float(microversion) < float(CONF.share.min_api_microversion)):
-        return False
-    return True
-
-
-def skip_if_microversion_not_supported(microversion):
-    """Decorator for tests that are microversion-specific."""
-    if not is_microversion_supported(microversion):
-        reason = ("Skipped. Test requires microversion '%s'." % microversion)
-        return testtools.skip(reason)
-    return lambda f: f
+skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
 
 
 class BaseSharesTest(test.BaseTestCase):
@@ -125,7 +100,7 @@
     method_isolated_creds = []
 
     def skip_if_microversion_not_supported(self, microversion):
-        if not is_microversion_supported(microversion):
+        if not utils.is_microversion_supported(microversion):
             raise self.skipException(
                 "Microversion '%s' is not supported." % microversion)
 
@@ -657,8 +632,8 @@
         data = {
             "name": data_utils.rand_name("ss-name"),
             "description": data_utils.rand_name("ss-desc"),
-            "dns_ip": rand_ip(),
-            "server": rand_ip(),
+            "dns_ip": utils.rand_ip(),
+            "server": utils.rand_ip(),
             "domain": data_utils.rand_name("ss-domain"),
             "user": data_utils.rand_name("ss-user"),
             "password": data_utils.rand_name("ss-password"),
diff --git a/manila_tempest_tests/tests/api/test_shares_actions.py b/manila_tempest_tests/tests/api/test_shares_actions.py
index 58ddbfe..36fd6e5 100644
--- a/manila_tempest_tests/tests/api/test_shares_actions.py
+++ b/manila_tempest_tests/tests/api/test_shares_actions.py
@@ -14,12 +14,13 @@
 #    under the License.
 
 import six
-from tempest import config  # noqa
-from tempest import test  # noqa
-from tempest_lib.common.utils import data_utils  # noqa
-import testtools  # noqa
+from tempest import config
+from tempest import test
+from tempest_lib.common.utils import data_utils
+import testtools
 
 from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
 
 CONF = config.CONF
 
@@ -79,11 +80,20 @@
             self.shares[0]['id'], version=six.text_type(version))
 
         # verify keys
-        expected_keys = ["status", "description", "links", "availability_zone",
-                         "created_at", "export_location", "share_proto",
-                         "name", "snapshot_id", "id", "size"]
-        if version > 2.1:
+        expected_keys = [
+            "status", "description", "links", "availability_zone",
+            "created_at", "export_location", "project_id",
+            "export_locations", "volume_type", "share_proto", "name",
+            "snapshot_id", "id", "size", "share_network_id", "metadata",
+            "host", "snapshot_id", "is_public",
+        ]
+        if utils.is_microversion_ge(version, '2.2'):
             expected_keys.append("snapshot_support")
+        if utils.is_microversion_ge(version, '2.4'):
+            expected_keys.extend(["consistency_group_id",
+                                  "source_cgsnapshot_member_id"])
+        if utils.is_microversion_ge(version, '2.5'):
+            expected_keys.append("share_type_name")
         actual_keys = list(share.keys())
         [self.assertIn(key, actual_keys) for key in expected_keys]
 
@@ -103,12 +113,22 @@
         self.assertEqual(self.share_size, int(share["size"]), msg)
 
     @test.attr(type=["gate", ])
-    def test_get_share_no_snapshot_support_key(self):
-        self._get_share(2.1)
+    def test_get_share_v2_1(self):
+        self._get_share('2.1')
 
     @test.attr(type=["gate", ])
     def test_get_share_with_snapshot_support_key(self):
-        self._get_share(2.2)
+        self._get_share('2.2')
+
+    @test.attr(type=["gate", ])
+    @utils.skip_if_microversion_not_supported('2.4')
+    def test_get_share_with_consistency_groups_keys(self):
+        self._get_share('2.4')
+
+    @test.attr(type=["gate", ])
+    @utils.skip_if_microversion_not_supported('2.6')
+    def test_get_share_with_share_type_name_key(self):
+        self._get_share('2.6')
 
     @test.attr(type=["gate", ])
     def test_list_shares(self):
@@ -135,11 +155,19 @@
         # verify keys
         keys = [
             "status", "description", "links", "availability_zone",
-            "created_at", "export_location", "share_proto", "host",
-            "name", "snapshot_id", "id", "size", "project_id",
+            "created_at", "export_location", "project_id",
+            "export_locations", "volume_type", "share_proto", "name",
+            "snapshot_id", "id", "size", "share_network_id", "metadata",
+            "host", "snapshot_id", "is_public", "share_type",
         ]
-        if version > 2.1:
+        if utils.is_microversion_ge(version, '2.2'):
             keys.append("snapshot_support")
+        if utils.is_microversion_ge(version, '2.4'):
+            keys.extend(["consistency_group_id",
+                         "source_cgsnapshot_member_id"])
+        if utils.is_microversion_ge(version, '2.6'):
+            keys.append("share_type_name")
+
         [self.assertIn(key, sh.keys()) for sh in shares for key in keys]
 
         # our shares in list and have no duplicates
@@ -149,12 +177,22 @@
             self.assertEqual(1, len(gen), msg)
 
     @test.attr(type=["gate", ])
-    def test_list_shares_with_detail_without_snapshot_support_key(self):
-        self._list_shares_with_detail(2.1)
+    def test_list_shares_with_detail_v2_1(self):
+        self._list_shares_with_detail('2.1')
 
     @test.attr(type=["gate", ])
     def test_list_shares_with_detail_and_snapshot_support_key(self):
-        self._list_shares_with_detail(2.2)
+        self._list_shares_with_detail('2.2')
+
+    @test.attr(type=["gate", ])
+    @utils.skip_if_microversion_not_supported('2.4')
+    def test_list_shares_with_detail_consistency_groups_keys(self):
+        self._list_shares_with_detail('2.4')
+
+    @test.attr(type=["gate", ])
+    @utils.skip_if_microversion_not_supported('2.6')
+    def test_list_shares_with_detail_share_type_name_key(self):
+        self._list_shares_with_detail('2.6')
 
     @test.attr(type=["gate", ])
     def test_list_shares_with_detail_filter_by_metadata(self):
diff --git a/manila_tempest_tests/utils.py b/manila_tempest_tests/utils.py
new file mode 100644
index 0000000..94d8cd3
--- /dev/null
+++ b/manila_tempest_tests/utils.py
@@ -0,0 +1,93 @@
+# Copyright 2015 Mirantis Inc.
+# 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 random
+import re
+
+import six
+from tempest import config
+import testtools
+
+CONF = config.CONF
+
+
+def get_microversion_as_tuple(microversion_str):
+    """Transforms string-like microversion to two-value tuple of integers.
+
+    Tuple of integers useful for microversion comparisons.
+    """
+    regex = r"^([1-9]\d*)\.([1-9]\d*|0)$"
+    match = re.match(regex, microversion_str)
+    if not match:
+        raise ValueError(
+            "Microversion does not fit template 'x.y' - %s" % microversion_str)
+    return int(match.group(1)), int(match.group(2))
+
+
+def is_microversion_gt(left, right):
+    """Is microversion for left is greater than the right one."""
+    return get_microversion_as_tuple(left) > get_microversion_as_tuple(right)
+
+
+def is_microversion_ge(left, right):
+    """Is microversion for left is greater than or equal to the right one."""
+    return get_microversion_as_tuple(left) >= get_microversion_as_tuple(right)
+
+
+def is_microversion_eq(left, right):
+    """Is microversion for left is equal to the right one."""
+    return get_microversion_as_tuple(left) == get_microversion_as_tuple(right)
+
+
+def is_microversion_ne(left, right):
+    """Is microversion for left is not equal to the right one."""
+    return get_microversion_as_tuple(left) != get_microversion_as_tuple(right)
+
+
+def is_microversion_le(left, right):
+    """Is microversion for left is less than or equal to the right one."""
+    return get_microversion_as_tuple(left) <= get_microversion_as_tuple(right)
+
+
+def is_microversion_lt(left, right):
+    """Is microversion for left is less than the right one."""
+    return get_microversion_as_tuple(left) < get_microversion_as_tuple(right)
+
+
+def is_microversion_supported(microversion):
+    bottom = get_microversion_as_tuple(CONF.share.min_api_microversion)
+    microversion = get_microversion_as_tuple(microversion)
+    top = get_microversion_as_tuple(CONF.share.max_api_microversion)
+    return bottom <= microversion <= top
+
+
+def skip_if_microversion_not_supported(microversion):
+    """Decorator for tests that are microversion-specific."""
+    if not is_microversion_supported(microversion):
+        reason = ("Skipped. Test requires microversion '%s'." % microversion)
+        return testtools.skip(reason)
+    return lambda f: f
+
+
+def rand_ip():
+    """This uses the TEST-NET-3 range of reserved IP addresses.
+
+    Using this range, which are reserved solely for use in
+    documentation and example source code, should avoid any potential
+    conflicts in real-world testing.
+    """
+    TEST_NET_3 = '203.0.113.'
+    final_octet = six.text_type(random.randint(0, 255))
+    return TEST_NET_3 + final_octet