432 lines
19 KiB
Python
432 lines
19 KiB
Python
# Copyright 2010 OpenStack Foundation
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Tests of the new image services, both as a service layer,
|
|
and as a WSGI layer
|
|
"""
|
|
|
|
import copy
|
|
from urllib import parse as urlparse
|
|
|
|
import mock
|
|
import webob
|
|
|
|
from nova.api.openstack.compute import images as images_v21
|
|
from nova.api.openstack.compute.views import images as images_view
|
|
from nova import exception
|
|
from nova.image import glance
|
|
from nova import test
|
|
from nova.tests.unit.api.openstack import fakes
|
|
from nova.tests.unit import image_fixtures
|
|
from nova.tests.unit import matchers
|
|
|
|
NS = "{http://docs.openstack.org/compute/api/v1.1}"
|
|
ATOMNS = "{http://www.w3.org/2005/Atom}"
|
|
NOW_API_FORMAT = "2010-10-11T10:30:22Z"
|
|
IMAGE_FIXTURES = image_fixtures.get_image_fixtures()
|
|
|
|
|
|
class ImagesControllerTestV21(test.NoDBTestCase):
|
|
"""Test of the OpenStack API /images application controller w/Glance.
|
|
"""
|
|
image_controller_class = images_v21.ImagesController
|
|
url_base = '/v2/%s' % fakes.FAKE_PROJECT_ID
|
|
bookmark_base = '/%s' % fakes.FAKE_PROJECT_ID
|
|
http_request = fakes.HTTPRequestV21
|
|
|
|
def setUp(self):
|
|
"""Run before each test."""
|
|
super(ImagesControllerTestV21, self).setUp()
|
|
self.flags(api_servers=['http://localhost:9292'], group='glance')
|
|
fakes.stub_out_networking(self)
|
|
fakes.stub_out_key_pair_funcs(self)
|
|
fakes.stub_out_compute_api_snapshot(self)
|
|
fakes.stub_out_compute_api_backup(self)
|
|
|
|
self.controller = self.image_controller_class()
|
|
self.url_prefix = "http://localhost%s/images" % self.url_base
|
|
self.bookmark_prefix = "http://localhost%s/images" % self.bookmark_base
|
|
self.uuid = 'fa95aaf5-ab3b-4cd8-88c0-2be7dd051aaf'
|
|
self.server_uuid = "aa640691-d1a7-4a67-9d3c-d35ee6b3cc74"
|
|
self.server_href = (
|
|
"http://localhost%s/servers/%s" % (self.url_base,
|
|
self.server_uuid))
|
|
self.server_bookmark = (
|
|
"http://localhost%s/servers/%s" % (self.bookmark_base,
|
|
self.server_uuid))
|
|
self.alternate = "%s/images/%s"
|
|
|
|
self.expected_image_123 = {
|
|
"image": {'id': '123',
|
|
'name': 'public image',
|
|
'metadata': {'key1': 'value1'},
|
|
'updated': NOW_API_FORMAT,
|
|
'created': NOW_API_FORMAT,
|
|
'status': 'ACTIVE',
|
|
'minDisk': 10,
|
|
'progress': 100,
|
|
'minRam': 128,
|
|
'OS-EXT-IMG-SIZE:size': 25165824,
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "%s/123" % self.url_prefix
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href":
|
|
"%s/123" % self.bookmark_prefix
|
|
},
|
|
{
|
|
"rel": "alternate",
|
|
"type": "application/vnd.openstack.image",
|
|
"href": self.alternate %
|
|
(glance.generate_glance_url('ctx'),
|
|
123),
|
|
}],
|
|
},
|
|
}
|
|
|
|
self.expected_image_124 = {
|
|
"image": {'id': '124',
|
|
'name': 'queued snapshot',
|
|
'metadata': {
|
|
u'instance_uuid': self.server_uuid,
|
|
u'user_id': u'fake',
|
|
},
|
|
'updated': NOW_API_FORMAT,
|
|
'created': NOW_API_FORMAT,
|
|
'status': 'SAVING',
|
|
'progress': 25,
|
|
'minDisk': 0,
|
|
'minRam': 0,
|
|
'OS-EXT-IMG-SIZE:size': 25165824,
|
|
'server': {
|
|
'id': self.server_uuid,
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": self.server_href,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.server_bookmark,
|
|
}],
|
|
},
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "%s/124" % self.url_prefix
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href":
|
|
"%s/124" % self.bookmark_prefix
|
|
},
|
|
{
|
|
"rel": "alternate",
|
|
"type":
|
|
"application/vnd.openstack.image",
|
|
"href": self.alternate %
|
|
(glance.generate_glance_url('ctx'),
|
|
124),
|
|
}],
|
|
},
|
|
}
|
|
|
|
@mock.patch('nova.image.glance.API.get', return_value=IMAGE_FIXTURES[0])
|
|
def test_get_image(self, get_mocked):
|
|
request = self.http_request.blank(self.url_base + 'images/123')
|
|
actual_image = self.controller.show(request, '123')
|
|
self.assertThat(actual_image,
|
|
matchers.DictMatches(self.expected_image_123))
|
|
get_mocked.assert_called_once_with(mock.ANY, '123')
|
|
|
|
@mock.patch('nova.image.glance.API.get', return_value=IMAGE_FIXTURES[1])
|
|
def test_get_image_with_custom_prefix(self, _get_mocked):
|
|
self.flags(compute_link_prefix='https://zoo.com:42',
|
|
glance_link_prefix='http://circus.com:34',
|
|
group='api')
|
|
fake_req = self.http_request.blank(self.url_base + 'images/124')
|
|
actual_image = self.controller.show(fake_req, '124')
|
|
|
|
expected_image = self.expected_image_124
|
|
expected_image["image"]["links"][0]["href"] = (
|
|
"https://zoo.com:42%s/images/124" % self.url_base)
|
|
expected_image["image"]["links"][1]["href"] = (
|
|
"https://zoo.com:42%s/images/124" % self.bookmark_base)
|
|
expected_image["image"]["links"][2]["href"] = (
|
|
"http://circus.com:34/images/124")
|
|
expected_image["image"]["server"]["links"][0]["href"] = (
|
|
"https://zoo.com:42%s/servers/%s" % (self.url_base,
|
|
self.server_uuid))
|
|
expected_image["image"]["server"]["links"][1]["href"] = (
|
|
"https://zoo.com:42%s/servers/%s" % (self.bookmark_base,
|
|
self.server_uuid))
|
|
|
|
self.assertThat(actual_image, matchers.DictMatches(expected_image))
|
|
|
|
@mock.patch('nova.image.glance.API.get',
|
|
side_effect=exception.ImageNotFound(image_id=''))
|
|
def test_get_image_404(self, _get_mocked):
|
|
fake_req = self.http_request.blank(self.url_base + 'images/unknown')
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.controller.show, fake_req, 'unknown')
|
|
|
|
@mock.patch('nova.image.glance.API.get_all', return_value=IMAGE_FIXTURES)
|
|
def test_get_image_details(self, get_all_mocked):
|
|
request = self.http_request.blank(self.url_base + 'images/detail')
|
|
response = self.controller.detail(request)
|
|
|
|
get_all_mocked.assert_called_once_with(mock.ANY, filters={})
|
|
response_list = response["images"]
|
|
|
|
image_125 = copy.deepcopy(self.expected_image_124["image"])
|
|
image_125['id'] = '125'
|
|
image_125['name'] = 'saving snapshot'
|
|
image_125['progress'] = 50
|
|
image_125["links"][0]["href"] = "%s/125" % self.url_prefix
|
|
image_125["links"][1]["href"] = "%s/125" % self.bookmark_prefix
|
|
image_125["links"][2]["href"] = (
|
|
"%s/images/125" % glance.generate_glance_url('ctx'))
|
|
|
|
image_126 = copy.deepcopy(self.expected_image_124["image"])
|
|
image_126['id'] = '126'
|
|
image_126['name'] = 'active snapshot'
|
|
image_126['status'] = 'ACTIVE'
|
|
image_126['progress'] = 100
|
|
image_126["links"][0]["href"] = "%s/126" % self.url_prefix
|
|
image_126["links"][1]["href"] = "%s/126" % self.bookmark_prefix
|
|
image_126["links"][2]["href"] = (
|
|
"%s/images/126" % glance.generate_glance_url('ctx'))
|
|
|
|
image_127 = copy.deepcopy(self.expected_image_124["image"])
|
|
image_127['id'] = '127'
|
|
image_127['name'] = 'killed snapshot'
|
|
image_127['status'] = 'ERROR'
|
|
image_127['progress'] = 0
|
|
image_127["links"][0]["href"] = "%s/127" % self.url_prefix
|
|
image_127["links"][1]["href"] = "%s/127" % self.bookmark_prefix
|
|
image_127["links"][2]["href"] = (
|
|
"%s/images/127" % glance.generate_glance_url('ctx'))
|
|
|
|
image_128 = copy.deepcopy(self.expected_image_124["image"])
|
|
image_128['id'] = '128'
|
|
image_128['name'] = 'deleted snapshot'
|
|
image_128['status'] = 'DELETED'
|
|
image_128['progress'] = 0
|
|
image_128["links"][0]["href"] = "%s/128" % self.url_prefix
|
|
image_128["links"][1]["href"] = "%s/128" % self.bookmark_prefix
|
|
image_128["links"][2]["href"] = (
|
|
"%s/images/128" % glance.generate_glance_url('ctx'))
|
|
|
|
image_129 = copy.deepcopy(self.expected_image_124["image"])
|
|
image_129['id'] = '129'
|
|
image_129['name'] = 'pending_delete snapshot'
|
|
image_129['status'] = 'DELETED'
|
|
image_129['progress'] = 0
|
|
image_129["links"][0]["href"] = "%s/129" % self.url_prefix
|
|
image_129["links"][1]["href"] = "%s/129" % self.bookmark_prefix
|
|
image_129["links"][2]["href"] = (
|
|
"%s/images/129" % glance.generate_glance_url('ctx'))
|
|
|
|
image_130 = copy.deepcopy(self.expected_image_123["image"])
|
|
image_130['id'] = '130'
|
|
image_130['name'] = None
|
|
image_130['metadata'] = {}
|
|
image_130['minDisk'] = 0
|
|
image_130['minRam'] = 0
|
|
image_130["links"][0]["href"] = "%s/130" % self.url_prefix
|
|
image_130["links"][1]["href"] = "%s/130" % self.bookmark_prefix
|
|
image_130["links"][2]["href"] = (
|
|
"%s/images/130" % glance.generate_glance_url('ctx'))
|
|
|
|
image_131 = copy.deepcopy(self.expected_image_123["image"])
|
|
image_131['id'] = '131'
|
|
image_131['name'] = None
|
|
image_131['metadata'] = {}
|
|
image_131['minDisk'] = 0
|
|
image_131['minRam'] = 0
|
|
image_131["links"][0]["href"] = "%s/131" % self.url_prefix
|
|
image_131["links"][1]["href"] = "%s/131" % self.bookmark_prefix
|
|
image_131["links"][2]["href"] = (
|
|
"%s/images/131" % glance.generate_glance_url('ctx'))
|
|
|
|
expected = [self.expected_image_123["image"],
|
|
self.expected_image_124["image"],
|
|
image_125, image_126, image_127,
|
|
image_128, image_129, image_130,
|
|
image_131]
|
|
|
|
self.assertThat(expected, matchers.DictListMatches(response_list))
|
|
|
|
@mock.patch('nova.image.glance.API.get_all')
|
|
def test_get_image_details_with_limit(self, get_all_mocked):
|
|
request = self.http_request.blank(self.url_base +
|
|
'images/detail?limit=2')
|
|
self.controller.detail(request)
|
|
get_all_mocked.assert_called_once_with(mock.ANY, limit=2, filters={})
|
|
|
|
@mock.patch('nova.image.glance.API.get_all')
|
|
def test_get_image_details_with_limit_and_page_size(self, get_all_mocked):
|
|
request = self.http_request.blank(
|
|
self.url_base + 'images/detail?limit=2&page_size=1')
|
|
self.controller.detail(request)
|
|
get_all_mocked.assert_called_once_with(mock.ANY, limit=2, filters={},
|
|
page_size=1)
|
|
|
|
@mock.patch('nova.image.glance.API.get_all')
|
|
def _detail_request(self, filters, request, get_all_mocked):
|
|
self.controller.detail(request)
|
|
get_all_mocked.assert_called_once_with(mock.ANY, filters=filters)
|
|
|
|
def test_image_detail_filter_with_name(self):
|
|
filters = {'name': 'testname'}
|
|
request = self.http_request.blank(self.url_base + 'images/detail'
|
|
'?name=testname')
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_with_status(self):
|
|
filters = {'status': 'active'}
|
|
request = self.http_request.blank(self.url_base + 'images/detail'
|
|
'?status=ACTIVE')
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_with_property(self):
|
|
filters = {'property-test': '3'}
|
|
request = self.http_request.blank(self.url_base + 'images/detail'
|
|
'?property-test=3')
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_server_href(self):
|
|
filters = {'property-instance_uuid': self.uuid}
|
|
request = self.http_request.blank(
|
|
self.url_base + 'images/detail?server=' + self.uuid)
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_server_uuid(self):
|
|
filters = {'property-instance_uuid': self.uuid}
|
|
request = self.http_request.blank(
|
|
self.url_base + 'images/detail?server=' + self.uuid)
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_changes_since(self):
|
|
filters = {'changes-since': '2011-01-24T17:08Z'}
|
|
request = self.http_request.blank(self.url_base + 'images/detail'
|
|
'?changes-since=2011-01-24T17:08Z')
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_with_type(self):
|
|
filters = {'property-image_type': 'BASE'}
|
|
request = self.http_request.blank(
|
|
self.url_base + 'images/detail?type=BASE')
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_filter_not_supported(self):
|
|
filters = {'status': 'active'}
|
|
request = self.http_request.blank(
|
|
self.url_base + 'images/detail?status='
|
|
'ACTIVE&UNSUPPORTEDFILTER=testname')
|
|
self._detail_request(filters, request)
|
|
|
|
def test_image_detail_no_filters(self):
|
|
filters = {}
|
|
request = self.http_request.blank(self.url_base + 'images/detail')
|
|
self._detail_request(filters, request)
|
|
|
|
@mock.patch('nova.image.glance.API.get_all', side_effect=exception.Invalid)
|
|
def test_image_detail_invalid_marker(self, _get_all_mocked):
|
|
request = self.http_request.blank(self.url_base + '?marker=invalid')
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.detail,
|
|
request)
|
|
|
|
def test_generate_alternate_link(self):
|
|
view = images_view.ViewBuilder()
|
|
request = self.http_request.blank(self.url_base + 'images/1')
|
|
generated_url = view._get_alternate_link(request, 1)
|
|
actual_url = "%s/images/1" % glance.generate_glance_url('ctx')
|
|
self.assertEqual(generated_url, actual_url)
|
|
|
|
def _check_response(self, controller_method, response, expected_code):
|
|
self.assertEqual(expected_code, controller_method.wsgi_code)
|
|
|
|
@mock.patch('nova.image.glance.API.delete')
|
|
def test_delete_image(self, delete_mocked):
|
|
request = self.http_request.blank(self.url_base + 'images/124')
|
|
request.method = 'DELETE'
|
|
delete_method = self.controller.delete
|
|
response = delete_method(request, '124')
|
|
self._check_response(delete_method, response, 204)
|
|
delete_mocked.assert_called_once_with(mock.ANY, '124')
|
|
|
|
@mock.patch('nova.image.glance.API.delete',
|
|
side_effect=exception.ImageNotAuthorized(image_id='123'))
|
|
def test_delete_deleted_image(self, _delete_mocked):
|
|
# If you try to delete a deleted image, you get back 403 Forbidden.
|
|
request = self.http_request.blank(self.url_base + 'images/123')
|
|
request.method = 'DELETE'
|
|
self.assertRaises(webob.exc.HTTPForbidden, self.controller.delete,
|
|
request, '123')
|
|
|
|
@mock.patch('nova.image.glance.API.delete',
|
|
side_effect=exception.ImageNotFound(image_id='123'))
|
|
def test_delete_image_not_found(self, _delete_mocked):
|
|
request = self.http_request.blank(self.url_base + 'images/300')
|
|
request.method = 'DELETE'
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.controller.delete, request, '300')
|
|
|
|
@mock.patch('nova.image.glance.API.get_all',
|
|
return_value=[IMAGE_FIXTURES[0]])
|
|
def test_get_image_next_link(self, get_all_mocked):
|
|
request = self.http_request.blank(
|
|
self.url_base + 'imagesl?limit=1')
|
|
response = self.controller.index(request)
|
|
response_links = response['images_links']
|
|
href_parts = urlparse.urlparse(response_links[0]['href'])
|
|
self.assertEqual(self.url_base + '/images', href_parts.path)
|
|
params = urlparse.parse_qs(href_parts.query)
|
|
self.assertThat({'limit': ['1'], 'marker': [IMAGE_FIXTURES[0]['id']]},
|
|
matchers.DictMatches(params))
|
|
|
|
@mock.patch('nova.image.glance.API.get_all',
|
|
return_value=[IMAGE_FIXTURES[0]])
|
|
def test_get_image_details_next_link(self, get_all_mocked):
|
|
request = self.http_request.blank(
|
|
self.url_base + 'images/detail?limit=1')
|
|
response = self.controller.detail(request)
|
|
response_links = response['images_links']
|
|
href_parts = urlparse.urlparse(response_links[0]['href'])
|
|
self.assertEqual(self.url_base + '/images/detail', href_parts.path)
|
|
params = urlparse.parse_qs(href_parts.query)
|
|
self.assertThat({'limit': ['1'], 'marker': [IMAGE_FIXTURES[0]['id']]},
|
|
matchers.DictMatches(params))
|
|
|
|
|
|
class ImagesControllerDeprecationTest(test.NoDBTestCase):
|
|
|
|
def setUp(self):
|
|
super(ImagesControllerDeprecationTest, self).setUp()
|
|
self.controller = images_v21.ImagesController()
|
|
self.req = fakes.HTTPRequest.blank('', version='2.36')
|
|
|
|
def test_not_found_for_all_images_api(self):
|
|
self.assertRaises(exception.VersionNotFoundForAPIMethod,
|
|
self.controller.show, self.req, fakes.FAKE_UUID)
|
|
self.assertRaises(exception.VersionNotFoundForAPIMethod,
|
|
self.controller.delete, self.req, fakes.FAKE_UUID)
|
|
self.assertRaises(exception.VersionNotFoundForAPIMethod,
|
|
self.controller.index, self.req)
|
|
self.assertRaises(exception.VersionNotFoundForAPIMethod,
|
|
self.controller.detail, self.req)
|