diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index 0b945386eb..e821dcbf6d 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -365,13 +365,12 @@ #host = localhost # Used for rolling upgrades. Setting this option downgrades -# (or pins) the internal ironic RPC communication and database -# objects to their respective versions, so they are compatible -# with older services. When doing a rolling upgrade from -# version N to version N+1, set (to pin) this to N. To unpin -# (default), leave it unset and the latest versions of RPC -# communication and database objects will be used. (string -# value) +# (or pins) the Bare Metal API, the internal ironic RPC +# communication, and the database objects to their respective +# versions, so they are compatible with older services. When +# doing a rolling upgrade from version N to version N+1, set +# (to pin) this to N. To unpin (default), leave it unset and +# the latest versions will be used. (string value) # Allowed values: pike, 9.2, 9.1, 9.0, 8.0 #pin_release_version = @@ -1025,7 +1024,9 @@ #domain_name = # Always use this endpoint URL for requests for this client. -# (string value) +# NOTE: The unversioned endpoint should be specified here; to +# request a particular API version, use the `version`, `min- +# version`, and/or `max-version` options. (string value) #endpoint_override = # Verify HTTPS connections. (boolean value) @@ -1662,7 +1663,9 @@ #domain_name = # Always use this endpoint URL for requests for this client. -# (string value) +# NOTE: The unversioned endpoint should be specified here; to +# request a particular API version, use the `version`, `min- +# version`, and/or `max-version` options. (string value) #endpoint_override = # DEPRECATED: Allow to perform insecure SSL (https) requests @@ -1972,7 +1975,9 @@ #enabled = false # Always use this endpoint URL for requests for this client. -# (string value) +# NOTE: The unversioned endpoint should be specified here; to +# request a particular API version, use the `version`, `min- +# version`, and/or `max-version` options. (string value) #endpoint_override = # Verify HTTPS connections. (boolean value) @@ -2234,6 +2239,24 @@ # service user utilizes for validating tokens, because normal # end users may not be able to reach that endpoint. (string # value) +# Deprecated group/name - [keystone_authtoken]/auth_uri +#www_authenticate_uri = + +# DEPRECATED: Complete "public" Identity API endpoint. This +# endpoint should not be an "admin" endpoint, as it should be +# accessible by all end users. Unauthenticated clients are +# redirected to this endpoint to authenticate. Although this +# endpoint should ideally be unversioned, client support in +# the wild varies. If you're using a versioned v2 endpoint +# here, then this should *not* be the same endpoint the +# service user utilizes for validating tokens, because normal +# end users may not be able to reach that endpoint. This +# option is deprecated in favor of www_authenticate_uri and +# will be removed in the S release. (string value) +# This option is deprecated for removal since Queens. +# Its value may be silently ignored in the future. +# Reason: The auth_uri option is deprecated in favor of +# www_authenticate_uri and will be removed in the S release. #auth_uri = # API version of the admin Identity API endpoint. (string @@ -3800,7 +3823,9 @@ #domain_name = # Always use this endpoint URL for requests for this client. -# (string value) +# NOTE: The unversioned endpoint should be specified here; to +# request a particular API version, use the `version`, `min- +# version`, and/or `max-version` options. (string value) #endpoint_override = # Verify HTTPS connections. (boolean value) diff --git a/ironic/api/controllers/root.py b/ironic/api/controllers/root.py index 1a359b5056..5fc14a5305 100644 --- a/ironic/api/controllers/root.py +++ b/ironic/api/controllers/root.py @@ -85,8 +85,8 @@ class Root(base.APIBase): root.description = ("Ironic is an OpenStack project which aims to " "provision baremetal machines.") root.default_version = Version(ID_VERSION1, - versions.MIN_VERSION_STRING, - versions.MAX_VERSION_STRING) + versions.min_version_string(), + versions.max_version_string()) root.versions = [root.default_version] return root diff --git a/ironic/api/controllers/v1/__init__.py b/ironic/api/controllers/v1/__init__.py index e2ac631a3e..a94a14ac9f 100644 --- a/ironic/api/controllers/v1/__init__.py +++ b/ironic/api/controllers/v1/__init__.py @@ -39,12 +39,17 @@ from ironic.common.i18n import _ BASE_VERSION = versions.BASE_VERSION -MIN_VER = base.Version( - {base.Version.string: versions.MIN_VERSION_STRING}, - versions.MIN_VERSION_STRING, versions.MAX_VERSION_STRING) -MAX_VER = base.Version( - {base.Version.string: versions.MAX_VERSION_STRING}, - versions.MIN_VERSION_STRING, versions.MAX_VERSION_STRING) + +def min_version(): + return base.Version( + {base.Version.string: versions.min_version_string()}, + versions.min_version_string(), versions.max_version_string()) + + +def max_version(): + return base.Version( + {base.Version.string: versions.max_version_string()}, + versions.min_version_string(), versions.max_version_string()) class MediaType(base.APIBase): @@ -200,29 +205,29 @@ class Controller(rest.RestController): "Mutually exclusive versions requested. Version %(ver)s " "requested but not supported by this service. The supported " "version range is: [%(min)s, %(max)s].") % - {'ver': version, 'min': versions.MIN_VERSION_STRING, - 'max': versions.MAX_VERSION_STRING}, + {'ver': version, 'min': versions.min_version_string(), + 'max': versions.max_version_string()}, headers=headers) # ensure the minor version is within the supported range - if version < MIN_VER or version > MAX_VER: + if version < min_version() or version > max_version(): raise exc.HTTPNotAcceptable(_( "Version %(ver)s was requested but the minor version is not " "supported by this service. The supported version range is: " "[%(min)s, %(max)s].") % - {'ver': version, 'min': versions.MIN_VERSION_STRING, - 'max': versions.MAX_VERSION_STRING}, + {'ver': version, 'min': versions.min_version_string(), + 'max': versions.max_version_string()}, headers=headers) @pecan.expose() def _route(self, args, request=None): - v = base.Version(pecan.request.headers, versions.MIN_VERSION_STRING, - versions.MAX_VERSION_STRING) + v = base.Version(pecan.request.headers, versions.min_version_string(), + versions.max_version_string()) # Always set the min and max headers pecan.response.headers[base.Version.min_string] = ( - versions.MIN_VERSION_STRING) + versions.min_version_string()) pecan.response.headers[base.Version.max_string] = ( - versions.MAX_VERSION_STRING) + versions.max_version_string()) # assert that requested version is supported self._check_version(v, pecan.response.headers) diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py index 9e2340f084..c49638928f 100644 --- a/ironic/api/controllers/v1/versions.py +++ b/ironic/api/controllers/v1/versions.py @@ -13,6 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg + +from ironic.common import release_mappings + +CONF = cfg.CONF + # This is the version 1 API BASE_VERSION = 1 @@ -104,11 +110,33 @@ MINOR_33_STORAGE_INTERFACE = 33 MINOR_34_PORT_PHYSICAL_NETWORK = 34 MINOR_35_REBUILD_CONFIG_DRIVE = 35 -# When adding another version, update MINOR_MAX_VERSION and also update -# doc/source/dev/webapi-version-history.rst with a detailed explanation of -# what the version has changed. +# When adding another version, update: +# - MINOR_MAX_VERSION +# - doc/source/dev/webapi-version-history.rst with a detailed explanation of +# what changed in the new version +# - common/release_mappings.py, RELEASE_MAPPING['master']['api'] + MINOR_MAX_VERSION = MINOR_35_REBUILD_CONFIG_DRIVE # String representations of the minor and maximum versions -MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) -MAX_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_MAX_VERSION) +_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) +_MAX_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_MAX_VERSION) + + +def min_version_string(): + """Returns the minimum supported API version (as a string)""" + return _MIN_VERSION_STRING + + +def max_version_string(): + """Returns the maximum supported API version (as a string). + + If the service is pinned, the maximum API version is the pinned + version. Otherwise, it is the maximum supported API version. + """ + release_ver = release_mappings.RELEASE_MAPPING.get( + CONF.pin_release_version) + if release_ver: + return release_ver['api'] + else: + return _MAX_VERSION_STRING diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 837de2e0f4..2f6a9ecaca 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -26,6 +26,7 @@ # NOTE(xek): The format of this dict is: # { '': { +# 'api': '', # 'rpc': '', # 'objects': { # '': [''], @@ -55,6 +56,7 @@ RELEASE_MAPPING = { '8.0': { + 'api': '1.31', 'rpc': '1.40', 'objects': { 'Node': ['1.21'], @@ -67,6 +69,7 @@ RELEASE_MAPPING = { } }, '9.0': { + 'api': '1.34', 'rpc': '1.41', 'objects': { 'Node': ['1.21'], @@ -79,6 +82,7 @@ RELEASE_MAPPING = { } }, '9.1': { + 'api': '1.34', 'rpc': '1.41', 'objects': { 'Node': ['1.21'], @@ -92,6 +96,7 @@ RELEASE_MAPPING = { }, '9.2': { 'rpc': '1.41', + 'api': '1.35', 'objects': { 'Node': ['1.21'], 'Conductor': ['1.2'], @@ -103,6 +108,7 @@ RELEASE_MAPPING = { } }, 'master': { + 'api': '1.35', 'rpc': '1.41', 'objects': { 'Node': ['1.21'], diff --git a/ironic/conf/default.py b/ironic/conf/default.py index 23fc187931..58cd7753aa 100644 --- a/ironic/conf/default.py +++ b/ironic/conf/default.py @@ -275,13 +275,13 @@ service_opts = [ choices=versions.RELEASE_VERSIONS, # TODO(xek): mutable=True, help=_('Used for rolling upgrades. Setting this option ' - 'downgrades (or pins) the internal ironic RPC ' - 'communication and database objects to their respective ' + 'downgrades (or pins) the Bare Metal API, ' + 'the internal ironic RPC communication, and ' + 'the database objects to their respective ' 'versions, so they are compatible with older services. ' 'When doing a rolling upgrade from version N to version ' 'N+1, set (to pin) this to N. To unpin (default), leave ' - 'it unset and the latest versions of RPC communication ' - 'and database objects will be used.')), + 'it unset and the latest versions will be used.')), ] utils_opts = [ diff --git a/ironic/tests/unit/api/controllers/v1/test_chassis.py b/ironic/tests/unit/api/controllers/v1/test_chassis.py index 87412e1d8c..94e278c575 100644 --- a/ironic/tests/unit/api/controllers/v1/test_chassis.py +++ b/ironic/tests/unit/api/controllers/v1/test_chassis.py @@ -77,7 +77,7 @@ class TestListChassis(test_api_base.BaseApiTest): fields = 'extra,description' data = self.get_json( '/chassis/%s?fields=%s' % (chassis.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) # We always append "links" self.assertItemsEqual(['description', 'extra', 'links'], data) @@ -89,7 +89,7 @@ class TestListChassis(test_api_base.BaseApiTest): data = self.get_json( '/chassis?fields=%s' % fields, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(3, len(data['chassis'])) for ch in data['chassis']: @@ -101,7 +101,7 @@ class TestListChassis(test_api_base.BaseApiTest): fields = 'uuid,spongebob' response = self.get_json( '/chassis/%s?fields=%s' % (chassis.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) @@ -112,7 +112,7 @@ class TestListChassis(test_api_base.BaseApiTest): fields = 'uuid,extra' response = self.get_json( '/chassis/%s?fields=%s' % (chassis.uuid, fields), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_int) diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py index c183c5d306..be6faf14e3 100644 --- a/ironic/tests/unit/api/controllers/v1/test_node.py +++ b/ironic/tests/unit/api/controllers/v1/test_node.py @@ -90,7 +90,8 @@ class TestListNodes(test_api_base.BaseApiTest): node = obj_utils.create_test_node(self.context, chassis_id=self.chassis.id) data = self.get_json( - '/nodes', headers={api_base.Version.string: str(api_v1.MAX_VER)}) + '/nodes', + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertIn('instance_uuid', data['nodes'][0]) self.assertIn('maintenance', data['nodes'][0]) self.assertIn('power_state', data['nodes'][0]) @@ -125,7 +126,7 @@ class TestListNodes(test_api_base.BaseApiTest): chassis_id=self.chassis.id) data = self.get_json( '/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(node.uuid, data['uuid']) self.assertIn('driver', data) self.assertIn('driver_info', data) @@ -184,7 +185,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields = 'extra,instance_info' data = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) # We always append "links" self.assertItemsEqual(['extra', 'instance_info', 'links'], data) @@ -197,7 +198,7 @@ class TestListNodes(test_api_base.BaseApiTest): data = self.get_json( '/nodes?fields=%s' % fields, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(3, len(data['nodes'])) for node in data['nodes']: @@ -210,7 +211,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields = 'uuid,spongebob' response = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) @@ -222,7 +223,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields = 'uuid,extra' response = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_int) @@ -233,7 +234,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields = 'driver_info' data = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) # We always append "links" self.assertItemsEqual(['driver_info', 'links'], data) self.assertEqual('******', data['driver_info']['fake_password']) @@ -254,7 +255,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields = 'network_interface' response = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertIn('network_interface', response) def test_get_all_interface_fields_invalid_api_version(self): @@ -273,7 +274,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields_arg = ','.join(api_utils.V31_FIELDS) response = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields_arg), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) for field in api_utils.V31_FIELDS: self.assertIn(field, response) @@ -293,7 +294,7 @@ class TestListNodes(test_api_base.BaseApiTest): fields = 'storage_interface' response = self.get_json( '/nodes/%s?fields=%s' % (node.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertIn('storage_interface', response) def test_detail(self): @@ -301,7 +302,7 @@ class TestListNodes(test_api_base.BaseApiTest): chassis_id=self.chassis.id) data = self.get_json( '/nodes/detail', - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(node.uuid, data['nodes'][0]["uuid"]) self.assertIn('name', data['nodes'][0]) self.assertIn('driver', data['nodes'][0]) @@ -339,7 +340,7 @@ class TestListNodes(test_api_base.BaseApiTest): data = self.get_json( '/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}) + headers={api_base.Version.string: str(api_v1.min_version())}) self.assertEqual(states.NOSTATE, data['provision_state']) data = self.get_json('/nodes/%s' % node.uuid, @@ -351,7 +352,7 @@ class TestListNodes(test_api_base.BaseApiTest): driver_internal_info={"foo": "bar"}) data = self.get_json( '/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}) + headers={api_base.Version.string: str(api_v1.min_version())}) self.assertNotIn('driver_internal_info', data) data = self.get_json('/nodes/%s' % node.uuid, @@ -375,7 +376,7 @@ class TestListNodes(test_api_base.BaseApiTest): inspection_started_at=some_time) data = self.get_json( '/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}) + headers={api_base.Version.string: str(api_v1.min_version())}) self.assertNotIn('inspection_finished_at', data) self.assertNotIn('inspection_started_at', data) @@ -391,7 +392,7 @@ class TestListNodes(test_api_base.BaseApiTest): clean_step={"foo": "bar"}) data = self.get_json( '/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}) + headers={api_base.Version.string: str(api_v1.min_version())}) self.assertNotIn('clean_step', data) data = self.get_json('/nodes/%s' % node.uuid, @@ -737,14 +738,14 @@ class TestListNodes(test_api_base.BaseApiTest): data = self.get_json( '/nodes/%s/volume/connectors' % node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(2, len(data['connectors'])) self.assertNotIn('next', data) # Test collection pagination data = self.get_json( '/nodes/%s/volume/connectors?limit=1' % node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(1, len(data['connectors'])) self.assertIn('next', data) @@ -755,7 +756,7 @@ class TestListNodes(test_api_base.BaseApiTest): response = self.get_json( '/nodes/volume/connectors', expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.NOT_FOUND, response.status_int) def test_volume_connectors_subresource_node_not_found(self): @@ -763,7 +764,7 @@ class TestListNodes(test_api_base.BaseApiTest): response = self.get_json( '/nodes/%s/volume/connectors' % non_existent_uuid, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.NOT_FOUND, response.status_int) def test_volume_targets_subresource(self): @@ -776,14 +777,14 @@ class TestListNodes(test_api_base.BaseApiTest): data = self.get_json( '/nodes/%s/volume/targets' % node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(2, len(data['targets'])) self.assertNotIn('next', data) # Test collection pagination data = self.get_json( '/nodes/%s/volume/targets?limit=1' % node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(1, len(data['targets'])) self.assertIn('next', data) @@ -794,7 +795,7 @@ class TestListNodes(test_api_base.BaseApiTest): response = self.get_json( '/nodes/volume/targets', expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.NOT_FOUND, response.status_int) def test_volume_targets_subresource_node_not_found(self): @@ -802,7 +803,7 @@ class TestListNodes(test_api_base.BaseApiTest): response = self.get_json( '/nodes/%s/volume/targets' % non_existent_uuid, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.NOT_FOUND, response.status_int) @mock.patch.object(timeutils, 'utcnow') @@ -1085,7 +1086,7 @@ class TestListNodes(test_api_base.BaseApiTest): def test_get_nodes_by_driver_invalid_api_version(self): response = self.get_json( '/nodes?driver=fake', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code) self.assertTrue(response.json['error_message']) @@ -1147,7 +1148,7 @@ class TestListNodes(test_api_base.BaseApiTest): response = self.get_json( base_url % 'fake', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code) self.assertTrue(response.json['error_message']) @@ -1307,7 +1308,7 @@ class TestListNodes(test_api_base.BaseApiTest): driver_info=driver_info) data = self.get_json( '/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual("******", data["driver_info"]["ssh_password"]) self.assertEqual("******", data["driver_info"]["ssh_key_contents"]) @@ -1562,7 +1563,7 @@ class TestPatch(test_api_base.BaseApiTest): '/nodes/%s/volume/connectors' % self.node.uuid, [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}], expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.BAD_REQUEST, response.status_int) def test_patch_volume_connectors_subresource(self): @@ -1574,7 +1575,7 @@ class TestPatch(test_api_base.BaseApiTest): connector.uuid), [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}], expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.FORBIDDEN, response.status_int) def test_patch_volume_targets_subresource(self): @@ -1585,7 +1586,7 @@ class TestPatch(test_api_base.BaseApiTest): target.uuid), [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}], expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.FORBIDDEN, response.status_int) def test_remove_uuid(self): @@ -1943,7 +1944,7 @@ class TestPatch(test_api_base.BaseApiTest): uuid=uuidutils.generate_uuid()) self.mock_update_node.return_value = node network_interface = 'flat' - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} response = self.patch_json('/nodes/%s' % node.uuid, [{'path': '/network_interface', 'value': network_interface, @@ -2029,7 +2030,7 @@ class TestPatch(test_api_base.BaseApiTest): node = obj_utils.create_test_node(self.context, uuid=uuidutils.generate_uuid()) self.mock_update_node.return_value = node - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} for field in api_utils.V31_FIELDS: response = self.patch_json('/nodes/%s' % node.uuid, [{'path': '/%s' % field, @@ -2075,7 +2076,7 @@ class TestPatch(test_api_base.BaseApiTest): uuid=uuidutils.generate_uuid()) self.mock_update_node.return_value = node storage_interface = 'cinder' - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} response = self.patch_json('/nodes/%s' % node.uuid, [{'path': '/storage_interface', 'value': storage_interface, @@ -2473,7 +2474,7 @@ class TestPost(test_api_base.BaseApiTest): response = self.post_json( '/nodes/volume/connectors', pdict, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.NOT_FOUND, response.status_int) def test_post_volume_connectors_subresource(self): @@ -2483,7 +2484,7 @@ class TestPost(test_api_base.BaseApiTest): response = self.post_json( '/nodes/%s/volume/connectors' % node.uuid, pdict, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.FORBIDDEN, response.status_int) def test_post_volume_targets_subresource(self): @@ -2493,7 +2494,7 @@ class TestPost(test_api_base.BaseApiTest): response = self.post_json( '/nodes/%s/volume/targets' % node.uuid, pdict, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.FORBIDDEN, response.status_int) def test_create_node_no_mandatory_field_driver(self): @@ -2589,11 +2590,11 @@ class TestPost(test_api_base.BaseApiTest): network_interface='flat') response = self.post_json('/nodes', ndict, headers={api_base.Version.string: - str(api_v1.MAX_VER)}) + str(api_v1.max_version())}) self.assertEqual(http_client.CREATED, response.status_int) result = self.get_json('/nodes/%s' % ndict['uuid'], headers={api_base.Version.string: - str(api_v1.MAX_VER)}) + str(api_v1.max_version())}) self.assertEqual('flat', result['network_interface']) def test_create_node_network_interface_old_api_version(self): @@ -2608,7 +2609,7 @@ class TestPost(test_api_base.BaseApiTest): network_interface='foo') response = self.post_json('/nodes', ndict, expect_errors=True, headers={api_base.Version.string: - str(api_v1.MAX_VER)}) + str(api_v1.max_version())}) self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.BAD_REQUEST, response.status_int) @@ -2617,11 +2618,11 @@ class TestPost(test_api_base.BaseApiTest): resource_class='foo') response = self.post_json('/nodes', ndict, headers={api_base.Version.string: - str(api_v1.MAX_VER)}) + str(api_v1.max_version())}) self.assertEqual(http_client.CREATED, response.status_int) result = self.get_json('/nodes/%s' % ndict['uuid'], headers={api_base.Version.string: - str(api_v1.MAX_VER)}) + str(api_v1.max_version())}) self.assertEqual('foo', result['resource_class']) def test_create_node_resource_class_old_api_version(self): @@ -2643,7 +2644,7 @@ class TestPost(test_api_base.BaseApiTest): ndict = test_api_utils.post_get_test_node(storage_interface='foo') response = self.post_json('/nodes', ndict, expect_errors=True, headers={api_base.Version.string: - str(api_v1.MAX_VER)}) + str(api_v1.max_version())}) self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.BAD_REQUEST, response.status_int) @@ -2750,7 +2751,7 @@ class TestDelete(test_api_base.BaseApiTest): response = self.delete( '/nodes/%s/volume/connectors' % node.uuid, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.BAD_REQUEST, response.status_int) def test_delete_volume_connectors_subresource(self): @@ -2760,7 +2761,7 @@ class TestDelete(test_api_base.BaseApiTest): response = self.delete( '/nodes/%s/volume/connectors/%s' % (node.uuid, connector.uuid), expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.FORBIDDEN, response.status_int) def test_delete_volume_targets_subresource(self): @@ -2770,7 +2771,7 @@ class TestDelete(test_api_base.BaseApiTest): response = self.delete( '/nodes/%s/volume/targets/%s' % (node.uuid, target.uuid), expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.FORBIDDEN, response.status_int) @mock.patch.object(notification_utils, '_emit_api_notification') diff --git a/ironic/tests/unit/api/controllers/v1/test_port.py b/ironic/tests/unit/api/controllers/v1/test_port.py index 09a01a89b9..183450aca5 100644 --- a/ironic/tests/unit/api/controllers/v1/test_port.py +++ b/ironic/tests/unit/api/controllers/v1/test_port.py @@ -233,7 +233,7 @@ class TestListPorts(test_api_base.BaseApiTest): fields = 'address,extra' data = self.get_json( '/ports/%s?fields=%s' % (port.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) # We always append "links" self.assertItemsEqual(['address', 'extra', 'links'], data) @@ -242,7 +242,7 @@ class TestListPorts(test_api_base.BaseApiTest): internal_info={"foo": "bar"}) data = self.get_json( '/ports/%s' % port.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}) + headers={api_base.Version.string: str(api_v1.min_version())}) self.assertNotIn('internal_info', data) data = self.get_json('/ports/%s' % port.uuid, @@ -313,7 +313,7 @@ class TestListPorts(test_api_base.BaseApiTest): data = self.get_json( '/ports?fields=%s' % fields, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(3, len(data['ports'])) for port in data['ports']: @@ -325,7 +325,7 @@ class TestListPorts(test_api_base.BaseApiTest): fields = 'uuid,spongebob' response = self.get_json( '/ports/%s?fields=%s' % (port.uuid, fields), - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) @@ -336,7 +336,7 @@ class TestListPorts(test_api_base.BaseApiTest): fields = 'uuid,extra' response = self.get_json( '/ports/%s?fields=%s' % (port.uuid, fields), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_int) @@ -380,7 +380,7 @@ class TestListPorts(test_api_base.BaseApiTest): physical_network='physnet1') data = self.get_json( '/ports/detail', - headers={api_base.Version.string: str(api_v1.MAX_VER)} + headers={api_base.Version.string: str(api_v1.max_version())} ) self.assertEqual(port.uuid, data['ports'][0]["uuid"]) self.assertIn('extra', data['ports'][0]) @@ -519,7 +519,7 @@ class TestListPorts(test_api_base.BaseApiTest): for invalid_key in invalid_keys_list: response = self.get_json( '/ports?sort_key=%s' % invalid_key, expect_errors=True, - headers={api_base.Version.string: str(api_v1.MAX_VER)} + headers={api_base.Version.string: str(api_v1.max_version())} ) self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) @@ -535,7 +535,7 @@ class TestListPorts(test_api_base.BaseApiTest): address='52:54:00:cf:2d:3%s' % id_, pxe_enabled=id_ % 2) port_uuids.append(port.uuid) - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} detail_str = '/detail' if detail else '' data = self.get_json('/ports%s?sort_key=pxe_enabled' % detail_str, headers=headers) @@ -1268,7 +1268,7 @@ class TestPatch(test_api_base.BaseApiTest): def test_invalid_physnet_non_text(self, mock_upd): physnet = 1234 - headers = {api_base.Version.string: versions.MAX_VERSION_STRING} + headers = {api_base.Version.string: versions.max_version_string()} response = self.patch_json('/ports/%s' % self.port.uuid, [{'path': '/physical_network', 'value': physnet, @@ -1281,7 +1281,7 @@ class TestPatch(test_api_base.BaseApiTest): def test_invalid_physnet_too_long(self, mock_upd): physnet = 'p' * 65 - headers = {api_base.Version.string: versions.MAX_VERSION_STRING} + headers = {api_base.Version.string: versions.max_version_string()} response = self.patch_json('/ports/%s' % self.port.uuid, [{'path': '/physical_network', 'value': physnet, @@ -1319,7 +1319,7 @@ class TestPost(test_api_base.BaseApiTest): self.portgroup = obj_utils.create_test_portgroup(self.context, node_id=self.node.id) self.headers = {api_base.Version.string: str( - versions.MAX_VERSION_STRING)} + versions.max_version_string())} p = mock.patch.object(rpcapi.ConductorAPI, 'get_topic_for') self.mock_gtf = p.start() @@ -1370,7 +1370,7 @@ class TestPost(test_api_base.BaseApiTest): pdict.pop('pxe_enabled') pdict.pop('extra') pdict.pop('physical_network') - headers = {api_base.Version.string: str(api_v1.MIN_VER)} + headers = {api_base.Version.string: str(api_v1.min_version())} response = self.post_json('/ports', pdict, headers=headers) self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.CREATED, response.status_int) diff --git a/ironic/tests/unit/api/controllers/v1/test_portgroup.py b/ironic/tests/unit/api/controllers/v1/test_portgroup.py index 57f0b5284d..48b301f55e 100644 --- a/ironic/tests/unit/api/controllers/v1/test_portgroup.py +++ b/ironic/tests/unit/api/controllers/v1/test_portgroup.py @@ -51,7 +51,7 @@ class TestPortgroupObject(base.TestCase): class TestListPortgroups(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestListPortgroups, self).setUp() @@ -152,7 +152,7 @@ class TestListPortgroups(test_api_base.BaseApiTest): node_id=self.node.id) response = self.get_json( '/portgroups/%s' % (portgroup.uuid), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -170,7 +170,7 @@ class TestListPortgroups(test_api_base.BaseApiTest): def test_detail_invalid_api_version(self): response = self.get_json( '/portgroups/detail', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -274,14 +274,14 @@ class TestListPortgroups(test_api_base.BaseApiTest): # Test get one old api version, /portgroups controller not allowed response = self.get_json('/portgroups/%s/ports/%s' % ( pg.uuid, uuidutils.generate_uuid()), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) # Test get one not allowed to access to /portgroups//ports/ response = self.get_json( '/portgroups/%s/ports/%s' % (pg.uuid, uuidutils.generate_uuid()), - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.FORBIDDEN, response.status_int) @@ -471,7 +471,7 @@ class TestListPortgroups(test_api_base.BaseApiTest): @mock.patch.object(rpcapi.ConductorAPI, 'update_portgroup') class TestPatch(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestPatch, self).setUp() @@ -865,7 +865,7 @@ class TestPatch(test_api_base.BaseApiTest): 'op': 'replace'}], expect_errors=True, headers={api_base.Version.string: - str(api_v1.MIN_VER)}) + str(api_v1.min_version())}) self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.NOT_FOUND, response.status_int) self.assertTrue(response.json['error_message']) @@ -929,7 +929,7 @@ class TestPatch(test_api_base.BaseApiTest): class TestPost(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestPost, self).setUp() @@ -1205,7 +1205,7 @@ class TestPost(test_api_base.BaseApiTest): @mock.patch.object(rpcapi.ConductorAPI, 'destroy_portgroup') class TestDelete(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestDelete, self).setUp() diff --git a/ironic/tests/unit/api/controllers/v1/test_ramdisk.py b/ironic/tests/unit/api/controllers/v1/test_ramdisk.py index 8d4ebc6d7f..d49d7dbedf 100644 --- a/ironic/tests/unit/api/controllers/v1/test_ramdisk.py +++ b/ironic/tests/unit/api/controllers/v1/test_ramdisk.py @@ -65,14 +65,14 @@ class TestLookup(test_api_base.BaseApiTest): def test_nothing_provided(self): response = self.get_json( '/lookup', - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.BAD_REQUEST, response.status_int) def test_not_found(self): response = self.get_json( '/lookup?addresses=%s' % ','.join(self.addresses), - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -83,7 +83,7 @@ class TestLookup(test_api_base.BaseApiTest): response = self.get_json( '/lookup?addresses=%s' % ','.join(self.addresses), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -94,7 +94,7 @@ class TestLookup(test_api_base.BaseApiTest): data = self.get_json( '/lookup?addresses=%s' % ','.join(self.addresses), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(self.node.uuid, data['node']['uuid']) self.assertEqual(set(ramdisk._LOOKUP_RETURN_FIELDS) | {'links'}, set(data['node'])) @@ -110,7 +110,7 @@ class TestLookup(test_api_base.BaseApiTest): ':f4:52:14:03:00:54:06:c2,' + ','.join(self.addresses)) data = self.get_json( '/lookup?addresses=%s' % addresses, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(self.node.uuid, data['node']['uuid']) self.assertEqual(set(ramdisk._LOOKUP_RETURN_FIELDS) | {'links'}, set(data['node'])) @@ -121,7 +121,7 @@ class TestLookup(test_api_base.BaseApiTest): data = self.get_json( '/lookup?addresses=%s&node_uuid=%s' % (','.join(self.addresses), self.node.uuid), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(self.node.uuid, data['node']['uuid']) self.assertEqual(set(ramdisk._LOOKUP_RETURN_FIELDS) | {'links'}, set(data['node'])) @@ -130,7 +130,7 @@ class TestLookup(test_api_base.BaseApiTest): def test_found_by_only_uuid(self): data = self.get_json( '/lookup?node_uuid=%s' % self.node.uuid, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(self.node.uuid, data['node']['uuid']) self.assertEqual(set(ramdisk._LOOKUP_RETURN_FIELDS) | {'links'}, set(data['node'])) @@ -140,7 +140,7 @@ class TestLookup(test_api_base.BaseApiTest): response = self.get_json( '/lookup?addresses=%s&node_uuid=%s' % (','.join(self.addresses), self.node2.uuid), - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -149,7 +149,7 @@ class TestLookup(test_api_base.BaseApiTest): data = self.get_json( '/lookup?addresses=%s&node_uuid=%s' % (','.join(self.addresses), self.node2.uuid), - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(self.node2.uuid, data['node']['uuid']) self.assertEqual(set(ramdisk._LOOKUP_RETURN_FIELDS) | {'links'}, set(data['node'])) @@ -163,7 +163,7 @@ class TestHeartbeat(test_api_base.BaseApiTest): response = self.post_json( '/heartbeat/%s' % uuidutils.generate_uuid(), {'callback_url': 'url'}, - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -171,7 +171,7 @@ class TestHeartbeat(test_api_base.BaseApiTest): response = self.post_json( '/heartbeat/%s' % uuidutils.generate_uuid(), {'callback_url': 'url'}, - headers={api_base.Version.string: str(api_v1.MAX_VER)}, + headers={api_base.Version.string: str(api_v1.max_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -181,7 +181,7 @@ class TestHeartbeat(test_api_base.BaseApiTest): response = self.post_json( '/heartbeat/%s' % node.uuid, {'callback_url': 'url'}, - headers={api_base.Version.string: str(api_v1.MAX_VER)}) + headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertEqual(b'', response.body) mock_heartbeat.assert_called_once_with(mock.ANY, mock.ANY, diff --git a/ironic/tests/unit/api/controllers/v1/test_root.py b/ironic/tests/unit/api/controllers/v1/test_root.py index 817d2b7d2a..449c65defd 100644 --- a/ironic/tests/unit/api/controllers/v1/test_root.py +++ b/ironic/tests/unit/api/controllers/v1/test_root.py @@ -40,7 +40,7 @@ class TestCheckVersions(test_base.TestCase): def test_check_version_invalid_major_version(self): self.version.major = v1_api.BASE_VERSION + 1 - self.version.minor = v1_api.MIN_VER.minor + self.version.minor = v1_api.min_version().minor self.assertRaises( webob_exc.HTTPNotAcceptable, v1_api.Controller()._check_version, @@ -48,7 +48,7 @@ class TestCheckVersions(test_base.TestCase): def test_check_version_too_low(self): self.version.major = v1_api.BASE_VERSION - self.version.minor = v1_api.MIN_VER.minor - 1 + self.version.minor = v1_api.min_version().minor - 1 self.assertRaises( webob_exc.HTTPNotAcceptable, v1_api.Controller()._check_version, @@ -56,14 +56,14 @@ class TestCheckVersions(test_base.TestCase): def test_check_version_too_high(self): self.version.major = v1_api.BASE_VERSION - self.version.minor = v1_api.MAX_VER.minor + 1 + self.version.minor = v1_api.max_version().minor + 1 e = self.assertRaises( webob_exc.HTTPNotAcceptable, v1_api.Controller()._check_version, - self.version, {'fake-headers': v1_api.MAX_VER.minor}) - self.assertEqual(v1_api.MAX_VER.minor, e.headers['fake-headers']) + self.version, {'fake-headers': v1_api.max_version().minor}) + self.assertEqual(v1_api.max_version().minor, e.headers['fake-headers']) def test_check_version_ok(self): self.version.major = v1_api.BASE_VERSION - self.version.minor = v1_api.MIN_VER.minor + self.version.minor = v1_api.min_version().minor v1_api.Controller()._check_version(self.version) diff --git a/ironic/tests/unit/api/controllers/v1/test_versions.py b/ironic/tests/unit/api/controllers/v1/test_versions.py index f5ac5672c5..9f265b5ec9 100644 --- a/ironic/tests/unit/api/controllers/v1/test_versions.py +++ b/ironic/tests/unit/api/controllers/v1/test_versions.py @@ -17,7 +17,11 @@ Tests for the versions constants and methods. import re +import mock + from ironic.api.controllers.v1 import versions +from ironic.common import release_mappings +from ironic.conf import CONF from ironic.tests import base @@ -36,16 +40,16 @@ class TestVersionConstants(base.TestCase): self.minor_consts.sort(key=minor_key) def test_max_ver_str(self): - # Test to make sure MAX_VERSION_STRING corresponds with the largest + # Test to make sure _MAX_VERSION_STRING corresponds with the largest # MINOR_ constant max_ver = '1.{}'.format(getattr(versions, self.minor_consts[-1])) - self.assertEqual(max_ver, versions.MAX_VERSION_STRING) + self.assertEqual(max_ver, versions._MAX_VERSION_STRING) def test_min_ver_str(self): - # Try to make sure someone doesn't change the MIN_VERSION_STRING by + # Try to make sure someone doesn't change the _MIN_VERSION_STRING by # accident and make sure it exists - self.assertEqual('1.1', versions.MIN_VERSION_STRING) + self.assertEqual('1.1', versions._MIN_VERSION_STRING) def test_name_value_match(self): # Test to make sure variable name matches the value. For example @@ -67,3 +71,25 @@ class TestVersionConstants(base.TestCase): value, seen_values, 'The value {} has been used more than once'.format(value)) seen_values.add(value) + + +class TestMaxVersionString(base.TestCase): + + def test_max_version_not_pinned(self): + CONF.set_override('pin_release_version', None) + self.assertEqual(versions._MAX_VERSION_STRING, + versions.max_version_string()) + + @mock.patch('ironic.common.release_mappings.RELEASE_MAPPING', + autospec=True) + def test_max_version_pinned(self, mock_release_mapping): + CONF.set_override('pin_release_version', + release_mappings.RELEASE_VERSIONS[-1]) + mock_release_mapping.get.return_value = { + 'api': '1.5', + 'rpc': '1.4', + 'objects': { + 'MyObj': ['1.4'], + } + } + self.assertEqual('1.5', versions.max_version_string()) diff --git a/ironic/tests/unit/api/controllers/v1/test_volume.py b/ironic/tests/unit/api/controllers/v1/test_volume.py index 16752fe090..13f17ac27c 100644 --- a/ironic/tests/unit/api/controllers/v1/test_volume.py +++ b/ironic/tests/unit/api/controllers/v1/test_volume.py @@ -32,7 +32,7 @@ class TestGetVolume(test_api_base.BaseApiTest): headers=headers)) def test_get_volume(self): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} data = self.get_json('/volume/', headers=headers) for key in ['links', 'connectors', 'targets']: self._test_links(data, key, headers) @@ -46,7 +46,7 @@ class TestGetVolume(test_api_base.BaseApiTest): data['targets'][1]['href']) def test_get_volume_invalid_api_version(self): - headers = {api_base.Version.string: str(api_v1.MIN_VER)} + headers = {api_base.Version.string: str(api_v1.min_version())} response = self.get_json('/volume/', headers=headers, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) diff --git a/ironic/tests/unit/api/controllers/v1/test_volume_connector.py b/ironic/tests/unit/api/controllers/v1/test_volume_connector.py index bdeef82dce..575c86bc18 100644 --- a/ironic/tests/unit/api/controllers/v1/test_volume_connector.py +++ b/ironic/tests/unit/api/controllers/v1/test_volume_connector.py @@ -59,7 +59,7 @@ class TestVolumeConnectorObject(base.TestCase): class TestListVolumeConnectors(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestListVolumeConnectors, self).setUp() @@ -83,7 +83,7 @@ class TestListVolumeConnectors(test_api_base.BaseApiTest): node_id=self.node.id) response = self.get_json( '/volume/connectors', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -103,7 +103,7 @@ class TestListVolumeConnectors(test_api_base.BaseApiTest): self.context, node_id=self.node.id) response = self.get_json( '/volume/connectors/%s' % connector.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -151,7 +151,7 @@ class TestListVolumeConnectors(test_api_base.BaseApiTest): fields = 'uuid,extra' response = self.get_json( '/volume/connectors/%s?fields=%s' % (connector.uuid, fields), - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -181,7 +181,7 @@ class TestListVolumeConnectors(test_api_base.BaseApiTest): node_id=self.node.id) response = self.get_json( '/volume/connectors?detail=True', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -348,7 +348,7 @@ class TestListVolumeConnectors(test_api_base.BaseApiTest): @mock.patch.object(rpcapi.ConductorAPI, 'update_volume_connector') class TestPatch(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestPatch, self).setUp() @@ -388,7 +388,7 @@ class TestPatch(test_api_base.BaseApiTest): node_uuid=self.node.uuid)]) def test_update_invalid_api_version(self, mock_upd): - headers = {api_base.Version.string: str(api_v1.MIN_VER)} + headers = {api_base.Version.string: str(api_v1.min_version())} response = self.patch_json('/volume/connectors/%s' % self.connector.uuid, [{'path': '/extra/foo', @@ -714,7 +714,7 @@ class TestPatch(test_api_base.BaseApiTest): class TestPost(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestPost, self).setUp() @@ -754,7 +754,7 @@ class TestPost(test_api_base.BaseApiTest): pdict = post_get_test_volume_connector() response = self.post_json( '/volume/connectors', pdict, - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -879,7 +879,7 @@ class TestPost(test_api_base.BaseApiTest): @mock.patch.object(rpcapi.ConductorAPI, 'destroy_volume_connector') class TestDelete(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestDelete, self).setUp() @@ -907,7 +907,7 @@ class TestDelete(test_api_base.BaseApiTest): node_uuid=self.node.uuid)]) def test_delete_volume_connector_byid_invalid_api_version(self, mock_dvc): - headers = {api_base.Version.string: str(api_v1.MIN_VER)} + headers = {api_base.Version.string: str(api_v1.min_version())} response = self.delete('/volume/connectors/%s' % self.connector.uuid, expect_errors=True, headers=headers) self.assertEqual(http_client.NOT_FOUND, response.status_int) diff --git a/ironic/tests/unit/api/controllers/v1/test_volume_target.py b/ironic/tests/unit/api/controllers/v1/test_volume_target.py index fa1028694b..64b9ddc169 100644 --- a/ironic/tests/unit/api/controllers/v1/test_volume_target.py +++ b/ironic/tests/unit/api/controllers/v1/test_volume_target.py @@ -59,7 +59,7 @@ class TestVolumeTargetObject(base.TestCase): class TestListVolumeTargets(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestListVolumeTargets, self).setUp() @@ -83,7 +83,7 @@ class TestListVolumeTargets(test_api_base.BaseApiTest): self.context, node_id=self.node.id) response = self.get_json( '/volume/targets', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -103,7 +103,7 @@ class TestListVolumeTargets(test_api_base.BaseApiTest): node_id=self.node.id) response = self.get_json( '/volume/targets/%s' % target.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -170,7 +170,7 @@ class TestListVolumeTargets(test_api_base.BaseApiTest): node_id=self.node.id) response = self.get_json( '/volume/targets?detail=True', - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -328,7 +328,7 @@ class TestListVolumeTargets(test_api_base.BaseApiTest): @mock.patch.object(rpcapi.ConductorAPI, 'update_volume_target') class TestPatch(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestPatch, self).setUp() @@ -368,7 +368,7 @@ class TestPatch(test_api_base.BaseApiTest): node_uuid=self.node.uuid)]) def test_update_byid_invalid_api_version(self, mock_upd): - headers = {api_base.Version.string: str(api_v1.MIN_VER)} + headers = {api_base.Version.string: str(api_v1.min_version())} response = self.patch_json('/volume/targets/%s' % self.target.uuid, [{'path': '/extra/foo', @@ -702,7 +702,7 @@ class TestPatch(test_api_base.BaseApiTest): class TestPost(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestPost, self).setUp() @@ -742,7 +742,7 @@ class TestPost(test_api_base.BaseApiTest): pdict = post_get_test_volume_target() response = self.post_json( '/volume/targets', pdict, - headers={api_base.Version.string: str(api_v1.MIN_VER)}, + headers={api_base.Version.string: str(api_v1.min_version())}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) @@ -860,7 +860,7 @@ class TestPost(test_api_base.BaseApiTest): @mock.patch.object(rpcapi.ConductorAPI, 'destroy_volume_target') class TestDelete(test_api_base.BaseApiTest): - headers = {api_base.Version.string: str(api_v1.MAX_VER)} + headers = {api_base.Version.string: str(api_v1.max_version())} def setUp(self): super(TestDelete, self).setUp() @@ -889,7 +889,7 @@ class TestDelete(test_api_base.BaseApiTest): node_uuid=self.node.uuid)]) def test_delete_volume_target_byid_invalid_api_version(self, mock_dvc): - headers = {api_base.Version.string: str(api_v1.MIN_VER)} + headers = {api_base.Version.string: str(api_v1.min_version())} response = self.delete('/volume/targets/%s' % self.target.uuid, headers=headers, expect_errors=True) diff --git a/ironic/tests/unit/api/test_root.py b/ironic/tests/unit/api/test_root.py index a4dade70ab..118a4cca32 100644 --- a/ironic/tests/unit/api/test_root.py +++ b/ironic/tests/unit/api/test_root.py @@ -31,8 +31,9 @@ class TestRoot(base.BaseApiTest): version1 = response['default_version'] self.assertEqual('v1', version1['id']) self.assertEqual('CURRENT', version1['status']) - self.assertEqual(versions.MIN_VERSION_STRING, version1['min_version']) - self.assertEqual(versions.MAX_VERSION_STRING, version1['version']) + self.assertEqual(versions.min_version_string(), + version1['min_version']) + self.assertEqual(versions.max_version_string(), version1['version']) class TestV1Root(base.BaseApiTest): diff --git a/ironic/tests/unit/common/test_release_mappings.py b/ironic/tests/unit/common/test_release_mappings.py index ec9f280f39..6ce78cf976 100644 --- a/ironic/tests/unit/common/test_release_mappings.py +++ b/ironic/tests/unit/common/test_release_mappings.py @@ -16,6 +16,7 @@ import mock from oslo_utils import versionutils import six +from ironic.api.controllers.v1 import versions as api_versions from ironic.common import release_mappings from ironic.conductor import rpcapi from ironic.db.sqlalchemy import models @@ -49,7 +50,11 @@ class ReleaseMappingsTestCase(base.TestCase): def test_structure(self): for value in release_mappings.RELEASE_MAPPING.values(): self.assertIsInstance(value, dict) - self.assertEqual({'rpc', 'objects'}, set(value)) + self.assertEqual({'api', 'rpc', 'objects'}, set(value)) + self.assertIsInstance(value['api'], six.string_types) + (major, minor) = value['api'].split('.') + self.assertEqual(1, int(major)) + self.assertLessEqual(int(minor), api_versions.MINOR_MAX_VERSION) self.assertIsInstance(value['rpc'], six.string_types) self.assertIsInstance(value['objects'], dict) for obj_value in value['objects'].values(): @@ -110,6 +115,7 @@ class GetObjectVersionsTestCase(base.TestCase): TEST_MAPPING = { '7.0': { + 'api': '1.30', 'rpc': '1.40', 'objects': { 'Node': ['1.21'], @@ -119,6 +125,7 @@ class GetObjectVersionsTestCase(base.TestCase): } }, '8.0': { + 'api': '1.30', 'rpc': '1.40', 'objects': { 'Node': ['1.22'], @@ -129,6 +136,7 @@ class GetObjectVersionsTestCase(base.TestCase): } }, 'master': { + 'api': '1.34', 'rpc': '1.40', 'objects': { 'Node': ['1.23'], diff --git a/releasenotes/notes/pin-api-version-029748f7d3be68d1.yaml b/releasenotes/notes/pin-api-version-029748f7d3be68d1.yaml new file mode 100644 index 0000000000..eb1bac0a0f --- /dev/null +++ b/releasenotes/notes/pin-api-version-029748f7d3be68d1.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + During a `rolling upgrade + `_ + when the new services are pinned to the old release, + the Bare Metal API version will also be pinned to the old release. This will + prevent new features from being accessed until after the upgrade is done.