2.45: createImage/createBackup image_id is in response body
This adds support for microversion 2.45 which changes the response on the createImage and createBackup server action APIs to return the image_id for the created snapshot image in a json dict in the response body rather than in a Location header (which is gone in microversion >= 2.45). Since the 'nova backup' command was not printing out the created image ID before, that is also added in this microversion. Part of blueprint remove-create-image-location-header-response Change-Id: Id48aa7b14e2d25008287549b04db437ca9c3f548
This commit is contained in:
parent
e303cf11bf
commit
603f0eae9f
@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
|
||||
# when client supported the max version, and bumped sequentially, otherwise
|
||||
# the client may break due to server side new version may include some
|
||||
# backward incompatible change.
|
||||
API_MAX_VERSION = api_versions.APIVersion("2.44")
|
||||
API_MAX_VERSION = api_versions.APIVersion("2.45")
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from novaclient import api_versions
|
||||
from novaclient.tests.unit import fakes
|
||||
from novaclient.tests.unit.fixture_data import base
|
||||
from novaclient.tests.unit.v2 import fakes as v2_fakes
|
||||
@ -443,6 +444,8 @@ class V1(Base):
|
||||
context.status_code = 202
|
||||
assert len(body.keys()) == 1
|
||||
action = list(body)[0]
|
||||
api_version = api_versions.APIVersion(
|
||||
request.headers.get('X-OpenStack-Nova-API-Version', '2.1'))
|
||||
|
||||
if v2_fakes.FakeSessionClient.check_server_actions(body):
|
||||
# NOTE(snikitin): No need to do any operations here. This 'pass'
|
||||
@ -475,6 +478,14 @@ class V1(Base):
|
||||
_body = {'adminPass': 'RescuePassword'}
|
||||
elif action == 'createImage':
|
||||
assert set(body[action].keys()) == set(['name', 'metadata'])
|
||||
if api_version >= api_versions.APIVersion('2.45'):
|
||||
return {'image_id': '456'}
|
||||
context.headers['location'] = "http://blah/images/456"
|
||||
elif action == 'createBackup':
|
||||
assert set(body[action].keys()) == set(['name', 'backup_type',
|
||||
'rotation'])
|
||||
if api_version >= api_versions.APIVersion('2.45'):
|
||||
return {'image_id': '456'}
|
||||
context.headers['location'] = "http://blah/images/456"
|
||||
elif action == 'os-getConsoleOutput':
|
||||
assert list(body[action]) == ['length']
|
||||
|
@ -47,6 +47,7 @@ FAKE_IMAGE_UUID_1 = 'c99d7632-bd66-4be9-aed5-3dd14b223a76'
|
||||
FAKE_IMAGE_UUID_2 = 'f27f479a-ddda-419a-9bbc-d6b56b210161'
|
||||
FAKE_IMAGE_UUID_SNAPSHOT = '555cae93-fb41-4145-9c52-f5b923538a26'
|
||||
FAKE_IMAGE_UUID_SNAP_DEL = '55bb23af-97a4-4068-bdf8-f10c62880ddf'
|
||||
FAKE_IMAGE_UUID_BACKUP = '2f87e889-41a4-4778-8553-83f5eea68c5d'
|
||||
|
||||
# fake request id
|
||||
FAKE_REQUEST_ID = fakes.FAKE_REQUEST_ID
|
||||
@ -716,9 +717,6 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
assert body[action] is None
|
||||
elif action in ['addSecurityGroup', 'removeSecurityGroup']:
|
||||
assert list(body[action]) == ['name']
|
||||
elif action == 'createBackup':
|
||||
assert set(body[action]) == set(['name', 'backup_type',
|
||||
'rotation'])
|
||||
elif action == 'trigger_crash_dump':
|
||||
assert body[action] is None
|
||||
else:
|
||||
@ -766,11 +764,22 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
_body = {'adminPass': 'RescuePassword'}
|
||||
elif action == 'createImage':
|
||||
assert set(body[action].keys()) == set(['name', 'metadata'])
|
||||
_headers = dict(location="http://blah/images/%s" %
|
||||
FAKE_IMAGE_UUID_SNAPSHOT)
|
||||
if self.api_version < api_versions.APIVersion('2.45'):
|
||||
_headers = dict(location="http://blah/images/%s" %
|
||||
FAKE_IMAGE_UUID_SNAPSHOT)
|
||||
else:
|
||||
_body = {'image_id': FAKE_IMAGE_UUID_SNAPSHOT}
|
||||
if body[action]['name'] == 'mysnapshot_deleted':
|
||||
_headers = dict(location="http://blah/images/%s" %
|
||||
FAKE_IMAGE_UUID_SNAP_DEL)
|
||||
elif action == 'createBackup':
|
||||
assert set(body[action].keys()) == set(['name', 'backup_type',
|
||||
'rotation'])
|
||||
if self.api_version < api_versions.APIVersion('2.45'):
|
||||
_headers = dict(location="http://blah/images/%s" %
|
||||
FAKE_IMAGE_UUID_BACKUP)
|
||||
else:
|
||||
_body = {'image_id': FAKE_IMAGE_UUID_BACKUP}
|
||||
elif action == 'os-getConsoleOutput':
|
||||
assert list(body[action]) == ['length']
|
||||
return (202, {}, {'output': 'foo'})
|
||||
@ -1039,7 +1048,17 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
"status": "SAVING",
|
||||
"progress": 80,
|
||||
"links": {},
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": FAKE_IMAGE_UUID_BACKUP,
|
||||
"name": "back1",
|
||||
"serverId": '1234',
|
||||
"updated": "2010-10-10T12:00:00Z",
|
||||
"created": "2010-08-10T12:00:00Z",
|
||||
"status": "SAVING",
|
||||
"progress": 80,
|
||||
"links": {},
|
||||
},
|
||||
]})
|
||||
|
||||
def get_images_555cae93_fb41_4145_9c52_f5b923538a26(self, **kw):
|
||||
@ -1054,6 +1073,9 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
def get_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw):
|
||||
return (200, {}, {'image': self.get_images()[2]['images'][3]})
|
||||
|
||||
def get_images_2f87e889_41a4_4778_8553_83f5eea68c5d(self, **kw):
|
||||
return (200, {}, {'image': self.get_images()[2]['images'][4]})
|
||||
|
||||
def get_images_3e861307_73a6_4d1f_8d68_f68b03223032(self):
|
||||
raise exceptions.NotFound('404')
|
||||
|
||||
|
@ -1409,3 +1409,51 @@ class ServersV2_37Test(ServersV226Test):
|
||||
def test_remove_floating_ip(self):
|
||||
# self.floating_ips.list() is not available after 2.35
|
||||
pass
|
||||
|
||||
|
||||
class ServersCreateImageBackupV2_45Test(utils.FixturedTestCase):
|
||||
"""Tests the 2.45 microversion for createImage and createBackup
|
||||
server actions.
|
||||
"""
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.V1
|
||||
api_version = '2.45'
|
||||
|
||||
def setUp(self):
|
||||
super(ServersCreateImageBackupV2_45Test, self).setUp()
|
||||
self.cs.api_version = api_versions.APIVersion(self.api_version)
|
||||
|
||||
def test_create_image(self):
|
||||
"""Tests the createImage API with the 2.45 microversion which
|
||||
does not return the Location header, it returns a json dict in the
|
||||
response body with an image_id key.
|
||||
"""
|
||||
s = self.cs.servers.get(1234)
|
||||
im = s.create_image('123')
|
||||
self.assertEqual('456', im)
|
||||
self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action')
|
||||
im = s.create_image('123', {})
|
||||
self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action')
|
||||
im = self.cs.servers.create_image(s, '123')
|
||||
self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action')
|
||||
im = self.cs.servers.create_image(s, '123', {})
|
||||
self.assert_request_id(im, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action')
|
||||
|
||||
def test_backup(self):
|
||||
s = self.cs.servers.get(1234)
|
||||
# Test backup on the Server object.
|
||||
sb = s.backup('back1', 'daily', 1)
|
||||
self.assertIn('image_id', sb)
|
||||
self.assertEqual('456', sb['image_id'])
|
||||
self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action')
|
||||
# Test backup on the ServerManager.
|
||||
sb = self.cs.servers.backup(s, 'back1', 'daily', 2)
|
||||
self.assertEqual('456', sb['image_id'])
|
||||
self.assert_request_id(sb, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.assert_called('POST', '/servers/1234/action')
|
||||
|
@ -1139,6 +1139,18 @@ class ShellTest(utils.TestCase):
|
||||
{'createImage': {'name': 'mysnapshot', 'metadata': {}}},
|
||||
)
|
||||
|
||||
def test_create_image_2_45(self):
|
||||
"""Tests the image-create command with microversion 2.45 which
|
||||
does not change the output of the command, just how the response
|
||||
from the server is processed.
|
||||
"""
|
||||
self.run_command('image-create sample-server mysnapshot',
|
||||
api_version='2.45')
|
||||
self.assert_called(
|
||||
'POST', '/servers/1234/action',
|
||||
{'createImage': {'name': 'mysnapshot', 'metadata': {}}},
|
||||
)
|
||||
|
||||
def test_create_image_with_incorrect_metadata(self):
|
||||
cmd = 'image-create sample-server mysnapshot --metadata foo'
|
||||
result = self.assertRaises(argparse.ArgumentTypeError,
|
||||
@ -2518,7 +2530,9 @@ class ShellTest(utils.TestCase):
|
||||
{'removeFixedIp': {'address': '10.0.0.10'}})
|
||||
|
||||
def test_backup(self):
|
||||
self.run_command('backup sample-server back1 daily 1')
|
||||
out, err = self.run_command('backup sample-server back1 daily 1')
|
||||
# With microversion < 2.45 there is no output from this command.
|
||||
self.assertEqual(0, len(out))
|
||||
self.assert_called('POST', '/servers/1234/action',
|
||||
{'createBackup': {'name': 'back1',
|
||||
'backup_type': 'daily',
|
||||
@ -2529,6 +2543,23 @@ class ShellTest(utils.TestCase):
|
||||
'backup_type': 'daily',
|
||||
'rotation': '1'}})
|
||||
|
||||
def test_backup_2_45(self):
|
||||
"""Tests the backup command with the 2.45 microversion which
|
||||
handles a different response and prints out the backup snapshot
|
||||
image details.
|
||||
"""
|
||||
out, err = self.run_command(
|
||||
'backup sample-server back1 daily 1',
|
||||
api_version='2.45')
|
||||
# We should see the backup snapshot image name in the output.
|
||||
self.assertIn('back1', out)
|
||||
self.assertIn('SAVING', out)
|
||||
self.assert_called_anytime(
|
||||
'POST', '/servers/1234/action',
|
||||
{'createBackup': {'name': 'back1',
|
||||
'backup_type': 'daily',
|
||||
'rotation': '1'}})
|
||||
|
||||
def test_limits(self):
|
||||
self.run_command('limits')
|
||||
self.assert_called('GET', '/limits')
|
||||
@ -2914,6 +2945,7 @@ class ShellTest(utils.TestCase):
|
||||
42, # There are no version-wrapped shell method changes for this.
|
||||
43, # There are no version-wrapped shell method changes for this.
|
||||
44, # There are no version-wrapped shell method changes for this.
|
||||
45, # There are no version-wrapped shell method changes for this.
|
||||
])
|
||||
versions_supported = set(range(0,
|
||||
novaclient.API_MAX_VERSION.ver_minor + 1))
|
||||
|
@ -1630,8 +1630,13 @@ class ServerManager(base.BootingManagerWithFind):
|
||||
body = {'name': image_name, 'metadata': metadata or {}}
|
||||
resp, body = self._action_return_resp_and_body('createImage', server,
|
||||
body)
|
||||
location = resp.headers['location']
|
||||
image_uuid = location.split('/')[-1]
|
||||
# The 2.45 microversion returns the image_id in the response body,
|
||||
# not as a location header.
|
||||
if self.api_version >= api_versions.APIVersion('2.45'):
|
||||
image_uuid = body['image_id']
|
||||
else:
|
||||
location = resp.headers['location']
|
||||
image_uuid = location.split('/')[-1]
|
||||
return base.StrWithMeta(image_uuid, resp)
|
||||
|
||||
def backup(self, server, backup_name, backup_type, rotation):
|
||||
@ -1643,7 +1648,8 @@ class ServerManager(base.BootingManagerWithFind):
|
||||
:param backup_type: The backup type, like 'daily' or 'weekly'
|
||||
:param rotation: Int parameter representing how many backups to
|
||||
keep around.
|
||||
:returns: An instance of novaclient.base.TupleWithMeta
|
||||
:returns: An instance of novaclient.base.TupleWithMeta if the request
|
||||
microversion is < 2.45, otherwise novaclient.base.DictWithMeta.
|
||||
"""
|
||||
body = {'name': backup_name,
|
||||
'backup_type': backup_type,
|
||||
|
@ -2040,9 +2040,13 @@ def do_image_create(cs, args):
|
||||
'around.'))
|
||||
def do_backup(cs, args):
|
||||
"""Backup a server by creating a 'backup' type snapshot."""
|
||||
_find_server(cs, args.server).backup(args.name,
|
||||
args.backup_type,
|
||||
args.rotation)
|
||||
result = _find_server(cs, args.server).backup(args.name,
|
||||
args.backup_type,
|
||||
args.rotation)
|
||||
# Microversion >= 2.45 will return a DictWithMeta that has the image_id
|
||||
# in it for the backup snapshot image.
|
||||
if cs.api_version >= api_versions.APIVersion('2.45'):
|
||||
_print_image(_find_image(cs, result['image_id']))
|
||||
|
||||
|
||||
@utils.arg(
|
||||
|
13
releasenotes/notes/microversion-v2_45-1bfcae3914280534.yaml
Normal file
13
releasenotes/notes/microversion-v2_45-1bfcae3914280534.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Support was added for microversion 2.45. This changes how the
|
||||
``createImage`` and ``createBackup`` server action APIs return
|
||||
the created snapshot image ID in the response. With microversion
|
||||
2.45 and later, the image ID is return in a json dict response body
|
||||
with an ``image_id`` key and uuid value. The old ``Location`` response
|
||||
header is no longer returned in microversion 2.45 or later.
|
||||
|
||||
There are no changes to the ``nova image-create`` CLI. However, the
|
||||
``nova backup`` CLI will print out the backup snapshot image information
|
||||
with microversion 2.45 or greater now.
|
Loading…
Reference in New Issue
Block a user