diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-08-24 15:31:08 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-08-24 15:31:08 +0000 |
commit | ed19cd3af3a0294f310d399a77058dc66c23372b (patch) | |
tree | 2cd82a21929adbccab69d8d501dd08809d1df230 | |
parent | e7a801d54aae4a5d9c858888165b431c5854132a (diff) | |
parent | 320bc6f0c991c85108f139971a5109b789fad3bf (diff) | |
download | python-glanceclient-ed19cd3af3a0294f310d399a77058dc66c23372b.tar.gz |
Merge "V2: Do not validate image schema when listing" into stable/kilo
-rw-r--r-- | glanceclient/tests/unit/v2/fixtures.py | 307 | ||||
-rw-r--r-- | glanceclient/tests/unit/v2/test_client_requests.py | 58 | ||||
-rw-r--r-- | glanceclient/v2/images.py | 30 |
3 files changed, 381 insertions, 14 deletions
diff --git a/glanceclient/tests/unit/v2/fixtures.py b/glanceclient/tests/unit/v2/fixtures.py new file mode 100644 index 0000000..ebf2f72 --- /dev/null +++ b/glanceclient/tests/unit/v2/fixtures.py @@ -0,0 +1,307 @@ +# Copyright (c) 2015 OpenStack Foundation +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# 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. + +UUID = "3fc2ba62-9a02-433e-b565-d493ffc69034" + +image_list_fixture = { + "images": [ + { + "checksum": "9cb02fe7fcac26f8a25d6db3109063ae", + "container_format": "bare", + "created_at": "2015-07-23T16:58:50.000000", + "deleted": "false", + "deleted_at": "null", + "disk_format": "raw", + "id": UUID, + "is_public": "false", + "min_disk": 0, + "min_ram": 0, + "name": "test", + "owner": "3447cea05d6947658d73791ed9e0ed9f", + "properties": { + "kernel_id": 1234, + "ramdisk_id": 5678 + }, + "protected": "false", + "size": 145, + "status": "active", + "updated_at": "2015-07-23T16:58:51.000000", + "virtual_size": "null" + } + ] +} + +image_show_fixture = { + "checksum": "9cb02fe7fcac26f8a25d6db3109063ae", + "container_format": "bare", + "created_at": "2015-07-24T12:18:13Z", + "disk_format": "raw", + "file": "/v2/images/%s/file" % UUID, + "id": UUID, + "kernel_id": "1234", + "min_disk": 0, + "min_ram": 0, + "name": "img1", + "owner": "411423405e10431fb9c47ac5b2446557", + "protected": "false", + "ramdisk_id": "5678", + "schema": "/v2/schemas/image", + "self": "/v2/images/%s" % UUID, + "size": 145, + "status": "active", + "tags": [], + "updated_at": "2015-07-24T12:18:13Z", + "virtual_size": "null", + "visibility": "private" +} + +schema_fixture = { + "additionalProperties": { + "type": "string" + }, + "links": [ + { + "href": "{self}", + "rel": "self" + }, + { + "href": "{file}", + "rel": "enclosure" + }, + { + "href": "{schema}", + "rel": "describedby" + } + ], + "name": "image", + "properties": { + "architecture": { + "description": "Operating system architecture as specified in " + "http://docs.openstack.org/trunk/openstack-compute" + "/admin/content/adding-images.html", + "is_base": "false", + "type": "string" + }, + "checksum": { + "description": "md5 hash of image contents. (READ-ONLY)", + "maxLength": 32, + "type": [ + "null", + "string" + ] + }, + "container_format": { + "description": "Format of the container", + "enum": [ + "null", + "ami", + "ari", + "aki", + "bare", + "ovf", + "ova" + ], + "type": [ + "null", + "string" + ] + }, + "created_at": { + "description": "Date and time of image registration (READ-ONLY)", + "type": "string" + }, + "direct_url": { + "description": "URL to access the image file kept in external " + "store (READ-ONLY)", + "type": "string" + }, + "disk_format": { + "description": "Format of the disk", + "enum": [ + "null", + "ami", + "ari", + "aki", + "vhd", + "vmdk", + "raw", + "qcow2", + "vdi", + "iso" + ], + "type": [ + "null", + "string" + ] + }, + "file": { + "description": "(READ-ONLY)", + "type": "string" + }, + "id": { + "description": "An identifier for the image", + "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])" + "{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", + "type": "string" + }, + "instance_uuid": { + "description": "ID of instance used to create this image.", + "is_base": "false", + "type": "string" + }, + "kernel_id": { + "description": "ID of image stored in Glance that should be used " + "as the kernel when booting an AMI-style image.", + "is_base": "false", + "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])" + "{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", + "type": [ + "null", + "string" + ] + }, + "locations": { + "description": "A set of URLs to access the image file kept " + "in external store", + "items": { + "properties": { + "metadata": { + "type": "object" + }, + "url": { + "maxLength": 255, + "type": "string" + } + }, + "required": [ + "url", + "metadata" + ], + "type": "object" + }, + "type": "array" + }, + "min_disk": { + "description": "Amount of disk space (in GB) required to " + "boot image.", + "type": "integer" + }, + "min_ram": { + "description": "Amount of ram (in MB) required to boot image.", + "type": "integer" + }, + "name": { + "description": "Descriptive name for the image", + "maxLength": 255, + "type": [ + "null", + "string" + ] + }, + "os_distro": { + "description": "Common name of operating system distribution as " + "specified in http://docs.openstack.org/trunk/" + "openstack-compute/admin/content/" + "adding-images.html", + "is_base": "false", + "type": "string" + }, + "os_version": { + "description": "Operating system version as specified " + "by the distributor", + "is_base": "false", + "type": "string" + }, + "owner": { + "description": "Owner of the image", + "maxLength": 255, + "type": [ + "null", + "string" + ] + }, + "protected": { + "description": "If true, image will not be deletable.", + "type": "boolean" + }, + "ramdisk_id": { + "description": "ID of image stored in Glance that should be used " + "as the ramdisk when booting an AMI-style image.", + "is_base": "false", + "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])" + "{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", + "type": [ + "null", + "string" + ] + }, + "schema": { + "description": "(READ-ONLY)", + "type": "string" + }, + "self": { + "description": "(READ-ONLY)", + "type": "string" + }, + "size": { + "description": "Size of image file in bytes (READ-ONLY)", + "type": [ + "null", + "integer" + ] + }, + "status": { + "description": "Status of the image (READ-ONLY)", + "enum": [ + "queued", + "saving", + "active", + "killed", + "deleted", + "pending_delete" + ], + "type": "string" + }, + "tags": { + "description": "List of strings related to the image", + "items": { + "maxLength": 255, + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "description": "Date and time of the last image " + "modification (READ-ONLY)", + "type": "string" + }, + "virtual_size": { + "description": "Virtual size of image in bytes (READ-ONLY)", + "type": [ + "null", + "integer" + ] + }, + "visibility": { + "description": "Scope of image accessibility", + "enum": [ + "public", + "private" + ], + "type": "string" + } + } +} diff --git a/glanceclient/tests/unit/v2/test_client_requests.py b/glanceclient/tests/unit/v2/test_client_requests.py new file mode 100644 index 0000000..d305a4e --- /dev/null +++ b/glanceclient/tests/unit/v2/test_client_requests.py @@ -0,0 +1,58 @@ +# Copyright (c) 2015 OpenStack Foundation +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# 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. + +from requests_mock.contrib import fixture as rm_fixture + +from glanceclient import client +from glanceclient.tests.unit.v2.fixtures import image_list_fixture +from glanceclient.tests.unit.v2.fixtures import image_show_fixture +from glanceclient.tests.unit.v2.fixtures import schema_fixture +from glanceclient.tests import utils as testutils + + +class ClientTestRequests(testutils.TestCase): + """Client tests using the requests mock library.""" + + def test_list_bad_image_schema(self): + # if kernel_id or ramdisk_id are not uuids, verify we can + # still perform an image listing. Regression test for bug + # 1477910 + self.requests = self.useFixture(rm_fixture.Fixture()) + self.requests.get('http://example.com/v2/schemas/image', + json=schema_fixture) + self.requests.get('http://example.com/v2/images?limit=20', + json=image_list_fixture) + gc = client.Client(2.2, "http://example.com/v2.1") + images = gc.images.list() + for image in images: + pass + + def test_show_bad_image_schema(self): + # if kernel_id or ramdisk_id are not uuids, verify we + # fail schema validation on 'show' + self.requests = self.useFixture(rm_fixture.Fixture()) + self.requests.get('http://example.com/v2/schemas/image', + json=schema_fixture) + self.requests.get('http://example.com/v2/images/%s' + % image_show_fixture['id'], + json=image_show_fixture) + gc = client.Client(2.2, "http://example.com/v2.1") + try: + gc.images.get(image_show_fixture['id']) + self.fail('Expected exception was not raised.') + except ValueError as e: + if 'ramdisk_id' not in str(e) and 'kernel_id' not in str(e): + self.fail('Expected exception message was not returned.') diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index c9fb0a7..f7ae927 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -39,7 +39,18 @@ class Controller(object): @utils.memoized_property def model(self): schema = self.schema_client.get('image') - return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel) + warlock_model = warlock.model_factory(schema.raw(), + schemas.SchemaBasedModel) + return warlock_model + + @utils.memoized_property + def unvalidated_model(self): + """A model which does not validate the image against the v2 schema.""" + schema = self.schema_client.get('image') + warlock_model = warlock.model_factory(schema.raw(), + schemas.SchemaBasedModel) + warlock_model.validate = lambda *args, **kwargs: None + return warlock_model @staticmethod def _wrap(value): @@ -77,9 +88,6 @@ class Controller(object): :returns generator over list of Images """ - ori_validate_fun = self.model.validate - empty_fun = lambda *args, **kwargs: None - limit = kwargs.get('limit') # NOTE(flaper87): Don't use `get('page_size', DEFAULT_SIZE)` otherwise, # it could be possible to send invalid data to the server by passing @@ -102,21 +110,15 @@ class Controller(object): # an elegant way to pass it into the model constructor # without conflict. image.pop('self', None) - yield self.model(**image) - # NOTE(zhiyan): In order to resolve the performance issue - # of JSON schema validation for image listing case, we - # don't validate each image entry but do it only on first - # image entry for each page. - self.model.validate = empty_fun - + # We do not validate the model when listing. + # This prevents side-effects of injecting invalid + # schema values via v1. + yield self.unvalidated_model(**image) if limit: limit -= 1 if limit <= 0: raise StopIteration - # NOTE(zhiyan); Reset validation function. - self.model.validate = ori_validate_fun - try: next_url = body['next'] except KeyError: |