Look up image names directly in glance

This does a lookup directly to the image service for name => id
mapping of images. This is required to move on to supporting the
2.36 microversion since that microversion makes the compute
image API proxy return a 404, so before the client can support
that microversion it has to first drop it's usage of the proxy
API.

Because of the way the FakeHTTPClient was stubbing the image
API proxy and the tests are not passing uuids for image IDs,
there is a lot of cleaning up of the tests to make this work
with a fake glance v2 API backend. The tests were basically
false though since you can't do 'nova image-show 1', but because
the stubs mask that it's just been a mountain of lies that has to
be cleaned up here.

As a side effect of fixing a bunch of the tests, this also
makes debugging assert_called less terrible with a better error
message.

Co-Authored-By: Matt Riedemann <mriedem@us.ibm.com>

Related to blueprint deprecate-api-proxies

Change-Id: Iaff3999eafb7d746e5c6032f07ce0756f7b5e868
This commit is contained in:
Sean Dague 2016-08-11 15:20:53 -04:00 committed by Matt Riedemann
parent fe1fd3321b
commit f839cf1625
8 changed files with 278 additions and 174 deletions

View File

@ -266,6 +266,18 @@ class Manager(HookableMixin):
for res in data if res]
return ListWithMeta(items, resp)
@contextlib.contextmanager
def alternate_service_type(self, default, allowed_types=()):
original_service_type = self.api.client.service_type
if original_service_type in allowed_types:
yield
else:
self.api.client.service_type = default
try:
yield
finally:
self.api.client.service_type = original_service_type
@contextlib.contextmanager
def completion_cache(self, cache_type, obj_class, mode):
"""The completion cache for bash autocompletion.
@ -332,7 +344,11 @@ class Manager(HookableMixin):
def _get(self, url, response_key):
resp, body = self.api.client.get(url)
return self.resource_class(self, body[response_key], loaded=True,
if response_key is not None:
content = body[response_key]
else:
content = body
return self.resource_class(self, content, loaded=True,
resp=resp)
def _create(self, url, body, response_key, return_raw=False, **kwargs):

View File

@ -78,13 +78,19 @@ class FakeClient(object):
}, pos=4)
"""
expected = (method, url)
called = self.client.callstack[pos][0:2]
assert self.client.callstack, \
"Expected %s %s but no calls were made." % expected
called = self.client.callstack[pos][0:2]
assert expected == called, \
'Expected %s %s; got %s %s' % (expected + called)
('\nExpected: %(expected)s'
'\nActual: %(called)s'
'\nCall position: %(pos)s'
'\nCalls:\n%(calls)s' %
{'expected': expected, 'called': called, 'pos': pos,
'calls': '\n'.join(str(c) for c in self.client.callstack)})
if body is not None:
if self.client.callstack[pos][2] != body:

View File

