Skip to content
Permalink
Browse files

AZ support for cells

Change-Id: I75d6f420cc3a7e9c11863dc5d8f037e7905c7f93
  • Loading branch information...
sorrison committed Nov 18, 2013
1 parent 8b86a6c commit 048bd2d6d438fb8fa9ad7d3e0d57e7d03c546f6f
@@ -282,8 +282,11 @@ def _describe_availability_zones(self, context, **kwargs):
result.append({'zoneName': zone,
'zoneState': "available"})
for zone in not_available_zones:
if zone == CONF.internal_service_availability_zone:
continue
result.append({'zoneName': zone,
'zoneState': "not available"})

return {'availabilityZoneInfo': result}

def _describe_availability_zones_verbose(self, context, **kwargs):
@@ -1233,7 +1236,7 @@ def _format_instances(self, context, instance_id=None, use_v6=False,
self._format_instance_bdm(context, instance['uuid'],
i['rootDeviceName'], i)
host = instance['host']
zone = ec2utils.get_availability_zone_by_host(host)
zone = availability_zones.get_instance_availability_zone(context, instance)
i['placement'] = {'availabilityZone': zone}
if instance['reservation_id'] not in reservations:
r = {}
@@ -37,7 +37,7 @@
from nova.openstack.common import timeutils
from nova import utils
from nova.virt import netutils

from nova import availability_zones

metadata_opts = [
cfg.StrOpt('config_drive_skip_versions',
@@ -152,7 +152,9 @@ def detail(self, req):
"""Returns a detailed list of availability zone."""
context = req.environ['nova.context']
authorize_detail(context)

if CONF.cells.enable:
#verbose doesn't work for cells
return self._describe_availability_zones(context)
return self._describe_availability_zones_verbose(context)


@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.

from oslo.config import cfg
import webob.exc

from nova.api.openstack import extensions
@@ -23,6 +24,10 @@
from nova import servicegroup
from nova import utils

CONF = cfg.CONF
CONF.import_opt('enable', 'nova.cells.opts', group='cells')


authorize = extensions.extension_authorizer('compute', 'services')


@@ -78,8 +83,11 @@ def __init__(self, ext_mgr=None, *args, **kwargs):
def _get_services(self, req):
context = req.environ['nova.context']
authorize(context)
set_zones = True
if CONF.cells.enable:
set_zones = False
services = self.host_api.service_get_all(
context, set_zones=True)
context, set_zones=set_zones)

host = ''
if 'host' in req.GET:
@@ -21,8 +21,10 @@

from nova import objects
from nova.cells import opts as cell_opts
from nova.cells import rpcapi as cell_rpcapi
from nova import db
from nova.openstack.common import memorycache
from nova.openstack.common import timeutils
from nova import utils

# NOTE(vish): azs don't change that often, so cache them for an hour to
@@ -40,6 +42,7 @@
]

CONF = cfg.CONF
CONF.import_opt('mute_child_interval', 'nova.cells.opts', group='cells')
CONF.register_opts(availability_zone_opts)


@@ -62,8 +65,8 @@ def reset_cache():
MC = None


def _make_cache_key(host):
return "azcache-%s" % host.encode('utf-8')
def _make_cache_key(host, cell_name=None):
return "azcache-%s-%s" % (cell_name or 'none', host.encode('utf-8'))


def _build_metadata_by_host(aggregates, hosts=None):
@@ -143,7 +146,36 @@ def get_availability_zones(context, get_only_available=False,
:param with_hosts: whether to return hosts part of the AZs
:type with_hosts: bool
"""
enabled_services = objects.ServiceList.get_all(context, disabled=False)
# Override for cells
cell_type = cell_opts.get_cell_type()
if cell_type == 'api':
cache = _get_cache()
available_zones = cache.get('az-availabile-list')
unavailable_zones = cache.get('az-unavailabile-list')

if not available_zones:
cells_rpcapi = cell_rpcapi.CellsAPI()
cell_info = cells_rpcapi.get_cell_info_for_neighbors(context)
global_azs = []
mute_azs = []
secs = CONF.cells.mute_child_interval
for cell in cell_info:
last_seen = cell['last_seen']
if 'availability_zones' not in cell['capabilities']:
continue
if last_seen and timeutils.is_older_than(last_seen, secs):
mute_azs.extend(cell['capabilities']['availability_zones'])
else:
global_azs.extend(cell['capabilities']['availability_zones'])
available_zones = list(set(global_azs))
unavailable_zones = list(set(mute_azs))
cache.set('az-availabile-list', available_zones, 300)
cache.set('az-unavailabile-list', unavailable_zones, 300)
if get_only_available:
return available_zones
return (available_zones, unavailable_zones)

enabled_services = objects.ServiceList.get_all(context, disabled=False)
enabled_services = set_availability_zones(context, enabled_services)

available_zones = []
@@ -0,0 +1,33 @@
from nova.cells import filters
from nova.openstack.common import log as logging
from nova.availability_zones import get_availability_zones

LOG = logging.getLogger(__name__)

class AvailabilityZoneFilter(filters.BaseCellFilter):
"""Filters cells by availability zone.
Works with cell capabilities using the key
'availability_zones'
Note: cell can have multiple availability zones
"""

def cell_passes(self, cell, filter_properties):
LOG.debug('Filtering on availability zones for cell %s' % cell)

available_zones = cell.capabilities.get('availability_zones', [])
LOG.debug('Aailable zones: %s' % available_zones)
spec = filter_properties.get('request_spec', {})
props = spec.get('instance_properties', {})
availability_zone = props.get('availability_zone')

if availability_zone:
return availability_zone in available_zones

# No AZ flag set, try deprecated scheduler hint
scheduler_hints = filter_properties.get('scheduler_hints', {}) or {}
availability_zone = scheduler_hints.get('cell', None)
if availability_zone:
return availability_zone in available_zones

return True
@@ -219,6 +219,43 @@ def _schedule_build_to_cells(self, message, instance_uuids,
try:
for i in xrange(max(0, CONF.cells.scheduler_retries) + 1):
try:
instances = method_kwargs.get('instances', [])
availability_zone = [ins.get('availability_zone', None)
for ins in instances]
our_azs = self.state_manager.get_my_state()\
.capabilities.get('availability_zones', [])

parent_cell = bool(self.state_manager.get_child_cells())
if not parent_cell and CONF.internal_service_availability_zone in our_azs:
our_azs.remove(CONF.internal_service_availability_zone)

if parent_cell:
# Try deprecated scheduler hint
if not any(availability_zone):
filter_props = method_kwargs.get(
'scheduler_hints', {})
scheduler_hints = filter_props.get(
'scheduler_hints', {})
availability_zone = scheduler_hints.get(
'cell', None)
cell_scheduled = scheduler_hints.pop('cell', None)
if scheduler_hints and cell_scheduled in our_azs:
scheduler_hints.pop('cell')

# If the instance is scheduled for our cell,
# then remove the AZ from the instance.
# If we're the bottom cell then the requested AZ must
# be in our list of AZs. If it's not, just remove it.
for instance in instances:
az = instance.get('availability_zone', None)
if ((parent_cell and az in our_azs) or
(not parent_cell and az not in our_azs)):
if 'availability_zone' in instance:
instance.pop('availability_zone')
try:
filter_properties['request_spec']['instance_properties']['availability_zone'] = None
except KeyError:
pass
target_cells = self._grab_target_cells(filter_properties)
if target_cells is None:
# a filter took care of scheduling. skip.
@@ -25,8 +25,10 @@
from oslo.db import exception as db_exc

from nova.cells import rpc_driver
from nova.cells import opts as cell_opts
from nova import context
from nova.db import base
from nova.availability_zones import get_availability_zones
from nova import exception
from nova.i18n import _
from nova.openstack.common import fileutils
@@ -66,7 +68,7 @@ class CellState(object):
def __init__(self, cell_name, is_me=False):
self.name = cell_name
self.is_me = is_me
self.last_seen = datetime.datetime.min
self.last_seen = timeutils.utcnow()
self.capabilities = {}
self.capacities = {}
self.db_info = {}
@@ -108,6 +110,7 @@ def get_cell_info(self):
if url.hosts:
for field, canonical in url_fields_to_return.items():
cell_info[canonical] = getattr(url.hosts[0], field)
cell_info['last_seen'] = self.last_seen
return cell_info

def send_message(self, message):
@@ -182,16 +185,6 @@ def __init__(self, cell_state_cls=None):
LOG.exception(_('DB error: %s') % e)
time.sleep(30)

my_cell_capabs = {}
for cap in CONF.cells.capabilities:
name, value = cap.split('=', 1)
if ';' in value:
values = set(value.split(';'))
else:
values = set([value])
my_cell_capabs[name] = values
self.my_cell_state.update_capabilities(my_cell_capabs)

def _refresh_cells_from_dict(self, db_cells_dict):
"""Make our cell info map match the db."""

@@ -220,6 +213,23 @@ def _time_to_sync(self):
diff = timeutils.utcnow() - self.last_cell_db_check
return diff.seconds >= CONF.cells.db_check_interval

def _update_our_capabilities(self, ctxt=None):
my_cell_capabs = {}
for cap in CONF.cells.capabilities:
name, value = cap.split('=', 1)
if ';' in value:
values = set(value.split(';'))
else:
values = set([value])
my_cell_capabs[name] = values
ctxt = context.get_admin_context()
cell_type = cell_opts.get_cell_type()
if cell_type == 'compute':
active, disabled = get_availability_zones(ctxt)
# Only send up available AZs
my_cell_capabs['availability_zones'] = set(active)
self.my_cell_state.update_capabilities(my_cell_capabs)

def _update_our_capacity(self, ctxt=None):
"""Update our capacity in the self.my_cell_state CellState.
@@ -453,6 +463,7 @@ def _cell_data_sync(self, force=False):
db_cells_dict = dict((cell['name'], cell) for cell in db_cells)
self._refresh_cells_from_dict(db_cells_dict)
self._update_our_capacity(ctxt)
self._update_our_capabilities(ctxt)

@sync_after
def cell_create(self, ctxt, values):
@@ -493,6 +504,7 @@ def _cell_data_sync(self, force=False):
if force or self._time_to_sync():
self.last_cell_db_check = timeutils.utcnow()
self._update_our_capacity()
self._update_our_capabilities()

def cell_create(self, ctxt, values):
raise exception.CellsUpdateProhibited()
@@ -733,6 +733,7 @@ def _validate_and_build_base_options(self, context, instance_type,
strategy being performed.
"""
if availability_zone:

available_zones = availability_zones.\
get_availability_zones(context.elevated(), True)
if forced_host is None and availability_zone not in \
@@ -90,7 +90,7 @@ def fake_set_availability_zones(context, services):
return services


def fake_get_availability_zones(context):
def fake_get_availability_zones(context, cells_api=None):
return ['nova'], []


@@ -66,11 +66,11 @@ def fake_compute_get_all(*args, **kwargs):
db_list, fields)


def fake_get_host_availability_zone(context, host):
def fake_get_host_availability_zone(context, host, cell=None):
return host


def fake_get_no_host_availability_zone(context, host):
def fake_get_no_host_availability_zone(context, host, cell=None):
return None


@@ -329,7 +329,7 @@ def _test(self, *args):

try:
self.scheduler._schedule_build_to_cells(None, None, None, _test,
None)
{})
except test.TestingException:
self.fail("Scheduling did not properly short circuit")

0 comments on commit 048bd2d

Please sign in to comment.
You can’t perform that action at this time.