1108 lines
38 KiB
Python
1108 lines
38 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2010 OpenStack LLC.
|
|
# 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
|
|
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import xml.dom.minidom as minidom
|
|
|
|
import mox
|
|
import stubout
|
|
import webob
|
|
|
|
from glance import client as glance_client
|
|
from nova import context
|
|
from nova import exception
|
|
from nova import flags
|
|
from nova import test
|
|
from nova import utils
|
|
import nova.api.openstack
|
|
from nova.api.openstack import images
|
|
from nova.tests.api.openstack import fakes
|
|
|
|
|
|
FLAGS = flags.FLAGS
|
|
|
|
|
|
class _BaseImageServiceTests(test.TestCase):
|
|
"""Tasks to test for all image services"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(_BaseImageServiceTests, self).__init__(*args, **kwargs)
|
|
self.service = None
|
|
self.context = None
|
|
|
|
def test_create(self):
|
|
fixture = self._make_fixture('test image')
|
|
num_images = len(self.service.index(self.context))
|
|
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
|
|
self.assertNotEquals(None, image_id)
|
|
self.assertEquals(num_images + 1,
|
|
len(self.service.index(self.context)))
|
|
|
|
def test_create_and_show_non_existing_image(self):
|
|
fixture = self._make_fixture('test image')
|
|
num_images = len(self.service.index(self.context))
|
|
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
|
|
self.assertNotEquals(None, image_id)
|
|
self.assertRaises(exception.NotFound,
|
|
self.service.show,
|
|
self.context,
|
|
'bad image id')
|
|
|
|
def test_create_and_show_non_existing_image_by_name(self):
|
|
fixture = self._make_fixture('test image')
|
|
num_images = len(self.service.index(self.context))
|
|
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
|
|
self.assertNotEquals(None, image_id)
|
|
self.assertRaises(exception.ImageNotFound,
|
|
self.service.show_by_name,
|
|
self.context,
|
|
'bad image id')
|
|
|
|
def test_update(self):
|
|
fixture = self._make_fixture('test image')
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
fixture['status'] = 'in progress'
|
|
|
|
self.service.update(self.context, image_id, fixture)
|
|
|
|
new_image_data = self.service.show(self.context, image_id)
|
|
self.assertEquals('in progress', new_image_data['status'])
|
|
|
|
def test_delete(self):
|
|
fixture1 = self._make_fixture('test image 1')
|
|
fixture2 = self._make_fixture('test image 2')
|
|
fixtures = [fixture1, fixture2]
|
|
|
|
num_images = len(self.service.index(self.context))
|
|
self.assertEquals(0, num_images, str(self.service.index(self.context)))
|
|
|
|
ids = []
|
|
for fixture in fixtures:
|
|
new_id = self.service.create(self.context, fixture)['id']
|
|
ids.append(new_id)
|
|
|
|
num_images = len(self.service.index(self.context))
|
|
self.assertEquals(2, num_images, str(self.service.index(self.context)))
|
|
|
|
self.service.delete(self.context, ids[0])
|
|
|
|
num_images = len(self.service.index(self.context))
|
|
self.assertEquals(1, num_images)
|
|
|
|
def test_index(self):
|
|
fixture = self._make_fixture('test image')
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
image_metas = self.service.index(self.context)
|
|
expected = [{'id': 'DONTCARE', 'name': 'test image'}]
|
|
self.assertDictListMatch(image_metas, expected)
|
|
|
|
@staticmethod
|
|
def _make_fixture(name):
|
|
fixture = {'name': name,
|
|
'updated': None,
|
|
'created': None,
|
|
'status': None,
|
|
'is_public': True}
|
|
return fixture
|
|
|
|
|
|
class GlanceImageServiceTest(_BaseImageServiceTests):
|
|
|
|
"""Tests the Glance image service, in particular that metadata translation
|
|
works properly.
|
|
|
|
At a high level, the translations involved are:
|
|
|
|
1. Glance -> ImageService - This is needed so we can support
|
|
multple ImageServices (Glance, Local, etc)
|
|
|
|
2. ImageService -> API - This is needed so we can support multple
|
|
APIs (OpenStack, EC2)
|
|
"""
|
|
def setUp(self):
|
|
super(GlanceImageServiceTest, self).setUp()
|
|
self.stubs = stubout.StubOutForTesting()
|
|
fakes.stub_out_glance(self.stubs)
|
|
fakes.stub_out_compute_api_snapshot(self.stubs)
|
|
service_class = 'nova.image.glance.GlanceImageService'
|
|
self.service = utils.import_object(service_class)
|
|
self.context = context.RequestContext(1, None)
|
|
self.service.delete_all()
|
|
self.sent_to_glance = {}
|
|
fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
|
|
|
|
def tearDown(self):
|
|
self.stubs.UnsetAll()
|
|
super(GlanceImageServiceTest, self).tearDown()
|
|
|
|
def test_create_with_instance_id(self):
|
|
"""Ensure instance_id is persisted as an image-property"""
|
|
fixture = {'name': 'test image',
|
|
'is_public': False,
|
|
'properties': {'instance_id': '42', 'user_id': '1'}}
|
|
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
expected = fixture
|
|
self.assertDictMatch(self.sent_to_glance['metadata'], expected)
|
|
|
|
image_meta = self.service.show(self.context, image_id)
|
|
expected = {'id': image_id,
|
|
'name': 'test image',
|
|
'is_public': False,
|
|
'properties': {'instance_id': '42', 'user_id': '1'}}
|
|
self.assertDictMatch(image_meta, expected)
|
|
|
|
image_metas = self.service.detail(self.context)
|
|
self.assertDictMatch(image_metas[0], expected)
|
|
|
|
def test_create_without_instance_id(self):
|
|
"""
|
|
Ensure we can create an image without having to specify an
|
|
instance_id. Public images are an example of an image not tied to an
|
|
instance.
|
|
"""
|
|
fixture = {'name': 'test image'}
|
|
image_id = self.service.create(self.context, fixture)['id']
|
|
|
|
expected = {'name': 'test image', 'properties': {}}
|
|
self.assertDictMatch(self.sent_to_glance['metadata'], expected)
|
|
|
|
def test_index_default_limit(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.index(self.context)
|
|
i = 0
|
|
for meta in image_metas:
|
|
expected = {'id': 'DONTCARE',
|
|
'name': 'TestImage %d' % (i)}
|
|
self.assertDictMatch(meta, expected)
|
|
i = i + 1
|
|
|
|
def test_index_marker(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.index(self.context, marker=ids[1])
|
|
self.assertEquals(len(image_metas), 8)
|
|
i = 2
|
|
for meta in image_metas:
|
|
expected = {'id': 'DONTCARE',
|
|
'name': 'TestImage %d' % (i)}
|
|
self.assertDictMatch(meta, expected)
|
|
i = i + 1
|
|
|
|
def test_index_limit(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.index(self.context, limit=3)
|
|
self.assertEquals(len(image_metas), 3)
|
|
|
|
def test_index_marker_and_limit(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.index(self.context, marker=ids[3], limit=1)
|
|
self.assertEquals(len(image_metas), 1)
|
|
i = 4
|
|
for meta in image_metas:
|
|
expected = {'id': 'DONTCARE',
|
|
'name': 'TestImage %d' % (i)}
|
|
self.assertDictMatch(meta, expected)
|
|
i = i + 1
|
|
|
|
def test_detail_marker(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.detail(self.context, marker=ids[1])
|
|
self.assertEquals(len(image_metas), 8)
|
|
i = 2
|
|
for meta in image_metas:
|
|
expected = {
|
|
'id': 'DONTCARE',
|
|
'status': None,
|
|
'is_public': True,
|
|
'name': 'TestImage %d' % (i),
|
|
'properties': {
|
|
'updated': None,
|
|
'created': None,
|
|
},
|
|
}
|
|
|
|
self.assertDictMatch(meta, expected)
|
|
i = i + 1
|
|
|
|
def test_detail_limit(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.detail(self.context, limit=3)
|
|
self.assertEquals(len(image_metas), 3)
|
|
|
|
def test_detail_marker_and_limit(self):
|
|
fixtures = []
|
|
ids = []
|
|
for i in range(10):
|
|
fixture = self._make_fixture('TestImage %d' % (i))
|
|
fixtures.append(fixture)
|
|
ids.append(self.service.create(self.context, fixture)['id'])
|
|
|
|
image_metas = self.service.detail(self.context, marker=ids[3], limit=3)
|
|
self.assertEquals(len(image_metas), 3)
|
|
i = 4
|
|
for meta in image_metas:
|
|
expected = {
|
|
'id': 'DONTCARE',
|
|
'status': None,
|
|
'is_public': True,
|
|
'name': 'TestImage %d' % (i),
|
|
'properties': {
|
|
'updated': None, 'created': None},
|
|
}
|
|
self.assertDictMatch(meta, expected)
|
|
i = i + 1
|
|
|
|
|
|
class ImageControllerWithGlanceServiceTest(test.TestCase):
|
|
"""
|
|
Test of the OpenStack API /images application controller w/Glance.
|
|
"""
|
|
NOW_GLANCE_FORMAT = "2010-10-11T10:30:22"
|
|
NOW_API_FORMAT = "2010-10-11T10:30:22Z"
|
|
|
|
def setUp(self):
|
|
"""Run before each test."""
|
|
super(ImageControllerWithGlanceServiceTest, self).setUp()
|
|
self.orig_image_service = FLAGS.image_service
|
|
FLAGS.image_service = 'nova.image.glance.GlanceImageService'
|
|
self.stubs = stubout.StubOutForTesting()
|
|
fakes.FakeAuthManager.reset_fake_data()
|
|
fakes.FakeAuthDatabase.data = {}
|
|
fakes.stub_out_networking(self.stubs)
|
|
fakes.stub_out_rate_limiting(self.stubs)
|
|
fakes.stub_out_auth(self.stubs)
|
|
fakes.stub_out_key_pair_funcs(self.stubs)
|
|
self.fixtures = self._make_image_fixtures()
|
|
fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures)
|
|
fakes.stub_out_compute_api_snapshot(self.stubs)
|
|
|
|
def tearDown(self):
|
|
"""Run after each test."""
|
|
self.stubs.UnsetAll()
|
|
FLAGS.image_service = self.orig_image_service
|
|
super(ImageControllerWithGlanceServiceTest, self).tearDown()
|
|
|
|
def _applicable_fixture(self, fixture, user_id):
|
|
"""Determine if this fixture is applicable for given user id."""
|
|
is_public = fixture["is_public"]
|
|
try:
|
|
uid = int(fixture["properties"]["user_id"])
|
|
except KeyError:
|
|
uid = None
|
|
return uid == user_id or is_public
|
|
|
|
def test_get_image_index(self):
|
|
request = webob.Request.blank('/v1.0/images')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
response_dict = json.loads(response.body)
|
|
response_list = response_dict["images"]
|
|
|
|
expected = [{'id': 123, 'name': 'public image'},
|
|
{'id': 124, 'name': 'queued backup'},
|
|
{'id': 125, 'name': 'saving backup'},
|
|
{'id': 126, 'name': 'active backup'},
|
|
{'id': 127, 'name': 'killed backup'},
|
|
{'id': 129, 'name': None}]
|
|
|
|
self.assertDictListMatch(response_list, expected)
|
|
|
|
def test_get_image(self):
|
|
request = webob.Request.blank('/v1.0/images/123')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
self.assertEqual(200, response.status_int)
|
|
|
|
actual_image = json.loads(response.body)
|
|
|
|
expected_image = {
|
|
"image": {
|
|
"id": 123,
|
|
"name": "public image",
|
|
"updated": self.NOW_API_FORMAT,
|
|
"created": self.NOW_API_FORMAT,
|
|
"status": "ACTIVE",
|
|
},
|
|
}
|
|
|
|
self.assertEqual(expected_image, actual_image)
|
|
|
|
def test_get_image_v1_1(self):
|
|
request = webob.Request.blank('/v1.1/images/123')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
actual_image = json.loads(response.body)
|
|
|
|
href = "http://localhost/v1.1/images/123"
|
|
|
|
expected_image = {
|
|
"image": {
|
|
"id": 123,
|
|
"name": "public image",
|
|
"updated": self.NOW_API_FORMAT,
|
|
"created": self.NOW_API_FORMAT,
|
|
"status": "ACTIVE",
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": href,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": href,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": href,
|
|
}],
|
|
},
|
|
}
|
|
|
|
self.assertEqual(expected_image, actual_image)
|
|
|
|
def test_get_image_xml(self):
|
|
request = webob.Request.blank('/v1.0/images/123')
|
|
request.accept = "application/xml"
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
actual_image = minidom.parseString(response.body.replace(" ", ""))
|
|
|
|
expected_now = self.NOW_API_FORMAT
|
|
expected_image = minidom.parseString("""
|
|
<image id="123"
|
|
name="public image"
|
|
updated="%(expected_now)s"
|
|
created="%(expected_now)s"
|
|
status="ACTIVE"
|
|
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
|
|
""" % (locals()))
|
|
|
|
self.assertEqual(expected_image.toxml(), actual_image.toxml())
|
|
|
|
def test_get_image_xml_no_name(self):
|
|
request = webob.Request.blank('/v1.0/images/129')
|
|
request.accept = "application/xml"
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
actual_image = minidom.parseString(response.body.replace(" ", ""))
|
|
|
|
expected_now = self.NOW_API_FORMAT
|
|
expected_image = minidom.parseString("""
|
|
<image id="129"
|
|
name="None"
|
|
updated="%(expected_now)s"
|
|
created="%(expected_now)s"
|
|
status="ACTIVE"
|
|
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
|
|
""" % (locals()))
|
|
|
|
self.assertEqual(expected_image.toxml(), actual_image.toxml())
|
|
|
|
def test_get_image_v1_1_xml(self):
|
|
request = webob.Request.blank('/v1.1/images/123')
|
|
request.accept = "application/xml"
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
actual_image = minidom.parseString(response.body.replace(" ", ""))
|
|
|
|
expected_href = "http://localhost/v1.1/images/123"
|
|
expected_now = self.NOW_API_FORMAT
|
|
expected_image = minidom.parseString("""
|
|
<image id="123"
|
|
name="public image"
|
|
updated="%(expected_now)s"
|
|
created="%(expected_now)s"
|
|
status="ACTIVE"
|
|
xmlns="http://docs.openstack.org/compute/api/v1.1">
|
|
<links>
|
|
<link href="%(expected_href)s" rel="self"/>
|
|
<link href="%(expected_href)s" rel="bookmark"
|
|
type="application/json" />
|
|
<link href="%(expected_href)s" rel="bookmark"
|
|
type="application/xml" />
|
|
</links>
|
|
</image>
|
|
""".replace(" ", "") % (locals()))
|
|
|
|
self.assertEqual(expected_image.toxml(), actual_image.toxml())
|
|
|
|
def test_get_image_404_json(self):
|
|
request = webob.Request.blank('/v1.0/images/NonExistantImage')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
self.assertEqual(404, response.status_int)
|
|
|
|
expected = {
|
|
"itemNotFound": {
|
|
"message": "Image not found.",
|
|
"code": 404,
|
|
},
|
|
}
|
|
|
|
actual = json.loads(response.body)
|
|
|
|
self.assertEqual(expected, actual)
|
|
|
|
def test_get_image_404_xml(self):
|
|
request = webob.Request.blank('/v1.0/images/NonExistantImage')
|
|
request.accept = "application/xml"
|
|
response = request.get_response(fakes.wsgi_app())
|
|
self.assertEqual(404, response.status_int)
|
|
|
|
expected = minidom.parseString("""
|
|
<itemNotFound code="404"
|
|
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
|
|
<message>
|
|
Image not found.
|
|
</message>
|
|
</itemNotFound>
|
|
""".replace(" ", ""))
|
|
|
|
actual = minidom.parseString(response.body.replace(" ", ""))
|
|
|
|
self.assertEqual(expected.toxml(), actual.toxml())
|
|
|
|
def test_get_image_404_v1_1_json(self):
|
|
request = webob.Request.blank('/v1.1/images/NonExistantImage')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
self.assertEqual(404, response.status_int)
|
|
|
|
expected = {
|
|
"itemNotFound": {
|
|
"message": "Image not found.",
|
|
"code": 404,
|
|
},
|
|
}
|
|
|
|
actual = json.loads(response.body)
|
|
|
|
self.assertEqual(expected, actual)
|
|
|
|
def test_get_image_404_v1_1_xml(self):
|
|
request = webob.Request.blank('/v1.1/images/NonExistantImage')
|
|
request.accept = "application/xml"
|
|
response = request.get_response(fakes.wsgi_app())
|
|
self.assertEqual(404, response.status_int)
|
|
|
|
# NOTE(justinsb): I believe this should still use the v1.0 XSD,
|
|
# because the element hasn't changed definition
|
|
expected = minidom.parseString("""
|
|
<itemNotFound code="404"
|
|
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
|
|
<message>
|
|
Image not found.
|
|
</message>
|
|
</itemNotFound>
|
|
""".replace(" ", ""))
|
|
|
|
actual = minidom.parseString(response.body.replace(" ", ""))
|
|
|
|
self.assertEqual(expected.toxml(), actual.toxml())
|
|
|
|
def test_get_image_index_v1_1(self):
|
|
request = webob.Request.blank('/v1.1/images')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
response_dict = json.loads(response.body)
|
|
response_list = response_dict["images"]
|
|
|
|
fixtures = copy.copy(self.fixtures)
|
|
|
|
for image in fixtures:
|
|
if not self._applicable_fixture(image, 1):
|
|
fixtures.remove(image)
|
|
continue
|
|
|
|
href = "http://localhost/v1.1/images/%s" % image["id"]
|
|
test_image = {
|
|
"id": image["id"],
|
|
"name": image["name"],
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/%s" % image["id"],
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": href,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": href,
|
|
}],
|
|
}
|
|
self.assertTrue(test_image in response_list)
|
|
|
|
self.assertEqual(len(response_list), len(fixtures))
|
|
|
|
def test_get_image_details(self):
|
|
request = webob.Request.blank('/v1.0/images/detail')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
response_dict = json.loads(response.body)
|
|
response_list = response_dict["images"]
|
|
|
|
expected = [{
|
|
'id': 123,
|
|
'name': 'public image',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'ACTIVE',
|
|
},
|
|
{
|
|
'id': 124,
|
|
'name': 'queued backup',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'QUEUED',
|
|
},
|
|
{
|
|
'id': 125,
|
|
'name': 'saving backup',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'SAVING',
|
|
'progress': 0,
|
|
},
|
|
{
|
|
'id': 126,
|
|
'name': 'active backup',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'ACTIVE'
|
|
},
|
|
{
|
|
'id': 127,
|
|
'name': 'killed backup',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'FAILED',
|
|
},
|
|
{
|
|
'id': 129,
|
|
'name': None,
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'ACTIVE',
|
|
}]
|
|
|
|
self.assertDictListMatch(expected, response_list)
|
|
|
|
def test_get_image_details_v1_1(self):
|
|
request = webob.Request.blank('/v1.1/images/detail')
|
|
response = request.get_response(fakes.wsgi_app())
|
|
|
|
response_dict = json.loads(response.body)
|
|
response_list = response_dict["images"]
|
|
|
|
expected = [{
|
|
'id': 123,
|
|
'name': 'public image',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'ACTIVE',
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/123",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": "http://localhost/v1.1/images/123",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": "http://localhost/v1.1/images/123",
|
|
}],
|
|
},
|
|
{
|
|
'id': 124,
|
|
'name': 'queued backup',
|
|
'serverRef': "http://localhost:8774/v1.1/servers/42",
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'QUEUED',
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/124",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": "http://localhost/v1.1/images/124",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": "http://localhost/v1.1/images/124",
|
|
}],
|
|
},
|
|
{
|
|
'id': 125,
|
|
'name': 'saving backup',
|
|
'serverRef': "http://localhost:8774/v1.1/servers/42",
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'SAVING',
|
|
'progress': 0,
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/125",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": "http://localhost/v1.1/images/125",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": "http://localhost/v1.1/images/125",
|
|
}],
|
|
},
|
|
{
|
|
'id': 126,
|
|
'name': 'active backup',
|
|
'serverRef': "http://localhost:8774/v1.1/servers/42",
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'ACTIVE',
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/126",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": "http://localhost/v1.1/images/126",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": "http://localhost/v1.1/images/126",
|
|
}],
|
|
},
|
|
{
|
|
'id': 127,
|
|
'name': 'killed backup',
|
|
'serverRef': "http://localhost:8774/v1.1/servers/42",
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'FAILED',
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/127",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": "http://localhost/v1.1/images/127",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": "http://localhost/v1.1/images/127",
|
|
}],
|
|
},
|
|
{
|
|
'id': 129,
|
|
'name': None,
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT,
|
|
'status': 'ACTIVE',
|
|
"links": [{
|
|
"rel": "self",
|
|
"href": "http://localhost/v1.1/images/129",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/json",
|
|
"href": "http://localhost/v1.1/images/129",
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"type": "application/xml",
|
|
"href": "http://localhost/v1.1/images/129",
|
|
}],
|
|
},
|
|
]
|
|
|
|
self.assertDictListMatch(expected, response_list)
|
|
|
|
def test_image_filter_with_name(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'name': 'testname'}
|
|
image_service.index(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images?name=testname')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.index(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_filter_with_status(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'status': 'ACTIVE'}
|
|
image_service.index(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images?status=ACTIVE')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.index(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_filter_with_property(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'property-test': '3'}
|
|
image_service.index(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images?property-test=3')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.index(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_filter_not_supported(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'status': 'ACTIVE'}
|
|
image_service.index(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images?status=ACTIVE&UNSUPPORTEDFILTER=testname')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.index(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_no_filters(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {}
|
|
image_service.index(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.index(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_detail_filter_with_name(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'name': 'testname'}
|
|
image_service.detail(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images/detail?name=testname')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.detail(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_detail_filter_with_status(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'status': 'ACTIVE'}
|
|
image_service.detail(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images/detail?status=ACTIVE')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.detail(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_detail_filter_with_property(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'property-test': '3'}
|
|
image_service.detail(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images/detail?property-test=3')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.detail(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_detail_filter_not_supported(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {'status': 'ACTIVE'}
|
|
image_service.detail(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images/detail?status=ACTIVE&UNSUPPORTEDFILTER=testname')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.detail(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_image_detail_no_filters(self):
|
|
mocker = mox.Mox()
|
|
image_service = mocker.CreateMockAnything()
|
|
context = object()
|
|
filters = {}
|
|
image_service.detail(
|
|
context, filters=filters).AndReturn([])
|
|
mocker.ReplayAll()
|
|
request = webob.Request.blank(
|
|
'/v1.1/images/detail')
|
|
request.environ['nova.context'] = context
|
|
controller = images.ControllerV11(image_service=image_service)
|
|
controller.detail(request)
|
|
mocker.VerifyAll()
|
|
|
|
def test_get_image_found(self):
|
|
req = webob.Request.blank('/v1.0/images/123')
|
|
res = req.get_response(fakes.wsgi_app())
|
|
image_meta = json.loads(res.body)['image']
|
|
expected = {'id': 123, 'name': 'public image',
|
|
'updated': self.NOW_API_FORMAT,
|
|
'created': self.NOW_API_FORMAT, 'status': 'ACTIVE'}
|
|
self.assertDictMatch(image_meta, expected)
|
|
|
|
def test_get_image_non_existent(self):
|
|
req = webob.Request.blank('/v1.0/images/4242')
|
|
res = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(res.status_int, 404)
|
|
|
|
def test_get_image_not_owned(self):
|
|
"""We should return a 404 if we request an image that doesn't belong
|
|
to us
|
|
"""
|
|
req = webob.Request.blank('/v1.0/images/128')
|
|
res = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(res.status_int, 404)
|
|
|
|
def test_create_image(self):
|
|
|
|
body = dict(image=dict(serverId='123', name='Backup 1'))
|
|
req = webob.Request.blank('/v1.0/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(200, response.status_int)
|
|
|
|
def test_create_image_no_server_id(self):
|
|
|
|
body = dict(image=dict(name='Backup 1'))
|
|
req = webob.Request.blank('/v1.0/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(400, response.status_int)
|
|
|
|
def test_create_image_v1_1(self):
|
|
|
|
body = dict(image=dict(serverRef='123', name='Backup 1'))
|
|
req = webob.Request.blank('/v1.1/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(200, response.status_int)
|
|
|
|
def test_create_image_v1_1_actual_server_ref(self):
|
|
|
|
serverRef = 'http://localhost/v1.1/servers/1'
|
|
body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
|
|
req = webob.Request.blank('/v1.1/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(200, response.status_int)
|
|
result = json.loads(response.body)
|
|
self.assertEqual(result['image']['serverRef'], serverRef)
|
|
|
|
def test_create_image_v1_1_server_ref_bad_hostname(self):
|
|
|
|
serverRef = 'http://asdf/v1.1/servers/1'
|
|
body = dict(image=dict(serverRef=serverRef, name='Backup 1'))
|
|
req = webob.Request.blank('/v1.1/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(400, response.status_int)
|
|
|
|
def test_create_image_v1_1_xml_serialization(self):
|
|
|
|
body = dict(image=dict(serverRef='123', name='Backup 1'))
|
|
req = webob.Request.blank('/v1.1/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
req.headers["accept"] = "application/xml"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(200, response.status_int)
|
|
resp_xml = minidom.parseString(response.body.replace(" ", ""))
|
|
expected_href = "http://localhost/v1.1/images/123"
|
|
expected_image = minidom.parseString("""
|
|
<image
|
|
created="None"
|
|
id="123"
|
|
name="Backup 1"
|
|
serverRef="http://localhost/v1.1/servers/123"
|
|
status="ACTIVE"
|
|
updated="None"
|
|
xmlns="http://docs.openstack.org/compute/api/v1.1">
|
|
<links>
|
|
<link href="%(expected_href)s" rel="self"/>
|
|
<link href="%(expected_href)s" rel="bookmark"
|
|
type="application/json" />
|
|
<link href="%(expected_href)s" rel="bookmark"
|
|
type="application/xml" />
|
|
</links>
|
|
</image>
|
|
""".replace(" ", "") % (locals()))
|
|
|
|
self.assertEqual(expected_image.toxml(), resp_xml.toxml())
|
|
|
|
def test_create_image_v1_1_no_server_ref(self):
|
|
|
|
body = dict(image=dict(name='Backup 1'))
|
|
req = webob.Request.blank('/v1.1/images')
|
|
req.method = 'POST'
|
|
req.body = json.dumps(body)
|
|
req.headers["content-type"] = "application/json"
|
|
response = req.get_response(fakes.wsgi_app())
|
|
self.assertEqual(400, response.status_int)
|
|
|
|
@classmethod
|
|
def _make_image_fixtures(cls):
|
|
image_id = 123
|
|
base_attrs = {'created_at': cls.NOW_GLANCE_FORMAT,
|
|
'updated_at': cls.NOW_GLANCE_FORMAT,
|
|
'deleted_at': None,
|
|
'deleted': False}
|
|
|
|
fixtures = []
|
|
|
|
def add_fixture(**kwargs):
|
|
kwargs.update(base_attrs)
|
|
fixtures.append(kwargs)
|
|
|
|
# Public image
|
|
add_fixture(id=image_id, name='public image', is_public=True,
|
|
status='active', properties={})
|
|
image_id += 1
|
|
|
|
# Backup for User 1
|
|
server_ref = 'http://localhost:8774/v1.1/servers/42'
|
|
backup_properties = {'instance_ref': server_ref, 'user_id': '1'}
|
|
for status in ('queued', 'saving', 'active', 'killed'):
|
|
add_fixture(id=image_id, name='%s backup' % status,
|
|
is_public=False, status=status,
|
|
properties=backup_properties)
|
|
image_id += 1
|
|
|
|
# Backup for User 2
|
|
other_backup_properties = {'instance_id': '43', 'user_id': '2'}
|
|
add_fixture(id=image_id, name='someone elses backup', is_public=False,
|
|
status='active', properties=other_backup_properties)
|
|
image_id += 1
|
|
|
|
# Image without a name
|
|
add_fixture(id=image_id, is_public=True, status='active',
|
|
properties={})
|
|
image_id += 1
|
|
|
|
return fixtures
|