@ -45,6 +45,8 @@ CALLBACK_RE = re.compile(r"^get_http:__nova_api:8774_v\d(_\d)?$")
# fake image uuids
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 request id
FAKE_REQUEST_ID = fakes.FAKE_REQUEST_ID
@ -128,6 +130,12 @@ class FakeHTTPClient(base_client.HTTPClient):
# more like we'd expect when making REST calls.
raise exceptions.NotFound('404')
# Handle fake glance v2 requests
v2_image = False
if callback.startswith('get_v2_images'):
v2_image = True
callback = callback.replace('get_v2_', 'get_')
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
@ -137,6 +145,12 @@ class FakeHTTPClient(base_client.HTTPClient):
self.callstack.append((method, url, kwargs.get('body')))
status, headers, body = getattr(self, callback)(**kwargs)
# If we're dealing with a glance v2 image response, the response
# isn't wrapped like the compute images API proxy is, so handle that.
if body and v2_image and 'image' in body:
body = body['image']
r = utils.TestResponse({
"status_code": status,
"text": body,
@ -417,7 +431,7 @@ class FakeHTTPClient(base_client.HTTPClient):
"id": 5678,
"name": "sample-server2",
"image": {
"id": 2,
"id": FAKE_IMAGE_UUID_1,
"name": "sample image",
},
"flavor": {
@ -742,9 +756,11 @@ class FakeHTTPClient(base_client.HTTPClient):
_body = {'adminPass': 'RescuePassword'}
elif action == 'createImage':
assert set(body[action].keys()) == set(['name', 'metadata'])
_headers = dict(location="http://blah/images/456")
_headers = dict(location="http://blah/images/%s" %
FAKE_IMAGE_UUID_SNAPSHOT)
if body[action]['name'] == 'mysnapshot_deleted':
_headers = dict(location="http://blah/images/457")
_headers = dict(location="http://blah/images/%s" %
FAKE_IMAGE_UUID_SNAP_DEL)
elif action == 'os-getConsoleOutput':
assert list(body[action]) == ['length']
return (202, {}, {'output': 'foo'})
@ -1079,8 +1095,6 @@ class FakeHTTPClient(base_client.HTTPClient):
#
def get_images(self, **kw):
return (200, {}, {'images': [
{'id': 1, 'name': 'CentOS 5.2'},
{'id': 2, 'name': 'My Server Backup'},
{'id': FAKE_IMAGE_UUID_1, 'name': 'CentOS 5.2'},
{'id': FAKE_IMAGE_UUID_2, 'name': 'My Server Backup'}
]})
@ -1088,18 +1102,7 @@ class FakeHTTPClient(base_client.HTTPClient):
def get_images_detail(self, **kw):
return (200, {}, {'images': [
{
'id': 1,
'name': 'CentOS 5.2',
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"status": "ACTIVE",
"metadata": {
"test_key": "test_value",
},
"links": {},
},
{
"id": 2,
"id": FAKE_IMAGE_UUID_SNAPSHOT,
"name": "My Server Backup",
"serverId": 1234,
"updated": "2010-10-10T12:00:00Z",
@ -1109,7 +1112,7 @@ class FakeHTTPClient(base_client.HTTPClient):
"links": {},
},
{
"id": 3,
"id": FAKE_IMAGE_UUID_SNAP_DEL,
"name": "My Server Backup Deleted",
"serverId": 1234,
"updated": "2010-10-10T12:00:00Z",
@ -1141,38 +1144,31 @@ class FakeHTTPClient(base_client.HTTPClient):
}
]})
def get_images_1(self, **kw):
def get_images_555cae93_fb41_4145_9c52_f5b923538a26(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][0]})
def get_images_2(self, **kw):
def get_images_55bb23af_97a4_4068_bdf8_f10c62880ddf(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][1]})
def get_images_456(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][1]})
def get_images_457(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][2]})
def get_images_c99d7632_bd66_4be9_aed5_3dd14b223a76(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][3]})
return (200, {}, {'image': self.get_images_detail()[2]['images'][2]})
def get_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw):
return (200, {}, {'image': self.get_images_detail()[2]['images'][4]})
return (200, {}, {'image': self.get_images_detail()[2]['images'][3]})
def get_images_3e861307_73a6_4d1f_8d68_f68b03223032(self):
raise exceptions.NotFound('404')
def post_images_1_metadata(self, body, **kw):
def post_images_c99d7632_bd66_4be9_aed5_3dd14b223a76_metadata(
self, body, **kw):
assert list(body) == ['metadata']
fakes.assert_has_keys(body['metadata'],
required=['test_key'])
get_image = self.get_images_c99d7632_bd66_4be9_aed5_3dd14b223a76
return (
200,
{},
{'metadata': self.get_images_1()[2]['image']['metadata']})
def delete_images_1(self, **kw):
return (204, {}, None)
{'metadata': get_image()[2]['image']['metadata']})
def delete_images_c99d7632_bd66_4be9_aed5_3dd14b223a76(self, **kw):
return (204, {}, None)
@ -1180,7 +1176,8 @@ class FakeHTTPClient(base_client.HTTPClient):
def delete_images_f27f479a_ddda_419a_9bbc_d6b56b210161(self, **kw):
return (204, {}, None)
def delete_images_1_metadata_test_key(self, **kw):
def delete_images_c99d7632_bd66_4be9_aed5_3dd14b223a76_metadata_test_key(
self, **kw):
return (204, {}, None)
#

View File

