121 lines
5.0 KiB
Python
121 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2014, Adrien Vergé <adrien.verge@numergy.com>
|
|
#
|
|
# 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_log import log as logging
|
|
from oslo_utils import uuidutils
|
|
|
|
from cinder.compute import nova
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder.scheduler import filters
|
|
from cinder.volume import volume_utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
HINT_KEYWORD = 'local_to_instance'
|
|
INSTANCE_HOST_PROP = 'OS-EXT-SRV-ATTR:host'
|
|
REQUESTS_TIMEOUT = 5
|
|
|
|
|
|
class InstanceLocalityFilter(filters.BaseBackendFilter):
|
|
"""Schedule volume on the same host as a given instance.
|
|
|
|
This filter enables selection of a storage back-end located on the host
|
|
where the instance's hypervisor is running. This provides data locality:
|
|
the instance and the volume are located on the same physical machine.
|
|
|
|
In order to work:
|
|
|
|
- The Extended Server Attributes extension needs to be active in Nova (this
|
|
is by default), so that the 'OS-EXT-SRV-ATTR:host' property is returned
|
|
when requesting instance info.
|
|
- Either an account with privileged rights for Nova must be configured in
|
|
Cinder configuration (configure a keystone authentication plugin in the
|
|
[nova] section), or the user making the call needs to have sufficient
|
|
rights (see 'extended_server_attributes' in Nova policy).
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Cache Nova API answers directly into the Filter object.
|
|
# Since a BaseBackendFilter instance lives only during the volume's
|
|
# scheduling, the cache is re-created for every new volume creation.
|
|
self._cache = {}
|
|
super(InstanceLocalityFilter, self).__init__()
|
|
|
|
def _nova_has_extended_server_attributes(self, context):
|
|
"""Check Extended Server Attributes presence
|
|
|
|
Find out whether the Extended Server Attributes extension is activated
|
|
in Nova or not. Cache the result to query Nova only once.
|
|
"""
|
|
|
|
if not hasattr(self, '_nova_ext_srv_attr'):
|
|
self._nova_ext_srv_attr = nova.API().has_extension(
|
|
context, 'ExtendedServerAttributes', timeout=REQUESTS_TIMEOUT)
|
|
|
|
return self._nova_ext_srv_attr
|
|
|
|
def backend_passes(self, backend_state, filter_properties):
|
|
context = filter_properties['context']
|
|
backend = volume_utils.extract_host(backend_state.backend_id, 'host')
|
|
|
|
scheduler_hints = filter_properties.get('scheduler_hints') or {}
|
|
instance_uuid = scheduler_hints.get(HINT_KEYWORD, None)
|
|
|
|
# Without 'local_to_instance' hint
|
|
if not instance_uuid:
|
|
return True
|
|
|
|
if not uuidutils.is_uuid_like(instance_uuid):
|
|
raise exception.InvalidUUID(uuid=instance_uuid)
|
|
|
|
# TODO(adrienverge): Currently it is not recommended to allow instance
|
|
# migrations for hypervisors where this hint will be used. In case of
|
|
# instance migration, a previously locally-created volume will not be
|
|
# automatically migrated. Also in case of instance migration during the
|
|
# volume's scheduling, the result is unpredictable. A future
|
|
# enhancement would be to subscribe to Nova migration events (e.g. via
|
|
# Ceilometer).
|
|
|
|
# First, lookup for already-known information in local cache
|
|
if instance_uuid in self._cache:
|
|
return self._cache[instance_uuid] == backend
|
|
|
|
if not self._nova_has_extended_server_attributes(context):
|
|
LOG.warning('Hint "%s" dropped because '
|
|
'ExtendedServerAttributes not active in Nova.',
|
|
HINT_KEYWORD)
|
|
raise exception.CinderException(_('Hint "%s" not supported.') %
|
|
HINT_KEYWORD)
|
|
|
|
server = nova.API().get_server(context, instance_uuid,
|
|
privileged_user=True,
|
|
timeout=REQUESTS_TIMEOUT)
|
|
|
|
if not hasattr(server, INSTANCE_HOST_PROP):
|
|
LOG.warning('Hint "%s" dropped because Nova did not return '
|
|
'enough information. Either Nova policy needs to '
|
|
'be changed or a privileged account for Nova '
|
|
'should be specified in conf.', HINT_KEYWORD)
|
|
raise exception.CinderException(_('Hint "%s" not supported.') %
|
|
HINT_KEYWORD)
|
|
|
|
self._cache[instance_uuid] = getattr(server, INSTANCE_HOST_PROP)
|
|
|
|
# Match if given instance is hosted on backend
|
|
return self._cache[instance_uuid] == backend
|