Remove user and key from location in swift
The image locations table stores the swift url for images which includes the user and key values. This if exposed, can cause security risk. Hence this patch, santizies that information out of the location before storing and plugs it back in when it is required. Introduced a new configuration file that supports multiple swift account references. It has the credentials and authurl for each store. It is specified using 'swift_store_config_file'. In addition, this patch does the following things: Differentiate user and system created swift locations Currently we do not differentiate between user supplied uri and system created locations that have the account reference. This patch introduces new scheme: 'swift+config' for this purpose. Image create in V1 should validate the uri in case where location isn't specified. This patch ensures that a store is not set while creating an image or updating it. Related to bp remove-sensitive-data-from-locations Implements blueprint: support-multiple-swift-backends Implements bp: v1-image-create-should-validate-the-location-uri DocImpact Co-authored by: sridevik <sridevi.koushik@rackspace.com>, iccha-sethi <iccha.sethi@rackspace.com>, amalabasha <amala.alungal@rackspace.com> Change-Id: I75af34145521f533dcd6f5fd7690f5a68f3b44b3
This commit is contained in:
parent
95fc85d5bf
commit
63195aaa3b
doc/source
etc
glance
@ -636,13 +636,33 @@ proxy).
|
||||
|
||||
The number of times a Swift download will be retried before the request
|
||||
fails.
|
||||
Optional. Default: ``0``
|
||||
|
||||
Configuring Multiple Swift Accounts/Stores
|
||||
------------------------------------------
|
||||
|
||||
In order to not store Swift account credentials in the database, and to
|
||||
have support for multiple accounts (or multiple Swift backing stores), a
|
||||
reference is stored in the database and the corresponding configuration
|
||||
(credentials/ parameters) details are stored in the configuration file.
|
||||
Optional. Default: not enabled.
|
||||
|
||||
The location for this file is specified using the ``swift_store_config_file`` config file
|
||||
in the section ``[DEFAULT]``. **If an incorrect value is specified, Glance API Swift store
|
||||
service will not be configured.**
|
||||
* ``swift_store_config_file=PATH``
|
||||
|
||||
`This option is specific to the Swift storage backend.`
|
||||
|
||||
* ``default_swift_reference=DEFAULT_REFERENCE``
|
||||
|
||||
Required when multiple Swift accounts/backing stores are configured.
|
||||
|
||||
Can only be specified in configuration files.
|
||||
|
||||
`This option is specific to the Swift storage backend.`
|
||||
|
||||
Optional. Default: ``0``
|
||||
|
||||
It is the default swift reference that is used to add any new images.
|
||||
* ``swift_store_auth_insecure``
|
||||
|
||||
If True, bypass SSL certificate verification for Swift.
|
||||
|
@ -327,6 +327,15 @@ swift_store_create_container_on_put = False
|
||||
# the maximum object size in Swift, which is 5GB
|
||||
swift_store_large_object_size = 5120
|
||||
|
||||
# swift_store_config_file = glance-swift.conf
|
||||
# This file contains references for each of the configured
|
||||
# Swift accounts/backing stores. If used, this option can prevent
|
||||
# credentials being stored in the database. Using Swift references
|
||||
# is disabled if this config is left blank.
|
||||
|
||||
# The reference to the default Swift parameters to use for adding new images.
|
||||
# default_swift_reference = 'ref1'
|
||||
|
||||
# When doing a large object manifest, what size, in MB, should
|
||||
# Glance write chunks to Swift? This amount of data is written
|
||||
# to a temporary disk buffer during the process of chunking
|
||||
|
21
etc/glance-swift.conf.sample
Normal file
21
etc/glance-swift.conf.sample
Normal file
@ -0,0 +1,21 @@
|
||||
# glance-swift.conf.sample
|
||||
#
|
||||
# This file is an example config file when
|
||||
# multiple swift accounts/backing stores are enabled.
|
||||
#
|
||||
# Specify the reference name in []
|
||||
# For each section, specify the auth_address, user and key.
|
||||
#
|
||||
# WARNING:
|
||||
# * If any of auth_address, user or key is not specified,
|
||||
# the glance-api's swift store will fail to configure
|
||||
|
||||
[ref1]
|
||||
user = tenant:user1
|
||||
key = key1
|
||||
auth_address = auth123@example.com
|
||||
|
||||
[ref2]
|
||||
user = user2
|
||||
key = key2
|
||||
auth_address = http://auth345@example.com
|
@ -51,6 +51,7 @@ from glance.store import get_known_stores
|
||||
from glance.store import get_size_from_backend
|
||||
from glance.store import get_store_from_location
|
||||
from glance.store import get_store_from_scheme
|
||||
from glance.store import validate_location
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
|
||||
@ -707,6 +708,12 @@ class Controller(controller.BaseController):
|
||||
self.pool.spawn_n(self._upload_and_activate, req, image_meta)
|
||||
else:
|
||||
if location:
|
||||
try:
|
||||
validate_location(req.context, location)
|
||||
except (exception.BadStoreUri) as bse:
|
||||
raise HTTPBadRequest(explanation=unicode(bse),
|
||||
request=req)
|
||||
|
||||
self._validate_image_for_activation(req, image_id, image_meta)
|
||||
image_size_meta = image_meta.get('size')
|
||||
if image_size_meta:
|
||||
|
@ -142,6 +142,10 @@ class InvalidPropertyProtectionConfiguration(Invalid):
|
||||
message = _("Invalid configuration in property protection file.")
|
||||
|
||||
|
||||
class InvalidSwiftStoreConfiguration(Invalid):
|
||||
message = _("Invalid configuration in glance-swift conf file.")
|
||||
|
||||
|
||||
class InvalidFilterRangeValue(Invalid):
|
||||
message = _("Unable to filter using the specified range.")
|
||||
|
||||
|
99
glance/common/swift_store_utils.py
Normal file
99
glance/common/swift_store_utils.py
Normal file
@ -0,0 +1,99 @@
|
||||
# Copyright 2014 Rackspace
|
||||
#
|
||||
# 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 ConfigParser
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from glance.common import exception
|
||||
from glance.openstack.common import log as logging
|
||||
|
||||
swift_opts = [
|
||||
cfg.StrOpt('default_swift_reference',
|
||||
default="ref1",
|
||||
help=_('The reference to the default swift account/backing '
|
||||
'store parameters to use for adding new images.')),
|
||||
cfg.StrOpt('swift_store_auth_address',
|
||||
help=_('The address where the Swift authentication service '
|
||||
'is listening.(deprecated)')),
|
||||
cfg.StrOpt('swift_store_user', secret=True,
|
||||
help=_('The user to authenticate against the Swift '
|
||||
'authentication service (deprecated)')),
|
||||
cfg.StrOpt('swift_store_key', secret=True,
|
||||
help=_('Auth key for the user authenticating against the '
|
||||
'Swift authentication service. (deprecated)')),
|
||||
cfg.StrOpt('swift_store_config_file', secret=True,
|
||||
help=_('The config file that has the swift account(s)'
|
||||
'configs.')),
|
||||
]
|
||||
|
||||
# NOTE(bourke): The default dict_type is collections.OrderedDict in py27, but
|
||||
# we must set manually for compatibility with py26
|
||||
CONFIG = ConfigParser.SafeConfigParser(dict_type=OrderedDict)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(swift_opts)
|
||||
|
||||
|
||||
def is_multiple_swift_store_accounts_enabled():
|
||||
if CONF.swift_store_config_file is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class SwiftParams(object):
|
||||
def __init__(self):
|
||||
if is_multiple_swift_store_accounts_enabled():
|
||||
self.params = self._load_config()
|
||||
else:
|
||||
self.params = self._form_default_params()
|
||||
|
||||
def _form_default_params(self):
|
||||
default = {}
|
||||
if CONF.swift_store_user and CONF.swift_store_key \
|
||||
and CONF.swift_store_auth_address:
|
||||
default['user'] = CONF.swift_store_user
|
||||
default['key'] = CONF.swift_store_key
|
||||
default['auth_address'] = CONF.swift_store_auth_address
|
||||
return {CONF.default_swift_reference: default}
|
||||
return {}
|
||||
|
||||
def _load_config(self):
|
||||
try:
|
||||
conf_file = CONF.find_file(CONF.swift_store_config_file)
|
||||
CONFIG.read(conf_file)
|
||||
except Exception as e:
|
||||
msg = (_("swift config file %(conf_file)s:%(exc)s not found") %
|
||||
{'conf_file': CONF.swift_store_config_file, 'exc': e})
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidSwiftStoreConfiguration()
|
||||
account_params = {}
|
||||
account_references = CONFIG.sections()
|
||||
for ref in account_references:
|
||||
reference = {}
|
||||
try:
|
||||
reference['auth_address'] = CONFIG.get(ref, 'auth_address')
|
||||
reference['user'] = CONFIG.get(ref, 'user')
|
||||
reference['key'] = CONFIG.get(ref, 'key')
|
||||
account_params[ref] = reference
|
||||
except (ValueError, SyntaxError, ConfigParser.NoOptionError) as e:
|
||||
LOG.exception(_("Invalid format of swift store config"
|
||||
"cfg"))
|
||||
return account_params
|
@ -289,6 +289,12 @@ def get_size_from_backend(context, uri):
|
||||
return store.get_size(loc)
|
||||
|
||||
|
||||
def validate_location(context, uri):
|
||||
loc = location.get_location_from_uri(uri)
|
||||
store = get_store_from_uri(context, uri, loc)
|
||||
store.validate_location(uri)
|
||||
|
||||
|
||||
def delete_from_backend(context, uri, **kwargs):
|
||||
"""Removes chunks of data from backend specified by uri"""
|
||||
loc = location.get_location_from_uri(uri)
|
||||
|
@ -92,6 +92,13 @@ class Store(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
def validate_location(self, location):
|
||||
"""
|
||||
Takes a location and validates it for the presence
|
||||
of any account references
|
||||
"""
|
||||
pass
|
||||
|
||||
def get(self, location):
|
||||
"""
|
||||
Takes a `glance.store.location.Location` object that indicates
|
||||
|
@ -23,9 +23,11 @@ import math
|
||||
|
||||
from oslo.config import cfg
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import urllib
|
||||
|
||||
from glance.common import auth
|
||||
from glance.common import exception
|
||||
from glance.common import swift_store_utils
|
||||
from glance.openstack.common import excutils
|
||||
import glance.openstack.common.log as logging
|
||||
import glance.store
|
||||
@ -48,19 +50,10 @@ swift_opts = [
|
||||
cfg.BoolOpt('swift_enable_snet', default=False,
|
||||
help=_('Whether to use ServiceNET to communicate with the '
|
||||
'Swift storage servers.')),
|
||||
cfg.StrOpt('swift_store_auth_address',
|
||||
help=_('The address where the Swift authentication service '
|
||||
'is listening.')),
|
||||
cfg.StrOpt('swift_store_user', secret=True,
|
||||
help=_('The user to authenticate against the Swift '
|
||||
'authentication service.')),
|
||||
cfg.StrOpt('swift_store_key', secret=True,
|
||||
help=_('Auth key for the user authenticating against the '
|
||||
'Swift authentication service.')),
|
||||
cfg.StrOpt('swift_store_auth_version', default='2',
|
||||
help=_('Version of the authentication service to use. '
|
||||
'Valid versions are 2 for keystone and 1 for swauth '
|
||||
'and rackspace.')),
|
||||
'and rackspace. (deprecated)')),
|
||||
cfg.BoolOpt('swift_store_auth_insecure', default=False,
|
||||
help=_('If True, swiftclient won\'t check for a valid SSL '
|
||||
'certificate when authenticating.')),
|
||||
@ -113,6 +106,8 @@ swift_opts = [
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(swift_opts)
|
||||
|
||||
SWIFT_STORE_REF_PARAMS = swift_store_utils.SwiftParams().params
|
||||
|
||||
|
||||
def swift_retry_iter(resp_iter, length, store, location):
|
||||
length = length if length else (resp_iter.len
|
||||
@ -179,11 +174,10 @@ class StoreLocation(glance.store.location.StoreLocation):
|
||||
|
||||
def _get_credstring(self):
|
||||
if self.user and self.key:
|
||||
return '%s:%s@' % (urlparse.quote(self.user),
|
||||
urlparse.quote(self.key))
|
||||
return '%s:%s' % (urllib.quote(self.user), urllib.quote(self.key))
|
||||
return ''
|
||||
|
||||
def get_uri(self):
|
||||
def get_uri(self, credentials_included=True):
|
||||
auth_or_store_url = self.auth_or_store_url
|
||||
if auth_or_store_url.startswith('http://'):
|
||||
auth_or_store_url = auth_or_store_url[len('http://'):]
|
||||
@ -195,36 +189,42 @@ class StoreLocation(glance.store.location.StoreLocation):
|
||||
container = self.container.strip('/')
|
||||
obj = self.obj.strip('/')
|
||||
|
||||
if not credentials_included:
|
||||
#Used only in case of an add
|
||||
#Get the current store from config
|
||||
store = CONF.default_swift_reference
|
||||
|
||||
return '%s://%s/%s/%s' % ('swift+config', store, container, obj)
|
||||
if self.scheme == 'swift+config':
|
||||
if self.ssl_enabled == True:
|
||||
self.scheme = 'swift+https'
|
||||
else:
|
||||
self.scheme = 'swift+http'
|
||||
if credstring != '':
|
||||
credstring = "%s@" % credstring
|
||||
return '%s://%s%s/%s/%s' % (self.scheme, credstring, auth_or_store_url,
|
||||
container, obj)
|
||||
|
||||
def parse_uri(self, uri):
|
||||
"""
|
||||
Parse URLs. This method fixes an issue where credentials specified
|
||||
in the URL are interpreted differently in Python 2.6.1+ than prior
|
||||
versions of Python. It also deals with the peculiarity that new-style
|
||||
Swift URIs have where a username can contain a ':', like so:
|
||||
def _get_conf_value_from_account_ref(self, netloc):
|
||||
try:
|
||||
self.user = SWIFT_STORE_REF_PARAMS[netloc]['user']
|
||||
self.key = SWIFT_STORE_REF_PARAMS[netloc]['key']
|
||||
netloc = SWIFT_STORE_REF_PARAMS[netloc]['auth_address']
|
||||
self.ssl_enabled = True
|
||||
if netloc != '':
|
||||
if netloc.startswith('http://'):
|
||||
self.ssl_enabled = False
|
||||
netloc = netloc[len('http://'):]
|
||||
elif netloc.startswith('https://'):
|
||||
netloc = netloc[len('https://'):]
|
||||
except KeyError:
|
||||
reason = _("Badly formed Swift URI. Credentials not found for"
|
||||
"account reference")
|
||||
LOG.debug(reason)
|
||||
raise exception.BadStoreUri()
|
||||
return netloc
|
||||
|
||||
swift://account:user:pass@authurl.com/container/obj
|
||||
"""
|
||||
# Make sure that URIs that contain multiple schemes, such as:
|
||||
# swift://user:pass@http://authurl.com/v1/container/obj
|
||||
# are immediately rejected.
|
||||
if uri.count('://') != 1:
|
||||
reason = ("URI cannot contain more than one occurrence "
|
||||
"of a scheme. If you have specified a URI like "
|
||||
"swift://user:pass@http://authurl.com/v1/container/obj"
|
||||
", you need to change it to use the "
|
||||
"swift+http:// scheme, like so: "
|
||||
"swift+http://user:pass@authurl.com/v1/container/obj")
|
||||
LOG.debug("Invalid store URI: %(reason)s", {'reason': reason})
|
||||
raise exception.BadStoreUri(message=reason)
|
||||
|
||||
pieces = urlparse.urlparse(uri)
|
||||
assert pieces.scheme in ('swift', 'swift+http', 'swift+https')
|
||||
self.scheme = pieces.scheme
|
||||
netloc = pieces.netloc
|
||||
path = pieces.path.lstrip('/')
|
||||
def _form_uri_parts(self, netloc, path):
|
||||
if netloc != '':
|
||||
# > Python 2.6.1
|
||||
if '@' in netloc:
|
||||
@ -242,16 +242,24 @@ class StoreLocation(glance.store.location.StoreLocation):
|
||||
path = path[path.find('/'):].strip('/')
|
||||
if creds:
|
||||
cred_parts = creds.split(':')
|
||||
if len(cred_parts) != 2:
|
||||
if len(cred_parts) < 2:
|
||||
reason = "Badly formed credentials in Swift URI."
|
||||
LOG.debug(reason)
|
||||
raise exception.BadStoreUri()
|
||||
user, key = cred_parts
|
||||
self.user = urlparse.unquote(user)
|
||||
self.key = urlparse.unquote(key)
|
||||
key = cred_parts.pop()
|
||||
user = ':'.join(cred_parts)
|
||||
creds = urllib.unquote(creds)
|
||||
try:
|
||||
self.user, self.key = creds.rsplit(':', 1)
|
||||
except exception.BadStoreConfiguration:
|
||||
self.user = urllib.unquote(user)
|
||||
self.key = urllib.unquote(key)
|
||||
else:
|
||||
self.user = None
|
||||
self.key = None
|
||||
return netloc, path
|
||||
|
||||
def _form_auth_or_store_url(self, netloc, path):
|
||||
path_parts = path.split('/')
|
||||
try:
|
||||
self.obj = path_parts.pop()
|
||||
@ -265,11 +273,52 @@ class StoreLocation(glance.store.location.StoreLocation):
|
||||
LOG.debug(reason)
|
||||
raise exception.BadStoreUri()
|
||||
|
||||
def parse_uri(self, uri):
|
||||
"""
|
||||
Parse URLs. This method fixes an issue where credentials specified
|
||||
in the URL are interpreted differently in Python 2.6.1+ than prior
|
||||
versions of Python. It also deals with the peculiarity that new-style
|
||||
Swift URIs have where a username can contain a ':', like so:
|
||||
|
||||
swift://account:user:pass@authurl.com/container/obj
|
||||
and for system created locations with account reference
|
||||
swift+config://account_reference/container/obj
|
||||
"""
|
||||
# Make sure that URIs that contain multiple schemes, such as:
|
||||
# swift://user:pass@http://authurl.com/v1/container/obj
|
||||
# are immediately rejected.
|
||||
if uri.count('://') != 1:
|
||||
reason = _("URI cannot contain more than one occurrence "
|
||||
"of a scheme. If you have specified a URI like "
|
||||
"swift://user:pass@http://authurl.com/v1/container/obj"
|
||||
", you need to change it to use the "
|
||||
"swift+http:// scheme, like so: "
|
||||
"swift+http://user:pass@authurl.com/v1/container/obj")
|
||||
LOG.debug("Invalid store URI: %(reason)s", {'reason': reason})
|
||||
raise exception.BadStoreUri(message=reason)
|
||||
|
||||
pieces = urlparse.urlparse(uri)
|
||||
assert pieces.scheme in ('swift', 'swift+http', 'swift+https',
|
||||
'swift+config')
|
||||
|
||||
self.scheme = pieces.scheme
|
||||
netloc = pieces.netloc
|
||||
path = pieces.path.lstrip('/')
|
||||
|
||||
# NOTE(Sridevi): Fix to map the account reference to the
|
||||
# corresponding CONF value
|
||||
if self.scheme == 'swift+config':
|
||||
netloc = self._get_conf_value_from_account_ref(netloc)
|
||||
else:
|
||||
netloc, path = self._form_uri_parts(netloc, path)
|
||||
|
||||
self._form_auth_or_store_url(netloc, path)
|
||||
|
||||
@property
|
||||
def swift_url(self):
|
||||
"""
|
||||
Creates a fully-qualified auth url that the Swift client library can
|
||||
use. The scheme for the auth_url is determined using the scheme
|
||||
Creates a fully-qualified auth address that the Swift client library
|
||||
can use. The scheme for the auth_address is determined using the scheme
|
||||
included in the `location` field.
|
||||
|
||||
HTTPS is assumed, unless 'swift+http' is specified.
|
||||
@ -277,6 +326,11 @@ class StoreLocation(glance.store.location.StoreLocation):
|
||||
if self.auth_or_store_url.startswith('http'):
|
||||
return self.auth_or_store_url
|
||||
else:
|
||||
if self.scheme == 'swift+config':
|
||||
if self.ssl_enabled == True:
|
||||
self.scheme = 'swift+https'
|
||||
else:
|
||||
self.scheme = 'swift+http'
|
||||
if self.scheme in ('swift+https', 'swift'):
|
||||
auth_scheme = 'https://'
|
||||
else:
|
||||
@ -296,7 +350,7 @@ class BaseStore(glance.store.base.Store):
|
||||
CHUNKSIZE = 65536
|
||||
|
||||
def get_schemes(self):
|
||||
return ('swift+https', 'swift', 'swift+http')
|
||||
return ('swift+https', 'swift', 'swift+http', 'swift+config')
|
||||
|
||||
def configure(self):
|
||||
_obj_size = self._option_get('swift_store_large_object_size')
|
||||
@ -363,8 +417,8 @@ class BaseStore(glance.store.base.Store):
|
||||
def _option_get(self, param):
|
||||
result = getattr(CONF, param)
|
||||
if not result:
|
||||
reason = (_("Could not find %(param)s in configuration "
|
||||
"options.") % {'param': param})
|
||||
reason = (_("Could not find %(param)s in configuration options.")
|
||||
% param)
|
||||
LOG.error(reason)
|
||||
raise exception.BadStoreConfiguration(store_name="swift",
|
||||
reason=reason)
|
||||
@ -491,8 +545,13 @@ class BaseStore(glance.store.base.Store):
|
||||
# image data. We *really* should consider NOT returning
|
||||
# the location attribute from GET /images/<ID> and
|
||||
# GET /images/details
|
||||
if swift_store_utils.is_multiple_swift_store_accounts_enabled():
|
||||
include_creds = False
|
||||
else:
|
||||
include_creds = True
|
||||
|
||||
return (location.get_uri(), image_size, obj_etag, {})
|
||||
return (location.get_uri(credentials_included=include_creds),
|
||||
image_size, obj_etag, {})
|
||||
except swiftclient.ClientException as e:
|
||||
if e.http_status == httplib.CONFLICT:
|
||||
raise exception.Duplicate(_("Swift already has an image at "
|
||||
@ -587,14 +646,29 @@ class SingleTenantStore(BaseStore):
|
||||
self.auth_version = self._option_get('swift_store_auth_version')
|
||||
|
||||
def configure_add(self):
|
||||
self.auth_address = self._option_get('swift_store_auth_address')
|
||||
default_swift_reference = \
|
||||
SWIFT_STORE_REF_PARAMS.get(
|
||||
CONF.default_swift_reference)
|
||||
if default_swift_reference:
|
||||
self.auth_address = default_swift_reference.get('auth_address')
|
||||
if (not default_swift_reference) or (not self.auth_address):
|
||||
reason = _("A value for swift_store_auth_address is required.")
|
||||
LOG.error(reason)
|
||||
raise exception.BadStoreConfiguration(store_name="swift",
|
||||
reason=reason)
|
||||
if self.auth_address.startswith('http://'):
|
||||
self.scheme = 'swift+http'
|
||||
else:
|
||||
self.scheme = 'swift+https'
|
||||
self.container = CONF.swift_store_container
|
||||
self.user = self._option_get('swift_store_user')
|
||||
self.key = self._option_get('swift_store_key')
|
||||
self.user = default_swift_reference.get('user')
|
||||
self.key = default_swift_reference.get('key')
|
||||
|
||||
if not (self.user or self.key):
|
||||
reason = _("A value for swift_store_ref_params is required.")
|
||||
LOG.error(reason)
|
||||
raise exception.BadStoreConfiguration(store_name="swift",
|
||||
reason=reason)
|
||||
|
||||
def create_location(self, image_id):
|
||||
specs = {'scheme': self.scheme,
|
||||
@ -605,6 +679,12 @@ class SingleTenantStore(BaseStore):
|
||||
'key': self.key}
|
||||
return StoreLocation(specs)
|
||||
|
||||
def validate_location(self, uri):
|
||||
pieces = urlparse.urlparse(uri)
|
||||
if pieces.scheme in ['swift+config']:
|
||||
reason = (_("Location credentials are invalid"))
|
||||
raise exception.BadStoreUri(message=reason)
|
||||
|
||||
def get_connection(self, location):
|
||||
if not location.user:
|
||||
reason = "Location is missing user:password information."
|
||||
|
34
glance/tests/etc/glance-swift.conf
Normal file
34
glance/tests/etc/glance-swift.conf
Normal file
@ -0,0 +1,34 @@
|
||||
[ref1]
|
||||
user = tenant:user1
|
||||
key = key1
|
||||
auth_address = example.com
|
||||
|
||||
[ref2]
|
||||
user = user2
|
||||
key = key2
|
||||
auth_address = http://example.com
|
||||
|
||||
[store_2]
|
||||
user = tenant:user1
|
||||
key = key1
|
||||
auth_address= https://localhost:8080
|
||||
|
||||
[store_3]
|
||||
user= tenant:user2
|
||||
key= key2
|
||||
auth_address= https://localhost:8080
|
||||
|
||||
[store_4]
|
||||
user = tenant:user1
|
||||
key = key1
|
||||
auth_address = http://localhost:80
|
||||
|
||||
[store_5]
|
||||
user = tenant:user1
|
||||
key = key1
|
||||
auth_address = http://localhost
|
||||
|
||||
[store_6]
|
||||
user = tenant:user1
|
||||
key = key1
|
||||
auth_address = https://localhost/v1
|
89
glance/tests/unit/common/test_swift_store_utils.py
Normal file
89
glance/tests/unit/common/test_swift_store_utils.py
Normal file
@ -0,0 +1,89 @@
|
||||
# Copyright 2014 Rackspace
|
||||
#
|
||||
# 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 fixtures
|
||||
|
||||
from glance.common import exception
|
||||
from glance.common import swift_store_utils
|
||||
from glance.tests.unit import base
|
||||
|
||||
|
||||
class TestSwiftParams(base.IsolatedUnitTest):
|
||||
|
||||
def setUp(self):
|
||||
conf_file = "glance-swift.conf"
|
||||
test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.swift_config_file = self._copy_data_file(conf_file, test_dir)
|
||||
self.config(swift_store_config_file=self.swift_config_file)
|
||||
super(TestSwiftParams, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestSwiftParams, self).tearDown()
|
||||
|
||||
def test_multiple_swift_account_enabled(self):
|
||||
self.config(swift_store_config_file="glance-swift.conf")
|
||||
self.assertTrue(
|
||||
swift_store_utils.is_multiple_swift_store_accounts_enabled())
|
||||
|
||||
def test_multiple_swift_account_disabled(self):
|
||||
self.config(swift_store_config_file=None)
|
||||
self.assertFalse(
|
||||
swift_store_utils.is_multiple_swift_store_accounts_enabled())
|
||||
|
||||
def test_swift_config_file_doesnt_exist(self):
|
||||
self.config(swift_store_config_file='fake-file.conf')
|
||||
self.assertRaises(exception.InvalidSwiftStoreConfiguration,
|
||||
swift_store_utils.SwiftParams)
|
||||
|
||||
def test_swift_config_uses_default_values_multiple_account_disabled(self):
|
||||
default_user = 'user_default'
|
||||
default_key = 'key_default'
|
||||
default_auth_address = 'auth@default.com'
|
||||
default_account_reference = 'ref_default'
|
||||
confs = {'swift_store_config_file': None,
|
||||
'swift_store_user': default_user,
|
||||
'swift_store_key': default_key,
|
||||
'swift_store_auth_address': default_auth_address,
|
||||
'default_swift_reference': default_account_reference}
|
||||
self.config(**confs)
|
||||
swift_params = swift_store_utils.SwiftParams().params
|
||||
self.assertEqual(1, len(swift_params.keys()))
|
||||
self.assertEqual(default_user,
|
||||
swift_params[default_account_reference]['user']
|
||||
)
|
||||
self.assertEqual(default_key,
|
||||
swift_params[default_account_reference]['key']
|
||||
)
|
||||
self.assertEqual(default_auth_address,
|
||||
swift_params[default_account_reference]
|
||||
['auth_address']
|
||||
)
|
||||
|
||||
def test_swift_store_config_validates_for_creds_auth_address(self):
|
||||
swift_params = swift_store_utils.SwiftParams().params
|
||||
self.assertEqual('tenant:user1',
|
||||
swift_params['ref1']['user']
|
||||
)
|
||||
self.assertEqual('key1',
|
||||
swift_params['ref1']['key']
|
||||
)
|
||||
self.assertEqual('example.com',
|
||||
swift_params['ref1']['auth_address'])
|
||||
self.assertEqual('user2',
|
||||
swift_params['ref2']['user'])
|
||||
self.assertEqual('key2',
|
||||
swift_params['ref2']['key'])
|
||||
self.assertEqual('http://example.com',
|
||||
swift_params['ref2']['auth_address']
|
||||
)
|
@ -12,6 +12,8 @@
|
||||
# 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 fixtures
|
||||
import mock
|
||||
|
||||
from glance.common import exception
|
||||
@ -27,6 +29,15 @@ import glance.store.vmware_datastore
|
||||
from glance.tests.unit import base
|
||||
|
||||
|
||||
CONF = {'default_store': 'file',
|
||||
'swift_store_auth_address': 'localhost:8080',
|
||||
'swift_store_container': 'glance',
|
||||
'swift_store_user': 'user',
|
||||
'swift_store_key': 'key',
|
||||
'default_swift_reference': 'store_1'
|
||||
}
|
||||
|
||||
|
||||
class TestStoreLocation(base.StoreClearingUnitTest):
|
||||
|
||||
def setUp(self):
|
||||
@ -45,6 +56,9 @@ class TestStoreLocation(base.StoreClearingUnitTest):
|
||||
"glance.store.gridfs.Store",
|
||||
"glance.store.vmware_datastore.Store",
|
||||
])
|
||||
conf = CONF.copy()
|
||||
self.config(**conf)
|
||||
reload(glance.store.swift)
|
||||
super(TestStoreLocation, self).setUp()
|
||||
|
||||
def test_get_location_from_uri_back_to_uri(self):
|
||||
@ -160,6 +174,42 @@ class TestStoreLocation(base.StoreClearingUnitTest):
|
||||
"""
|
||||
Test the specific StoreLocation for the Swift store
|
||||
"""
|
||||
uri = 'swift+config://store_1/images/1'
|
||||
loc = glance.store.swift.StoreLocation({})
|
||||
loc.parse_uri(uri)
|
||||
|
||||
self.assertEqual("swift+config", loc.scheme)
|
||||
self.assertEqual("localhost:8080", loc.auth_or_store_url)
|
||||
self.assertEqual("https://localhost:8080", loc.swift_url)
|
||||
self.assertEqual("images", loc.container)
|
||||
self.assertEqual("1", loc.obj)
|
||||
self.assertEqual('user', loc.user)
|
||||
self.assertEqual('swift+https://user:key@localhost:8080/images/1',
|
||||
loc.get_uri())
|
||||
|
||||
conf_file = "glance-swift.conf"
|
||||
test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.swift_config_file = self._copy_data_file(conf_file, test_dir)
|
||||
conf = CONF.copy()
|
||||
conf.update({'swift_store_config_file': self.swift_config_file})
|
||||
self.config(**conf)
|
||||
reload(glance.store.swift)
|
||||
|
||||
uri = 'swift+config://store_2/images/1'
|
||||
loc = glance.store.swift.StoreLocation({})
|
||||
loc.parse_uri(uri)
|
||||
|
||||
self.assertEqual("swift+config", loc.scheme)
|
||||
self.assertEqual("localhost:8080", loc.auth_or_store_url)
|
||||
self.assertEqual("https://localhost:8080", loc.swift_url)
|
||||
self.assertEqual("images", loc.container)
|
||||
self.assertEqual("1", loc.obj)
|
||||
self.assertEqual('tenant:user1', loc.user)
|
||||
self.assertEqual('key1', loc.key)
|
||||
self.assertEqual('swift+https://tenant%3Auser1:key1@localhost:8080'
|
||||
'/images/1',
|
||||
loc.get_uri())
|
||||
|
||||
uri = 'swift://example.com/images/1'
|
||||
loc = glance.store.swift.StoreLocation({})
|
||||
loc.parse_uri(uri)
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
"""Tests the Swift backend store"""
|
||||
|
||||
import copy
|
||||
import fixtures
|
||||
import hashlib
|
||||
import httplib
|
||||
import mock
|
||||
@ -23,20 +25,22 @@ import uuid
|
||||
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import stubout
|
||||
import swiftclient
|
||||
|
||||
import glance.common.auth
|
||||
from glance.common import exception
|
||||
from glance.common import swift_store_utils
|
||||
from glance.common import utils
|
||||
from glance.openstack.common import units
|
||||
|
||||
from glance.store import BackendException
|
||||
from glance.store.location import get_location_from_uri
|
||||
from glance.store import swift
|
||||
from glance.store.swift import swift_retry_iter
|
||||
from glance.tests.unit import base
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
FAKE_UUID = lambda: str(uuid.uuid4())
|
||||
@ -50,11 +54,15 @@ SWIFT_CONF = {'verbose': True,
|
||||
'debug': True,
|
||||
'known_stores': ['glance.store.swift.Store'],
|
||||
'default_store': 'swift',
|
||||
'swift_store_auth_address': 'localhost:8080',
|
||||
'swift_store_container': 'glance',
|
||||
'swift_store_user': 'user',
|
||||
'swift_store_key': 'key',
|
||||
'swift_store_auth_address': 'localhost:8080',
|
||||
'swift_store_container': 'glance',
|
||||
'swift_store_retry_get_count': 1}
|
||||
'swift_store_retry_get_count': 1,
|
||||
'default_swift_reference': 'ref1'
|
||||
}
|
||||
|
||||
|
||||
# We stub out as little as possible to ensure that the code paths
|
||||
@ -223,7 +231,7 @@ class SwiftTests(object):
|
||||
|
||||
@property
|
||||
def swift_store_user(self):
|
||||
return urlparse.quote(CONF.swift_store_user)
|
||||
return 'tenant:user1'
|
||||
|
||||
def test_get_size(self):
|
||||
"""
|
||||
@ -235,6 +243,27 @@ class SwiftTests(object):
|
||||
image_size = self.store.get_size(loc)
|
||||
self.assertEqual(image_size, 5120)
|
||||
|
||||
def test_validate_location_for_invalid_uri(self):
|
||||
"""
|
||||
Test that validate location raises when the location contains
|
||||
any account reference.
|
||||
"""
|
||||
uri = "swift+config://store_1/glance/%s"
|
||||
self.assertRaises(exception.BadStoreUri,
|
||||
self.store.validate_location,
|
||||
uri)
|
||||
|
||||
def test_validate_location_for_valid_uri(self):
|
||||
"""
|
||||
Test that validate location verifies that the location does not
|
||||
contain any account reference
|
||||
"""
|
||||
uri = "swift://user:key@auth_address/glance/%s"
|
||||
try:
|
||||
self.assertIsNone(self.store.validate_location(uri))
|
||||
except Exception:
|
||||
self.fail('Location uri validation failed')
|
||||
|
||||
def test_get_size_with_multi_tenant_on(self):
|
||||
"""Test that single tenant uris work with multi tenant on."""
|
||||
uri = ("swift://%s:key@auth_address/glance/%s" %
|
||||
@ -315,13 +344,16 @@ class SwiftTests(object):
|
||||
|
||||
def test_add(self):
|
||||
"""Test that we can add an image via the swift backend"""
|
||||
swift_store_utils.is_multiple_swift_store_accounts_enabled = \
|
||||
mock.Mock(return_value=False)
|
||||
reload(swift)
|
||||
self.store = Store()
|
||||
expected_swift_size = FIVE_KB
|
||||
expected_swift_contents = "*" * expected_swift_size
|
||||
expected_checksum = hashlib.md5(expected_swift_contents).hexdigest()
|
||||
expected_image_id = str(uuid.uuid4())
|
||||
loc = 'swift+https://%s:key@localhost:8080/glance/%s'
|
||||
expected_location = loc % (self.swift_store_user,
|
||||
expected_image_id)
|
||||
loc = "swift+https://tenant%%3Auser1:key@localhost:8080/glance/%s"
|
||||
expected_location = loc % (expected_image_id)
|
||||
image_swift = six.StringIO(expected_swift_contents)
|
||||
|
||||
global SWIFT_PUT_OBJECT_CALLS
|
||||
@ -345,35 +377,48 @@ class SwiftTests(object):
|
||||
self.assertEqual(expected_swift_contents, new_image_contents)
|
||||
self.assertEqual(expected_swift_size, new_image_swift_size)
|
||||
|
||||
def test_add_multi_store(self):
|
||||
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
conf['default_swift_reference'] = 'store_2'
|
||||
self.config(**conf)
|
||||
reload(swift)
|
||||
self.store = Store()
|
||||
|
||||
expected_swift_size = FIVE_KB
|
||||
expected_swift_contents = "*" * expected_swift_size
|
||||
expected_image_id = str(uuid.uuid4())
|
||||
image_swift = six.StringIO(expected_swift_contents)
|
||||
global SWIFT_PUT_OBJECT_CALLS
|
||||
SWIFT_PUT_OBJECT_CALLS = 0
|
||||
loc = 'swift+config://store_2/glance/%s'
|
||||
|
||||
expected_location = loc % (expected_image_id)
|
||||
|
||||
location, size, checksum, arg = self.store.add(expected_image_id,
|
||||
image_swift,
|
||||
expected_swift_size)
|
||||
self.assertEqual(expected_location, location)
|
||||
|
||||
def test_add_auth_url_variations(self):
|
||||
"""
|
||||
Test that we can add an image via the swift backend with
|
||||
a variety of different auth_address values
|
||||
"""
|
||||
swift_store_utils.is_multiple_swift_store_accounts_enabled = \
|
||||
mock.Mock(return_value=True)
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
self.config(**conf)
|
||||
|
||||
variations = {
|
||||
'http://localhost:80': 'swift+http://%s:key@localhost:80'
|
||||
'/glance/%s',
|
||||
'http://localhost': 'swift+http://%s:key@localhost/glance/%s',
|
||||
'http://localhost/v1': 'swift+http://%s:key@localhost'
|
||||
'/v1/glance/%s',
|
||||
'http://localhost/v1/': 'swift+http://%s:key@localhost'
|
||||
'/v1/glance/%s',
|
||||
'https://localhost': 'swift+https://%s:key@localhost/glance/%s',
|
||||
'https://localhost:8080': 'swift+https://%s:key@localhost:8080'
|
||||
'/glance/%s',
|
||||
'https://localhost/v1': 'swift+https://%s:key@localhost'
|
||||
'/v1/glance/%s',
|
||||
'https://localhost/v1/': 'swift+https://%s:key@localhost'
|
||||
'/v1/glance/%s',
|
||||
'localhost': 'swift+https://%s:key@localhost/glance/%s',
|
||||
'localhost:8080/v1': 'swift+https://%s:key@localhost:8080'
|
||||
'/v1/glance/%s',
|
||||
'store_4': 'swift+config://store_4/glance/%s',
|
||||
'store_5': 'swift+config://store_5/glance/%s',
|
||||
'store_6': 'swift+config://store_6/glance/%s'
|
||||
}
|
||||
|
||||
for variation, expected_location in variations.items():
|
||||
image_id = str(uuid.uuid4())
|
||||
expected_location = expected_location % (
|
||||
self.swift_store_user, image_id)
|
||||
expected_location = expected_location % image_id
|
||||
expected_swift_size = FIVE_KB
|
||||
expected_swift_contents = "*" * expected_swift_size
|
||||
expected_checksum = \
|
||||
@ -383,8 +428,9 @@ class SwiftTests(object):
|
||||
|
||||
global SWIFT_PUT_OBJECT_CALLS
|
||||
SWIFT_PUT_OBJECT_CALLS = 0
|
||||
|
||||
self.config(swift_store_auth_address=variation)
|
||||
conf['default_swift_reference'] = variation
|
||||
self.config(**conf)
|
||||
reload(swift)
|
||||
self.store = Store()
|
||||
location, size, checksum, _ = self.store.add(image_id, image_swift,
|
||||
expected_swift_size)
|
||||
@ -407,8 +453,13 @@ class SwiftTests(object):
|
||||
Tests that adding an image with a non-existing container
|
||||
raises an appropriate exception
|
||||
"""
|
||||
self.config(swift_store_create_container_on_put=False,
|
||||
swift_store_container='noexist')
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
conf['swift_store_user'] = 'tenant:user'
|
||||
conf['swift_store_create_container_on_put'] = False
|
||||
conf['swift_store_container'] = 'noexist'
|
||||
self.config(**conf)
|
||||
reload(swift)
|
||||
|
||||
self.store = Store()
|
||||
|
||||
image_swift = six.StringIO("nevergonnamakeit")
|
||||
@ -434,20 +485,24 @@ class SwiftTests(object):
|
||||
Tests that adding an image with a non-existing container
|
||||
creates the container automatically if flag is set
|
||||
"""
|
||||
swift_store_utils.is_multiple_swift_store_accounts_enabled = \
|
||||
mock.Mock(return_value=True)
|
||||
expected_swift_size = FIVE_KB
|
||||
expected_swift_contents = "*" * expected_swift_size
|
||||
expected_checksum = hashlib.md5(expected_swift_contents).hexdigest()
|
||||
expected_image_id = str(uuid.uuid4())
|
||||
loc = 'swift+https://%s:key@localhost:8080/noexist/%s'
|
||||
expected_location = loc % (self.swift_store_user,
|
||||
expected_image_id)
|
||||
loc = 'swift+config://ref1/noexist/%s'
|
||||
expected_location = loc % (expected_image_id)
|
||||
image_swift = six.StringIO(expected_swift_contents)
|
||||
|
||||
global SWIFT_PUT_OBJECT_CALLS
|
||||
SWIFT_PUT_OBJECT_CALLS = 0
|
||||
|
||||
self.config(swift_store_create_container_on_put=True,
|
||||
swift_store_container='noexist')
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
conf['swift_store_user'] = 'tenant:user'
|
||||
conf['swift_store_create_container_on_put'] = True
|
||||
conf['swift_store_container'] = 'noexist'
|
||||
self.config(**conf)
|
||||
reload(swift)
|
||||
self.store = Store()
|
||||
location, size, checksum, _ = self.store.add(expected_image_id,
|
||||
image_swift,
|
||||
@ -473,19 +528,19 @@ class SwiftTests(object):
|
||||
and then verify that there have been a number of calls to
|
||||
put_object()...
|
||||
"""
|
||||
swift_store_utils.is_multiple_swift_store_accounts_enabled = \
|
||||
mock.Mock(return_value=True)
|
||||
expected_swift_size = FIVE_KB
|
||||
expected_swift_contents = "*" * expected_swift_size
|
||||
expected_checksum = hashlib.md5(expected_swift_contents).hexdigest()
|
||||
expected_image_id = str(uuid.uuid4())
|
||||
loc = 'swift+https://%s:key@localhost:8080/glance/%s'
|
||||
expected_location = loc % (self.swift_store_user,
|
||||
expected_image_id)
|
||||
loc = 'swift+config://ref1/glance/%s'
|
||||
expected_location = loc % (expected_image_id)
|
||||
image_swift = six.StringIO(expected_swift_contents)
|
||||
|
||||
global SWIFT_PUT_OBJECT_CALLS
|
||||
SWIFT_PUT_OBJECT_CALLS = 0
|
||||
|
||||
self.config(swift_store_container='glance')
|
||||
self.store = Store()
|
||||
orig_max_size = self.store.large_object_size
|
||||
orig_temp_size = self.store.large_object_chunk_size
|
||||
@ -530,9 +585,8 @@ class SwiftTests(object):
|
||||
expected_swift_contents = "*" * expected_swift_size
|
||||
expected_checksum = hashlib.md5(expected_swift_contents).hexdigest()
|
||||
expected_image_id = str(uuid.uuid4())
|
||||
loc = 'swift+https://%s:key@localhost:8080/glance/%s'
|
||||
expected_location = loc % (self.swift_store_user,
|
||||
expected_image_id)
|
||||
loc = 'swift+config://ref1/glance/%s'
|
||||
expected_location = loc % (expected_image_id)
|
||||
image_swift = six.StringIO(expected_swift_contents)
|
||||
|
||||
global SWIFT_PUT_OBJECT_CALLS
|
||||
@ -540,7 +594,7 @@ class SwiftTests(object):
|
||||
|
||||
# Temporarily set Swift MAX_SWIFT_OBJECT_SIZE to 1KB and add our image,
|
||||
# explicitly setting the image_length to 0
|
||||
self.config(swift_store_container='glance')
|
||||
|
||||
self.store = Store()
|
||||
orig_max_size = self.store.large_object_size
|
||||
orig_temp_size = self.store.large_object_chunk_size
|
||||
@ -621,23 +675,25 @@ class SwiftTests(object):
|
||||
return False
|
||||
return False
|
||||
|
||||
def test_no_user(self):
|
||||
def test_no_store_credentials(self):
|
||||
"""
|
||||
Tests that options without user disables the add method
|
||||
Tests that options without a valid credentials disables the add method
|
||||
"""
|
||||
self.assertTrue(self._option_required('swift_store_user'))
|
||||
|
||||
def test_no_key(self):
|
||||
"""
|
||||
Tests that options without key disables the add method
|
||||
"""
|
||||
self.assertTrue(self._option_required('swift_store_key'))
|
||||
swift.SWIFT_STORE_REF_PARAMS = {'ref1': {'auth_address':
|
||||
'authurl.com', 'user': '',
|
||||
'key': ''}}
|
||||
self.store = Store()
|
||||
self.assertEqual(self.store.add, self.store.add_disabled)
|
||||
|
||||
def test_no_auth_address(self):
|
||||
"""
|
||||
Tests that options without auth address disables the add method
|
||||
"""
|
||||
self.assertTrue(self._option_required('swift_store_auth_address'))
|
||||
swift.SWIFT_STORE_REF_PARAMS = {'ref1': {'auth_address':
|
||||
'', 'user': 'user1',
|
||||
'key': 'key1'}}
|
||||
self.store = Store()
|
||||
self.assertEqual(self.store.add, self.store.add_disabled)
|
||||
|
||||
def test_delete(self):
|
||||
"""
|
||||
@ -650,6 +706,16 @@ class SwiftTests(object):
|
||||
|
||||
self.assertRaises(exception.NotFound, self.store.get, loc)
|
||||
|
||||
def test_delete_with_reference_params(self):
|
||||
"""
|
||||
Test we can delete an existing image in the swift store
|
||||
"""
|
||||
uri = "swift+config://ref1/glance/%s" % (FAKE_UUID)
|
||||
loc = get_location_from_uri(uri)
|
||||
self.store.delete(loc)
|
||||
|
||||
self.assertRaises(exception.NotFound, self.store.get, loc)
|
||||
|
||||
def test_delete_non_existing(self):
|
||||
"""
|
||||
Test that trying to delete a swift that doesn't exist
|
||||
@ -712,14 +778,20 @@ class TestStoreAuthV1(base.StoreClearingUnitTest, SwiftTests):
|
||||
def getConfig(self):
|
||||
conf = SWIFT_CONF.copy()
|
||||
conf['swift_store_auth_version'] = '1'
|
||||
conf['swift_store_user'] = 'user'
|
||||
conf['swift_store_user'] = 'tenant:user1'
|
||||
return conf
|
||||
|
||||
def setUp(self):
|
||||
"""Establish a clean test environment"""
|
||||
conf = self.getConfig()
|
||||
self.config(**conf)
|
||||
conf_file = 'glance-swift.conf'
|
||||
self.test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.swift_config_file = self._copy_data_file(conf_file, self.test_dir)
|
||||
conf.update({'swift_store_config_file': conf_file})
|
||||
self.config(swift_store_config_file=self.swift_config_file)
|
||||
super(TestStoreAuthV1, self).setUp()
|
||||
swift.SWIFT_STORE_REF_PARAMS = swift_store_utils.SwiftParams().params
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
stub_out_swiftclient(self.stubs, conf['swift_store_auth_version'])
|
||||
self.store = Store()
|
||||
@ -730,15 +802,12 @@ class TestStoreAuthV2(TestStoreAuthV1):
|
||||
|
||||
def getConfig(self):
|
||||
conf = super(TestStoreAuthV2, self).getConfig()
|
||||
conf['swift_store_user'] = 'tenant:user'
|
||||
conf['swift_store_auth_version'] = '2'
|
||||
conf['swift_store_user'] = 'tenant:user1'
|
||||
return conf
|
||||
|
||||
def test_v2_with_no_tenant(self):
|
||||
conf = self.getConfig()
|
||||
conf['swift_store_user'] = 'failme'
|
||||
uri = "swift://%s:key@auth_address/glance/%s" % (
|
||||
conf['swift_store_user'], FAKE_UUID)
|
||||
uri = "swift://failme:key@auth_address/glance/%s" % (FAKE_UUID)
|
||||
loc = get_location_from_uri(uri)
|
||||
self.assertRaises(exception.BadStoreUri,
|
||||
self.store.get,
|
||||
@ -779,8 +848,8 @@ class TestSingleTenantStoreConnections(base.IsolatedUnitTest):
|
||||
self.store = glance.store.swift.SingleTenantStore()
|
||||
specs = {'scheme': 'swift',
|
||||
'auth_or_store_url': 'example.com/v2/',
|
||||
'user': 'tenant:user',
|
||||
'key': 'abcdefg',
|
||||
'user': 'tenant:user1',
|
||||
'key': 'key1',
|
||||
'container': 'cont',
|
||||
'obj': 'object'}
|
||||
self.location = glance.store.swift.StoreLocation(specs)
|
||||
@ -789,10 +858,10 @@ class TestSingleTenantStoreConnections(base.IsolatedUnitTest):
|
||||
connection = self.store.get_connection(self.location)
|
||||
self.assertEqual(connection.authurl, 'https://example.com/v2/')
|
||||
self.assertEqual(connection.auth_version, '2')
|
||||
self.assertEqual(connection.user, 'user')
|
||||
self.assertEqual(connection.user, 'user1')
|
||||
self.assertEqual(connection.tenant_name, 'tenant')
|
||||
self.assertEqual(connection.key, 'abcdefg')
|
||||
self.assertFalse(connection.snet)
|
||||
self.assertEqual(connection.key, 'key1')
|
||||
self.assertIsNone(connection.preauthurl)
|
||||
self.assertIsNone(connection.preauthtoken)
|
||||
self.assertFalse(connection.insecure)
|
||||
@ -863,13 +932,34 @@ class TestSingleTenantStoreConnections(base.IsolatedUnitTest):
|
||||
connection = self.store.get_connection(self.location)
|
||||
self.assertTrue(connection.snet)
|
||||
|
||||
def test_bad_location_uri(self):
|
||||
self.store.configure()
|
||||
self.location.uri = 'http://bad_uri://'
|
||||
self.assertRaises(exception.BadStoreUri,
|
||||
self.location.parse_uri,
|
||||
self.location.uri)
|
||||
|
||||
def test_bad_location_uri_invalid_credentials(self):
|
||||
self.store.configure()
|
||||
self.location.uri = 'swift://bad_creds@uri/cont/obj'
|
||||
self.assertRaises(exception.BadStoreUri,
|
||||
self.location.parse_uri,
|
||||
self.location.uri)
|
||||
|
||||
def test_bad_location_uri_invalid_object_path(self):
|
||||
self.store.configure()
|
||||
self.location.uri = 'swift://user:key@uri/cont'
|
||||
self.assertRaises(exception.BadStoreUri,
|
||||
self.location.parse_uri,
|
||||
self.location.uri)
|
||||
|
||||
|
||||
class TestMultiTenantStoreConnections(base.IsolatedUnitTest):
|
||||
def setUp(self):
|
||||
super(TestMultiTenantStoreConnections, self).setUp()
|
||||
self.stubs.Set(swiftclient, 'Connection', FakeConnection)
|
||||
self.context = glance.context.RequestContext(
|
||||
user='user', tenant='tenant', auth_tok='0123')
|
||||
user='tenant:user1', tenant='tenant', auth_tok='0123')
|
||||
self.store = glance.store.swift.MultiTenantStore(self.context)
|
||||
specs = {'scheme': 'swift',
|
||||
'auth_or_store_url': 'example.com',
|
||||
@ -882,7 +972,7 @@ class TestMultiTenantStoreConnections(base.IsolatedUnitTest):
|
||||
connection = self.store.get_connection(self.location)
|
||||
self.assertIsNone(connection.authurl)
|
||||
self.assertEqual(connection.auth_version, '2')
|
||||
self.assertEqual(connection.user, 'user')
|
||||
self.assertEqual(connection.user, 'tenant:user1')
|
||||
self.assertEqual(connection.tenant_name, 'tenant')
|
||||
self.assertIsNone(connection.key)
|
||||
self.assertFalse(connection.snet)
|
||||
@ -910,29 +1000,44 @@ class FakeGetEndpoint(object):
|
||||
|
||||
|
||||
class TestCreatingLocations(base.IsolatedUnitTest):
|
||||
def setUp(self):
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
self.config(**conf)
|
||||
reload(swift)
|
||||
super(TestCreatingLocations, self).setUp()
|
||||
|
||||
def test_single_tenant_location(self):
|
||||
self.config(swift_store_auth_address='example.com/v2',
|
||||
swift_store_container='container',
|
||||
swift_store_user='tenant:user',
|
||||
swift_store_key='auth_key')
|
||||
store = glance.store.swift.SingleTenantStore()
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
conf['swift_store_container'] = 'container'
|
||||
conf_file = "glance-swift.conf"
|
||||
test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.swift_config_file = self._copy_data_file(conf_file, test_dir)
|
||||
conf.update({'swift_store_config_file': self.swift_config_file})
|
||||
conf['default_swift_reference'] = 'ref1'
|
||||
self.config(**conf)
|
||||
reload(swift)
|
||||
|
||||
store = swift.SingleTenantStore()
|
||||
location = store.create_location('image-id')
|
||||
self.assertEqual(location.scheme, 'swift+https')
|
||||
self.assertEqual(location.swift_url, 'https://example.com/v2')
|
||||
self.assertEqual(location.swift_url, 'https://example.com')
|
||||
self.assertEqual(location.container, 'container')
|
||||
self.assertEqual(location.obj, 'image-id')
|
||||
self.assertEqual(location.user, 'tenant:user')
|
||||
self.assertEqual(location.key, 'auth_key')
|
||||
self.assertEqual(location.user, 'tenant:user1')
|
||||
self.assertEqual(location.key, 'key1')
|
||||
|
||||
def test_single_tenant_location_http(self):
|
||||
self.config(swift_store_auth_address='http://example.com/v2',
|
||||
swift_store_container='container',
|
||||
swift_store_user='tenant:user',
|
||||
swift_store_key='auth_key')
|
||||
conf_file = "glance-swift.conf"
|
||||
test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.swift_config_file = self._copy_data_file(conf_file, test_dir)
|
||||
self.config(swift_store_container='container',
|
||||
default_swift_reference='ref2',
|
||||
swift_store_config_file=self.swift_config_file)
|
||||
swift.SWIFT_STORE_REF_PARAMS = swift_store_utils.SwiftParams().params
|
||||
store = glance.store.swift.SingleTenantStore()
|
||||
location = store.create_location('image-id')
|
||||
self.assertEqual(location.scheme, 'swift+http')
|
||||
self.assertEqual(location.swift_url, 'http://example.com/v2')
|
||||
self.assertEqual(location.swift_url, 'http://example.com')
|
||||
|
||||
def test_multi_tenant_location(self):
|
||||
self.config(swift_store_container='container')
|
||||
@ -994,6 +1099,10 @@ class TestCreatingLocations(base.IsolatedUnitTest):
|
||||
|
||||
|
||||
class TestChunkReader(base.StoreClearingUnitTest):
|
||||
def setUp(self):
|
||||
conf = copy.deepcopy(SWIFT_CONF)
|
||||
self.config(**conf)
|
||||
super(TestChunkReader, self).setUp()
|
||||
|
||||
def test_read_all_data(self):
|
||||
"""
|
||||
|
@ -91,6 +91,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
|
||||
'metadata': {}}],
|
||||
'properties': {}}]
|
||||
self.context = glance.context.RequestContext(is_admin=True)
|
||||
glance.api.v1.images.validate_location = mock.Mock()
|
||||
db_api.get_engine()
|
||||
self.destroy_fixtures()
|
||||
self.create_fixtures()
|
||||
@ -959,6 +960,28 @@ class TestGlanceAPI(base.IsolatedUnitTest):
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 409)
|
||||
|
||||
def test_add_location_with_invalid_location(self):
|
||||
"""Tests creates an image from location and conflict image size"""
|
||||
|
||||
mock_validate_location = mock.Mock()
|
||||
glance.api.v1.images.validate_location = mock_validate_location
|
||||
mock_validate_location.side_effect = exception.BadStoreUri()
|
||||
|
||||
fixture_headers = {'x-image-meta-store': 'file',
|
||||
'x-image-meta-disk-format': 'vhd',
|
||||
'x-image-meta-location': 'http://a/b/c.tar.gz',
|
||||
'x-image-meta-container-format': 'ovf',
|
||||
'x-image-meta-name': 'fake image #F',
|
||||
'x-image-meta-size': '1'}
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
req.method = 'POST'
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, 400)
|
||||
|
||||
def test_add_copy_from_with_location(self):
|
||||
"""Tests creates an image from copy-from and location"""
|
||||
fixture_headers = {'x-image-meta-store': 'file',
|
||||
|
Loading…
x
Reference in New Issue
Block a user