@ -131,13 +131,14 @@ class ShellTest(utils.TestCase):
"md5hash": "add6bb58e139be103324d04d82d8f546"}})
def test_boot(self):
self.run_command('boot --flavor 1 --image 1 some-server')
self.run_command('boot --flavor 1 --image %s '
'some-server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
}},
@ -151,20 +152,21 @@ class ShellTest(utils.TestCase):
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
}},
)
def test_boot_key(self):
self.run_command('boot --flavor 1 --image 1 --key-name 1 some-server')
self.run_command('boot --flavor 1 --image %s --key-name 1 some-server'
% FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'key_name': '1',
'min_count': 1,
'max_count': 1,
@ -177,13 +179,14 @@ class ShellTest(utils.TestCase):
data = testfile_fd.read().encode('utf-8')
expected_file_data = base64.b64encode(data).decode('utf-8')
self.run_command(
'boot --flavor 1 --image 1 --user-data %s some-server' % testfile)
'boot --flavor 1 --image %s --user-data %s some-server' %
(FAKE_UUID_1, testfile))
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'user_data': expected_file_data
@ -192,14 +195,14 @@ class ShellTest(utils.TestCase):
def test_boot_avzone(self):
self.run_command(
'boot --flavor 1 --image 1 --availability-zone avzone '
'some-server')
'boot --flavor 1 --image %s --availability-zone avzone '
'some-server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'availability_zone': 'avzone',
'min_count': 1,
'max_count': 1
@ -208,8 +211,8 @@ class ShellTest(utils.TestCase):
def test_boot_secgroup(self):
self.run_command(
'boot --flavor 1 --image 1 --security-groups secgroup1,'
'secgroup2 some-server')
'boot --flavor 1 --image %s --security-groups secgroup1,'
'secgroup2 some-server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
@ -217,7 +220,7 @@ class ShellTest(utils.TestCase):
{'name': 'secgroup2'}],
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
}},
@ -225,13 +228,14 @@ class ShellTest(utils.TestCase):
def test_boot_config_drive(self):
self.run_command(
'boot --flavor 1 --image 1 --config-drive 1 some-server')
'boot --flavor 1 --image %s --config-drive 1 some-server' %
FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'config_drive': True
@ -240,14 +244,14 @@ class ShellTest(utils.TestCase):
def test_boot_access_ip(self):
self.run_command(
'boot --flavor 1 --image 1 --access-ip-v4 10.10.10.10 '
'--access-ip-v6 ::1 some-server')
'boot --flavor 1 --image %s --access-ip-v4 10.10.10.10 '
'--access-ip-v6 ::1 some-server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'accessIPv4': '10.10.10.10',
'accessIPv6': '::1',
'max_count': 1,
@ -257,13 +261,14 @@ class ShellTest(utils.TestCase):
def test_boot_config_drive_custom(self):
self.run_command(
'boot --flavor 1 --image 1 --config-drive /dev/hda some-server')
'boot --flavor 1 --image %s --config-drive /dev/hda some-server' %
FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'config_drive': '/dev/hda'
@ -273,8 +278,8 @@ class ShellTest(utils.TestCase):
def test_boot_invalid_user_data(self):
invalid_file = os.path.join(os.path.dirname(__file__),
'no_such_file')
cmd = ('boot some-server --flavor 1 --image 1'
' --user-data %s' % invalid_file)
cmd = ('boot some-server --flavor 1 --image %s'
' --user-data %s' % (FAKE_UUID_1, invalid_file))
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_no_image_no_bdms(self):
@ -282,7 +287,7 @@ class ShellTest(utils.TestCase):
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_no_flavor(self):
cmd = 'boot --image 1 some-server'
cmd = 'boot --image %s some-server' % FAKE_UUID_1
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_no_image_bdms(self):
@ -309,9 +314,9 @@ class ShellTest(utils.TestCase):
def test_boot_image_bdms_v2(self):
self.run_command(
'boot --flavor 1 --image 1 --block-device id=fake-id,'
'boot --flavor 1 --image %s --block-device id=fake-id,'
'source=volume,dest=volume,device=vda,size=1,format=ext4,'
'type=disk,shutdown=preserve some-server'
'type=disk,shutdown=preserve some-server' % FAKE_UUID_1
)
self.assert_called_anytime(
'POST', '/os-volumes_boot',
@ -320,7 +325,7 @@ class ShellTest(utils.TestCase):
'name': 'some-server',
'block_device_mapping_v2': [
{
'uuid': 1,
'uuid': FAKE_UUID_1,
'source_type': 'image',
'destination_type': 'local',
'boot_index': 0,
@ -337,7 +342,7 @@ class ShellTest(utils.TestCase):
'delete_on_termination': False,
},
],
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
}},
@ -345,9 +350,9 @@ class ShellTest(utils.TestCase):
def test_boot_image_bdms_v2_with_tag(self):
self.run_command(
'boot --flavor 1 --image 1 --block-device id=fake-id,'
'boot --flavor 1 --image %s --block-device id=fake-id,'
'source=volume,dest=volume,device=vda,size=1,format=ext4,'
'type=disk,shutdown=preserve,tag=foo some-server',
'type=disk,shutdown=preserve,tag=foo some-server' % FAKE_UUID_1,
api_version='2.32'
)
self.assert_called_anytime(
@ -357,7 +362,7 @@ class ShellTest(utils.TestCase):
'name': 'some-server',
'block_device_mapping_v2': [
{
'uuid': 1,
'uuid': FAKE_UUID_1,
'source_type': 'image',
'destination_type': 'local',
'boot_index': 0,
@ -375,7 +380,7 @@ class ShellTest(utils.TestCase):
'tag': 'foo',
},
],
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
}},
@ -504,20 +509,20 @@ class ShellTest(utils.TestCase):
def test_boot_bdms_v2_invalid_shutdown_value(self):
self.assertRaises(exceptions.CommandError, self.run_command,
('boot --flavor 1 --image 1 --block-device '
('boot --flavor 1 --image %s --block-device '
'id=fake-id,source=volume,dest=volume,device=vda,'
'size=1,format=ext4,type=disk,shutdown=foobar '
'some-server'))
'some-server' % FAKE_UUID_1))
def test_boot_metadata(self):
self.run_command('boot --image 1 --flavor 1 --meta foo=bar=pants'
' --meta spam=eggs some-server ')
self.run_command('boot --image %s --flavor 1 --meta foo=bar=pants'
' --meta spam=eggs some-server ' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'metadata': {'foo': 'bar=pants', 'spam': 'eggs'},
'min_count': 1,
'max_count': 1,
@ -525,15 +530,16 @@ class ShellTest(utils.TestCase):
)
def test_boot_hints(self):
self.run_command('boot --image 1 --flavor 1 '
'--hint a=b0=c0 --hint a=b1=c1 some-server ')
self.run_command('boot --image %s --flavor 1 '
'--hint a=b0=c0 --hint a=b1=c1 some-server ' %
FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
},
@ -542,8 +548,9 @@ class ShellTest(utils.TestCase):
)
def test_boot_nics(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1 some-server' %
FAKE_UUID_1)
self.run_command(cmd)
self.assert_called_anytime(
'POST', '/servers',
@ -551,7 +558,7 @@ class ShellTest(utils.TestCase):
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'networks': [
@ -562,8 +569,9 @@ class ShellTest(utils.TestCase):
)
def test_boot_nics_with_tag(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,tag=foo some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,tag=foo some-server' %
FAKE_UUID_1)
self.run_command(cmd, api_version='2.32')
self.assert_called_anytime(
'POST', '/servers',
@ -571,7 +579,7 @@ class ShellTest(utils.TestCase):
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'networks': [
@ -583,23 +591,24 @@ class ShellTest(utils.TestCase):
def test_boot_invalid_nics_pre_v2_32(self):
# This is a negative test to make sure we fail with the correct message
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=1,port-id=2 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=1,port-id=2 some-server' % FAKE_UUID_1)
ex = self.assertRaises(exceptions.CommandError, self.run_command,
cmd, api_version='2.1')
self.assertNotIn('tag=tag', six.text_type(ex))
def test_boot_invalid_nics_v2_32(self):
# This is a negative test to make sure we fail with the correct message
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=1,port-id=2 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=1,port-id=2 some-server' % FAKE_UUID_1)
ex = self.assertRaises(exceptions.CommandError, self.run_command,
cmd, api_version='2.32')
self.assertIn('tag=tag', six.text_type(ex))
def test_boot_nics_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server' %
FAKE_UUID_1)
self.run_command(cmd)
self.assert_called_anytime(
'POST', '/servers',
@ -607,7 +616,7 @@ class ShellTest(utils.TestCase):
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'networks': [
@ -618,44 +627,48 @@ class ShellTest(utils.TestCase):
)
def test_boot_nics_both_ipv4_and_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,'
'v6-fixed-ip=2001:db9:0:1::10 some-server')
'v6-fixed-ip=2001:db9:0:1::10 some-server' % FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_no_value(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id some-server' % FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_random_key(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,foo=bar some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=10.0.0.1,foo=bar some-server' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_no_netid_or_portid(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic v4-fixed-ip=10.0.0.1 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic v4-fixed-ip=10.0.0.1 some-server' % FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_netid_and_portid(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic port-id=some=port,net-id=some=net some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic port-id=some=port,net-id=some=net some-server' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_invalid_ipv4(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=2001:db9:0:1::10 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v4-fixed-ip=2001:db9:0:1::10 some-server' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_invalid_ipv6(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=10.0.0.1 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=10.0.0.1 some-server' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_nics_net_id_twice(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-id=net-id1,net-id=net-id2 some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=net-id1,net-id=net-id2 some-server' % FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@mock.patch(
@ -664,8 +677,8 @@ class ShellTest(utils.TestCase):
mock_networks_list.return_value = (200, {}, {
'networks': [{"label": "some-net", 'id': '1'}]})
cmd = ('boot --image 1 --flavor 1 '
'--nic net-name=some-net some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-name=some-net some-server' % FAKE_UUID_1)
self.run_command(cmd)
self.assert_called_anytime(
'POST', '/servers',
@ -673,7 +686,7 @@ class ShellTest(utils.TestCase):
'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'networks': [
@ -684,8 +697,8 @@ class ShellTest(utils.TestCase):
)
def test_boot_nics_net_name_not_found(self):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-name=some-net some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-name=some-net some-server' % FAKE_UUID_1)
self.assertRaises(exceptions.ResourceNotFound, self.run_command, cmd)
@mock.patch(
@ -695,20 +708,22 @@ class ShellTest(utils.TestCase):
'networks': [{"label": "some-net", 'id': '1'},
{"label": "some-net", 'id': '2'}]})
cmd = ('boot --image 1 --flavor 1 '
'--nic net-name=some-net some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-name=some-net some-server' % FAKE_UUID_1)
self.assertRaises(exceptions.NoUniqueMatch, self.run_command, cmd)
@mock.patch('novaclient.v2.shell._find_network_id', return_value='net-id')
def test_boot_nics_net_name_and_net_id(self, mock_find_network_id):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-name=some-net,net-id=some-id some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-name=some-net,net-id=some-id some-server' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@mock.patch('novaclient.v2.shell._find_network_id', return_value='net-id')
def test_boot_nics_net_name_and_port_id(self, mock_find_network_id):
cmd = ('boot --image 1 --flavor 1 '
'--nic net-name=some-net,port-id=some-id some-server')
cmd = ('boot --image %s --flavor 1 '
'--nic net-name=some-net,port-id=some-id some-server' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_files(self):
@ -717,16 +732,16 @@ class ShellTest(utils.TestCase):
data = testfile_fd.read()
expected = base64.b64encode(data.encode('utf-8')).decode('utf-8')
cmd = ('boot some-server --flavor 1 --image 1'
cmd = ('boot some-server --flavor 1 --image %s'
' --file /tmp/foo=%s --file /tmp/bar=%s')
self.run_command(cmd % (testfile, testfile))
self.run_command(cmd % (FAKE_UUID_1, testfile, testfile))
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
'personality': [
@ -739,92 +754,96 @@ class ShellTest(utils.TestCase):
def test_boot_invalid_files(self):
invalid_file = os.path.join(os.path.dirname(__file__),
'asdfasdfasdfasdf')
cmd = ('boot some-server --flavor 1 --image 1'
' --file /foo=%s' % invalid_file)
cmd = ('boot some-server --flavor 1 --image %s'
' --file /foo=%s' % (FAKE_UUID_1, invalid_file))
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_max_min_count(self):
self.run_command('boot --image 1 --flavor 1 --min-count 1'
' --max-count 3 server')
self.run_command('boot --image %s --flavor 1 --min-count 1'
' --max-count 3 server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 3,
}
})
def test_boot_invalid_min_count(self):
cmd = 'boot --image 1 --flavor 1 --min-count 0 server'
cmd = 'boot --image %s --flavor 1 --min-count 0 server' % FAKE_UUID_1
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
def test_boot_min_max_count(self):
self.run_command('boot --image 1 --flavor 1 --max-count 3 server')
self.run_command('boot --image %s --flavor 1 --max-count 3 server' %
FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 --min-count 3 server')
self.run_command('boot --image %s --flavor 1 --min-count 3 server' %
FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 3,
'max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 '
'--min-count 3 --max-count 3 server')
self.run_command('boot --image %s --flavor 1 '
'--min-count 3 --max-count 3 server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 3,
'max_count': 3,
}
})
self.run_command('boot --image 1 --flavor 1 '
'--min-count 3 --max-count 5 server')
self.run_command('boot --image %s --flavor 1 '
'--min-count 3 --max-count 5 server' % FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{
'server': {
'flavorRef': '1',
'name': 'server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 3,
'max_count': 5,
}
})
cmd = 'boot --image 1 --flavor 1 --min-count 3 --max-count 1 serv'
cmd = ('boot --image %s --flavor 1 --min-count 3 --max-count 1 serv' %
FAKE_UUID_1)
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
@mock.patch('novaclient.v2.shell._poll_for_status')
def test_boot_with_poll(self, poll_method):
self.run_command('boot --flavor 1 --image 1 some-server --poll')
self.run_command('boot --flavor 1 --image %s some-server --poll' %
FAKE_UUID_1)
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'some-server',
'imageRef': '1',
'imageRef': FAKE_UUID_1,
'min_count': 1,
'max_count': 1,
}},
@ -836,13 +855,14 @@ class ShellTest(utils.TestCase):
def test_boot_with_poll_to_check_VM_state_error(self):
self.assertRaises(exceptions.ResourceInErrorState, self.run_command,
'boot --flavor 1 --image 1 some-bad-server --poll')
'boot --flavor 1 --image %s some-bad-server --poll' %
FAKE_UUID_1)
def test_boot_named_flavor(self):
self.run_command(["boot", "--image", FAKE_UUID_1,
"--flavor", "512 MB Server",
"--max-count", "3", "server"])
self.assert_called('GET', '/images/' + FAKE_UUID_1, pos=0)
self.assert_called('GET', '/v2/images/' + FAKE_UUID_1, pos=0)
self.assert_called('GET', '/flavors/512 MB Server', pos=1)
self.assert_called('GET', '/flavors?is_public=None', pos=2)
self.assert_called('GET', '/flavors/2', pos=3)
@ -859,7 +879,8 @@ class ShellTest(utils.TestCase):
}, pos=4)
def test_boot_invalid_ephemeral_data_format(self):
cmd = 'boot --flavor 1 --image 1 --ephemeral 1 some-server'
cmd = ('boot --flavor 1 --image %s --ephemeral 1 some-server' %
FAKE_UUID_1)
self.assertRaises(argparse.ArgumentTypeError, self.run_command, cmd)
def test_flavor_list(self):
@ -949,19 +970,22 @@ class ShellTest(utils.TestCase):
{'removeTenantAccess': {'tenant': 'proj2'}})
def test_image_show(self):
_, err = self.run_command('image-show 1')
_, err = self.run_command('image-show %s' % FAKE_UUID_1)
self.assertIn('Command image-show is deprecated', err)
self.assert_called('GET', '/images/1')
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1)
def test_image_meta_set(self):
_, err = self.run_command('image-meta 1 set test_key=test_value')
_, err = self.run_command('image-meta %s set test_key=test_value' %
FAKE_UUID_1)
self.assertIn('Command image-meta is deprecated', err)
self.assert_called('POST', '/images/1/metadata',
self.assert_called('POST', '/images/%s/metadata' % FAKE_UUID_1,
{'metadata': {'test_key': 'test_value'}})
def test_image_meta_del(self):
self.run_command('image-meta 1 delete test_key=test_value')
self.assert_called('DELETE', '/images/1/metadata/test_key')
self.run_command('image-meta %s delete test_key=test_value' %
FAKE_UUID_1)
self.assert_called('DELETE', '/images/%s/metadata/test_key' %
FAKE_UUID_1)
@mock.patch('sys.stdout', six.StringIO())
@mock.patch('sys.stderr', six.StringIO())
@ -1009,7 +1033,8 @@ class ShellTest(utils.TestCase):
)
self.assertEqual(1, poll_method.call_count)
poll_method.assert_has_calls(
[mock.call(self.shell.cs.images.get, '456', 'snapshotting',
[mock.call(self.shell.cs.glance.find_image,
fakes.FAKE_IMAGE_UUID_SNAPSHOT, 'snapshotting',
['active'])])
def test_create_image_with_poll_to_check_image_state_deleted(self):
@ -1018,15 +1043,15 @@ class ShellTest(utils.TestCase):
'image-create sample-server mysnapshot_deleted --poll')
def test_image_delete(self):
_, err = self.run_command('image-delete 1')
_, err = self.run_command('image-delete %s' % FAKE_UUID_1)
self.assertIn('Command image-delete is deprecated', err)
self.assert_called('DELETE', '/images/1')
self.assert_called('DELETE', '/images/%s' % FAKE_UUID_1)
def test_image_delete_multiple(self):
self.run_command('image-delete %s %s' % (FAKE_UUID_1, FAKE_UUID_2))
self.assert_called('GET', '/images/' + FAKE_UUID_1, pos=0)
self.assert_called('GET', '/v2/images/' + FAKE_UUID_1, pos=0)
self.assert_called('DELETE', '/images/' + FAKE_UUID_1, pos=1)
self.assert_called('GET', '/images/' + FAKE_UUID_2, pos=2)
self.assert_called('GET', '/v2/images/' + FAKE_UUID_2, pos=2)
self.assert_called('DELETE', '/images/' + FAKE_UUID_2, pos=3)
def test_list(self):
@ -1042,8 +1067,8 @@ class ShellTest(utils.TestCase):
self.assert_called('GET', '/servers/detail?deleted=True')
def test_list_with_images(self):
self.run_command('list --image 1')
self.assert_called('GET', '/servers/detail?image=1')
self.run_command('list --image %s' % FAKE_UUID_1)
self.assert_called('GET', '/servers/detail?image=%s' % FAKE_UUID_1)
def test_list_with_flavors(self):
self.run_command('list --flavor 1')
@ -1180,11 +1205,11 @@ class ShellTest(utils.TestCase):
output, _ = self.run_command('rebuild sample-server %s' % FAKE_UUID_1)
self.assert_called('GET', '/servers?name=sample-server', pos=0)
self.assert_called('GET', '/servers/1234', pos=1)
self.assert_called('GET', '/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': FAKE_UUID_1}}, pos=3)
self.assert_called('GET', '/flavors/1', pos=4)
self.assert_called('GET', '/images/%s' % FAKE_UUID_2, pos=5)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5)
self.assertIn('adminPass', output)
def test_rebuild_password(self):
@ -1193,12 +1218,12 @@ class ShellTest(utils.TestCase):
% FAKE_UUID_1)
self.assert_called('GET', '/servers?name=sample-server', pos=0)
self.assert_called('GET', '/servers/1234', pos=1)
self.assert_called('GET', '/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': FAKE_UUID_1,
'adminPass': 'asdf'}}, pos=3)
self.assert_called('GET', '/flavors/1', pos=4)
self.assert_called('GET', '/images/%s' % FAKE_UUID_2, pos=5)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5)
self.assertIn('adminPass', output)
def test_rebuild_preserve_ephemeral(self):
@ -1206,25 +1231,25 @@ class ShellTest(utils.TestCase):
% FAKE_UUID_1)
self.assert_called('GET', '/servers?name=sample-server', pos=0)
self.assert_called('GET', '/servers/1234', pos=1)
self.assert_called('GET', '/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': FAKE_UUID_1,
'preserve_ephemeral': True}}, pos=3)
self.assert_called('GET', '/flavors/1', pos=4)
self.assert_called('GET', '/images/%s' % FAKE_UUID_2, pos=5)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5)
def test_rebuild_name_meta(self):
self.run_command('rebuild sample-server %s --name asdf --meta '
'foo=bar' % FAKE_UUID_1)
self.assert_called('GET', '/servers?name=sample-server', pos=0)
self.assert_called('GET', '/servers/1234', pos=1)
self.assert_called('GET', '/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
self.assert_called('POST', '/servers/1234/action',
{'rebuild': {'imageRef': FAKE_UUID_1,
'name': 'asdf',
'metadata': {'foo': 'bar'}}}, pos=3)
self.assert_called('GET', '/flavors/1', pos=4)
self.assert_called('GET', '/images/%s' % FAKE_UUID_2, pos=5)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=5)
def test_start(self):
self.run_command('start sample-server')
@ -1282,9 +1307,9 @@ class ShellTest(utils.TestCase):
{'rescue': {'adminPass': 'asdf'}})
def test_rescue_image(self):
self.run_command('rescue sample-server --image 1')
self.run_command('rescue sample-server --image %s' % FAKE_UUID_1)
self.assert_called('POST', '/servers/1234/action',
{'rescue': {'rescue_image_ref': 1}})
{'rescue': {'rescue_image_ref': FAKE_UUID_1}})
def test_unrescue(self):
self.run_command('unrescue sample-server')
@ -1356,7 +1381,7 @@ class ShellTest(utils.TestCase):
self.assert_called('GET', '/servers?name=1234', pos=1)
self.assert_called('GET', '/servers/1234', pos=2)
self.assert_called('GET', '/flavors/1', pos=3)
self.assert_called('GET', '/images/%s' % FAKE_UUID_2, pos=4)
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4)
def test_show_no_image(self):
self.run_command('show 9012')
@ -1369,13 +1394,13 @@ class ShellTest(utils.TestCase):
def test_show_unavailable_image_and_flavor(self):
output, _ = self.run_command('show 9013')
self.assert_called('GET', '/servers/9013', pos=-8)
self.assert_called('GET', '/servers/9013', pos=-6)
self.assert_called('GET',
'/flavors/80645cf4-6ad3-410a-bbc8-6f3e1e291f51',
pos=-7)
pos=-5)
self.assert_called('GET',
'/images/3e861307-73a6-4d1f-8d68-f68b03223032',
pos=-3)
'/v2/images/3e861307-73a6-4d1f-8d68-f68b03223032',
pos=-1)
self.assertIn('Image not found', output)
self.assertIn('Flavor not found', output)

