281 lines
9.3 KiB
Python
281 lines
9.3 KiB
Python
# Copyright 2016 Massachusetts Open Cloud
|
|
#
|
|
# 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 json
|
|
from six.moves.urllib import parse
|
|
from oslo_config import fixture as config_fixture
|
|
from mixmatch.config import CONF
|
|
|
|
from testtools import testcase
|
|
|
|
from mixmatch import services
|
|
from mixmatch.tests.unit import samples
|
|
|
|
|
|
class Response:
|
|
def __init__(self, text):
|
|
self.text = text
|
|
|
|
|
|
# Source: http://stackoverflow.com/a/9468284
|
|
class Url(object):
|
|
"""A url object that can be compared with other url orbjects
|
|
without regard to the vagaries of encoding, escaping, and ordering
|
|
of parameters in query strings."""
|
|
|
|
def __init__(self, url):
|
|
parts = parse.urlparse(url)
|
|
_query = frozenset(parse.parse_qsl(parts.query))
|
|
_path = parse.unquote_plus(parts.path)
|
|
parts = parts._replace(query=_query, path=_path)
|
|
self.parts = parts
|
|
|
|
def __eq__(self, other):
|
|
return self.parts == other.parts
|
|
|
|
def __hash__(self):
|
|
return hash(self.parts)
|
|
|
|
|
|
VOLUMES = {'default': Response(json.dumps(samples.VOLUME_LIST_V2)),
|
|
'sp1': Response(json.dumps(samples.VOLUME_LIST_V2))}
|
|
|
|
IMAGES = {'default': Response(json.dumps(samples.IMAGE_LIST_V2)),
|
|
'sp1': Response(json.dumps(samples.IMAGE_LIST_V2_2))}
|
|
|
|
SMALLEST_IMAGE = '941882c5-b992-4fa9-bcba-9d25d2f4e3b8'
|
|
EARLIEST_IMAGE = '781b3762-9469-4cec-b58d-3349e5de4e9c'
|
|
SECOND_EARLIEST_IMAGE = '1bea47ed-f6a9-463b-b423-14b9cca9ad27'
|
|
LATEST_IMAGE = '61f655c0-4511-4307-a257-4162c87a5130'
|
|
|
|
IMAGE_PATH = 'http://localhost/image/images'
|
|
|
|
IMAGES_IN_SAMPLE = 5
|
|
VOLUMES_IN_SAMPLE = 2
|
|
|
|
API_VERSIONS = 'v3.2, v2.0, v1'
|
|
NUM_OF_VERSIONS = 3
|
|
IMAGE_UNVERSIONED = 'http://localhost/image'
|
|
IMAGE_VERSIONED = 'http://localhost/image/v3/'
|
|
|
|
|
|
class TestServices(testcase.TestCase):
|
|
def setUp(self):
|
|
super(TestServices, self).setUp()
|
|
self.config_fixture = self.useFixture(config_fixture.Config(conf=CONF))
|
|
|
|
def test_aggregate_key(self):
|
|
# Aggregate 'images'
|
|
response = json.loads(services.aggregate(IMAGES, 'images'))
|
|
self.assertEqual(IMAGES_IN_SAMPLE, len(response['images']))
|
|
|
|
# Aggregate 'volumes'
|
|
response = json.loads(services.aggregate(VOLUMES, 'volumes'))
|
|
self.assertEqual(VOLUMES_IN_SAMPLE, len(response['volumes']))
|
|
|
|
def test_aggregate_limit(self):
|
|
params = {
|
|
'limit': 1
|
|
}
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
self.assertEqual(1, len(response['images']))
|
|
|
|
def test_aggregate_sort_images_ascending(self):
|
|
"""Sort images by smallest size, ascending."""
|
|
params = {
|
|
'sort': 'size:asc'
|
|
}
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
self.assertEqual(response['images'][0]['id'], SMALLEST_IMAGE)
|
|
|
|
def test_aggregate_sort_images_limit(self):
|
|
"""Sort images by smallest size, ascending, limit to 1, alt format."""
|
|
params = {
|
|
'sort_key': 'size',
|
|
'sort_dir': 'asc',
|
|
'limit': 1
|
|
}
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
|
|
# Ensure the smallest is first and there is only 1 entry.
|
|
self.assertEqual(response['images'][0]['id'], SMALLEST_IMAGE)
|
|
self.assertEqual(1, len(response['images']))
|
|
|
|
# Ensure the 'next' url is correct.
|
|
self.assertEqual(
|
|
Url(response['next']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params, marker=SMALLEST_IMAGE)
|
|
))
|
|
)
|
|
|
|
def test_sort_images_date_limit_ascending(self):
|
|
"""Sort images by last update, ascending, limit to 2."""
|
|
params = {
|
|
'sort': 'updated_at:asc',
|
|
'limit': 2
|
|
}
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
|
|
# Check the first and second are the correct ids.
|
|
self.assertEqual(response['images'][0]['id'], EARLIEST_IMAGE)
|
|
self.assertEqual(response['images'][1]['id'], SECOND_EARLIEST_IMAGE)
|
|
self.assertEqual(2, len(response['images']))
|
|
|
|
# Check the next link
|
|
self.assertEqual(
|
|
Url(response['next']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params, marker=SECOND_EARLIEST_IMAGE)
|
|
))
|
|
)
|
|
|
|
def test_sort_images_date_limit_descending(self):
|
|
"""Sort images by last update, descending, limit 1."""
|
|
params = {
|
|
'sort': 'updated_at:desc',
|
|
'limit': 1
|
|
}
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
|
|
# Check the id and size
|
|
self.assertEqual(response['images'][0]['id'], LATEST_IMAGE)
|
|
self.assertEqual(1, len(response['images']))
|
|
|
|
# Check the next link
|
|
self.assertEqual(
|
|
Url(response['next']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params, marker=LATEST_IMAGE)
|
|
))
|
|
)
|
|
|
|
def test_sort_images_date_ascending_pagination(self):
|
|
"""Sort images by last update, ascending, skip the first one."""
|
|
params = {
|
|
'sort': 'updated_at:asc',
|
|
'limit': 1,
|
|
'marker': EARLIEST_IMAGE
|
|
}
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
|
|
# Ensure we skipped the first one
|
|
self.assertEqual(response['images'][0]['id'], SECOND_EARLIEST_IMAGE)
|
|
self.assertEqual(1, len(response['images']))
|
|
|
|
# Next link
|
|
self.assertEqual(
|
|
Url(response['next']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params, marker=SECOND_EARLIEST_IMAGE)
|
|
))
|
|
)
|
|
|
|
# Start link
|
|
self.assertEqual(
|
|
Url(response['start']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params)
|
|
))
|
|
)
|
|
|
|
def test_marker_without_limit(self):
|
|
"""Test marker without limit."""
|
|
params = {
|
|
'sort': 'updated_at:asc',
|
|
'marker': EARLIEST_IMAGE
|
|
}
|
|
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
|
|
# Ensure we skipped the first one
|
|
self.assertEqual(response['images'][0]['id'], SECOND_EARLIEST_IMAGE)
|
|
self.assertEqual(IMAGES_IN_SAMPLE - 1, len(response['images']))
|
|
|
|
# Start link
|
|
self.assertEqual(
|
|
Url(response['start']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params)
|
|
))
|
|
)
|
|
|
|
def test_marker_last(self):
|
|
"""Test marker without limit, nothing to return."""
|
|
params = {
|
|
'sort': 'updated_at:asc',
|
|
'marker': LATEST_IMAGE
|
|
}
|
|
|
|
response = json.loads(services.aggregate(IMAGES, 'images',
|
|
params, IMAGE_PATH))
|
|
|
|
# Ensure we skipped the first one
|
|
self.assertEqual(0, len(response['images']))
|
|
|
|
# Start link
|
|
self.assertEqual(
|
|
Url(response['start']),
|
|
Url(self._prepare_url(
|
|
IMAGE_PATH,
|
|
self._prepare_params(params)
|
|
))
|
|
)
|
|
|
|
def test_list_api_versions(self):
|
|
|
|
self.config_fixture.load_raw_values(group='proxy',
|
|
image_api_versions=API_VERSIONS,
|
|
volume_api_versions=API_VERSIONS)
|
|
|
|
# List image api
|
|
response = json.loads(services.list_api_versions('image',
|
|
IMAGE_UNVERSIONED))
|
|
current_version = response['versions'][0]['id']
|
|
current_version_status = response['versions'][0]['status']
|
|
current_version_url = response['versions'][0]['links'][0]['href']
|
|
|
|
self.assertEqual(NUM_OF_VERSIONS, len(response['versions']))
|
|
self.assertEqual(current_version, 'v3.2')
|
|
self.assertEqual(current_version_status, 'CURRENT')
|
|
self.assertEqual(
|
|
Url(current_version_url),
|
|
Url(IMAGE_VERSIONED))
|
|
|
|
@staticmethod
|
|
def _prepare_params(user_params, marker=None):
|
|
params = user_params.copy()
|
|
if marker:
|
|
params['marker'] = marker
|
|
else:
|
|
params.pop('marker', None)
|
|
return params
|
|
|
|
@staticmethod
|
|
def _prepare_url(url, params):
|
|
return '%s?%s' % (url, parse.urlencode(params))
|