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
This commit is contained in:
Ruby Loo 2017-11-07 17:47:40 -05:00
parent 638943dbf8
commit feac8cfb78
19 changed files with 262 additions and 154 deletions

View File

@ -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 = <None>
@ -1025,7 +1024,9 @@
#domain_name = <None>
# 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 = <None>
# Verify HTTPS connections. (boolean value)
@ -1662,7 +1663,9 @@
#domain_name = <None>
# 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 = <None>
# 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 = <None>
# 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 = <None>
# 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 = <None>
# API version of the admin Identity API endpoint. (string
@ -3800,7 +3823,9 @@
#domain_name = <None>
# 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 = <None>
# Verify HTTPS connections. (boolean value)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -26,6 +26,7 @@
# NOTE(xek): The format of this dict is:
# { '<release version>': {
# 'api': '<Bare Metal API version>',
# 'rpc': '<RPC API version>',
# 'objects': {
# '<object class name>': ['<object version>'],
@ -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'],

View File

@ -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 = [

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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/<uuid>/ports/<uuid>
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()

View File

@ -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,

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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'],

View File

@ -0,0 +1,8 @@
---
upgrade:
- |
During a `rolling upgrade
<https://docs.openstack.org/ironic/latest/admin/upgrade-guide.html#during-maintenance-window>`_
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.