From feac8cfb786f7490743b3f270aa569054795ef4f Mon Sep 17 00:00:00 2001 From: Ruby Loo Date: Tue, 7 Nov 2017 17:47:40 -0500 Subject: [PATCH] Pin API version during rolling upgrade During a rolling upgrade, when the new services are pinned to the old release, the API version will also be pinned to the old release. This will prevent users from accessing new features that may not quite work. The .sample was updated to reflect the change to the help string for the [DEFAULT]/pin_release_version configuration option. The update also pulled in changes for other options, from other (non-ironic) libraries. Change-Id: I38a0f106b589945fb62071f3dfe5bff43c6fee93 Partial-Bug: #1708549 --- etc/ironic/ironic.conf.sample | 47 +++++++--- ironic/api/controllers/root.py | 4 +- ironic/api/controllers/v1/__init__.py | 35 ++++---- ironic/api/controllers/v1/versions.py | 38 ++++++-- ironic/common/release_mappings.py | 6 ++ ironic/conf/default.py | 8 +- .../unit/api/controllers/v1/test_chassis.py | 8 +- .../unit/api/controllers/v1/test_node.py | 89 ++++++++++--------- .../unit/api/controllers/v1/test_port.py | 24 ++--- .../unit/api/controllers/v1/test_portgroup.py | 18 ++-- .../unit/api/controllers/v1/test_ramdisk.py | 24 ++--- .../unit/api/controllers/v1/test_root.py | 12 +-- .../unit/api/controllers/v1/test_versions.py | 34 ++++++- .../unit/api/controllers/v1/test_volume.py | 4 +- .../controllers/v1/test_volume_connector.py | 22 ++--- .../api/controllers/v1/test_volume_target.py | 20 ++--- ironic/tests/unit/api/test_root.py | 5 +- .../unit/common/test_release_mappings.py | 10 ++- .../pin-api-version-029748f7d3be68d1.yaml | 8 ++ 19 files changed, 262 insertions(+), 154 deletions(-) create mode 100644 releasenotes/notes/pin-api-version-029748f7d3be68d1.yaml diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index 90242a0a15..dd0702ddb0 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.