Add Host Reservation Admin Manager

API extensions are provided using a RPC dispatcher
We need to explicitly mention plugin:method when calling.
If not, it will stick to core methods for ManagerService.

Implements bp:host-provisioning-api

Change-Id: Ice5a07eef54664651c22b6b1185be38062c7296e
This commit is contained in:
sbauza 2013-10-25 14:40:40 +02:00 committed by Sylvain Bauza
parent bac9cf583b
commit 984a43683e
17 changed files with 801 additions and 167 deletions

View File

@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from climate import exceptions
from climate.manager.oshosts import rpcapi as manager_rpcapi
from climate.openstack.common import log as logging
@ -35,9 +34,6 @@ class API(object):
:param data: New computehost characteristics.
:type data: dict
"""
# here API should go to Keystone API v3 and create trust
trust = 'trust'
data.update({'trust': trust})
return self.manager_rpcapi.create_computehost(data)
@ -57,13 +53,6 @@ class API(object):
:param data: New computehost characteristics.
:type data: dict
"""
new_name = data.pop('name', None)
if len(data) > 0:
raise exceptions.ClimateException('Only name changing may be '
'proceeded.')
data = {}
if new_name:
data['name'] = new_name
return self.manager_rpcapi.update_computehost(host_id, data)
def delete_computehost(self, host_id):

View File

@ -334,3 +334,9 @@ def host_extra_capability_destroy(host_extra_capability_id):
def host_extra_capability_update(host_extra_capability_id, values):
"""Update specific host ExtraCapability."""
IMPL.host_extra_capability_update(host_extra_capability_id, values)
def host_extra_capability_get_all_per_name(host_id,
extra_capability_name):
return IMPL.host_extra_capability_get_all_per_name(host_id,
extra_capability_name)

View File

@ -558,12 +558,12 @@ def host_extra_capability_get(host_extra_capability_id):
def _host_extra_capability_get_all_per_host(session, host_id):
query = model_query(models.ComputeHostExtraCapability, session)
return query.filter_by(computehost_id=host_id).all()
return query.filter_by(computehost_id=host_id)
def host_extra_capability_get_all_per_host(host_id):
return _host_extra_capability_get_all_per_host(get_session(),
host_id)
host_id).all()
def host_extra_capability_create(values):
@ -607,3 +607,11 @@ def host_extra_capability_destroy(host_extra_capability_id):
raise RuntimeError("Host Extracapability not found!")
session.delete(host_extra_capability)
def host_extra_capability_get_all_per_name(host_id, capability_name):
session = get_session()
with session.begin():
query = _host_extra_capability_get_all_per_host(session, host_id)
return query.filter_by(capability_name=capability_name).all()

View File

@ -128,7 +128,7 @@ class ComputeHost(mb.ClimateBase):
__tablename__ = 'computehosts'
id = _id_column()
vcpu = sa.Column(sa.Integer, nullable=False)
vcpus = sa.Column(sa.Integer, nullable=False)
cpu_info = sa.Column(MediumText(), nullable=False)
hypervisor_type = sa.Column(MediumText(), nullable=False)
hypervisor_version = sa.Column(sa.Integer, nullable=False)

View File

