230 lines
10 KiB
Python
230 lines
10 KiB
Python
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from oslo_config import cfg
|
|
from oslo_utils import uuidutils
|
|
from swiftclient import utils as swift_utils
|
|
|
|
from iotronic.common import exception as exc
|
|
from iotronic.common.glance_service import base_image_service
|
|
from iotronic.common.glance_service import service
|
|
from iotronic.common.glance_service import service_utils
|
|
from iotronic.common.i18n import _
|
|
|
|
|
|
glance_opts = [
|
|
cfg.ListOpt('allowed_direct_url_schemes',
|
|
default=[],
|
|
help='A list of URL schemes that can be downloaded directly '
|
|
'via the direct_url. Currently supported schemes: '
|
|
'[file].'),
|
|
# To upload this key to Swift:
|
|
# swift post -m Temp-Url-Key:correcthorsebatterystaple
|
|
cfg.StrOpt('swift_temp_url_key',
|
|
help='The secret token given to Swift to allow temporary URL '
|
|
'downloads. Required for temporary URLs.',
|
|
secret=True),
|
|
cfg.IntOpt('swift_temp_url_duration',
|
|
min=0,
|
|
default=1200,
|
|
help='The length of time in seconds that the temporary URL '
|
|
'will be valid for. Defaults to 20 minutes. If some '
|
|
'deploys get a 401 response code when trying to download '
|
|
'from the temporary URL, try raising this duration.'),
|
|
cfg.StrOpt('swift_endpoint_url',
|
|
help='The "endpoint" (scheme, hostname, optional port) for '
|
|
'the Swift URL of the form '
|
|
'"endpoint_url/api_version/account/container/object_id". '
|
|
'Do not include trailing "/". '
|
|
'For example, use "https://swift.example.com". '
|
|
'Required for temporary URLs.'),
|
|
cfg.StrOpt('swift_api_version',
|
|
default='v1',
|
|
help='The Swift API version to create a temporary URL for. '
|
|
'Defaults to "v1". Swift temporary URL format: '
|
|
'"endpoint_url/api_version/account/container/object_id"'),
|
|
cfg.StrOpt('swift_account',
|
|
help='The account that Glance uses to communicate with '
|
|
'Swift. The format is "AUTH_uuid". "uuid" is the '
|
|
'UUID for the account configured in the glance-api.conf. '
|
|
'Required for temporary URLs. For example: '
|
|
'"AUTH_a422b2-91f3-2f46-74b7-d7c9e8958f5d30". '
|
|
'Swift temporary URL format: '
|
|
'"endpoint_url/api_version/account/container/object_id"'),
|
|
cfg.StrOpt('swift_container',
|
|
default='glance',
|
|
help='The Swift container Glance is configured to store its '
|
|
'images in. Defaults to "glance", which is the default '
|
|
'in glance-api.conf. '
|
|
'Swift temporary URL format: '
|
|
'"endpoint_url/api_version/account/container/object_id"'),
|
|
cfg.IntOpt('swift_store_multiple_containers_seed',
|
|
default=0,
|
|
help='This should match a config by the same name in the '
|
|
'Glance configuration file. When set to 0, a '
|
|
'single-tenant store will only use one '
|
|
'container to store all images. When set to an integer '
|
|
'value between 1 and 32, a single-tenant store will use '
|
|
'multiple containers to store images, and this value '
|
|
'will determine how many containers are created.'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(glance_opts, group='glance')
|
|
|
|
|
|
class GlanceImageService(base_image_service.BaseImageService,
|
|
service.ImageService):
|
|
|
|
def detail(self, **kwargs):
|
|
return self._detail(method='list', **kwargs)
|
|
|
|
def show(self, image_id):
|
|
return self._show(image_id, method='get')
|
|
|
|
def download(self, image_id, data=None):
|
|
return self._download(image_id, method='data', data=data)
|
|
|
|
def create(self, image_meta, data=None):
|
|
image_id = self._create(image_meta, method='create', data=None)['id']
|
|
return self.update(image_id, None, data)
|
|
|
|
def update(self, image_id, image_meta, data=None, purge_props=False):
|
|
# NOTE(ghe): purge_props not working until bug 1206472 solved
|
|
return self._update(image_id, image_meta, data, method='update',
|
|
purge_props=False)
|
|
|
|
def delete(self, image_id):
|
|
return self._delete(image_id, method='delete')
|
|
|
|
def swift_temp_url(self, image_info):
|
|
"""Generate a no-auth Swift temporary URL.
|
|
|
|
This function will generate the temporary Swift URL using the image
|
|
id from Glance and the config options: 'swift_endpoint_url',
|
|
'swift_api_version', 'swift_account' and 'swift_container'.
|
|
The temporary URL will be valid for 'swift_temp_url_duration' seconds.
|
|
This allows Iotronic to download a Glance image without passing around
|
|
an auth_token.
|
|
|
|
:param image_info: The return from a GET request to Glance for a
|
|
certain image_id. Should be a dictionary, with keys like 'name' and
|
|
'checksum'. See
|
|
http://docs.openstack.org/developer/glance/glanceapi.html for
|
|
examples.
|
|
:returns: A signed Swift URL from which an image can be downloaded,
|
|
without authentication.
|
|
|
|
:raises: InvalidParameterValue if Swift config options are not set
|
|
correctly.
|
|
:raises: MissingParameterValue if a required parameter is not set.
|
|
:raises: ImageUnacceptable if the image info from Glance does not
|
|
have a image ID.
|
|
"""
|
|
self._validate_temp_url_config()
|
|
|
|
if ('id' not in image_info or not
|
|
uuidutils.is_uuid_like(image_info['id'])):
|
|
raise exc.ImageUnacceptable(_(
|
|
'The given image info does not have a valid image id: %s')
|
|
% image_info)
|
|
|
|
url_fragments = {
|
|
'endpoint_url': CONF.glance.swift_endpoint_url,
|
|
'api_version': CONF.glance.swift_api_version,
|
|
'account': CONF.glance.swift_account,
|
|
'container': self._get_swift_container(image_info['id']),
|
|
'object_id': image_info['id']
|
|
}
|
|
|
|
template = '/{api_version}/{account}/{container}/{object_id}'
|
|
url_path = template.format(**url_fragments)
|
|
path = swift_utils.generate_temp_url(
|
|
path=url_path,
|
|
seconds=CONF.glance.swift_temp_url_duration,
|
|
key=CONF.glance.swift_temp_url_key,
|
|
method='GET')
|
|
|
|
return '{endpoint_url}{url_path}'.format(
|
|
endpoint_url=url_fragments['endpoint_url'], url_path=path)
|
|
|
|
def _validate_temp_url_config(self):
|
|
"""Validate the required settings for a temporary URL."""
|
|
if not CONF.glance.swift_temp_url_key:
|
|
raise exc.MissingParameterValue(_(
|
|
'Swift temporary URLs require a shared secret to be created. '
|
|
'You must provide "swift_temp_url_key" as a config option.'))
|
|
if not CONF.glance.swift_endpoint_url:
|
|
raise exc.MissingParameterValue(_(
|
|
'Swift temporary URLs require a Swift endpoint URL. '
|
|
'You must provide "swift_endpoint_url" as a config option.'))
|
|
if not CONF.glance.swift_account:
|
|
raise exc.MissingParameterValue(_(
|
|
'Swift temporary URLs require a Swift account string. '
|
|
'You must provide "swift_account" as a config option.'))
|
|
seed_num_chars = CONF.glance.swift_store_multiple_containers_seed
|
|
if (seed_num_chars is None or seed_num_chars < 0
|
|
or seed_num_chars > 32):
|
|
raise exc.InvalidParameterValue(_(
|
|
"An integer value between 0 and 32 is required for"
|
|
" swift_store_multiple_containers_seed."))
|
|
|
|
def _get_swift_container(self, image_id):
|
|
"""Get the Swift container the image is stored in.
|
|
|
|
Code based on: https://github.com/openstack/glance_store/blob/3cd690b3
|
|
7dc9d935445aca0998e8aec34a3e3530/glance_store/
|
|
_drivers/swift/store.py#L725
|
|
|
|
Returns appropriate container name depending upon value of
|
|
``swift_store_multiple_containers_seed``. In single-container mode,
|
|
which is a seed value of 0, simply returns ``swift_container``.
|
|
In multiple-container mode, returns ``swift_container`` as the
|
|
prefix plus a suffix determined by the multiple container seed
|
|
|
|
examples:
|
|
single-container mode: 'glance'
|
|
multiple-container mode: 'glance_3a1' for image uuid 3A1xxxxxxx...
|
|
|
|
:param image_id: UUID of image
|
|
:returns: The name of the swift container the image is stored in
|
|
"""
|
|
seed_num_chars = CONF.glance.swift_store_multiple_containers_seed
|
|
|
|
if seed_num_chars > 0:
|
|
image_id = str(image_id).lower()
|
|
|
|
num_dashes = image_id[:seed_num_chars].count('-')
|
|
num_chars = seed_num_chars + num_dashes
|
|
name_suffix = image_id[:num_chars]
|
|
new_container_name = (CONF.glance.swift_container +
|
|
'_' + name_suffix)
|
|
return new_container_name
|
|
else:
|
|
return CONF.glance.swift_container
|
|
|
|
def _get_location(self, image_id):
|
|
"""Get storage URL.
|
|
|
|
Returns the direct url representing the backend storage location,
|
|
or None if this attribute is not shown by Glance.
|
|
"""
|
|
image_meta = self.call('get', image_id)
|
|
|
|
if not service_utils.is_image_available(self.context, image_meta):
|
|
raise exc.ImageNotFound(image_id=image_id)
|
|
|
|
return getattr(image_meta, 'direct_url', None)
|