View File

@ -131,6 +131,7 @@ class Client(object):
self.flavors = flavors.FlavorManager(self)
self.flavor_access = flavor_access.FlavorAccessManager(self)
self.images = images.ImageManager(self)
self.glance = images.GlanceManager(self)
self.limits = limits.LimitsManager(self)
self.servers = servers.ServerManager(self)
self.versions = versions.VersionManager(self)

View File

@ -18,9 +18,12 @@ DEPRECATED: Image interface.
import warnings
from oslo_utils import uuidutils
from six.moves.urllib import parse
from novaclient import base
from novaclient import exceptions
from novaclient.i18n import _
class Image(base.Resource):
@ -42,6 +45,46 @@ class Image(base.Resource):
return self.manager.delete(self)
class GlanceManager(base.Manager):
"""Use glance directly from service catalog.
This is used to do name to id lookups for images. Do not use it
for anything else besides that. You have been warned.
"""
resource_class = Image
def find_image(self, name_or_id):
"""Find an image by name or id (user provided input)."""
with self.alternate_service_type(
'image', allowed_types=('image',)):
# glance catalog entries are the unversioned endpoint, so
# we need to jam a version bit in here.
if uuidutils.is_uuid_like(name_or_id):
# if it's a uuid, then just go collect the info and be
# done with it.
return self._get('/v2/images/%s' % name_or_id, None)
else:
matches = self._list('/v2/images?name=%s' % name_or_id,
"images")
num_matches = len(matches)
if num_matches == 0:
msg = "No %s matching %s." % (
self.resource_class.__name__, name_or_id)
raise exceptions.NotFound(404, msg)
elif num_matches > 1:
msg = (_("Multiple %(class)s matches found for "
"'%(name)s', use an ID to be more specific.") %
{'class': self.resource_class.__name__.lower(),
'name': name_or_id})
raise exceptions.NoUniqueMatch(msg)
else:
matches[0].append_request_ids(matches.request_ids)
return matches[0]
class ImageManager(base.ManagerWithFind):
"""
DEPRECATED: Manage :class:`Image` resources.

View File

@ -1274,7 +1274,7 @@ def _print_image(image):
info = image._info.copy()
# ignore links, we don't need to present those
info.pop('links')
info.pop('links', None)
# try to replace a server entity to just an id
server = info.pop('server', None)
@ -1322,7 +1322,10 @@ def do_image_delete(cs, args):
emit_image_deprecation_warning('image-delete')
for image in args.image:
try:
_find_image(cs, image).delete()
# _find_image is using the GlanceManager which doesn't implement
# the delete() method so use the ImagesManager for that.
image = _find_image(cs, image)
cs.images.delete(image)
except Exception as e:
print(_("Delete for image %(image)s failed: %(e)s") %
{'image': image, 'e': e})
@ -2080,7 +2083,7 @@ def do_image_create(cs, args):
image_uuid = cs.servers.create_image(server, args.name, meta)
if args.poll:
_poll_for_status(cs.images.get, image_uuid, 'snapshotting',
_poll_for_status(cs.glance.find_image, image_uuid, 'snapshotting',
['active'])
# NOTE(sirp): A race-condition exists between when the image finishes
@ -2099,7 +2102,7 @@ def do_image_create(cs, args):
show_progress=False, silent=True)
if args.show:
_print_image(cs.images.get(image_uuid))
_print_image(_find_image(cs, image_uuid))
@utils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@ -2253,7 +2256,10 @@ def _find_server(cs, server, raise_if_notfound=True, **find_args):
def _find_image(cs, image):
"""Get an image by name or ID."""
return utils.find_resource(cs.images, image)
try:
return cs.glance.find_image(image)
except (exceptions.NotFound, exceptions.NoUniqueMatch) as e:
raise exceptions.CommandError(six.text_type(e))
def _find_flavor(cs, flavor):

View File

@ -0,0 +1,10 @@
---
upgrade:
- |
The 2.36 microversion deprecated the image proxy API. As such, CLI calls
now directly call the image service to get image details, for example,
as a convenience to boot a server with an image name rather than the image
id. To do this the following is assumed:
#. There is an **image** entry in the service catalog.
#. The image v2 API is available.