blazar/climate/plugins/oshosts/reservation_pool.py

274 lines
9.9 KiB
Python

# Copyright (c) 2013 OpenStack Foundation
#
# Author: Swann Croiset <swann.croiset@bull.net>
#
# 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 uuid as uuidgen
from novaclient import client
from novaclient import exceptions as nova_exceptions
from oslo.config import cfg
from climate import context
from climate.manager import exceptions as manager_exceptions
from climate.openstack.common import log as logging
LOG = logging.getLogger(__name__)
opts = [
cfg.StrOpt('aggregate_freepool_name',
default='freepool',
help='Name of the special aggregate where all hosts '
'are candidate for physical host reservation'),
cfg.StrOpt('tenant_id_key',
default='climate:tenant',
help='Aggregate metadata value for key matching tenant_id'),
cfg.StrOpt('climate_owner',
default='climate:owner',
help='Aggregate metadata key for knowing owner tenant_id'),
cfg.StrOpt('climate_az_prefix',
default='climate:',
help='Prefix for Availability Zones created by Climate'),
]
cfg.CONF.register_opts(opts, 'physical:host')
class ReservationPool(object):
def __init__(self):
self.ctx = context.current()
self.freepool_name = cfg.CONF['physical:host'].aggregate_freepool_name
#TODO(scroiset): use catalog to find the url
auth_url = "%s://%s:%s/v2.0" % (cfg.CONF.os_auth_protocol,
cfg.CONF.os_auth_host,
cfg.CONF.os_auth_port)
self.nova = client.Client('2',
username=cfg.CONF.climate_username,
api_key=cfg.CONF.climate_password,
auth_url=auth_url,
project_id=cfg.CONF.climate_tenant_name)
def get_aggregate_from_name_or_id(self, aggregate_obj):
"""Return an aggregate by name or an id."""
aggregate = None
agg_id = None
try:
agg_id = int(aggregate_obj)
except (ValueError, TypeError):
if hasattr(aggregate_obj, 'id') and aggregate_obj.id:
# pool is an aggregate
agg_id = aggregate_obj.id
if agg_id is not None:
try:
aggregate = self.nova.aggregates.get(agg_id)
except nova_exceptions.NotFound:
aggregate = None
else:
#FIXME(scroiset): can't get an aggregate by name
# so iter over all aggregate and check for the good one
all_aggregates = self.nova.aggregates.list()
for agg in all_aggregates:
if aggregate_obj == agg.name:
aggregate = agg
if aggregate:
return aggregate
else:
raise manager_exceptions.AggregateNotFound(pool=aggregate_obj)
@staticmethod
def _generate_aggregate_name():
return str(uuidgen.uuid4())
def create(self, name=None, az=True):
"""Create a Pool (an Aggregate) with or without Availability Zone.
By default expose to user the aggregate with an Availability Zone.
Return an aggregate or raise a nova exception.
"""
name = name or self._generate_aggregate_name()
if az:
az_name = "%s%s" % (cfg.CONF['physical:host'].climate_az_prefix,
name)
LOG.debug('Creating pool aggregate: %s'
'with Availability Zone %s' % (name, az_name))
agg = self.nova.aggregates.create(name, az_name)
else:
LOG.debug('Creating pool aggregate: %s'
'without Availability Zone' % name)
agg = self.nova.aggregates.create(name, None)
meta = {cfg.CONF['physical:host'].climate_owner: self.ctx.tenant_id}
self.nova.aggregates.set_metadata(agg, meta)
return agg
def delete(self, pool, force=True):
"""Delete an aggregate.
pool can be an aggregate name or an aggregate id.
Remove all hosts before delete aggregate (default).
If force is False, raise exception if at least one
host is attached to.
"""
agg = self.get_aggregate_from_name_or_id(pool)
hosts = agg.hosts
if len(hosts) > 0 and not force:
raise manager_exceptions.AggregateHaveHost(name=agg.name,
hosts=agg.hosts)
try:
freepool_agg = self.get(self.freepool_name)
except manager_exceptions.AggregateNotFound:
raise manager_exceptions.NoFreePool()
for host in hosts:
LOG.debug("Removing host '%s' from aggregate "
"'%s')" % (host, agg.id))
self.nova.aggregates.remove_host(agg.id, host)
if freepool_agg.id != agg.id:
self.nova.aggregates.add_host(freepool_agg.id, host)
self.nova.aggregates.delete(agg.id)
def get_all(self):
"""Return all aggregate."""
return self.nova.aggregates.list()
def get(self, pool):
"""return details for aggregate pool or raise AggregateNotFound."""
return self.get_aggregate_from_name_or_id(pool)
def get_computehosts(self, pool):
"""Return a list of compute host names for an aggregate."""
try:
agg = self.get_aggregate_from_name_or_id(pool)
return agg.hosts
except manager_exceptions.AggregateNotFound:
return []
def add_computehost(self, pool, host):
"""Add a compute host to an aggregate.
The `host` must exist otherwise raise an error
and the `host` must be in the freepool.
Return the related aggregate.
Raise an aggregate exception if something wrong.
"""
agg = self.get_aggregate_from_name_or_id(pool)
try:
freepool_agg = self.get(self.freepool_name)
except manager_exceptions.AggregateNotFound:
raise manager_exceptions.NoFreePool()
if freepool_agg.id != agg.id:
if host not in freepool_agg.hosts:
raise manager_exceptions.HostNotInFreePool(
host=host, freepool_name=freepool_agg.name)
LOG.info("removing host '%s' "
"from aggregate freepool %s" % (host, freepool_agg.name))
try:
self.remove_computehost(freepool_agg.id, host)
except nova_exceptions.NotFound:
raise manager_exceptions.HostNotFound(host=host)
LOG.info("adding host '%s' to aggregate %s" % (host, agg.id))
try:
return self.nova.aggregates.add_host(agg.id, host)
except nova_exceptions.NotFound:
raise manager_exceptions.HostNotFound(host=host)
except nova_exceptions.Conflict:
raise manager_exceptions.AggregateAlreadyHasHost(pool=pool,
host=host)
def remove_all_computehosts(self, pool):
"""Remove all compute hosts attached to an aggregate."""
hosts = self.get_computehosts(pool)
self.remove_computehost(pool, hosts)
def remove_computehost(self, pool, hosts):
"""Remove compute host(s) from an aggregate."""
if not isinstance(hosts, list):
hosts = [hosts]
agg = self.get_aggregate_from_name_or_id(pool)
try:
freepool_agg = self.get(self.freepool_name)
except manager_exceptions.AggregateNotFound:
raise manager_exceptions.NoFreePool()
hosts_failing_to_remove = []
hosts_failing_to_add = []
hosts_not_in_freepool = []
for host in hosts:
if freepool_agg.id == agg.id:
if host not in freepool_agg.hosts:
hosts_not_in_freepool.append(host)
continue
try:
self.nova.aggregates.remove_host(agg.id, host)
except nova_exceptions.ClientException:
hosts_failing_to_remove.append(host)
if freepool_agg.id != agg.id:
# NOTE(sbauza) : We don't want to put again the host in
# freepool if the requested pool is the freepool...
try:
self.nova.aggregates.add_host(freepool_agg.id, host)
except nova_exceptions.ClientException:
hosts_failing_to_add.append(host)
if hosts_failing_to_remove:
raise manager_exceptions.CantRemoveHost(
host=hosts_failing_to_remove, pool=agg)
if hosts_failing_to_add:
raise manager_exceptions.CantAddHost(host=hosts_failing_to_add,
pool=freepool_agg)
if hosts_not_in_freepool:
raise manager_exceptions.HostNotInFreePool(
host=hosts_not_in_freepool, freepool_name=freepool_agg.name)
def add_project(self, pool, project_id):
"""Add a project to an aggregate."""
metadata = {project_id: cfg.CONF['physical:host'].tenant_id_key}
agg = self.get_aggregate_from_name_or_id(pool)
return self.nova.aggregates.set_metadata(agg.id, metadata)
def remove_project(self, pool, project_id):
"""Remove a project from an aggregate."""
agg = self.get_aggregate_from_name_or_id(pool)
metadata = {project_id: None}
return self.nova.aggregates.set_metadata(agg.id, metadata)