1# Copyright 2016 Mirantis
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16from unittest import mock
17
18import ddt
19import six
20
21from cinderclient import api_versions
22from cinderclient import client as base_client
23from cinderclient import exceptions
24from cinderclient.tests.unit import test_utils
25from cinderclient.tests.unit import utils
26from cinderclient.v3 import client
27
28
29@ddt.ddt
30class APIVersionTestCase(utils.TestCase):
31    def test_valid_version_strings(self):
32        def _test_string(version, exp_major, exp_minor):
33            v = api_versions.APIVersion(version)
34            self.assertEqual(v.ver_major, exp_major)
35            self.assertEqual(v.ver_minor, exp_minor)
36
37        _test_string("1.1", 1, 1)
38        _test_string("2.10", 2, 10)
39        _test_string("5.234", 5, 234)
40        _test_string("12.5", 12, 5)
41        _test_string("2.0", 2, 0)
42        _test_string("2.200", 2, 200)
43
44    def test_null_version(self):
45        v = api_versions.APIVersion()
46        self.assertFalse(v)
47
48    def test_not_null_version(self):
49        v = api_versions.APIVersion('1.1')
50        self.assertTrue(v)
51
52    @ddt.data("2", "200", "2.1.4", "200.23.66.3", "5 .3", "5. 3", "5.03",
53              "02.1", "2.001", "", " 2.1", "2.1 ")
54    def test_invalid_version_strings(self, version_string):
55        self.assertRaises(exceptions.UnsupportedVersion,
56                          api_versions.APIVersion, version_string)
57
58    def test_version_comparisons(self):
59        v1 = api_versions.APIVersion("2.0")
60        v2 = api_versions.APIVersion("2.5")
61        v3 = api_versions.APIVersion("5.23")
62        v4 = api_versions.APIVersion("2.0")
63        v_null = api_versions.APIVersion()
64
65        self.assertLess(v1, v2)
66        self.assertGreater(v3, v2)
67        self.assertNotEqual(v1, v2)
68        self.assertEqual(v1, v4)
69        self.assertNotEqual(v1, v_null)
70        self.assertEqual(v_null, v_null)
71        self.assertRaises(TypeError, v1.__le__, "2.1")
72
73    def test_version_matches(self):
74        v1 = api_versions.APIVersion("2.0")
75        v2 = api_versions.APIVersion("2.5")
76        v3 = api_versions.APIVersion("2.45")
77        v4 = api_versions.APIVersion("3.3")
78        v5 = api_versions.APIVersion("3.23")
79        v6 = api_versions.APIVersion("2.0")
80        v7 = api_versions.APIVersion("3.3")
81        v8 = api_versions.APIVersion("4.0")
82        v_null = api_versions.APIVersion()
83
84        self.assertTrue(v2.matches(v1, v3))
85        self.assertTrue(v2.matches(v1, v_null))
86        self.assertTrue(v1.matches(v6, v2))
87        self.assertTrue(v4.matches(v2, v7))
88        self.assertTrue(v4.matches(v_null, v7))
89        self.assertTrue(v4.matches(v_null, v8))
90        self.assertFalse(v1.matches(v2, v3))
91        self.assertFalse(v5.matches(v2, v4))
92        self.assertFalse(v2.matches(v3, v1))
93
94        self.assertRaises(ValueError, v_null.matches, v1, v3)
95
96    def test_get_string(self):
97        v1_string = "3.23"
98        v1 = api_versions.APIVersion(v1_string)
99        self.assertEqual(v1_string, v1.get_string())
100
101        self.assertRaises(ValueError,
102                          api_versions.APIVersion().get_string)
103
104
105class ManagerTest(utils.TestCase):
106    def test_api_version(self):
107        # The function manager.return_api_version has two versions,
108        # when called with api version 3.1 it should return the
109        # string '3.1' and when called with api version 3.2 or higher
110        # it should return the string '3.2'.
111        version = api_versions.APIVersion('3.1')
112        api = client.Client(api_version=version)
113        manager = test_utils.FakeManagerWithApi(api)
114        self.assertEqual('3.1', manager.return_api_version())
115
116        version = api_versions.APIVersion('3.2')
117        api = client.Client(api_version=version)
118        manager = test_utils.FakeManagerWithApi(api)
119        self.assertEqual('3.2', manager.return_api_version())
120
121        # pick up the highest version
122        version = api_versions.APIVersion('3.3')
123        api = client.Client(api_version=version)
124        manager = test_utils.FakeManagerWithApi(api)
125        self.assertEqual('3.2', manager.return_api_version())
126
127        version = api_versions.APIVersion('3.0')
128        api = client.Client(api_version=version)
129        manager = test_utils.FakeManagerWithApi(api)
130        # An exception will be returned here because the function
131        # return_api_version doesn't support version 3.0
132        self.assertRaises(exceptions.VersionNotFoundForAPIMethod,
133                          manager.return_api_version)
134
135
136class UpdateHeadersTestCase(utils.TestCase):
137    def test_api_version_is_null(self):
138        headers = {}
139        api_versions.update_headers(headers, api_versions.APIVersion())
140        self.assertEqual({}, headers)
141
142    def test_api_version_is_major(self):
143        headers = {}
144        api_versions.update_headers(headers, api_versions.APIVersion("7.0"))
145        self.assertEqual({}, headers)
146
147    def test_api_version_is_not_null(self):
148        api_version = api_versions.APIVersion("2.3")
149        headers = {}
150        api_versions.update_headers(headers, api_version)
151        self.assertEqual(
152            {"OpenStack-API-Version": "volume " + api_version.get_string()},
153            headers)
154
155
156class GetAPIVersionTestCase(utils.TestCase):
157    def test_get_available_client_versions(self):
158        output = api_versions.get_available_major_versions()
159        self.assertNotEqual([], output)
160
161    def test_wrong_format(self):
162        self.assertRaises(exceptions.UnsupportedVersion,
163                          api_versions.get_api_version, "something_wrong")
164
165    def test_wrong_major_version(self):
166        self.assertRaises(exceptions.UnsupportedVersion,
167                          api_versions.get_api_version, "4")
168
169    @mock.patch("cinderclient.api_versions.get_available_major_versions")
170    @mock.patch("cinderclient.api_versions.APIVersion")
171    def test_only_major_part_is_presented(self, mock_apiversion,
172                                          mock_get_majors):
173        mock_get_majors.return_value = [
174            str(mock_apiversion.return_value.ver_major)]
175        version = 7
176        self.assertEqual(mock_apiversion.return_value,
177                         api_versions.get_api_version(version))
178        mock_apiversion.assert_called_once_with("%s.0" % str(version))
179
180    @mock.patch("cinderclient.api_versions.get_available_major_versions")
181    @mock.patch("cinderclient.api_versions.APIVersion")
182    def test_major_and_minor_parts_is_presented(self, mock_apiversion,
183                                                mock_get_majors):
184        version = "2.7"
185        mock_get_majors.return_value = [
186            str(mock_apiversion.return_value.ver_major)]
187        self.assertEqual(mock_apiversion.return_value,
188                         api_versions.get_api_version(version))
189        mock_apiversion.assert_called_once_with(version)
190
191
192@ddt.ddt
193class DiscoverVersionTestCase(utils.TestCase):
194    def setUp(self):
195        super(DiscoverVersionTestCase, self).setUp()
196        self.orig_max = api_versions.MAX_VERSION
197        self.orig_min = api_versions.MIN_VERSION or None
198        self.addCleanup(self._clear_fake_version)
199        self.fake_client = mock.MagicMock()
200
201    def _clear_fake_version(self):
202        api_versions.MAX_VERSION = self.orig_max
203        api_versions.MIN_VERSION = self.orig_min
204
205    def _mock_returned_server_version(self, server_version,
206                                      server_min_version):
207        version_mock = mock.MagicMock(version=server_version,
208                                      min_version=server_min_version,
209                                      status='CURRENT')
210        val = [version_mock]
211        if not server_version and not server_min_version:
212            val = []
213        self.fake_client.services.server_api_version.return_value = val
214
215    @ddt.data(
216        ("3.1", "3.3", "3.4", "3.7", "3.3", True),   # Server too new
217        ("3.9", "3.10", "3.0", "3.3", "3.10", True),   # Server too old
218        ("3.3", "3.9", "3.7", "3.17", "3.9", False),  # Requested < server
219        # downgraded because of server:
220        ("3.5", "3.8", "3.0", "3.7", "3.8", False, "3.7"),
221        # downgraded because of client:
222        ("3.5", "3.8", "3.0", "3.9", "3.9", False, "3.8"),
223        # downgraded because of both:
224        ("3.5", "3.7", "3.0", "3.8", "3.9", False, "3.7"),
225        ("3.5", "3.5", "3.0", "3.5", "3.5", False),  # Server & client same
226        ("3.5", "3.5", "3.0", "3.5", "3.5", False, "2.0", []),  # Pre-micro
227        ("3.1", "3.11", "3.4", "3.7", "3.7", False),  # Requested in range
228        ("3.1", "3.11", None, None, "3.7", False),  # Server w/o support
229        ("3.5", "3.5", "3.0", "3.5", "1.0", True)    # Requested too old
230    )
231    @ddt.unpack
232    def test_microversion(self, client_min, client_max, server_min, server_max,
233                          requested_version, exp_range, end_version=None,
234                          ret_val=None):
235        if ret_val is not None:
236            self.fake_client.services.server_api_version.return_value = ret_val
237        else:
238            self._mock_returned_server_version(server_max, server_min)
239
240        api_versions.MAX_VERSION = client_max
241        api_versions.MIN_VERSION = client_min
242
243        if exp_range:
244            self.assertRaisesRegex(exceptions.UnsupportedVersion,
245                                   ".*range is '%s' to '%s'.*" %
246                                   (server_min, server_max),
247                                   api_versions.discover_version,
248                                   self.fake_client,
249                                   api_versions.APIVersion(requested_version))
250        else:
251            discovered_version = api_versions.discover_version(
252                self.fake_client,
253                api_versions.APIVersion(requested_version))
254
255            version = requested_version
256            if server_min is None and server_max is None:
257                version = api_versions.DEPRECATED_VERSION
258            elif end_version is not None:
259                version = end_version
260            self.assertEqual(version,
261                             discovered_version.get_string())
262            self.assertTrue(
263                self.fake_client.services.server_api_version.called)
264
265    def test_get_highest_version(self):
266        self._mock_returned_server_version("3.14", "3.0")
267        highest_version = api_versions.get_highest_version(self.fake_client)
268        self.assertEqual("3.14", highest_version.get_string())
269        self.assertTrue(self.fake_client.services.server_api_version.called)
270
271    def test_get_highest_version_bad_client(self):
272        """Tests that we gracefully handle the wrong version of client."""
273        v2_client = base_client.Client('2.0')
274        ex = self.assertRaises(exceptions.UnsupportedVersion,
275                               api_versions.get_highest_version, v2_client)
276        self.assertIn('Invalid client version 2.0 to get', six.text_type(ex))
277