Merge "Apply image location selection strategy"

This commit is contained in:
Jenkins 2014-02-10 13:03:27 +00:00 committed by Gerrit Code Review
commit 6582d5367c
7 changed files with 198 additions and 2 deletions

View File

@ -16,6 +16,7 @@
"""Policy Engine For Glance""" """Policy Engine For Glance"""
import copy
import os.path import os.path
from oslo.config import cfg from oslo.config import cfg
@ -304,6 +305,14 @@ class ImageLocationsProxy(object):
self.context = context self.context = context
self.policy = policy self.policy = policy
def __copy__(self):
return type(self)(self.locations, self.context, self.policy)
def __deepcopy__(self, memo):
# NOTE(zhiyan): Only copy location entries, others can be reused.
return type(self)(copy.deepcopy(self.locations, memo),
self.context, self.policy)
def _get_checker(action, func_name): def _get_checker(action, func_name):
def _checker(self, *args, **kwargs): def _checker(self, *args, **kwargs):
self.policy.enforce(self.context, action, {}) self.policy.enforce(self.context, action, {})

View File

@ -21,6 +21,7 @@ import webob.exc
from glance.api import policy from glance.api import policy
from glance.common import exception from glance.common import exception
from glance.common import location_strategy
from glance.common import utils from glance.common import utils
from glance.common import wsgi from glance.common import wsgi
import glance.db import glance.db
@ -582,7 +583,10 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
image_view['locations'] = [] image_view['locations'] = []
if CONF.show_image_direct_url and image.locations: if CONF.show_image_direct_url and image.locations:
image_view['direct_url'] = image.locations[0]['url'] # Choose best location configured strategy
best_location = (
location_strategy.choose_best_location(image.locations))
image_view['direct_url'] = best_location['url']
image_view['tags'] = list(image.tags) image_view['tags'] = list(image.tags)
image_view['self'] = self._get_image_href(image) image_view['self'] = self._get_image_href(image)

View File

@ -20,6 +20,7 @@ from oslo.config import cfg
from glance.common import crypt from glance.common import crypt
from glance.common import exception from glance.common import exception
from glance.common import location_strategy
import glance.domain import glance.domain
import glance.domain.proxy import glance.domain.proxy
from glance.openstack.common import importutils from glance.openstack.common import importutils
@ -105,7 +106,7 @@ class ImageRepo(object):
min_disk=db_image['min_disk'], min_disk=db_image['min_disk'],
min_ram=db_image['min_ram'], min_ram=db_image['min_ram'],
protected=db_image['protected'], protected=db_image['protected'],
locations=locations, locations=location_strategy.get_ordered_locations(locations),
checksum=db_image['checksum'], checksum=db_image['checksum'],
owner=db_image['owner'], owner=db_image['owner'],
disk_format=db_image['disk_format'], disk_format=db_image['disk_format'],

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
from oslo.config import cfg from oslo.config import cfg
@ -242,6 +243,14 @@ class QuotaImageLocationsProxy(object):
self.db_api) self.db_api)
_enforce_image_location_quota(self.image, locations) _enforce_image_location_quota(self.image, locations)
def __copy__(self):
return type(self)(self.image, self.context, self.db_api)
def __deepcopy__(self, memo):
# NOTE(zhiyan): Only copy location entries, others can be reused.
self.image.locations = copy.deepcopy(self.locations, memo)
return type(self)(self.image, self.context, self.db_api)
def append(self, object): def append(self, object):
self._check_user_storage_quota([object]) self._check_user_storage_quota([object])
return self.locations.append(object) return self.locations.append(object)

View File

@ -14,6 +14,7 @@
# under the License. # under the License.
import collections import collections
import copy
import sys import sys
from oslo.config import cfg from oslo.config import cfg
@ -622,6 +623,15 @@ class StoreLocations(collections.MutableSequence):
def __iter__(self): def __iter__(self):
return iter(self.value) return iter(self.value)
def __copy__(self):
return type(self)(self.image_proxy, self.value)
def __deepcopy__(self, memo):
# NOTE(zhiyan): Only copy location entries, others can be reused.
value = copy.deepcopy(self.value, memo)
self.image_proxy.image.locations = value
return type(self)(self.image_proxy, value)
def _locations_proxy(target, attr): def _locations_proxy(target, attr):
""" """

View File

