ironic/ironic/common/glance_service/service_utils.py
Naohiro Tamura f55ab34e54 Add iRMC Virtual Media Deploy module for iRMC Driver
This module enables PXE/TFTP less deployment capability to provision
PRIMERGY bare metal nodes (having iRMC S4 and beyond) by booting the
bare metal node with virtual media using NFS or CIFS from conductor
node to deploy the image.

Implements: blueprint irmc-virtualmedia-deploy-driver
Change-Id: I747b3245c7ba5e991abb07c894c8cd2b2d3c45e8
2015-07-06 01:22:25 +09:00

263 lines
8.8 KiB
Python

# Copyright 2012 OpenStack Foundation
# 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.
import copy
import itertools
import random
from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
import six
import six.moves.urllib.parse as urlparse
from ironic.common import exception
from ironic.common import image_service
CONF = cfg.CONF
LOG = log.getLogger(__name__)
_GLANCE_API_SERVER = None
""" iterator that cycles (indefinitely) over glance API servers. """
def generate_glance_url():
"""Generate the URL to glance."""
return "%s://%s:%d" % (CONF.glance.glance_protocol,
CONF.glance.glance_host,
CONF.glance.glance_port)
def generate_image_url(image_ref):
"""Generate an image URL from an image_ref."""
return "%s/images/%s" % (generate_glance_url(), image_ref)
def _extract_attributes(image):
IMAGE_ATTRIBUTES = ['size', 'disk_format', 'owner',
'container_format', 'checksum', 'id',
'name', 'created_at', 'updated_at',
'deleted_at', 'deleted', 'status',
'min_disk', 'min_ram', 'is_public']
IMAGE_ATTRIBUTES_V2 = ['tags', 'visibility', 'protected',
'file', 'schema']
output = {}
for attr in IMAGE_ATTRIBUTES:
output[attr] = getattr(image, attr, None)
output['properties'] = getattr(image, 'properties', {})
if hasattr(image, 'schema') and 'v2' in image['schema']:
IMAGE_ATTRIBUTES = IMAGE_ATTRIBUTES + IMAGE_ATTRIBUTES_V2
for attr in IMAGE_ATTRIBUTES_V2:
output[attr] = getattr(image, attr, None)
output['schema'] = image['schema']
for image_property in set(image.keys()) - set(IMAGE_ATTRIBUTES):
output['properties'][image_property] = image[image_property]
return output
def _convert_timestamps_to_datetimes(image_meta):
"""Returns image with timestamp fields converted to datetime objects."""
for attr in ['created_at', 'updated_at', 'deleted_at']:
if image_meta.get(attr):
image_meta[attr] = timeutils.parse_isotime(image_meta[attr])
return image_meta
_CONVERT_PROPS = ('block_device_mapping', 'mappings')
def _convert(metadata, method):
metadata = copy.deepcopy(metadata)
properties = metadata.get('properties')
if properties:
for attr in _CONVERT_PROPS:
if attr in properties:
prop = properties[attr]
if method == 'from':
if isinstance(prop, six.string_types):
properties[attr] = jsonutils.loads(prop)
if method == 'to':
if not isinstance(prop, six.string_types):
properties[attr] = jsonutils.dumps(prop)
return metadata
def _remove_read_only(image_meta):
IMAGE_ATTRIBUTES = ['status', 'updated_at', 'created_at', 'deleted_at']
output = copy.deepcopy(image_meta)
for attr in IMAGE_ATTRIBUTES:
if attr in output:
del output[attr]
return output
def _get_api_server_iterator():
"""Return iterator over shuffled API servers.
Shuffle a list of CONF.glance.glance_api_servers and return an iterator
that will cycle through the list, looping around to the beginning if
necessary.
If CONF.glance.glance_api_servers isn't set, we fall back to using this
as the server: CONF.glance.glance_host:CONF.glance.glance_port.
:returns: iterator that cycles (indefinitely) over shuffled glance API
servers. The iterator returns tuples of (host, port, use_ssl).
"""
api_servers = []
configured_servers = (CONF.glance.glance_api_servers or
['%s:%s' % (CONF.glance.glance_host,
CONF.glance.glance_port)])
for api_server in configured_servers:
if '//' not in api_server:
api_server = '%s://%s' % (CONF.glance.glance_protocol, api_server)
url = urlparse.urlparse(api_server)
port = url.port or 80
host = url.netloc.split(':', 1)[0]
use_ssl = (url.scheme == 'https')
api_servers.append((host, port, use_ssl))
random.shuffle(api_servers)
return itertools.cycle(api_servers)
def _get_api_server():
"""Return a Glance API server.
:returns: for an API server, the tuple (host-or-IP, port, use_ssl), where
use_ssl is True to use the 'https' scheme, and False to use 'http'.
"""
global _GLANCE_API_SERVER
if not _GLANCE_API_SERVER:
_GLANCE_API_SERVER = _get_api_server_iterator()
return six.next(_GLANCE_API_SERVER)
def parse_image_ref(image_href):
"""Parse an image href into composite parts.
:param image_href: href of an image
:returns: a tuple of the form (image_id, host, port, use_ssl)
:raises ValueError
"""
if '/' not in str(image_href):
image_id = image_href
(glance_host, glance_port, use_ssl) = _get_api_server()
return (image_id, glance_host, glance_port, use_ssl)
else:
try:
url = urlparse.urlparse(image_href)
if url.scheme == 'glance':
(glance_host, glance_port, use_ssl) = _get_api_server()
image_id = image_href.split('/')[-1]
else:
glance_port = url.port or 80
glance_host = url.netloc.split(':', 1)[0]
image_id = url.path.split('/')[-1]
use_ssl = (url.scheme == 'https')
return (image_id, glance_host, glance_port, use_ssl)
except ValueError:
raise exception.InvalidImageRef(image_href=image_href)
def extract_query_params(params, version):
_params = {}
accepted_params = ('filters', 'marker', 'limit',
'sort_key', 'sort_dir')
for param in accepted_params:
if params.get(param):
_params[param] = params.get(param)
# ensure filters is a dict
_params.setdefault('filters', {})
# NOTE(vish): don't filter out private images
# NOTE(ghe): in v2, not passing any visibility doesn't filter prvate images
if version == 1:
_params['filters'].setdefault('is_public', 'none')
return _params
def translate_to_glance(image_meta):
image_meta = _convert(image_meta, 'to')
image_meta = _remove_read_only(image_meta)
return image_meta
def translate_from_glance(image):
image_meta = _extract_attributes(image)
image_meta = _convert_timestamps_to_datetimes(image_meta)
image_meta = _convert(image_meta, 'from')
return image_meta
def is_image_available(context, image):
"""Check image availability.
This check is needed in case Nova and Glance are deployed
without authentication turned on.
"""
# The presence of an auth token implies this is an authenticated
# request and we need not handle the noauth use-case.
if hasattr(context, 'auth_token') and context.auth_token:
return True
if image.is_public or context.is_admin:
return True
properties = image.properties
if context.project_id and ('owner_id' in properties):
return str(properties['owner_id']) == str(context.project_id)
if context.project_id and ('project_id' in properties):
return str(properties['project_id']) == str(context.project_id)
try:
user_id = properties['user_id']
except KeyError:
return False
return str(user_id) == str(context.user_id)
def is_glance_image(image_href):
if not isinstance(image_href, six.string_types):
return False
return (image_href.startswith('glance://') or
uuidutils.is_uuid_like(image_href))
def is_image_href_ordinary_file_name(image_href):
"""judge if image_href is a ordinary file name.
This method judges if image_href is a ordinary file name or not,
which is a file supposed to be stored in share file system.
The ordinary file name is neither glance image href
nor image service href.
:returns: True if image_href is ordinary file name, False otherwise.
"""
return not (is_glance_image(image_href) or
urlparse.urlparse(image_href).scheme.lower() in
image_service.protocol_mapping)