cinder/cinder/scheduler/filters/capacity_filter.py

214 lines
9.7 KiB
Python

# Copyright (c) 2012 Intel
# Copyright (c) 2012 OpenStack Foundation
# Copyright (c) 2015 EMC Corporation
#
# 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_log import log as logging
from cinder.scheduler import filters
from cinder import utils
LOG = logging.getLogger(__name__)
class CapacityFilter(filters.BaseBackendFilter):
"""Capacity filters based on volume backend's capacity utilization."""
def backend_passes(self, backend_state, filter_properties):
"""Return True if host has sufficient capacity."""
volid = None
# If the volume already exists on this host, don't fail it for
# insufficient capacity (e.g., if we are retyping)
if backend_state.backend_id == filter_properties.get('vol_exists_on'):
return True
spec = filter_properties.get('request_spec')
if spec:
volid = spec.get('volume_id')
grouping = 'cluster' if backend_state.cluster_name else 'host'
if filter_properties.get('new_size'):
# If new_size is passed, we are allocating space to extend a volume
requested_size = (int(filter_properties.get('new_size')) -
int(filter_properties.get('size')))
LOG.debug('Checking if %(grouping)s %(grouping_name)s can extend '
'the volume %(id)s in %(size)s GB',
{'grouping': grouping,
'grouping_name': backend_state.backend_id, 'id': volid,
'size': requested_size})
else:
requested_size = filter_properties.get('size')
LOG.debug('Checking if %(grouping)s %(grouping_name)s can create '
'a %(size)s GB volume (%(id)s)',
{'grouping': grouping,
'grouping_name': backend_state.backend_id, 'id': volid,
'size': requested_size})
# requested_size is 0 means that it's a manage request.
if requested_size == 0:
return True
if backend_state.free_capacity_gb is None:
# Fail Safe
LOG.error("Free capacity not set: "
"volume node info collection broken.")
return False
free_space = backend_state.free_capacity_gb
total_space = backend_state.total_capacity_gb
reserved = float(backend_state.reserved_percentage) / 100
if free_space in ['infinite', 'unknown']:
# NOTE(zhiteng) for those back-ends cannot report actual
# available capacity, we assume it is able to serve the
# request. Even if it was not, the retry mechanism is
# able to handle the failure by rescheduling
return True
elif total_space in ['infinite', 'unknown']:
# If total_space is 'infinite' or 'unknown' and reserved
# is 0, we assume the back-ends can serve the request.
# If total_space is 'infinite' or 'unknown' and reserved
# is not 0, we cannot calculate the reserved space.
# float(total_space) will throw an exception. total*reserved
# also won't work. So the back-ends cannot serve the request.
if reserved == 0:
return True
LOG.debug("Cannot calculate GB of reserved space (%s%%) with "
"backend's reported total capacity '%s'",
backend_state.reserved_percentage, total_space)
return False
total = float(total_space)
if total <= 0:
LOG.warning("Insufficient free space for volume creation. "
"Total capacity is %(total).2f on %(grouping)s "
"%(grouping_name)s.",
{"total": total,
"grouping": grouping,
"grouping_name": backend_state.backend_id})
return False
# NOTE(xyang): If 'provisioning:type' is 'thick' in extra_specs,
# we will not use max_over_subscription_ratio and
# provisioned_capacity_gb to determine whether a volume can be
# provisioned. Instead free capacity will be used to evaluate.
thin = True
vol_type = filter_properties.get('volume_type', {}) or {}
provision_type = vol_type.get('extra_specs', {}).get(
'provisioning:type')
if provision_type == 'thick':
thin = False
thin_support = backend_state.thin_provisioning_support
if thin_support:
max_over_subscription_ratio = (
backend_state.max_over_subscription_ratio
)
else:
max_over_subscription_ratio = 1
# NOTE(hemna): this takes into consideration all major factors
# including reserved space, free_space (reported by driver),
# and over subscription ratio.
factors = utils.calculate_capacity_factors(
total_space,
free_space,
backend_state.provisioned_capacity_gb,
thin_support,
max_over_subscription_ratio,
backend_state.reserved_percentage,
thin
)
virtual_free_space = factors["virtual_free_capacity"]
LOG.debug("Storage Capacity factors %s", factors)
msg_args = {"grouping_name": backend_state.backend_id,
"grouping": grouping,
"requested": requested_size,
"available": virtual_free_space}
# Only evaluate using max_over_subscription_ratio if
# thin_provisioning_support is True. Check if the ratio of
# provisioned capacity over total capacity has exceeded over
# subscription ratio.
if (thin and backend_state.thin_provisioning_support and
backend_state.max_over_subscription_ratio >= 1):
provisioned_ratio = (
(backend_state.provisioned_capacity_gb + requested_size) / (
factors["total_available_capacity"]
)
)
LOG.debug("Checking provisioning for request of %s GB. "
"Backend: %s", requested_size, backend_state)
if provisioned_ratio > backend_state.max_over_subscription_ratio:
msg_args = {
"provisioned_ratio": provisioned_ratio,
"oversub_ratio": backend_state.max_over_subscription_ratio,
"grouping": grouping,
"grouping_name": backend_state.backend_id,
}
LOG.warning(
"Insufficient free space for thin provisioning. "
"The ratio of provisioned capacity over total capacity "
"%(provisioned_ratio).2f has exceeded the maximum over "
"subscription ratio %(oversub_ratio).2f on %(grouping)s "
"%(grouping_name)s.", msg_args)
return False
else:
# Thin provisioning is enabled and projected over-subscription
# ratio does not exceed max_over_subscription_ratio. The host
# passes if virtual free capacity is enough to
# accommodate the volume. Adjusted free virtual capacity is
# the currently available free capacity (taking into account
# of reserved space) which we can over-subscribe.
msg_args["available"] = virtual_free_space
res = virtual_free_space >= requested_size
if not res:
LOG.warning("Insufficient free virtual space "
"(%(available)sGB) to accommodate thin "
"provisioned %(requested)sGB volume on "
"%(grouping)s %(grouping_name)s.", msg_args)
else:
LOG.debug("Space information for volume creation "
"on %(grouping)s %(grouping_name)s "
"(requested / avail): "
"%(requested)s/%(available)s", msg_args)
return res
elif thin and backend_state.thin_provisioning_support:
LOG.warning("Filtering out %(grouping)s %(grouping_name)s "
"with an invalid maximum over subscription ratio "
"of %(oversub_ratio).2f. The ratio should be a "
"minimum of 1.0.",
{"oversub_ratio":
backend_state.max_over_subscription_ratio,
"grouping": grouping,
"grouping_name": backend_state.backend_id})
return False
if virtual_free_space < requested_size:
LOG.warning("Insufficient free space for volume creation "
"on %(grouping)s %(grouping_name)s (requested / "
"avail): %(requested)s/%(available)s",
msg_args)
return False
LOG.debug("Space information for volume creation "
"on %(grouping)s %(grouping_name)s (requested / avail): "
"%(requested)s/%(available)s", msg_args)
return True