@ -322,6 +322,9 @@ class ApiServer(Server):
self.user_storage_quota = 0 self.user_storage_quota = 0
self.lock_path = self.test_dir self.lock_path = self.test_dir
self.location_strategy = 'location_order'
self.store_type_location_strategy_preference = ""
self.conf_base = """[DEFAULT] self.conf_base = """[DEFAULT]
verbose = %(verbose)s verbose = %(verbose)s
debug = %(debug)s debug = %(debug)s
@ -379,8 +382,11 @@ image_member_quota=%(image_member_quota)s
image_property_quota=%(image_property_quota)s image_property_quota=%(image_property_quota)s
image_tag_quota=%(image_tag_quota)s image_tag_quota=%(image_tag_quota)s
image_location_quota=%(image_location_quota)s image_location_quota=%(image_location_quota)s
location_strategy=%(location_strategy)s
[paste_deploy] [paste_deploy]
flavor = %(deployment_flavor)s flavor = %(deployment_flavor)s
[store_type_location_strategy]
store_type_preference = %(store_type_location_strategy_preference)s
""" """
self.paste_conf_base = """[pipeline:glance-api] self.paste_conf_base = """[pipeline:glance-api]
pipeline = versionnegotiation gzip unauthenticated-context rootapp pipeline = versionnegotiation gzip unauthenticated-context rootapp

View File

@ -14,12 +14,15 @@
# under the License. # under the License.
import os import os
import signal
import tempfile
import uuid import uuid
import requests import requests
from glance.openstack.common import jsonutils from glance.openstack.common import jsonutils
from glance.tests import functional from glance.tests import functional
from glance.tests.functional.store import test_http
TENANT1 = str(uuid.uuid4()) TENANT1 = str(uuid.uuid4())
@ -1976,6 +1979,160 @@ class TestImageDirectURLVisibility(functional.FunctionalTest):
self.stop_servers() self.stop_servers()
class TestImageLocationSelectionStrategy(functional.FunctionalTest):
def setUp(self):
super(TestImageLocationSelectionStrategy, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.foo_image_file = tempfile.NamedTemporaryFile()
self.foo_image_file.write("foo image file")
self.foo_image_file.flush()
self.addCleanup(self.foo_image_file.close)
ret = test_http.http_server("foo_image_id", "foo_image")
self.http_server_pid, self.http_port = ret
def tearDown(self):
if self.http_server_pid is not None:
os.kill(self.http_server_pid, signal.SIGKILL)
super(TestImageLocationSelectionStrategy, self).tearDown()
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_image_locations_with_order_strategy(self):
self.api_server.show_image_direct_url = True
self.api_server.show_multiple_locations = True
self.image_location_quota = 10
self.api_server.location_strategy = 'location_order'
preference = "http, swift, filesystem"
self.api_server.store_type_location_strategy_preference = preference
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Image locations should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = jsonutils.loads(response.text)
self.assertTrue('locations' in image)
self.assertTrue(image["locations"] == [])
# Update image locations via PATCH
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
values = [{'url': 'file://%s' % self.foo_image_file.name,
'metadata': {'idx': '1'}},
{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
'metadata': {'idx': '0'}}]
doc = [{'op': 'replace',
'path': '/locations',
'value': values}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(200, response.status_code)
# Image locations should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = jsonutils.loads(response.text)
self.assertTrue('locations' in image)
self.assertEqual(image['locations'], values)
self.assertTrue('direct_url' in image)
self.assertEqual(image['direct_url'], values[0]['url'])
self.stop_servers()
def test_image_locatons_with_store_type_strategy(self):
self.api_server.show_image_direct_url = True
self.api_server.show_multiple_locations = True
self.image_location_quota = 10
self.api_server.location_strategy = 'store_type'
preference = "http, swift, filesystem"
self.api_server.store_type_location_strategy_preference = preference
self.start_servers(**self.__dict__.copy())
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the image id
image = jsonutils.loads(response.text)
image_id = image['id']
# Image locations should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = jsonutils.loads(response.text)
self.assertTrue('locations' in image)
self.assertTrue(image["locations"] == [])
# Update image locations via PATCH
path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type})
values = [{'url': 'file://%s' % self.foo_image_file.name,
'metadata': {'idx': '1'}},
{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
'metadata': {'idx': '0'}}]
doc = [{'op': 'replace',
'path': '/locations',
'value': values}]
data = jsonutils.dumps(doc)
response = requests.patch(path, headers=headers, data=data)
self.assertEqual(200, response.status_code)
values.sort(key=lambda loc: int(loc['metadata']['idx']))
# Image locations should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = jsonutils.loads(response.text)
self.assertTrue('locations' in image)
self.assertEqual(image['locations'], values)
self.assertTrue('direct_url' in image)
self.assertEqual(image['direct_url'], values[0]['url'])
self.stop_servers()
class TestImageMembers(functional.FunctionalTest): class TestImageMembers(functional.FunctionalTest):
def setUp(self): def setUp(self):