1506 lines
54 KiB
Python
1506 lines
54 KiB
Python
# Copyright 2012 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.
|
|
|
|
import errno
|
|
import hashlib
|
|
import testtools
|
|
from unittest import mock
|
|
|
|
import ddt
|
|
|
|
from glanceclient.common import utils as common_utils
|
|
from glanceclient import exc
|
|
from glanceclient.tests.unit.v2 import base
|
|
from glanceclient.tests import utils
|
|
from glanceclient.v2 import images
|
|
|
|
_CHKSUM = '93264c3edf5972c9f1cb309543d38a5c'
|
|
_CHKSUM1 = '54264c3edf5972c9f1cb309453d38a46'
|
|
|
|
_HASHVAL = '54264c3edf93264c3edf5972c9f1cb309543d38a5c5972c9f1cb309453d38a46'
|
|
_HASHVAL1 = 'cb309543d38a5c5972c9f1cb309453d38a4654264c3edf93264c3edf5972c9f1'
|
|
_HASHBAD = '93264c3edf597254264c3edf5972c9f1cb309453d38a46c9f1cb309543d38a5c'
|
|
|
|
_TAG1 = 'power'
|
|
_TAG2 = '64bit'
|
|
|
|
_BOGUS_ID = '63e7f218-29de-4477-abdc-8db7c9533188'
|
|
_EVERYTHING_ID = '802cbbb7-0379-4c38-853f-37302b5e3d29'
|
|
_OWNED_IMAGE_ID = 'a4963502-acc7-42ba-ad60-5aa0962b7faf'
|
|
_OWNER_ID = '6bd473f0-79ae-40ad-a927-e07ec37b642f'
|
|
_PRIVATE_ID = 'e33560a7-3964-4de5-8339-5a24559f99ab'
|
|
_PUBLIC_ID = '857806e7-05b6-48e0-9d40-cb0e6fb727b9'
|
|
_SHARED_ID = '331ac905-2a38-44c5-a83d-653db8f08313'
|
|
_COMMUNITY_ID = '609ec9fc-0ee4-44c4-854d-0480af576929'
|
|
_STATUS_REJECTED_ID = 'f3ea56ff-d7e4-4451-998c-1e3d33539c8e'
|
|
|
|
data_fixtures = {
|
|
'/v2/schemas/image': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'name': 'image',
|
|
'properties': {
|
|
'id': {},
|
|
'name': {},
|
|
'locations': {
|
|
'type': 'array',
|
|
'items': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'metadata': {'type': 'object'},
|
|
'url': {'type': 'string'},
|
|
},
|
|
'required': ['url', 'metadata'],
|
|
},
|
|
},
|
|
'color': {'type': 'string', 'is_base': False},
|
|
},
|
|
'additionalProperties': {'type': 'string'},
|
|
},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=2': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'images': [
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
],
|
|
'next': ('/v2/images?limit=2&'
|
|
'marker=6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'),
|
|
},
|
|
),
|
|
},
|
|
'/v2/images?limit=1': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'images': [
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
],
|
|
'next': ('/v2/images?limit=1&'
|
|
'marker=3a4560a1-e585-443e-9b39-553b46ec92d1'),
|
|
},
|
|
),
|
|
},
|
|
('/v2/images?limit=1&marker=3a4560a1-e585-443e-9b39-553b46ec92d1'): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
('/v2/images?limit=1&marker=6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '3f99bf80-2ee6-47cf-acfe-1f1fabb7e811',
|
|
'name': 'image-3',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
),
|
|
'PATCH': (
|
|
{},
|
|
'',
|
|
),
|
|
},
|
|
'/v2/images/e7e59ff6-fa2e-4075-87d3-1a1398a07dc3': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'id': 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3',
|
|
'name': 'image-3',
|
|
'barney': 'rubble',
|
|
'george': 'jetson',
|
|
'color': 'red',
|
|
},
|
|
),
|
|
'PATCH': (
|
|
{},
|
|
'',
|
|
),
|
|
},
|
|
'/v2/images': {
|
|
'POST': (
|
|
{},
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/87b634c1-f893-33c9-28a9-e5673c99239a': {
|
|
'DELETE': (
|
|
{},
|
|
{
|
|
'id': '87b634c1-f893-33c9-28a9-e5673c99239a',
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/606b0e88-7c5a-4d54-b5bb-046105d4de6f/file': {
|
|
'PUT': (
|
|
{},
|
|
'',
|
|
),
|
|
},
|
|
'/v2/images/5cc4bebc-db27-11e1-a1eb-080027cbe205/file': {
|
|
'GET': (
|
|
{},
|
|
'A',
|
|
),
|
|
},
|
|
'/v2/images/5cc4bebc-db27-11e1-a1eb-080027cbe205': {
|
|
'GET': (
|
|
{},
|
|
{},
|
|
),
|
|
},
|
|
'/v2/images/headeronly-db27-11e1-a1eb-080027cbe205/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': 'wrong'
|
|
},
|
|
'BB',
|
|
),
|
|
},
|
|
'/v2/images/headeronly-db27-11e1-a1eb-080027cbe205': {
|
|
'GET': (
|
|
{},
|
|
{},
|
|
),
|
|
},
|
|
'/v2/images/chkonly-db27-11e1-a1eb-080027cbe205/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': 'wrong'
|
|
},
|
|
'BB',
|
|
),
|
|
},
|
|
'/v2/images/chkonly-db27-11e1-a1eb-080027cbe205': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'checksum': 'wrong',
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/multihash-db27-11e1-a1eb-080027cbe205/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': 'wrong'
|
|
},
|
|
'BB',
|
|
),
|
|
},
|
|
'/v2/images/multihash-db27-11e1-a1eb-080027cbe205': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'checksum': 'wrong',
|
|
'os_hash_algo': 'md5',
|
|
'os_hash_value': 'junk'
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/badalgo-db27-11e1-a1eb-080027cbe205/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': hashlib.md5(b'BB').hexdigest()
|
|
},
|
|
'BB',
|
|
),
|
|
},
|
|
'/v2/images/badalgo-db27-11e1-a1eb-080027cbe205': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'checksum': hashlib.md5(b'BB').hexdigest(),
|
|
'os_hash_algo': 'not_an_algo',
|
|
'os_hash_value': 'whatever'
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/bad-multihash-value-good-checksum/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': hashlib.md5(b'GOODCHECKSUM').hexdigest()
|
|
},
|
|
'GOODCHECKSUM',
|
|
),
|
|
},
|
|
'/v2/images/bad-multihash-value-good-checksum': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'checksum': hashlib.md5(b'GOODCHECKSUM').hexdigest(),
|
|
'os_hash_algo': 'sha512',
|
|
'os_hash_value': 'badmultihashvalue'
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/headeronly-dd57-11e1-af0f-02163e68b1d8/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae'
|
|
},
|
|
'CCC',
|
|
),
|
|
},
|
|
'/v2/images/headeronly-dd57-11e1-af0f-02163e68b1d8': {
|
|
'GET': (
|
|
{},
|
|
{},
|
|
),
|
|
},
|
|
'/v2/images/chkonly-dd57-11e1-af0f-02163e68b1d8/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae'
|
|
},
|
|
'CCC',
|
|
),
|
|
},
|
|
'/v2/images/chkonly-dd57-11e1-af0f-02163e68b1d8': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'checksum': 'defb99e69a9f1f6e06f15006b1f166ae',
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/multihash-dd57-11e1-af0f-02163e68b1d8/file': {
|
|
'GET': (
|
|
{
|
|
'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae'
|
|
},
|
|
'CCC',
|
|
),
|
|
},
|
|
'/v2/images/multihash-dd57-11e1-af0f-02163e68b1d8': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'checksum': 'defb99e69a9f1f6e06f15006b1f166ae',
|
|
'os_hash_algo': 'sha384',
|
|
'os_hash_value': hashlib.sha384(b'CCC').hexdigest()
|
|
},
|
|
),
|
|
},
|
|
'/v2/images/87b634c1-f893-33c9-28a9-e5673c99239a/actions/reactivate': {
|
|
'POST': ({}, None)
|
|
},
|
|
'/v2/images/87b634c1-f893-33c9-28a9-e5673c99239a/actions/deactivate': {
|
|
'POST': ({}, None)
|
|
},
|
|
'/v2/images/606b0e88-7c5a-4d54-b5bb-046105d4de6f/import': {
|
|
'POST': ({}, None)
|
|
},
|
|
'/v2/images?limit=%d&visibility=public' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _PUBLIC_ID,
|
|
'harvey': 'lipshitz',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&visibility=private' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _PRIVATE_ID,
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&visibility=shared' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _SHARED_ID,
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&visibility=community' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _COMMUNITY_ID,
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&member_status=rejected' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _STATUS_REJECTED_ID,
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&member_status=pending' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': []},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&owner=%s' % (images.DEFAULT_PAGE_SIZE, _OWNER_ID): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _OWNED_IMAGE_ID,
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&owner=%s' % (images.DEFAULT_PAGE_SIZE, _BOGUS_ID): {
|
|
'GET': (
|
|
{},
|
|
{'images': []},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&member_status=pending&owner=%s&visibility=shared'
|
|
% (images.DEFAULT_PAGE_SIZE, _BOGUS_ID): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': _EVERYTHING_ID,
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?checksum=%s&limit=%d' % (_CHKSUM, images.DEFAULT_PAGE_SIZE): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
}
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?checksum=%s&limit=%d' % (_CHKSUM1, images.DEFAULT_PAGE_SIZE): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?checksum=wrong&limit=%d' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': []},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&os_hash_value=%s' % (images.DEFAULT_PAGE_SIZE,
|
|
_HASHVAL): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
}
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&os_hash_value=%s' % (images.DEFAULT_PAGE_SIZE,
|
|
_HASHVAL1): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&os_hash_value=%s' % (images.DEFAULT_PAGE_SIZE,
|
|
_HASHBAD): {
|
|
'GET': (
|
|
{},
|
|
{'images': []},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&tag=%s' % (images.DEFAULT_PAGE_SIZE, _TAG1): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
}
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&tag=%s' % (images.DEFAULT_PAGE_SIZE, _TAG2): {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&tag=%s&tag=%s' % (images.DEFAULT_PAGE_SIZE,
|
|
_TAG1, _TAG2):
|
|
{
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
}
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&tag=fake' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': []},
|
|
),
|
|
},
|
|
'/v2/images/a2b83adc-888e-11e3-8872-78acc0b951d8': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'id': 'a2b83adc-888e-11e3-8872-78acc0b951d8',
|
|
'name': 'image-location-tests',
|
|
'locations': [{'url': 'http://foo.com/',
|
|
'metadata': {'foo': 'foometa'}},
|
|
{'url': 'http://bar.com/',
|
|
'metadata': {'bar': 'barmeta'}}],
|
|
},
|
|
),
|
|
'PATCH': (
|
|
{},
|
|
'',
|
|
)
|
|
},
|
|
'/v2/images?limit=%d&os_distro=NixOS' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '8b052954-c76c-4e02-8e90-be89a70183a8',
|
|
'name': 'image-5',
|
|
'os_distro': 'NixOS',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&my_little_property=cant_be_this_cute' %
|
|
images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': []},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&sort_key=name' % images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&sort_key=name&sort_key=id'
|
|
% images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image',
|
|
},
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&sort_dir=desc&sort_key=id'
|
|
% images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&sort_dir=desc&sort_key=name&sort_key=id'
|
|
% images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&sort_dir=desc&sort_dir=asc&sort_key=name&sort_key=id'
|
|
% images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images?limit=%d&sort=name%%3Adesc%%2Csize%%3Aasc'
|
|
% images.DEFAULT_PAGE_SIZE: {
|
|
'GET': (
|
|
{},
|
|
{'images': [
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'name': 'image-2',
|
|
},
|
|
{
|
|
'id': '2a4560b2-e585-443e-9b39-553b46ec92d1',
|
|
'name': 'image-1',
|
|
},
|
|
]},
|
|
),
|
|
},
|
|
'/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1/tasks': {
|
|
'GET': (
|
|
{},
|
|
{'tasks': [
|
|
{
|
|
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
|
|
'status': 'succeed',
|
|
'message': 'Copied 44 MiB',
|
|
'updated_at': '2021-03-01T18:28:26.000000'
|
|
}
|
|
]},
|
|
),
|
|
},
|
|
}
|
|
|
|
schema_fixtures = {
|
|
'image': {
|
|
'GET': (
|
|
{},
|
|
{
|
|
'name': 'image',
|
|
'properties': {
|
|
'id': {},
|
|
'name': {},
|
|
'locations': {
|
|
'type': 'array',
|
|
'items': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'metadata': {'type': 'object'},
|
|
'url': {'type': 'string'},
|
|
},
|
|
'required': ['url', 'metadata'],
|
|
}
|
|
},
|
|
'color': {'type': 'string', 'is_base': False},
|
|
'tags': {'type': 'array'},
|
|
},
|
|
'additionalProperties': {'type': 'string'},
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
@ddt.ddt
|
|
class TestController(testtools.TestCase):
|
|
def setUp(self):
|
|
super(TestController, self).setUp()
|
|
self.api = utils.FakeAPI(data_fixtures)
|
|
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
|
|
self.controller = base.BaseController(self.api, self.schema_api,
|
|
images.Controller)
|
|
|
|
def test_image_tasks_supported(self):
|
|
with mock.patch.object(common_utils,
|
|
'has_version') as mock_has_version:
|
|
mock_has_version.return_value = True
|
|
image_tasks = self.controller.get_associated_image_tasks(
|
|
'3a4560a1-e585-443e-9b39-553b46ec92d1')
|
|
self.assertEqual(1, len(image_tasks['tasks']))
|
|
|
|
def test_image_tasks_not_supported(self):
|
|
with mock.patch.object(common_utils,
|
|
'has_version') as mock_has_version:
|
|
mock_has_version.return_value = False
|
|
self.assertRaises(exc.HTTPNotImplemented,
|
|
self.controller.get_associated_image_tasks,
|
|
'3a4560a1-e585-443e-9b39-553b46ec92d1')
|
|
|
|
def test_list_images(self):
|
|
images = self.controller.list()
|
|
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
|
|
self.assertEqual('image-1', images[0].name)
|
|
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
|
|
self.assertEqual('image-2', images[1].name)
|
|
|
|
def test_list_images_paginated(self):
|
|
images = self.controller.list(page_size=1)
|
|
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
|
|
self.assertEqual('image-1', images[0].name)
|
|
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
|
|
self.assertEqual('image-2', images[1].name)
|
|
|
|
def test_list_images_paginated_with_limit(self):
|
|
images = self.controller.list(limit=3, page_size=2)
|
|
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', images[0].id)
|
|
self.assertEqual('image-1', images[0].name)
|
|
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[1].id)
|
|
self.assertEqual('image-2', images[1].name)
|
|
self.assertEqual('3f99bf80-2ee6-47cf-acfe-1f1fabb7e811', images[2].id)
|
|
self.assertEqual('image-3', images[2].name)
|
|
self.assertEqual(3, len(images))
|
|
|
|
def test_list_images_with_marker(self):
|
|
images = self.controller.list(
|
|
limit=1, marker='3a4560a1-e585-443e-9b39-553b46ec92d1')
|
|
self.assertEqual('6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', images[0].id)
|
|
self.assertEqual('image-2', images[0].name)
|
|
|
|
def test_list_images_visibility_public(self):
|
|
filters = {'filters': {'visibility': 'public'}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(_PUBLIC_ID, images[0].id)
|
|
|
|
def test_list_images_visibility_private(self):
|
|
filters = {'filters': {'visibility': 'private'}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(_PRIVATE_ID, images[0].id)
|
|
|
|
def test_list_images_visibility_shared(self):
|
|
filters = {'filters': {'visibility': 'shared'}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(_SHARED_ID, images[0].id)
|
|
|
|
def test_list_images_visibility_community(self):
|
|
filters = {'filters': {'visibility': 'community'}}
|
|
images = list(self.controller.list(**filters))
|
|
self.assertEqual(_COMMUNITY_ID, images[0].id)
|
|
|
|
def test_list_images_member_status_rejected(self):
|
|
filters = {'filters': {'member_status': 'rejected'}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(_STATUS_REJECTED_ID, images[0].id)
|
|
|
|
def test_list_images_for_owner(self):
|
|
filters = {'filters': {'owner': _OWNER_ID}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(_OWNED_IMAGE_ID, images[0].id)
|
|
|
|
def test_list_images_for_checksum_single_image(self):
|
|
fake_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
filters = {'filters': {'checksum': _CHKSUM}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(1, len(images))
|
|
self.assertEqual('%s' % fake_id, images[0].id)
|
|
|
|
def test_list_images_for_checksum_multiple_images(self):
|
|
fake_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
fake_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'
|
|
filters = {'filters': {'checksum': _CHKSUM1}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % fake_id1, images[0].id)
|
|
self.assertEqual('%s' % fake_id2, images[1].id)
|
|
|
|
def test_list_images_for_wrong_checksum(self):
|
|
filters = {'filters': {'checksum': 'wrong'}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(0, len(images))
|
|
|
|
def test_list_images_for_hash_single_image(self):
|
|
fake_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
filters = {'filters': {'os_hash_value': _HASHVAL}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(1, len(images))
|
|
self.assertEqual('%s' % fake_id, images[0].id)
|
|
|
|
def test_list_images_for_hash_multiple_images(self):
|
|
fake_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
fake_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'
|
|
filters = {'filters': {'os_hash_value': _HASHVAL1}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % fake_id1, images[0].id)
|
|
self.assertEqual('%s' % fake_id2, images[1].id)
|
|
|
|
def test_list_images_for_wrong_hash(self):
|
|
filters = {'filters': {'os_hash_value': _HASHBAD}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(0, len(images))
|
|
|
|
def test_list_images_for_bogus_owner(self):
|
|
filters = {'filters': {'owner': _BOGUS_ID}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual([], images)
|
|
|
|
def test_list_images_for_bunch_of_filters(self):
|
|
filters = {'filters': {'owner': _BOGUS_ID,
|
|
'visibility': 'shared',
|
|
'member_status': 'pending'}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(_EVERYTHING_ID, images[0].id)
|
|
|
|
def test_list_images_filters_encoding(self):
|
|
filters = {"owner": u"ni\xf1o"}
|
|
try:
|
|
self.controller.list(filters=filters)
|
|
except KeyError:
|
|
# NOTE(flaper87): It raises KeyError because there's
|
|
# no fixture supporting this query:
|
|
# /v2/images?owner=ni%C3%B1o&limit=20
|
|
# We just want to make sure filters are correctly encoded.
|
|
pass
|
|
self.assertEqual(b"ni\xc3\xb1o", filters["owner"])
|
|
|
|
def test_list_images_for_tag_single_image(self):
|
|
img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
filters = {'filters': {'tag': [_TAG1]}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(1, len(images))
|
|
self.assertEqual('%s' % img_id, images[0].id)
|
|
|
|
def test_list_images_for_tag_multiple_images(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
img_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810'
|
|
filters = {'filters': {'tag': [_TAG2]}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[0].id)
|
|
self.assertEqual('%s' % img_id2, images[1].id)
|
|
|
|
def test_list_images_for_multi_tags(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
filters = {'filters': {'tag': [_TAG1, _TAG2]}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(1, len(images))
|
|
self.assertEqual('%s' % img_id1, images[0].id)
|
|
|
|
def test_list_images_for_non_existent_tag(self):
|
|
filters = {'filters': {'tag': ['fake']}}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(0, len(images))
|
|
|
|
def test_list_images_for_invalid_tag(self):
|
|
filters = {'filters': {'tag': [[]]}}
|
|
|
|
self.assertRaises(exc.HTTPBadRequest,
|
|
self.controller.list, **filters)
|
|
|
|
def test_list_images_with_single_sort_key(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
sort_key = 'name'
|
|
images = self.controller.list(sort_key=sort_key)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[0].id)
|
|
|
|
def test_list_with_multiple_sort_keys(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
sort_key = ['name', 'id']
|
|
images = self.controller.list(sort_key=sort_key)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[0].id)
|
|
|
|
def test_list_images_with_desc_sort_dir(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
sort_key = 'id'
|
|
sort_dir = 'desc'
|
|
images = self.controller.list(sort_key=sort_key, sort_dir=sort_dir)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[1].id)
|
|
|
|
def test_list_images_with_multiple_sort_keys_and_one_sort_dir(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
sort_key = ['name', 'id']
|
|
sort_dir = 'desc'
|
|
images = self.controller.list(sort_key=sort_key, sort_dir=sort_dir)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[1].id)
|
|
|
|
def test_list_images_with_multiple_sort_dirs(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
sort_key = ['name', 'id']
|
|
sort_dir = ['desc', 'asc']
|
|
images = self.controller.list(sort_key=sort_key, sort_dir=sort_dir)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[1].id)
|
|
|
|
def test_list_images_with_new_sorting_syntax(self):
|
|
img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1'
|
|
sort = 'name:desc,size:asc'
|
|
images = self.controller.list(sort=sort)
|
|
self.assertEqual(2, len(images))
|
|
self.assertEqual('%s' % img_id1, images[1].id)
|
|
|
|
def test_list_images_sort_dirs_fewer_than_keys(self):
|
|
sort_key = ['name', 'id', 'created_at']
|
|
sort_dir = ['desc', 'asc']
|
|
self.assertRaises(exc.HTTPBadRequest, self.controller.list,
|
|
sort_key=sort_key, sort_dir=sort_dir)
|
|
|
|
def test_list_images_combined_syntax(self):
|
|
sort_key = ['name', 'id']
|
|
sort_dir = ['desc', 'asc']
|
|
sort = 'name:asc'
|
|
self.assertRaises(exc.HTTPBadRequest,
|
|
self.controller.list, sort=sort, sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
def test_list_images_new_sorting_syntax_invalid_key(self):
|
|
sort = 'INVALID:asc'
|
|
self.assertRaises(exc.HTTPBadRequest, self.controller.list,
|
|
sort=sort)
|
|
|
|
def test_list_images_new_sorting_syntax_invalid_direction(self):
|
|
sort = 'name:INVALID'
|
|
self.assertRaises(exc.HTTPBadRequest, self.controller.list,
|
|
sort=sort)
|
|
|
|
def test_list_images_for_property(self):
|
|
filters = {'filters': dict([('os_distro', 'NixOS')])}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(1, len(images))
|
|
|
|
def test_list_images_for_non_existent_property(self):
|
|
filters = {'filters': dict([('my_little_property',
|
|
'cant_be_this_cute')])}
|
|
images = self.controller.list(**filters)
|
|
self.assertEqual(0, len(images))
|
|
|
|
def test_get_image(self):
|
|
image = self.controller.get('3a4560a1-e585-443e-9b39-553b46ec92d1')
|
|
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', image.id)
|
|
self.assertEqual('image-1', image.name)
|
|
|
|
def test_create_image(self):
|
|
properties = {
|
|
'name': 'image-1'
|
|
}
|
|
image = self.controller.create(**properties)
|
|
self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1', image.id)
|
|
self.assertEqual('image-1', image.name)
|
|
|
|
def test_create_bad_additionalProperty_type(self):
|
|
properties = {
|
|
'name': 'image-1',
|
|
'bad_prop': True,
|
|
}
|
|
with testtools.ExpectedException(TypeError):
|
|
self.controller.create(**properties)
|
|
|
|
def test_delete_image(self):
|
|
self.controller.delete('87b634c1-f893-33c9-28a9-e5673c99239a')
|
|
expect = [
|
|
('DELETE',
|
|
'/v2/images/87b634c1-f893-33c9-28a9-e5673c99239a',
|
|
{},
|
|
None)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_deactivate_image(self):
|
|
id_image = '87b634c1-f893-33c9-28a9-e5673c99239a'
|
|
self.controller.deactivate(id_image)
|
|
expect = [('POST',
|
|
'/v2/images/%s/actions/deactivate' % id_image,
|
|
{},
|
|
None)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_reactivate_image(self):
|
|
id_image = '87b634c1-f893-33c9-28a9-e5673c99239a'
|
|
self.controller.reactivate(id_image)
|
|
expect = [('POST',
|
|
'/v2/images/%s/actions/reactivate' % id_image,
|
|
{},
|
|
None)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_data_upload(self):
|
|
image_data = 'CCC'
|
|
image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f'
|
|
self.controller.upload(image_id, image_data)
|
|
expect = [('PUT', '/v2/images/%s/file' % image_id,
|
|
{'Content-Type': 'application/octet-stream'},
|
|
image_data)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_data_upload_w_size(self):
|
|
image_data = 'CCC'
|
|
image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f'
|
|
self.controller.upload(image_id, image_data, image_size=3)
|
|
expect = [('PUT', '/v2/images/%s/file' % image_id,
|
|
{'Content-Type': 'application/octet-stream'},
|
|
image_data)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_data_without_checksum(self):
|
|
body = self.controller.data('5cc4bebc-db27-11e1-a1eb-080027cbe205',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('A', body)
|
|
|
|
body = self.controller.data('5cc4bebc-db27-11e1-a1eb-080027cbe205')
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('A', body)
|
|
|
|
def test_data_with_wrong_checksum(self):
|
|
body = self.controller.data('headeronly-db27-11e1-a1eb-080027cbe205',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('BB', body)
|
|
body = self.controller.data('headeronly-db27-11e1-a1eb-080027cbe205')
|
|
try:
|
|
body = ''.join([b for b in body])
|
|
self.fail('data did not raise an error.')
|
|
except IOError as e:
|
|
self.assertEqual(errno.EPIPE, e.errno)
|
|
msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected wrong'
|
|
self.assertIn(msg, str(e))
|
|
|
|
body = self.controller.data('chkonly-db27-11e1-a1eb-080027cbe205',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('BB', body)
|
|
body = self.controller.data('chkonly-db27-11e1-a1eb-080027cbe205')
|
|
try:
|
|
body = ''.join([b for b in body])
|
|
self.fail('data did not raise an error.')
|
|
except IOError as e:
|
|
self.assertEqual(errno.EPIPE, e.errno)
|
|
msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected wrong'
|
|
self.assertIn(msg, str(e))
|
|
|
|
body = self.controller.data('multihash-db27-11e1-a1eb-080027cbe205',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('BB', body)
|
|
body = self.controller.data('multihash-db27-11e1-a1eb-080027cbe205')
|
|
try:
|
|
body = ''.join([b for b in body])
|
|
self.fail('data did not raise an error.')
|
|
except IOError as e:
|
|
self.assertEqual(errno.EPIPE, e.errno)
|
|
msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected junk'
|
|
self.assertIn(msg, str(e))
|
|
|
|
body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('BB', body)
|
|
try:
|
|
body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205')
|
|
self.fail('bad os_hash_algo did not raise an error.')
|
|
except ValueError as e:
|
|
msg = 'unsupported hash type not_an_algo'
|
|
self.assertIn(msg, str(e))
|
|
|
|
def test_data_with_checksum(self):
|
|
for prefix in ['headeronly', 'chkonly', 'multihash']:
|
|
body = self.controller.data(prefix +
|
|
'-dd57-11e1-af0f-02163e68b1d8',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('CCC', body)
|
|
|
|
body = self.controller.data(prefix +
|
|
'-dd57-11e1-af0f-02163e68b1d8')
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('CCC', body)
|
|
|
|
@ddt.data('headeronly', 'chkonly', 'multihash')
|
|
def test_data_with_checksum_but_no_md5_algo(self, prefix):
|
|
with mock.patch('hashlib.new', mock.MagicMock(
|
|
side_effect=ValueError('unsupported hash type'))):
|
|
body = self.controller.data(prefix +
|
|
'-dd57-11e1-af0f-02163e68b1d8',
|
|
allow_md5_fallback=True)
|
|
try:
|
|
body = ''.join([b for b in body])
|
|
self.fail('missing md5 algo did not raise an error')
|
|
except IOError as e:
|
|
self.assertEqual(errno.EPIPE, e.errno)
|
|
msg = 'md5 algorithm is not available on the client'
|
|
self.assertIn(msg, str(e))
|
|
|
|
def test_data_with_checksum_and_fallback(self):
|
|
# make sure the allow_md5_fallback option does not cause any
|
|
# incorrect behavior when fallback is not needed
|
|
for prefix in ['headeronly', 'chkonly', 'multihash']:
|
|
body = self.controller.data(prefix +
|
|
'-dd57-11e1-af0f-02163e68b1d8',
|
|
do_checksum=False,
|
|
allow_md5_fallback=True)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('CCC', body)
|
|
|
|
body = self.controller.data(prefix +
|
|
'-dd57-11e1-af0f-02163e68b1d8',
|
|
allow_md5_fallback=True)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('CCC', body)
|
|
|
|
def test_data_with_bad_hash_algo_and_fallback(self):
|
|
# shouldn't matter when do_checksum is False
|
|
body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205',
|
|
do_checksum=False,
|
|
allow_md5_fallback=True)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('BB', body)
|
|
|
|
# default value for do_checksum is True
|
|
body = self.controller.data('badalgo-db27-11e1-a1eb-080027cbe205',
|
|
allow_md5_fallback=True)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('BB', body)
|
|
|
|
def test_neg_data_with_bad_hash_value_and_fallback_enabled(self):
|
|
# make sure download fails when good hash_algo but bad hash_value
|
|
# even when correct checksum is present regardless of
|
|
# allow_md5_fallback setting
|
|
body = self.controller.data('bad-multihash-value-good-checksum',
|
|
allow_md5_fallback=False)
|
|
try:
|
|
body = ''.join([b for b in body])
|
|
self.fail('bad os_hash_value did not raise an error.')
|
|
except IOError as e:
|
|
self.assertEqual(errno.EPIPE, e.errno)
|
|
msg = 'expected badmultihashvalue'
|
|
self.assertIn(msg, str(e))
|
|
|
|
body = self.controller.data('bad-multihash-value-good-checksum',
|
|
allow_md5_fallback=True)
|
|
try:
|
|
body = ''.join([b for b in body])
|
|
self.fail('bad os_hash_value did not raise an error.')
|
|
except IOError as e:
|
|
self.assertEqual(errno.EPIPE, e.errno)
|
|
msg = 'expected badmultihashvalue'
|
|
self.assertIn(msg, str(e))
|
|
|
|
# download should succeed when do_checksum is off, though
|
|
body = self.controller.data('bad-multihash-value-good-checksum',
|
|
do_checksum=False)
|
|
body = ''.join([b for b in body])
|
|
self.assertEqual('GOODCHECKSUM', body)
|
|
|
|
def test_image_import_web_download(self):
|
|
uri = 'http://example.com/image.qcow'
|
|
data = [('method', {'name': 'web-download',
|
|
'uri': uri})]
|
|
image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f'
|
|
self.controller.image_import(image_id, 'web-download', uri)
|
|
expect = [('POST', '/v2/images/%s/import' % image_id, {},
|
|
data)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_image_import_glance_download(self):
|
|
region = 'REGION2'
|
|
remote_image_id = '75baf7b6-253a-11ed-8307-4b1057986a78'
|
|
image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f'
|
|
service_interface = 'public'
|
|
data = [('method',
|
|
{'name': 'glance-download',
|
|
'glance_region': region,
|
|
'glance_image_id': remote_image_id,
|
|
'glance_service_interface': service_interface})]
|
|
self.controller.image_import(
|
|
image_id, 'glance-download', remote_region=region,
|
|
remote_image_id=remote_image_id,
|
|
remote_service_interface=service_interface)
|
|
expect = [('POST', '/v2/images/%s/import' % image_id, {},
|
|
data)]
|
|
self.assertEqual(expect, self.api.calls)
|
|
|
|
def test_download_no_data(self):
|
|
resp = utils.FakeResponse(headers={}, status_code=204)
|
|
self.controller.controller.http_client.get = mock.Mock(
|
|
return_value=(resp, {}))
|
|
self.controller.data('image_id')
|
|
|
|
def test_download_forbidden(self):
|
|
self.controller.controller.http_client.get = mock.Mock(
|
|
side_effect=exc.HTTPForbidden())
|
|
try:
|
|
self.controller.data('image_id')
|
|
self.fail('No forbidden exception raised.')
|
|
except exc.HTTPForbidden:
|
|
pass
|
|
|
|
def test_update_replace_prop(self):
|
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
params = {'name': 'pong'}
|
|
image = self.controller.update(image_id, **params)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = [[('op', 'replace'), ('path', '/name'),
|
|
('value', 'pong')]]
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
# NOTE(bcwaldon):due to limitations of our fake api framework, the name
|
|
# will not actually change - yet in real life it will...
|
|
self.assertEqual('image-1', image.name)
|
|
|
|
def test_update_add_prop(self):
|
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
params = {'finn': 'human'}
|
|
image = self.controller.update(image_id, **params)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = [[('op', 'add'), ('path', '/finn'), ('value', 'human')]]
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
# NOTE(bcwaldon):due to limitations of our fake api framework, the name
|
|
# will not actually change - yet in real life it will...
|
|
self.assertEqual('image-1', image.name)
|
|
|
|
def test_update_remove_prop(self):
|
|
image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3'
|
|
remove_props = ['barney']
|
|
image = self.controller.update(image_id, remove_props)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = [[('op', 'remove'), ('path', '/barney')]]
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
# NOTE(bcwaldon):due to limitations of our fake api framework, the name
|
|
# will not actually change - yet in real life it will...
|
|
self.assertEqual('image-3', image.name)
|
|
|
|
def test_update_replace_remove_same_prop(self):
|
|
image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3'
|
|
# Updating a property takes precedence over removing a property
|
|
params = {'barney': 'miller'}
|
|
remove_props = ['barney']
|
|
image = self.controller.update(image_id, remove_props, **params)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = ([[('op', 'replace'), ('path', '/barney'),
|
|
('value', 'miller')]])
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
# NOTE(bcwaldon):due to limitations of our fake api framework, the name
|
|
# will not actually change - yet in real life it will...
|
|
self.assertEqual('image-3', image.name)
|
|
|
|
def test_update_add_remove_same_prop(self):
|
|
image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3'
|
|
# Adding a property takes precedence over removing a property
|
|
params = {'finn': 'human'}
|
|
remove_props = ['finn']
|
|
image = self.controller.update(image_id, remove_props, **params)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = [[('op', 'add'), ('path', '/finn'), ('value', 'human')]]
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
# NOTE(bcwaldon):due to limitations of our fake api framework, the name
|
|
# will not actually change - yet in real life it will...
|
|
self.assertEqual('image-3', image.name)
|
|
|
|
def test_update_bad_additionalProperty_type(self):
|
|
image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3'
|
|
params = {'name': 'pong', 'bad_prop': False}
|
|
with testtools.ExpectedException(TypeError):
|
|
self.controller.update(image_id, **params)
|
|
|
|
def test_update_add_custom_property(self):
|
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
params = {'color': 'red'}
|
|
image = self.controller.update(image_id, **params)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = [[('op', 'add'), ('path', '/color'), ('value', 'red')]]
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
|
|
def test_update_replace_custom_property(self):
|
|
image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3'
|
|
params = {'color': 'blue'}
|
|
image = self.controller.update(image_id, **params)
|
|
expect_hdrs = {
|
|
'Content-Type': 'application/openstack-images-v2.1-json-patch',
|
|
}
|
|
expect_body = [[('op', 'replace'), ('path', '/color'),
|
|
('value', 'blue')]]
|
|
expect = [
|
|
('GET', '/v2/images/%s' % image_id, {}, None),
|
|
('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body),
|
|
('GET', '/v2/images/%s' % image_id,
|
|
{'x-openstack-request-id': 'req-1234'}, None),
|
|
]
|
|
self.assertEqual(expect, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
|
|
def test_location_ops_when_server_disabled_location_ops(self):
|
|
# Location operations should not be allowed if server has not
|
|
# enabled location related operations. There is no need to check it
|
|
# when do location add, because the check would be done in server side.
|
|
image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
|
estr = 'The administrator has disabled API access to image locations'
|
|
url = 'http://bar.com/'
|
|
meta = {'bar': 'barmeta'}
|
|
|
|
e = self.assertRaises(exc.HTTPBadRequest,
|
|
self.controller.delete_locations,
|
|
image_id, set([url]))
|
|
self.assertIn(estr, str(e))
|
|
|
|
e = self.assertRaises(exc.HTTPBadRequest,
|
|
self.controller.update_location,
|
|
image_id, url, meta)
|
|
self.assertIn(estr, str(e))
|
|
|
|
def _empty_get(self, image_id, headers=None):
|
|
return ('GET', '/v2/images/%s' % image_id,
|
|
headers or {}, None)
|
|
|
|
def _patch_req(self, image_id, patch_body):
|
|
c_type = 'application/openstack-images-v2.1-json-patch'
|
|
data = [sorted(d.items()) for d in patch_body]
|
|
return ('PATCH',
|
|
'/v2/images/%s' % image_id,
|
|
{'Content-Type': c_type},
|
|
data)
|
|
|
|
def test_add_location(self):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
new_loc = {'url': 'http://spam.com/', 'metadata': {'spam': 'ham'}}
|
|
add_patch = {'path': '/locations/-', 'value': new_loc, 'op': 'add'}
|
|
headers = {'x-openstack-request-id': 'req-1234'}
|
|
self.controller.add_location(image_id, **new_loc)
|
|
self.assertEqual([self._patch_req(image_id, [add_patch]),
|
|
self._empty_get(image_id, headers=headers)],
|
|
self.api.calls)
|
|
|
|
@mock.patch.object(images.Controller, '_send_image_update_request',
|
|
side_effect=exc.HTTPBadRequest)
|
|
def test_add_duplicate_location(self, mock_request):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
new_loc = {'url': 'http://foo.com/', 'metadata': {'foo': 'newfoo'}}
|
|
|
|
self.assertRaises(exc.HTTPBadRequest,
|
|
self.controller.add_location,
|
|
image_id, **new_loc)
|
|
|
|
def test_remove_location(self):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
url_set = set(['http://foo.com/', 'http://bar.com/'])
|
|
del_patches = [{'path': '/locations/1', 'op': 'remove'},
|
|
{'path': '/locations/0', 'op': 'remove'}]
|
|
self.controller.delete_locations(image_id, url_set)
|
|
self.assertEqual([self._empty_get(image_id),
|
|
self._patch_req(image_id, del_patches)],
|
|
self.api.calls)
|
|
|
|
def test_remove_missing_location(self):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
url_set = set(['http://spam.ham/'])
|
|
err_str = 'Unknown URL(s): %s' % list(url_set)
|
|
|
|
err = self.assertRaises(exc.HTTPNotFound,
|
|
self.controller.delete_locations,
|
|
image_id, url_set)
|
|
self.assertIn(err_str, str(err))
|
|
|
|
def test_update_location(self):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
new_loc = {'url': 'http://foo.com/', 'metadata': {'spam': 'ham'}}
|
|
headers = {'x-openstack-request-id': 'req-1234'}
|
|
fixture_idx = '/v2/images/%s' % (image_id)
|
|
orig_locations = data_fixtures[fixture_idx]['GET'][1]['locations']
|
|
loc_map = dict([(l['url'], l) for l in orig_locations])
|
|
loc_map[new_loc['url']] = new_loc
|
|
mod_patch = [{'path': '/locations', 'op': 'replace',
|
|
'value': list(loc_map.values())}]
|
|
self.controller.update_location(image_id, **new_loc)
|
|
self.assertEqual([self._empty_get(image_id),
|
|
self._patch_req(image_id, mod_patch),
|
|
self._empty_get(image_id, headers=headers)],
|
|
self.api.calls)
|
|
|
|
def test_update_tags(self):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
tag_map = {'tags': ['tag01', 'tag02', 'tag03']}
|
|
headers = {'x-openstack-request-id': 'req-1234'}
|
|
|
|
image = self.controller.update(image_id, **tag_map)
|
|
|
|
expected_body = [{'path': '/tags', 'op': 'replace',
|
|
'value': tag_map['tags']}]
|
|
expected = [
|
|
self._empty_get(image_id),
|
|
self._patch_req(image_id, expected_body),
|
|
self._empty_get(image_id, headers=headers)
|
|
]
|
|
self.assertEqual(expected, self.api.calls)
|
|
self.assertEqual(image_id, image.id)
|
|
|
|
def test_update_missing_location(self):
|
|
image_id = 'a2b83adc-888e-11e3-8872-78acc0b951d8'
|
|
new_loc = {'url': 'http://spam.com/', 'metadata': {'spam': 'ham'}}
|
|
err_str = 'Unknown URL: %s' % new_loc['url']
|
|
err = self.assertRaises(exc.HTTPNotFound,
|
|
self.controller.update_location,
|
|
image_id, **new_loc)
|
|
self.assertIn(err_str, str(err))
|