@ -1,31 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2013 Julien Danjou <julien@danjou.info>
#
# 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 abc
class Plugin(object):
"""Base class for inventory plugins."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def list_hosts(self):
"""List hosts."""
@abc.abstractmethod
def get_host_details(self, host):
"""Get host details."""

View File

@ -1,60 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2013 Julien Danjou <julien@danjou.info>
#
# 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 os
from novaclient import client
from oslo.config import cfg
from climate import inventory
CLI_OPTIONS = [
cfg.StrOpt('os-username',
default=os.environ.get('OS_USERNAME', 'climate'),
help='Username to use for OpenStack service access'),
cfg.StrOpt('os-password',
default=os.environ.get('OS_PASSWORD', 'admin'),
help='Password to use for OpenStack service access'),
cfg.StrOpt('os-tenant-id',
default=os.environ.get('OS_TENANT_ID', ''),
help='Tenant ID to use for OpenStack service access'),
cfg.StrOpt('os-tenant-name',
default=os.environ.get('OS_TENANT_NAME', 'admin'),
help='Tenant name to use for OpenStack service access'),
cfg.StrOpt('os-auth-url',
default=os.environ.get('OS_AUTH_URL',
'http://localhost:5000/v2.0'),
help='Auth URL to use for openstack service access'),
]
cfg.CONF.register_cli_opts(CLI_OPTIONS)
class NovaInventory(inventory.Plugin):
def __init__(self):
self.novaclient = client.Client(
"2",
username=cfg.CONF.os_username,
api_key=cfg.CONF.os_password,
auth_url=cfg.CONF.os_auth_url,
project_id=cfg.CONF.os_tenant_name or cfg.CONF.os_tenant_id)
def list_hosts(self):
return self.novaclient.hypervisors.list()
def get_host_details(self, host):
return self.novaclient.hypervisors.get(host)

View File

@ -37,20 +37,23 @@ class ManagerRPCAPI(service.RpcProxy):
def get_computehost(self, host_id):
"""Get detailed info about some computehost."""
return self.call('get_computehost', host_id=host_id)
return self.call('physical:host:get_computehost', host_id=host_id)
def list_computehosts(self):
"""List all computehosts."""
return self.call('list_computehosts')
return self.call('physical:host:list_computehosts')
def create_computehost(self, host_values):
"""Create computehost with specified parameters."""
return self.call('create_computehost', host_values=host_values)
return self.call('physical:host:create_computehost',
host_values=host_values)
def update_computehost(self, host_id, values):
"""Update computehost with passes values dictionary."""
return self.call('update_computehost', host_id=host_id, values=values)
return self.call('physical:host:update_computehost', host_id=host_id,
values=values)
def delete_computehost(self, host_id):
"""Delete specified computehost."""
return self.cast('delete_computehost', host_id=host_id)
return self.cast('physical:host:delete_computehost',
host_id=host_id)

View File

@ -103,7 +103,7 @@ class ManagerService(rpc_service.Service):
actions[resource_type] = {}
actions[resource_type]['on_start'] = plugin.on_start
actions[resource_type]['on_end'] = plugin.on_end
plugin.setup(None)
return actions
@service_utils.with_empty_context
@ -211,3 +211,26 @@ class ManagerService(rpc_service.Service):
{'status': reservation_status})
db_api.event_update(event_id, {'status': 'DONE'})
def __getattr__(self, name):
"""RPC Dispatcher for plugins methods."""
fn = None
try:
resource_type, method = name.rsplit(':', 1)
except ValueError:
# NOTE(sbauza) : the dispatcher needs to know which plugin to use,
# raising error if consequently not
raise AttributeError(name)
try:
try:
fn = getattr(self.plugins[resource_type], method)
except KeyError:
LOG.error("Plugin with resource type %s not found",
resource_type)
except AttributeError:
LOG.error("Plugin %s doesn't include method %s",
self.plugins[resource_type], method)
if fn is not None:
return fn
raise AttributeError(name)

View File

@ -0,0 +1,106 @@
# Copyright (c) 2013 Bull.
#
# 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 novaclient import client
from novaclient import exceptions as nova_exceptions
from oslo.config import cfg
from climate import context
from climate import exceptions
from climate.openstack.common.gettextutils import _ # noqa
class HostNotFound(exceptions.ClimateException):
msg_fmt = _("Host '%(host)s' not found!")
class InvalidHost(exceptions.ClimateException):
msg_fmt = _("Invalid values for host %(host)s")
class MultipleHostsFound(exceptions.ClimateException):
msg_fmt = _("Multiple Hosts found for pattern '%(host)s'")
class HostHavingServers(exceptions.ClimateException):
msg_fmt = _("Servers [%(servers)s] found for host %(host)s")
class NovaInventory(object):
def __init__(self):
self.ctx = context.current()
#TODO(sbauza): 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.os_admin_username,
api_key=cfg.CONF.os_admin_password,
auth_url=auth_url,
project_id=cfg.CONF.os_admin_tenant_name)
def get_host_details(self, host):
"""Get Nova capabilities of a single host
:param host: UUID or name of nova-compute host
:return: Dict of capabilities or raise HostNotFound
"""
try:
hypervisor = self.nova.hypervisors.get(host)
except nova_exceptions.NotFound:
try:
hypervisors_list = self.nova.hypervisors.search(host)
except nova_exceptions.NotFound:
raise HostNotFound(host=host)
if len(hypervisors_list) > 1:
raise MultipleHostsFound(host)
else:
hypervisor_id = hypervisors_list[0].id
# NOTE(sbauza): No need to catch the exception as we're sure
# that the hypervisor exists
hypervisor = self.nova.hypervisors.get(hypervisor_id)
try:
return {'id': hypervisor.id,
'hypervisor_hostname': hypervisor.hypervisor_hostname,
'vcpus': hypervisor.vcpus,
'cpu_info': hypervisor.cpu_info,
'hypervisor_type': hypervisor.hypervisor_type,
'hypervisor_version': hypervisor.hypervisor_version,
'memory_mb': hypervisor.memory_mb,
'local_gb': hypervisor.local_gb}
except AttributeError:
raise InvalidHost(host=host)
def get_servers_per_host(self, host):
"""List all servers of a nova-compute host
:param host: Name (not UUID) of nova-compute host
:return: Dict of servers or None
"""
try:
hypervisors_list = self.nova.hypervisors.search(host, servers=True)
except nova_exceptions.NotFound:
raise HostNotFound(host=host)
if len(hypervisors_list) > 1:
raise MultipleHostsFound(host)
else:
try:
return hypervisors_list[0].servers
except AttributeError:
# NOTE(sbauza): nova.hypervisors.search(servers=True) returns
# a list of hosts without 'servers' attribute if no servers
# are running on that host
return None

View File

@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
#
# Author: François Rossigneux <francois.rossigneux@inria.fr>
#
# 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.config import cfg
from climate import context
from climate.db import api as db_api
from climate import exceptions
from climate.openstack.common.gettextutils import _ # noqa
from climate.plugins import base
from climate.plugins.oshosts import nova_inventory
from climate.plugins.oshosts import reservation_pool as rp
from climate.utils import service as service_utils
class CantAddExtraCapability(exceptions.ClimateException):
msg_fmt = _("Can't add extracapabilities %(keys)s to Host %(host)s")
class PhysicalHostPlugin(base.BasePlugin):
"""Plugin for physical host resource."""
resource_type = 'physical:host'
title = 'Physical Host Plugin'
description = 'This plugin starts and shutdowns the hosts.'
freepool_name = cfg.CONF[resource_type].aggregate_freepool_name
pool = None
inventory = None
def on_start(self, resource_id):
"""Add the hosts in the pool."""
pass
def on_end(self, resource_id):
"""Remove the hosts from the pool."""
pass
def setup(self, conf):
# Create freepool if not exists
with context.ClimateContext() as ctx:
ctx = ctx.elevated()
if self.pool is None:
self.pool = rp.ReservationPool()
if self.inventory is None:
self.inventory = nova_inventory.NovaInventory()
if not self._freepool_exists():
self.pool.create(name=self.freepool_name, az=None)
def _freepool_exists(self):
try:
self.pool.get_aggregate_from_name_or_id(self.freepool_name)
return True
except rp.AggregateNotFound:
return False
def _get_extra_capabilities(self, host_id):
extra_capabilities = {}
raw_extra_capabilities = \
db_api.host_extra_capability_get_all_per_host(host_id)
for capability in raw_extra_capabilities:
extra_capabilities[capability['capability_name']] = \
capability['capability_value']
return extra_capabilities
@service_utils.export_context
def get_computehost(self, host_id):
host = db_api.host_get(host_id)
extra_capabilities = self._get_extra_capabilities(host_id)
if host is not None and extra_capabilities:
res = host.copy()
res.update(extra_capabilities)
return res
else:
return host
@service_utils.export_context
def list_computehosts(self):
raw_host_list = db_api.host_list()
host_list = []
for host in raw_host_list:
host_list.append(self.get_computehost(host['id']))
return host_list
@service_utils.export_context
def create_computehost(self, host_values):
# TODO(sbauza):
# - Exception handling for HostNotFound
host_id = host_values.pop('id', None)
host_name = host_values.pop('name', None)
host_ref = host_id or host_name
if host_ref is None:
raise nova_inventory.InvalidHost(host=host_values)
servers = self.inventory.get_servers_per_host(host_ref)
if servers:
raise nova_inventory.HostHavingServers(host=host_ref,
servers=servers)
host_details = self.inventory.get_host_details(host_ref)
# NOTE(sbauza): Only last duplicate name for same extra capability will
# be stored
extra_capabilities_keys = \
set(host_values.keys()) - set(host_details.keys())
extra_capabilities = \
dict((key, host_values[key]) for key in extra_capabilities_keys)
self.pool.add_computehost(self.freepool_name, host_ref)
host = None
cantaddextracapability = []
try:
host = db_api.host_create(host_details)
except RuntimeError:
#We need to rollback
# TODO(sbauza): Investigate use of Taskflow for atomic transactions
self.pool.remove_computehost(self.freepool_name, host_ref)
if host:
for key in extra_capabilities:
values = {'computehost_id': host['id'],
'capability_name': key,
'capability_value': extra_capabilities[key]}
try:
db_api.host_extra_capability_create(values)
except RuntimeError:
cantaddextracapability.append(key)
if cantaddextracapability:
raise CantAddExtraCapability(keys=cantaddextracapability,
host=host['id'])
if host:
return self.get_computehost(host['id'])
else:
return None
@service_utils.export_context
def update_computehost(self, host_id, values):
# NOTE (sbauza): Only update existing extra capabilites, don't create
# other ones
if values:
cantupdateextracapability = []
for value in values:
capabilities = \
db_api.host_extra_capability_get_all_per_name(host_id,
value)
for raw_capability in capabilities:
capability = {'capability_name': value,
'capability_value': values[value]}
try:
db_api.host_extra_capability_update(
raw_capability['id'], capability)
except RuntimeError:
cantupdateextracapability.append(
raw_capability['capability_name'])
if cantupdateextracapability:
raise CantAddExtraCapability(host=host_id,
keys=cantupdateextracapability)
return self.get_computehost(host_id)
@service_utils.export_context
def delete_computehost(self, host_id):
# TODO(sbauza):
# - Check if no leases having this host scheduled
servers = self.inventory.get_servers_per_host(host_id)
if servers:
raise nova_inventory.HostHavingServers(host=host_id,
servers=servers)
host = db_api.host_get(host_id)
if not host:
raise rp.HostNotFound(host=host_id)
try:
self.pool.remove_computehost(self.freepool_name,
host['hypervisor_hostname'])
# NOTE(sbauza): Extracapabilities will be destroyed thanks to
# the DB FK.
db_api.host_destroy(host_id)
except RuntimeError:
# Nothing so bad, but we need to advert the admin he has to rerun
raise rp.CantRemoveHost(host=host_id, pool=self.freepool_name)

View File

@ -111,7 +111,7 @@ def _get_fake_cpu_info():
def _get_fake_host_values(id=_get_fake_random_uuid(), mem=8192, disk=10):
return {'id': id,
'vcpu': 1,
'vcpus': 1,
'cpu_info': _get_fake_cpu_info(),
'hypervisor_type': 'QEMU',
'hypervisor_version': 1000,
@ -410,3 +410,12 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
self.assertEqual(None, db_api.host_extra_capability_get('1'))
self.assertRaises(RuntimeError,
db_api.host_extra_capability_destroy, '1')
def test_host_extra_capability_get_all_per_name(self):
db_api.host_extra_capability_create(
_get_fake_host_extra_capabilities(id='1', computehost_id='1'))
res = db_api.host_extra_capability_get_all_per_name('1', 'vgpu')
self.assertEqual(1, len(res))
self.assertEqual([],
db_api.host_extra_capability_get_all_per_name('1',
'bad'))

View File

@ -1,54 +0,0 @@
# Copyright (c) 2013 Julien Danjou <julien@danjou.info>
#
# 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 mock
from climate.inventory import nova
from climate import tests
class ServiceTestCase(tests.TestCase):
"""This test class should be removed, but is kept as an example for unit
testing.
"""
@staticmethod
def fake_hypervisors_list():
a, b = mock.MagicMock(), mock.MagicMock()
a.id = 1
b.id = 2
return [a, b]
@staticmethod
def fake_hypervisors_get(h):
return {'stuff': 'foobar',
'cpu_info': {'arch': 'x86'}}
def setUp(self):
super(ServiceTestCase, self).setUp()
self.i = nova.NovaInventory()
self.patch(self.i.novaclient.hypervisors, "list").\
side_effect = self.fake_hypervisors_list
self.patch(self.i.novaclient.hypervisors, "get").\
side_effect = self.fake_hypervisors_get
def test_list_hosts(self):
hosts = self.i.list_hosts()
self.assertEqual(len(hosts), 2)
def test_get_host_detail(self):
hosts = self.i.list_hosts()
detail = self.i.get_host_details(hosts[0])
self.assertEqual(detail['cpu_info']['arch'], 'x86')

View File

@ -25,6 +25,7 @@ from climate.db import api as db_api
from climate import exceptions
from climate.manager import service
from climate.plugins import dummy_vm_plugin
from climate.plugins import physical_host_plugin
from climate import tests
@ -42,9 +43,14 @@ class ServiceTestCase(tests.TestCase):
self.db_api = db_api
self.dummy_plugin = dummy_vm_plugin
self.manager = self.service.ManagerService('127.0.0.1')
self.fake_plugin = self.patch(self.dummy_plugin, 'DummyVMPlugin')
self.physical_host_plugin = physical_host_plugin
self.fake_phys_plugin = self.patch(self.physical_host_plugin,
'PhysicalHostPlugin')
self.manager = self.service.ManagerService('127.0.0.1')
self.lease_id = '11-22-33'
self.lease = {'id': self.lease_id,
'reservations': [{'id': '111',
@ -237,3 +243,39 @@ class ServiceTestCase(tests.TestCase):
self.reservation_update.assert_called_once_with(
'111', {'status': 'IN_USE'})
self.event_update.assert_called_once_with('1', {'status': 'DONE'})
def test_getattr_with_correct_plugin_and_method(self):
self.fake_list_computehosts = \
self.patch(self.fake_phys_plugin, 'list_computehosts')
self.fake_list_computehosts.return_value = 'foo'
self.manager.plugins = {'physical:host': self.fake_phys_plugin}
self.assertEqual('foo', getattr(self.manager,
'physical:host:list_computehosts')())
def test_getattr_with_incorrect_method_name(self):
self.fake_list_computehosts = \
self.patch(self.fake_phys_plugin, 'list_computehosts')
self.fake_list_computehosts.return_value = 'foo'
self.manager.plugins = {'physical:host': self.fake_phys_plugin}
self.assertRaises(AttributeError, getattr, self.manager,
'simplefakecallwithValueError')
def test_getattr_with_missing_plugin(self):
self.fake_list_computehosts = \
self.patch(self.fake_phys_plugin, 'list_computehosts')
self.fake_list_computehosts.return_value = 'foo'
self.manager.plugins = {'physical:host': self.fake_phys_plugin}
self.assertRaises(AttributeError, getattr, self.manager,
'plugin:not_present:list_computehosts')
def test_getattr_with_missing_method_in_plugin(self):
self.fake_list_computehosts = \
self.patch(self.fake_phys_plugin, 'list_computehosts')
self.fake_list_computehosts.return_value = 'foo'
self.manager.plugins = {'physical:host': None}
self.assertRaises(AttributeError, getattr, self.manager,
'physical:host:method_not_present')

View File

@ -0,0 +1,136 @@
# Copyright (c) 2013 Bull.
#
# 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 novaclient import client
from novaclient import exceptions as nova_exceptions
from climate import context
from climate.plugins.oshosts import nova_inventory
from climate import tests
class FakeNovaHypervisors(object):
class FakeHost(object):
id = 1
hypervisor_hostname = 'fake_name'
vcpus = 1
cpu_info = 'fake_cpu'
hypervisor_type = 'fake_type'
hypervisor_version = 1000000
memory_mb = 8192
local_gb = 10
servers = ['server1', 'server2']
@classmethod
def get(cls, host):
try:
host = int(host)
except ValueError:
raise nova_exceptions.NotFound(404)
if host == cls.FakeHost.id:
return cls.FakeHost
else:
raise nova_exceptions.NotFound(404)
@classmethod
def search(cls, host, servers=False):
if host == 'multiple':
return [cls.FakeHost, cls.FakeHost]
if host == cls.FakeHost.hypervisor_hostname:
return [cls.FakeHost]
else:
raise nova_exceptions.NotFound(404)
@classmethod
def expected(cls):
return {'id': cls.FakeHost.id,
'hypervisor_hostname': cls.FakeHost.hypervisor_hostname,
'vcpus': cls.FakeHost.vcpus,
'cpu_info': cls.FakeHost.cpu_info,
'hypervisor_type': cls.FakeHost.hypervisor_type,
'hypervisor_version': cls.FakeHost.hypervisor_version,
'memory_mb': cls.FakeHost.memory_mb,
'local_gb': cls.FakeHost.local_gb}
class NovaInventoryTestCase(tests.TestCase):
def setUp(self):
super(NovaInventoryTestCase, self).setUp()
self.context = context
self.patch(self.context, 'ClimateContext')
self.nova_inventory = nova_inventory
self.client = client
self.inventory = self.nova_inventory.NovaInventory()
self.hypervisors_get = self.patch(self.inventory.nova.hypervisors,
'get')
self.hypervisors_get.side_effect = FakeNovaHypervisors.get
self.hypervisors_search = self.patch(self.inventory.nova.hypervisors,
'search')
self.hypervisors_search.side_effect = FakeNovaHypervisors.search
def test_get_host_details_with_host_id(self):
host = self.inventory.get_host_details('1')
expected = FakeNovaHypervisors.expected()
self.assertEqual(expected, host)
def test_get_host_details_with_host_name(self):
host = self.inventory.get_host_details('fake_name')
expected = FakeNovaHypervisors.expected()
self.assertEqual(expected, host)
def test_get_host_details_with_host_name_having_multiple_results(self):
self.assertRaises(nova_inventory.MultipleHostsFound,
self.inventory.get_host_details, 'multiple')
def test_get_host_details_with_host_id_not_found(self):
self.assertRaises(nova_inventory.HostNotFound,
self.inventory.get_host_details, '2')
def test_get_host_details_with_host_name_not_found(self):
self.assertRaises(nova_inventory.HostNotFound,
self.inventory.get_host_details, 'wrong_name')
def test_get_host_details_with_invalid_host(self):
invalid_host = FakeNovaHypervisors.FakeHost
del invalid_host.vcpus
self.hypervisors_get.return_value = invalid_host
self.assertRaises(nova_inventory.InvalidHost,
self.inventory.get_host_details, '1')
def test_get_servers_per_host(self):
servers = self.inventory.get_servers_per_host('fake_name')
self.assertEqual(FakeNovaHypervisors.FakeHost.servers, servers)
def test_get_servers_per_host_with_host_id(self):
self.assertRaises(nova_inventory.HostNotFound,
self.inventory.get_servers_per_host, '1')
def test_get_servers_per_host_with_host_not_found(self):
self.assertRaises(nova_inventory.HostNotFound,
self.inventory.get_servers_per_host, 'wrong_name')
def test_get_servers_per_host_having_multiple_results(self):
self.assertRaises(nova_inventory.MultipleHostsFound,
self.inventory.get_servers_per_host, 'multiple')
def test_get_servers_per_host_with_host_having_no_servers(self):
host_with_zero_servers = FakeNovaHypervisors.FakeHost
# NOTE(sbauza): We need to simulate a host having zero servers
del host_with_zero_servers.servers
servers = self.inventory.get_servers_per_host('fake_name')
self.assertEqual(None, servers)

View File

@ -0,0 +1,269 @@
# Copyright (c) 2013 Bull.
#
# 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 testtools
from climate import context
from climate.db import api as db_api
from climate.manager import service
from climate.plugins.oshosts import nova_inventory
from climate.plugins.oshosts import reservation_pool as rp
from climate.plugins import physical_host_plugin
from climate import tests
class PhysicalHostPlugingSetupOnlyTestCase(tests.TestCase):
def setUp(self):
super(PhysicalHostPlugingSetupOnlyTestCase, self).setUp()
self.context = context
self.patch(self.context, 'ClimateContext')
self.physical_host_plugin = physical_host_plugin
self.fake_phys_plugin = \
self.physical_host_plugin.PhysicalHostPlugin()
self.rp = rp
self.nova_inventory = nova_inventory
self.rp_create = self.patch(self.rp.ReservationPool, 'create')
self.db_api = db_api
self.db_host_extra_capability_get_all_per_host = \
self.patch(self.db_api, 'host_extra_capability_get_all_per_host')
def test_setup(self):
def fake_setup():
freepool = self.patch(self.fake_phys_plugin, '_freepool_exists')
freepool.return_value = False
pool = self.patch(self.rp.ReservationPool, '__init__')
pool.side_effect = fake_setup
inventory = self.patch(self.nova_inventory.NovaInventory, '__init__')
inventory.return_value = None
self.fake_phys_plugin.setup(None)
pool.assert_called_once_with()
inventory.assert_called_once_with()
self.rp_create.assert_called_once_with(name='freepool', az=None)
def test__freepool_exists_with_freepool_present(self):
self.patch(self.rp.ReservationPool, 'get_aggregate_from_name_or_id')
self.fake_phys_plugin.setup(None)
self.assertEqual(self.fake_phys_plugin._freepool_exists(), True)
def test__freepool_exists_with_freepool_missing(self):
def fake_get_aggregate_from_name_or_id(*args, **kwargs):
raise rp.AggregateNotFound
mock = self.patch(self.rp.ReservationPool,
'get_aggregate_from_name_or_id')
mock.side_effect = fake_get_aggregate_from_name_or_id
self.fake_phys_plugin.setup(None)
self.assertEqual(self.fake_phys_plugin._freepool_exists(), False)
def test__get_extra_capabilities_with_values(self):
self.db_host_extra_capability_get_all_per_host.return_value = \
[{'id': 1,
'capability_name': 'foo',
'capability_value': 'bar',
'other': 'value',
'computehost_id': 1},
{'id': 2,
'capability_name': 'buzz',
'capability_value': 'word',
'computehost_id': 1}]
res = self.fake_phys_plugin._get_extra_capabilities(1)
self.assertEqual({'foo': 'bar', 'buzz': 'word'}, res)
def test__get_extra_capabilities_with_no_capabilities(self):
self.db_host_extra_capability_get_all_per_host.return_value = []
res = self.fake_phys_plugin._get_extra_capabilities(1)
self.assertEqual({}, res)
class PhysicalHostPluginTestCase(tests.TestCase):
def setUp(self):
super(PhysicalHostPluginTestCase, self).setUp()
self.context = context
self.patch(self.context, 'ClimateContext')
self.service = service
self.manager = self.service.ManagerService('127.0.0.1')
self.fake_host_id = '1'
self.fake_host = {'id': self.fake_host_id,
'hypervisor_hostname': 'foo',
'vcpus': 4,
'cpu_info': 'foo',
'hypervisor_type': 'xen',
'hypervisor_version': 1,
'memory_mb': 8192,
'local_gb': 10}
self.physical_host_plugin = physical_host_plugin
self.fake_phys_plugin = \
self.physical_host_plugin.PhysicalHostPlugin()
self.db_api = db_api
self.db_host_get = self.patch(self.db_api, 'host_get')
self.db_host_get.return_value = self.fake_host
self.db_host_list = self.patch(self.db_api, 'host_list')
self.db_host_create = self.patch(self.db_api, 'host_create')
self.db_host_update = self.patch(self.db_api, 'host_update')
self.db_host_destroy = self.patch(self.db_api, 'host_destroy')
self.db_host_extra_capability_get_all_per_host = \
self.patch(self.db_api, 'host_extra_capability_get_all_per_host')
self.db_host_extra_capability_get_all_per_name = \
self.patch(self.db_api, 'host_extra_capability_get_all_per_name')
self.db_host_extra_capability_create = \
self.patch(self.db_api, 'host_extra_capability_create')
self.db_host_extra_capability_update = \
self.patch(self.db_api, 'host_extra_capability_update')
self.rp = rp
self.nova_inventory = nova_inventory
self.rp_create = self.patch(self.rp.ReservationPool, 'create')
self.patch(self.rp.ReservationPool, 'get_aggregate_from_name_or_id')
self.patch(self.rp.ReservationPool, 'add_computehost')
self.patch(self.rp.ReservationPool, 'remove_computehost')
self.get_host_details = self.patch(self.nova_inventory.NovaInventory,
'get_host_details')
self.get_host_details.return_value = self.fake_host
self.get_servers_per_host = self.patch(
self.nova_inventory.NovaInventory, 'get_servers_per_host')
self.get_servers_per_host.return_value = None
self.get_extra_capabilities = self.patch(self.fake_phys_plugin,
'_get_extra_capabilities')
self.get_extra_capabilities.return_value = {'foo': 'bar',
'buzz': 'word'}
self.fake_phys_plugin.setup(None)
def test_get_host(self):
host = self.fake_phys_plugin.get_computehost(self.fake_host_id)
self.db_host_get.assert_called_once_with('1')
expected = self.fake_host.copy()
expected.update({'foo': 'bar', 'buzz': 'word'})
self.assertEqual(host, expected)
def test_get_host_without_extracapabilities(self):
self.get_extra_capabilities.return_value = {}
host = self.fake_phys_plugin.get_computehost(self.fake_host_id)
self.db_host_get.assert_called_once_with('1')
self.assertEqual(host, self.fake_host)
@testtools.skip('incorrect decorator')
def test_list_hosts(self):
self.fake_phys_plugin.list_computehosts()
self.db_host_list.assert_called_once_with()
del self.service_utils
def test_create_host_without_extra_capabilities(self):
self.get_extra_capabilities.return_value = {}
host = self.fake_phys_plugin.create_computehost(self.fake_host)
self.db_host_create.assert_called_once_with(self.fake_host)
self.assertEqual(host, self.fake_host)
def test_create_host_with_extra_capabilities(self):
fake_host = self.fake_host.copy()
fake_host.update({'foo': 'bar'})
# NOTE(sbauza): 'id' will be pop'd, we need to keep track of it
fake_request = fake_host.copy()
fake_capa = {'computehost_id': '1',
'capability_name': 'foo',
'capability_value': 'bar'}
self.get_extra_capabilities.return_value = {'foo': 'bar'}
self.db_host_create.return_value = self.fake_host
host = self.fake_phys_plugin.create_computehost(fake_request)
self.db_host_create.assert_called_once_with(self.fake_host)
self.db_host_extra_capability_create.assert_called_once_with(fake_capa)
self.assertEqual(host, fake_host)
def test_create_host_with_invalid_values(self):
self.assertRaises(nova_inventory.InvalidHost,
self.fake_phys_plugin.create_computehost, {})
def test_create_host_with_existing_vms(self):
self.get_servers_per_host.return_value = ['server1', 'server2']
self.assertRaises(nova_inventory.HostHavingServers,
self.fake_phys_plugin.create_computehost,
self.fake_host)
def test_create_host_issuing_rollback(self):
def fake_db_host_create(*args, **kwargs):
raise RuntimeError
self.db_host_create.side_effect = fake_db_host_create
host = self.fake_phys_plugin.create_computehost(self.fake_host)
self.assertEqual(None, host)
def test_create_host_having_issue_when_storing_extra_capability(self):
def fake_db_host_extra_capability_create(*args, **kwargs):
raise RuntimeError
fake_host = self.fake_host.copy()
fake_host.update({'foo': 'bar'})
fake_request = fake_host.copy()
self.get_extra_capabilities.return_value = {'foo': 'bar'}
self.db_host_create.return_value = self.fake_host
self.db_host_extra_capability_create.side_effect = \
fake_db_host_extra_capability_create
self.assertRaises(physical_host_plugin.CantAddExtraCapability,
self.fake_phys_plugin.create_computehost,
fake_request)
def test_update_host(self):
host_values = {'foo': 'baz'}
self.db_host_extra_capability_get_all_per_name.return_value = \
[{'id': '1',
'capability_name': 'foo',
'capability_value': 'bar'}]
self.fake_phys_plugin.update_computehost(self.fake_host_id,
host_values)
self.db_host_extra_capability_update.assert_called_once_with(
'1', {'capability_name': 'foo', 'capability_value': 'baz'})
def test_update_host_having_issue_when_storing_extra_capability(self):
def fake_db_host_extra_capability_update(*args, **kwargs):
raise RuntimeError
host_values = {'foo': 'baz'}
self.db_host_extra_capability_get_all_per_name.return_value = \
[{'id': '1',
'capability_name': 'foo',
'capability_value': 'bar'}]
self.db_host_extra_capability_update.side_effect = \
fake_db_host_extra_capability_update
self.assertRaises(physical_host_plugin.CantAddExtraCapability,
self.fake_phys_plugin.update_computehost,
self.fake_host_id, host_values)
def test_delete_host(self):
self.fake_phys_plugin.delete_computehost(self.fake_host_id)
self.db_host_destroy.assert_called_once_with(self.fake_host_id)
def test_delete_host_having_vms(self):
self.get_servers_per_host.return_value = ['server1', 'server2']
self.assertRaises(nova_inventory.HostHavingServers,
self.fake_phys_plugin.delete_computehost,
self.fake_host_id)
def test_delete_host_not_existing_in_db(self):
self.db_host_get.return_value = None
self.assertRaises(rp.HostNotFound,
self.fake_phys_plugin.delete_computehost,
self.fake_host_id)
def test_delete_host_issuing_rollback(self):
def fake_db_host_destroy(*args, **kwargs):
raise RuntimeError
self.db_host_destroy.side_effect = fake_db_host_destroy
self.assertRaises(rp.CantRemoveHost,
self.fake_phys_plugin.delete_computehost,
self.fake_host_id)

View File

@ -35,6 +35,7 @@ console_scripts =
climate.resource.plugins =
dummy.vm.plugin=climate.plugins.dummy_vm_plugin:DummyVMPlugin
physical.host.plugin=climate.plugins.physical_host_plugin:PhysicalHostPlugin
[build_sphinx]
all_files = 1