Added use of trusts to Physical Host plugin

Now the physical host plugin uses trusts to communicate with Nova.

Added a decorator in order to create a trust in certain API calls.
Added the trust_id to the ComputeHost model. Added a DB migration for
this change and a unit test for it.
Added the MissingTrustId exception, which is raised when no trust_id is
provided to some methods of the RPC API.
Removed all the configuration keys related to the admin Climate user.
Modified NovaClientWrapper in order to use the information stored in
context.

Change-Id: I0b83a933c0d72871654f3c6252be5d5e2c4cfd54
Closes-Bug: #1285585
This commit is contained in:
Pablo Andres Fuente 2014-03-13 16:54:46 -03:00
parent b5fd4387e0
commit c9b7307cf3
24 changed files with 501 additions and 278 deletions

View File

@ -15,6 +15,7 @@
from climate.manager.oshosts import rpcapi as manager_rpcapi from climate.manager.oshosts import rpcapi as manager_rpcapi
from climate import policy from climate import policy
from climate.utils import trusts
class API(object): class API(object):
@ -27,6 +28,7 @@ class API(object):
return self.manager_rpcapi.list_computehosts() return self.manager_rpcapi.list_computehosts()
@policy.authorize('oshosts', 'create') @policy.authorize('oshosts', 'create')
@trusts.use_trust_auth()
def create_computehost(self, data): def create_computehost(self, data):
"""Create new computehost. """Create new computehost.

View File

@ -41,17 +41,13 @@ class API(object):
return self.manager_rpcapi.list_leases(project_id=project_id) return self.manager_rpcapi.list_leases(project_id=project_id)
@policy.authorize('leases', 'create') @policy.authorize('leases', 'create')
@trusts.use_trust_auth()
def create_lease(self, data): def create_lease(self, data):
"""Create new lease. """Create new lease.
:param data: New lease characteristics. :param data: New lease characteristics.
:type data: dict :type data: dict
""" """
# here API should go to Keystone API v3 and create trust
trust = trusts.create_trust()
trust_id = trust.id
data.update({'trust_id': trust_id})
return self.manager_rpcapi.create_lease(data) return self.manager_rpcapi.create_lease(data)
@policy.authorize('leases', 'get') @policy.authorize('leases', 'get')

View File

@ -23,6 +23,7 @@ from climate.api.v2.controllers import types
from climate import exceptions from climate import exceptions
from climate.openstack.common.gettextutils import _ # noqa from climate.openstack.common.gettextutils import _ # noqa
from climate import policy from climate import policy
from climate.utils import trusts
from climate.openstack.common import log as logging from climate.openstack.common import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -58,6 +59,9 @@ class Host(base._Base):
cpu_info = types.CPUInfo() cpu_info = types.CPUInfo()
"The CPU info JSON data given by the hypervisor" "The CPU info JSON data given by the hypervisor"
trust_id = types.UuidType()
"The ID of the trust created for delegating the rights of the user"
extra_capas = wtypes.DictType(wtypes.text, types.TextOrInteger()) extra_capas = wtypes.DictType(wtypes.text, types.TextOrInteger())
"Extra capabilities for the host" "Extra capabilities for the host"
@ -125,6 +129,7 @@ class HostsController(extensions.BaseController):
@policy.authorize('oshosts', 'create') @policy.authorize('oshosts', 'create')
@wsme_pecan.wsexpose(Host, body=Host, status_code=202) @wsme_pecan.wsexpose(Host, body=Host, status_code=202)
@trusts.use_trust_auth()
def post(self, host): def post(self, host):
"""Creates a new host. """Creates a new host.

View File

@ -114,19 +114,15 @@ class LeasesController(extensions.BaseController):
@policy.authorize('leases', 'create') @policy.authorize('leases', 'create')
@wsme_pecan.wsexpose(Lease, body=Lease, status_code=202) @wsme_pecan.wsexpose(Lease, body=Lease, status_code=202)
@trusts.use_trust_auth()
def post(self, lease): def post(self, lease):
"""Creates a new lease. """Creates a new lease.
:param lease: a lease within the request body. :param lease: a lease within the request body.
""" """
# here API should go to Keystone API v3 and create trust
trust = trusts.create_trust()
trust_id = trust.id
lease_dct = lease.as_dict()
lease_dct.update({'trust_id': trust_id})
# FIXME(sbauza): DB exceptions are currently catched and return a lease # FIXME(sbauza): DB exceptions are currently catched and return a lease
# equal to None instead of being sent to the API # equal to None instead of being sent to the API
lease_dct = lease.as_dict()
lease = pecan.request.rpcapi.create_lease(lease_dct) lease = pecan.request.rpcapi.create_lease(lease_dct)
if lease is not None: if lease is not None:
return Lease.convert(lease) return Lease.convert(lease)

View File

@ -0,0 +1,61 @@
# Copyright 2014 OpenStack Foundation.
#
# 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.
"""Add trust_id to ComputeHost
Revision ID: 1fd6c2eded89
Revises: 0_1
Create Date: 2014-03-28 01:12:02.735519
"""
# revision identifiers, used by Alembic.
revision = '1fd6c2eded89'
down_revision = '23d6240b51b2'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('computehosts',
sa.Column('trust_id',
sa.String(length=36),
nullable=True))
if op.get_bind().engine.name != 'sqlite':
#I need to do it in this way because Postgress fails
#if I use SQLAlchemy
connection = op.get_bind()
connection.execute("UPDATE computehosts SET trust_id = ''")
op.alter_column('computehosts', 'trust_id',
existing_type=sa.String(length=36), nullable=False)
def downgrade():
engine = op.get_bind().engine
if engine.name == 'sqlite':
# Only for testing purposes with sqlite
op.execute('CREATE TABLE tmp_computehosts as SELECT id, '
'vcpus, cpu_info, hypervisor_type, '
'hypervisor_version, hypervisor_hostname, service_name, '
'memory_mb, local_gb, status '
'FROM computehosts')
op.execute('DROP TABLE computehosts')
op.execute('ALTER TABLE tmp_computehosts RENAME TO computehosts')
return
op.drop_column('computehosts', 'trust_id')

View File

@ -185,6 +185,7 @@ class ComputeHost(mb.ClimateBase):
memory_mb = sa.Column(sa.Integer, nullable=False) memory_mb = sa.Column(sa.Integer, nullable=False)
local_gb = sa.Column(sa.Integer, nullable=False) local_gb = sa.Column(sa.Integer, nullable=False)
status = sa.Column(sa.String(13)) status = sa.Column(sa.String(13))
trust_id = sa.Column(sa.String(36), nullable=False)
computehost_extra_capabilities = relationship('ComputeHostExtraCapability', computehost_extra_capabilities = relationship('ComputeHostExtraCapability',
cascade="all,delete", cascade="all,delete",
backref='computehost', backref='computehost',

View File

@ -90,6 +90,10 @@ class LeaseNameAlreadyExists(exceptions.ClimateException):
msg_fmt = _("The lease with name: %(name)s already exists") msg_fmt = _("The lease with name: %(name)s already exists")
class MissingTrustId(exceptions.ClimateException):
msg_fmt = _("A trust id is required")
# oshost plugin related exceptions # oshost plugin related exceptions
class CantAddExtraCapability(exceptions.ClimateException): class CantAddExtraCapability(exceptions.ClimateException):
@ -137,3 +141,7 @@ class InvalidState(exceptions.ClimateException):
class InvalidStateUpdate(InvalidState): class InvalidStateUpdate(InvalidState):
msg_fmt = _("Unable to update ID %(id)s state with %(action)s:%(status)s") msg_fmt = _("Unable to update ID %(id)s state with %(action)s:%(status)s")
class ProjectIdNotFound(exceptions.ClimateException):
msg_fmt = _("No project_id found in current context")

View File

@ -20,7 +20,6 @@ import eventlet
from oslo.config import cfg from oslo.config import cfg
from stevedore import enabled from stevedore import enabled
from climate import context
from climate.db import api as db_api from climate.db import api as db_api
from climate.db import exceptions as db_ex from climate.db import exceptions as db_ex
from climate import exceptions as common_ex from climate import exceptions as common_ex
@ -173,6 +172,11 @@ class ManagerService(service_utils.RPCServer):
Return either the model of created lease or None if any error. Return either the model of created lease or None if any error.
""" """
try:
trust_id = lease_values.pop('trust_id')
except KeyError:
raise exceptions.MissingTrustId()
# Remove and keep reservation values # Remove and keep reservation values
reservations = lease_values.pop("reservations", []) reservations = lease_values.pop("reservations", [])
@ -196,78 +200,80 @@ class ManagerService(service_utils.RPCServer):
raise common_ex.NotAuthorized( raise common_ex.NotAuthorized(
'Start date must later than current date') 'Start date must later than current date')
ctx = context.current() with trusts.create_ctx_from_trust(trust_id) as ctx:
lease_values['user_id'] = ctx.user_id lease_values['user_id'] = ctx.user_id
lease_values['project_id'] = ctx.project_id lease_values['project_id'] = ctx.project_id
lease_values['start_date'] = start_date lease_values['start_date'] = start_date
lease_values['end_date'] = end_date lease_values['end_date'] = end_date
if not lease_values.get('events'): if not lease_values.get('events'):
lease_values['events'] = [] lease_values['events'] = []
lease_values['events'].append({'event_type': 'start_lease', lease_values['events'].append({'event_type': 'start_lease',
'time': start_date, 'time': start_date,
'status': 'UNDONE'}) 'status': 'UNDONE'})
lease_values['events'].append({'event_type': 'end_lease', lease_values['events'].append({'event_type': 'end_lease',
'time': end_date, 'time': end_date,
'status': 'UNDONE'}) 'status': 'UNDONE'})
before_end_date = lease_values.get('before_end_notification', None)
if before_end_date:
# incoming param. Validation check
try:
before_end_date = self._date_from_string(
before_end_date)
self._check_date_within_lease_limits(before_end_date,
lease_values)
except common_ex.ClimateException as e:
LOG.error("Invalid before_end_date param. %s" % e.message)
raise e
elif CONF.manager.notify_hours_before_lease_end > 0:
delta = datetime.timedelta(
hours=CONF.manager.notify_hours_before_lease_end)
before_end_date = lease_values['end_date'] - delta
if before_end_date:
event = {'event_type': 'before_end_lease',
'status': 'UNDONE'}
lease_values['events'].append(event)
self._update_before_end_event_date(event, before_end_date,
lease_values)
before_end_date = lease_values.get('before_end_notification', None)
if before_end_date:
# incoming param. Validation check
try: try:
before_end_date = self._date_from_string( lease = db_api.lease_create(lease_values)
before_end_date) lease_id = lease['id']
self._check_date_within_lease_limits(before_end_date, except db_ex.ClimateDBDuplicateEntry:
lease_values) LOG.exception('Cannot create a lease - duplicated lease name')
except common_ex.ClimateException as e: raise exceptions.LeaseNameAlreadyExists(
LOG.error("Invalid before_end_date param. %s" % e.message) name=lease_values['name'])
raise e except db_ex.ClimateDBException:
elif CONF.manager.notify_hours_before_lease_end > 0: LOG.exception('Cannot create a lease')
delta = datetime.timedelta(
hours=CONF.manager.notify_hours_before_lease_end)
before_end_date = lease_values['end_date'] - delta
if before_end_date:
event = {'event_type': 'before_end_lease',
'status': 'UNDONE'}
lease_values['events'].append(event)
self._update_before_end_event_date(event, before_end_date,
lease_values)
try:
lease = db_api.lease_create(lease_values)
lease_id = lease['id']
except db_ex.ClimateDBDuplicateEntry:
LOG.exception('Cannot create a lease - duplicated lease name')
raise exceptions.LeaseNameAlreadyExists(name=lease_values['name'])
except db_ex.ClimateDBException:
LOG.exception('Cannot create a lease')
raise
else:
try:
for reservation in reservations:
reservation['lease_id'] = lease['id']
reservation['start_date'] = lease['start_date']
reservation['end_date'] = lease['end_date']
resource_type = reservation['resource_type']
if resource_type in self.plugins:
self.plugins[resource_type].create_reservation(
reservation)
else:
raise exceptions.UnsupportedResourceType(resource_type)
except (exceptions.UnsupportedResourceType,
common_ex.ClimateException):
LOG.exception("Failed to create reservation for a lease. "
"Rollback the lease and associated reservations")
db_api.lease_destroy(lease_id)
raise raise
else: else:
lease = db_api.lease_get(lease['id']) try:
with trusts.create_ctx_from_trust(lease['trust_id']) as ctx: for reservation in reservations:
reservation['lease_id'] = lease['id']
reservation['start_date'] = lease['start_date']
reservation['end_date'] = lease['end_date']
resource_type = reservation['resource_type']
if resource_type in self.plugins:
self.plugins[resource_type].create_reservation(
reservation)
else:
raise exceptions.UnsupportedResourceType(
resource_type)
except (exceptions.UnsupportedResourceType,
common_ex.ClimateException):
LOG.exception("Failed to create reservation for a lease. "
"Rollback the lease and associated "
"reservations")
db_api.lease_destroy(lease_id)
raise
else:
lease = db_api.lease_get(lease['id'])
self._send_notification(lease, ctx, events=['create']) self._send_notification(lease, ctx, events=['create'])
return lease return lease
def update_lease(self, lease_id, values): def update_lease(self, lease_id, values):
if not values: if not values:
@ -320,22 +326,25 @@ class ManagerService(service_utils.RPCServer):
raise common_ex.NotAuthorized( raise common_ex.NotAuthorized(
'End date must be later than current and start date') 'End date must be later than current and start date')
if before_end_date: with trusts.create_ctx_from_trust(lease['trust_id']):
try: if before_end_date:
before_end_date = self._date_from_string(before_end_date) try:
self._check_date_within_lease_limits(before_end_date, values) before_end_date = self._date_from_string(before_end_date)
except common_ex.ClimateException as e: self._check_date_within_lease_limits(before_end_date,
LOG.error("Invalid before_end_date param. %s" % e.message) values)
raise e except common_ex.ClimateException as e:
LOG.error("Invalid before_end_date param. %s" % e.message)
raise e
#TODO(frossigneux) rollback if an exception is raised #TODO(frossigneux) rollback if an exception is raised
for reservation in \ for reservation in \
db_api.reservation_get_all_by_lease_id(lease_id): db_api.reservation_get_all_by_lease_id(lease_id):
reservation['start_date'] = values['start_date'] reservation['start_date'] = values['start_date']
reservation['end_date'] = values['end_date'] reservation['end_date'] = values['end_date']
resource_type = reservation['resource_type'] resource_type = reservation['resource_type']
self.plugins[resource_type].update_reservation( self.plugins[resource_type].update_reservation(
reservation['id'], reservation) reservation['id'],
reservation)
event = db_api.event_get_first_sorted_by_filters( event = db_api.event_get_first_sorted_by_filters(
'lease_id', 'lease_id',

View File

@ -13,20 +13,4 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from oslo.config import cfg
RESOURCE_TYPE = u'physical:host' RESOURCE_TYPE = u'physical:host'
admin_opts = [
cfg.StrOpt('climate_username',
default='climate_admin',
help='Name of the user for write operations'),
cfg.StrOpt('climate_password',
default='climate_password',
help='Password of the user for write operations'),
cfg.StrOpt('climate_project_name',
default='admin',
help='Project of the user for write operations'),
]
cfg.CONF.register_opts(admin_opts, group=RESOURCE_TYPE)

View File

@ -21,7 +21,6 @@ import uuid
from oslo.config import cfg from oslo.config import cfg
from climate import context
from climate.db import api as db_api from climate.db import api as db_api
from climate.db import exceptions as db_ex from climate.db import exceptions as db_ex
from climate.db import utils as db_utils from climate.db import utils as db_utils
@ -31,6 +30,7 @@ from climate.plugins import oshosts as plugin
from climate.plugins.oshosts import nova_inventory from climate.plugins.oshosts import nova_inventory
from climate.plugins.oshosts import reservation_pool as rp from climate.plugins.oshosts import reservation_pool as rp
from climate.utils.openstack import nova from climate.utils.openstack import nova
from climate.utils import trusts
plugin_opts = [ plugin_opts = [
cfg.StrOpt('on_end', cfg.StrOpt('on_end',
@ -53,17 +53,10 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
description = 'This plugin starts and shutdowns the hosts.' description = 'This plugin starts and shutdowns the hosts.'
freepool_name = CONF[resource_type].aggregate_freepool_name freepool_name = CONF[resource_type].aggregate_freepool_name
pool = None pool = None
inventory = None
def __init__(self): def __init__(self):
super(PhysicalHostPlugin, self).__init__() super(PhysicalHostPlugin, self).__init__()
# Used by nova cli
config = cfg.CONF[self.resource_type]
self.username = config.climate_username
self.api_key = config.climate_password
self.project_id = config.climate_project_name
def create_reservation(self, values): def create_reservation(self, values):
"""Create reservation.""" """Create reservation."""
pool = rp.ReservationPool() pool = rp.ReservationPool()
@ -103,7 +96,8 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
"""Update reservation.""" """Update reservation."""
reservation = db_api.reservation_get(reservation_id) reservation = db_api.reservation_get(reservation_id)
lease = db_api.lease_get(reservation['lease_id']) lease = db_api.lease_get(reservation['lease_id'])
hosts_in_pool = self.pool.get_computehosts( pool = rp.ReservationPool()
hosts_in_pool = pool.get_computehosts(
reservation['resource_id']) reservation['resource_id'])
if (values['start_date'] < lease['start_date'] or if (values['start_date'] < lease['start_date'] or
values['end_date'] > lease['end_date']): values['end_date'] > lease['end_date']):
@ -149,8 +143,8 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
if hosts_in_pool: if hosts_in_pool:
old_hosts = [allocation['compute_host_id'] old_hosts = [allocation['compute_host_id']
for allocation in allocations] for allocation in allocations]
self.pool.remove_computehost(reservation['resource_id'], pool.remove_computehost(reservation['resource_id'],
old_hosts) old_hosts)
for allocation in allocations: for allocation in allocations:
db_api.host_allocation_destroy(allocation['id']) db_api.host_allocation_destroy(allocation['id'])
for host_id in host_ids: for host_id in host_ids:
@ -159,8 +153,8 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
'reservation_id': reservation_id}) 'reservation_id': reservation_id})
if hosts_in_pool: if hosts_in_pool:
host = db_api.host_get(host_id) host = db_api.host_get(host_id)
self.pool.add_computehost(reservation['resource_id'], pool.add_computehost(reservation['resource_id'],
host['service_name']) host['service_name'])
def on_start(self, resource_id): def on_start(self, resource_id):
"""Add the hosts in the pool.""" """Add the hosts in the pool."""
@ -197,15 +191,6 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
pool.delete(reservation['resource_id']) pool.delete(reservation['resource_id'])
#TODO(frossigneux) Kill, migrate, or increase fees... #TODO(frossigneux) Kill, migrate, or increase fees...
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()
def _get_extra_capabilities(self, host_id): def _get_extra_capabilities(self, host_id):
extra_capabilities = {} extra_capabilities = {}
raw_extra_capabilities = \ raw_extra_capabilities = \
@ -237,52 +222,63 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
# - Exception handling for HostNotFound # - Exception handling for HostNotFound
host_id = host_values.pop('id', None) host_id = host_values.pop('id', None)
host_name = host_values.pop('name', None) host_name = host_values.pop('name', None)
try:
trust_id = host_values.pop('trust_id')
except KeyError:
raise manager_ex.MissingTrustId()
host_ref = host_id or host_name host_ref = host_id or host_name
if host_ref is None: if host_ref is None:
raise manager_ex.InvalidHost(host=host_values) raise manager_ex.InvalidHost(host=host_values)
servers = self.inventory.get_servers_per_host(host_ref)
if servers:
raise manager_ex.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
to_store = set(host_values.keys()) - set(host_details.keys())
extra_capabilities_keys = to_store
extra_capabilities = dict(
(key, host_values[key]) for key in extra_capabilities_keys
)
self.pool.add_computehost(self.freepool_name,
host_details['service_name'])
host = None with trusts.create_ctx_from_trust(trust_id):
cantaddextracapability = [] inventory = nova_inventory.NovaInventory()
try: servers = inventory.get_servers_per_host(host_ref)
host = db_api.host_create(host_details) if servers:
except db_ex.ClimateDBException: raise manager_ex.HostHavingServers(host=host_ref,
#We need to rollback servers=servers)
# TODO(sbauza): Investigate use of Taskflow for atomic transactions host_details = inventory.get_host_details(host_ref)
self.pool.remove_computehost(self.freepool_name, # NOTE(sbauza): Only last duplicate name for same extra capability
host_details['service_name']) # will be stored
if host: to_store = set(host_values.keys()) - set(host_details.keys())
for key in extra_capabilities: extra_capabilities_keys = to_store
values = {'computehost_id': host['id'], extra_capabilities = dict(
'capability_name': key, (key, host_values[key]) for key in extra_capabilities_keys
'capability_value': extra_capabilities[key], )
} pool = rp.ReservationPool()
try: pool.add_computehost(self.freepool_name,
db_api.host_extra_capability_create(values) host_details['service_name'])
except db_ex.ClimateDBException:
cantaddextracapability.append(key) host = None
if cantaddextracapability: cantaddextracapability = []
raise manager_ex.CantAddExtraCapability( try:
keys=cantaddextracapability, if trust_id:
host=host['id']) host_details.update({'trust_id': trust_id})
if host: host = db_api.host_create(host_details)
return self.get_computehost(host['id']) except db_ex.ClimateDBException:
else: #We need to rollback
return None # TODO(sbauza): Investigate use of Taskflow for atomic
# transactions
pool.remove_computehost(self.freepool_name,
host_details['service_name'])
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 db_ex.ClimateDBException:
cantaddextracapability.append(key)
if cantaddextracapability:
raise manager_ex.CantAddExtraCapability(
keys=cantaddextracapability,
host=host['id'])
if host:
return self.get_computehost(host['id'])
else:
return None
def update_computehost(self, host_id, values): def update_computehost(self, host_id, values):
# NOTE (sbauza): Only update existing extra capabilites, don't create # NOTE (sbauza): Only update existing extra capabilites, don't create
@ -316,23 +312,28 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
if not host: if not host:
raise manager_ex.HostNotFound(host=host_id) raise manager_ex.HostNotFound(host=host_id)
# TODO(sbauza): with trusts.create_ctx_from_trust(host['trust_id']):
# - Check if no leases having this host scheduled # TODO(sbauza):
servers = self.inventory.get_servers_per_host( # - Check if no leases having this host scheduled
host['hypervisor_hostname']) inventory = nova_inventory.NovaInventory()
if servers: servers = inventory.get_servers_per_host(
raise manager_ex.HostHavingServers( host['hypervisor_hostname'])
host=host['hypervisor_hostname'], servers=servers) if servers:
try: raise manager_ex.HostHavingServers(
self.pool.remove_computehost(self.freepool_name, host=host['hypervisor_hostname'], servers=servers)
host['service_name'])
# NOTE(sbauza): Extracapabilities will be destroyed thanks to try:
# the DB FK. pool = rp.ReservationPool()
db_api.host_destroy(host_id) pool.remove_computehost(self.freepool_name,
except db_ex.ClimateDBException: host['service_name'])
# Nothing so bad, but we need to advert the admin he has to rerun # NOTE(sbauza): Extracapabilities will be destroyed thanks to
raise manager_ex.CantRemoveHost(host=host_id, # the DB FK.
pool=self.freepool_name) db_api.host_destroy(host_id)
except db_ex.ClimateDBException:
# Nothing so bad, but we need to advert the admin
# he has to rerun
raise manager_ex.CantRemoveHost(host=host_id,
pool=self.freepool_name)
def _matching_hosts(self, hypervisor_properties, resource_properties, def _matching_hosts(self, hypervisor_properties, resource_properties,
count_range, start_date, end_date): count_range, start_date, end_date):

View File

@ -14,24 +14,14 @@
# limitations under the License. # limitations under the License.
from novaclient import exceptions as nova_exceptions 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.manager import exceptions as manager_exceptions
from climate.plugins import oshosts as plugin
from climate.utils.openstack import nova from climate.utils.openstack import nova
class NovaInventory(nova.NovaClientWrapper): class NovaInventory(nova.NovaClientWrapper):
def __init__(self): def __init__(self):
super(NovaInventory, self).__init__() super(NovaInventory, self).__init__()
self.ctx = context.current()
# Used by nova cli
config = cfg.CONF[plugin.RESOURCE_TYPE]
self.username = config.climate_username
self.api_key = config.climate_password
self.project_id = config.climate_project_name
def get_host_details(self, host): def get_host_details(self, host):
"""Get Nova capabilities of a single host """Get Nova capabilities of a single host

View File

@ -51,16 +51,9 @@ CONF.register_opts(OPTS, group=plugin.RESOURCE_TYPE)
class ReservationPool(nova.NovaClientWrapper): class ReservationPool(nova.NovaClientWrapper):
def __init__(self): def __init__(self):
super(ReservationPool, self).__init__() super(ReservationPool, self).__init__()
self.ctx = context.current()
self.config = CONF[plugin.RESOURCE_TYPE] self.config = CONF[plugin.RESOURCE_TYPE]
self.freepool_name = self.config.aggregate_freepool_name self.freepool_name = self.config.aggregate_freepool_name
# Used by nova cli
config = cfg.CONF[plugin.RESOURCE_TYPE]
self.username = config.climate_username
self.api_key = config.climate_password
self.project_id = config.climate_project_name
def get_aggregate_from_name_or_id(self, aggregate_obj): def get_aggregate_from_name_or_id(self, aggregate_obj):
"""Return an aggregate by name or an id.""" """Return an aggregate by name or an id."""
@ -115,7 +108,16 @@ class ReservationPool(nova.NovaClientWrapper):
'without Availability Zone' % name) 'without Availability Zone' % name)
agg = self.nova.aggregates.create(name, None) agg = self.nova.aggregates.create(name, None)
meta = {self.config.climate_owner: self.ctx.project_id} project_id = None
try:
ctx = context.current()
project_id = ctx.project_id
except RuntimeError:
e = manager_exceptions.ProjectIdNotFound()
LOG.error(e.message)
raise e
meta = {self.config.climate_owner: project_id}
self.nova.aggregates.set_metadata(agg, meta) self.nova.aggregates.set_metadata(agg, meta)
return agg return agg

View File

@ -20,6 +20,7 @@ import uuid
from climate.tests import api from climate.tests import api
from climate.utils import trusts
def fake_computehost(**kw): def fake_computehost(**kw):
@ -29,6 +30,8 @@ def fake_computehost(**kw):
u'hypervisor_type': kw.get('hypervisor_type', u'QEMU'), u'hypervisor_type': kw.get('hypervisor_type', u'QEMU'),
u'vcpus': kw.get('vcpus', 1), u'vcpus': kw.get('vcpus', 1),
u'hypervisor_version': kw.get('hypervisor_version', 1000000), u'hypervisor_version': kw.get('hypervisor_version', 1000000),
u'trust_id': kw.get('trust_id',
u'35b17138-b364-4e6a-a131-8f3099c5be68'),
u'memory_mb': kw.get('memory_mb', 8192), u'memory_mb': kw.get('memory_mb', 8192),
u'local_gb': kw.get('local_gb', 50), u'local_gb': kw.get('local_gb', 50),
u'cpu_info': kw.get('cpu_info', u'cpu_info': kw.get('cpu_info',
@ -61,6 +64,12 @@ def fake_computehost_from_rpc(**kw):
return computehost return computehost
def fake_trust(id=fake_computehost()['trust_id']):
return type('Trust', (), {
'id': id,
})
class TestIncorrectHostFromRPC(api.APITest): class TestIncorrectHostFromRPC(api.APITest):
def setUp(self): def setUp(self):
@ -206,6 +215,9 @@ class TestCreateHost(api.APITest):
self.headers = {'X-Roles': 'admin'} self.headers = {'X-Roles': 'admin'}
self.trusts = trusts
self.patch(self.trusts, 'create_trust').return_value = fake_trust()
def test_create_one(self): def test_create_one(self):
response = self.post_json(self.path, self.fake_computehost_body, response = self.post_json(self.path, self.fake_computehost_body,
headers=self.headers) headers=self.headers)
@ -293,7 +305,8 @@ class TestUpdateHost(api.APITest):
self.headers = {'X-Roles': 'admin'} self.headers = {'X-Roles': 'admin'}
def test_update_one(self): def test_update_one(self):
response = self.put_json(self.path, self.fake_computehost_body, response = self.put_json(self.path, fake_computehost_request_body(
exclude=['trust_id']),
headers=self.headers) headers=self.headers)
self.assertEqual(202, response.status_int) self.assertEqual(202, response.status_int)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)

View File

@ -119,3 +119,64 @@ class TestMigrations(migration.BaseWalkMigrationTestCase,
def _check_2bcfe76b0474(self, engine, data): def _check_2bcfe76b0474(self, engine, data):
self.assertColumnExists(engine, 'leases', 'project_id') self.assertColumnExists(engine, 'leases', 'project_id')
self.assertColumnNotExists(engine, 'leases', 'tenant_id') self.assertColumnNotExists(engine, 'leases', 'tenant_id')
def _pre_upgrade_1fd6c2eded89(self, engine):
data = [{
'id': '1',
'hypervisor_hostname': 'host01',
'hypervisor_type': 'QEMU',
'hypervisor_version': 1000000,
'service_name': 'host01',
'vcpus': 1,
'memory_mb': 8192,
'local_gb': 50,
'cpu_info': "{\"vendor\": \"Intel\", \"model\": \"qemu32\", "
"\"arch\": \"x86_64\", \"features\": [],"
" \"topology\": {\"cores\": 1}}",
'extra_capas': {'vgpus': 2}},
{'id': '2',
'hypervisor_hostname': 'host01',
'hypervisor_type': 'QEMU',
'hypervisor_version': 1000000,
'service_name': 'host02',
'vcpus': 1,
'memory_mb': 8192,
'local_gb': 50,
'cpu_info': "{\"vendor\": \"Intel\", \"model\": \"qemu32\", "
"\"arch\": \"x86_64\", \"features\": [],"
" \"topology\": {\"cores\": 1}}",
'extra_capas': {'vgpus': 2}}]
computehosts_table = self.get_table(engine, 'computehosts')
engine.execute(computehosts_table.insert(), data)
return data
def _check_1fd6c2eded89(self, engine, data):
self.assertColumnExists(engine, 'computehosts', 'trust_id')
metadata = sqlalchemy.MetaData()
metadata.bind = engine
computehosts_table = self.get_table(engine, 'computehosts')
all_computehosts = computehosts_table.select().execute()
for computehost in all_computehosts:
self.assertIn(computehost.trust_id, ['', None])
data = {'id': '3',
'hypervisor_hostname': 'host01',
'hypervisor_type': 'QEMU',
'hypervisor_version': 1000000,
'service_name': 'host02',
'vcpus': 1,
'memory_mb': 8192,
'local_gb': 50,
'cpu_info': "{\"vendor\": \"Intel\", \"model\": \"qemu32\", "
"\"arch\": \"x86_64\", \"features\": [],"
" \"topology\": {\"cores\": 1}}",
'extra_capas': {'vgpus': 2},
'trust_id': None}
if engine.name != 'sqlite':
self.assertRaises(sqlalchemy.exc.DBAPIError,
engine.execute,
computehosts_table.insert(),
data)

View File

@ -45,7 +45,8 @@ def _get_fake_phys_reservation_values(id=_get_fake_random_uuid(),
'resource_type': host_plugin.RESOURCE_TYPE, 'resource_type': host_plugin.RESOURCE_TYPE,
'hypervisor_properties': '[\"=\", \"$hypervisor_type\", \"QEMU\"]', 'hypervisor_properties': '[\"=\", \"$hypervisor_type\", \"QEMU\"]',
'resource_properties': '', 'resource_properties': '',
'min': 1, 'max': 1} 'min': 1, 'max': 1,
'trust_id': 'exxee111qwwwwe'}
def _get_fake_virt_reservation_values(lease_id=_get_fake_lease_uuid(), def _get_fake_virt_reservation_values(lease_id=_get_fake_lease_uuid(),
@ -161,7 +162,8 @@ def _get_fake_host_reservation_values(id=_get_fake_random_uuid(),
'reservation_id': reservation_id, 'reservation_id': reservation_id,
'resource_properties': "fake", 'resource_properties': "fake",
'hypervisor_properties': "fake", 'hypervisor_properties': "fake",
'min': 1, 'max': 1} 'min': 1, 'max': 1,
'trust_id': 'exxee111qwwwwe'}
def _get_fake_cpu_info(): def _get_fake_cpu_info():
@ -181,7 +183,8 @@ def _get_fake_host_values(id=_get_fake_random_uuid(), mem=8192, disk=10):
'hypervisor_version': 1000, 'hypervisor_version': 1000,
'memory_mb': mem, 'memory_mb': mem,
'local_gb': disk, 'local_gb': disk,
'status': 'free' 'status': 'free',
'trust_id': 'exxee111qwwwwe',
} }

View File

@ -31,6 +31,7 @@ from climate.plugins import base
from climate.plugins import dummy_vm_plugin from climate.plugins import dummy_vm_plugin
from climate.plugins.oshosts import host_plugin from climate.plugins.oshosts import host_plugin
from climate import tests from climate import tests
from climate.utils.openstack import base as base_utils
from climate.utils import trusts from climate.utils import trusts
@ -82,6 +83,7 @@ class ServiceTestCase(tests.TestCase):
self.dummy_plugin = dummy_vm_plugin self.dummy_plugin = dummy_vm_plugin
self.trusts = trusts self.trusts = trusts
self.notifier_api = notifier_api self.notifier_api = notifier_api
self.base_utils = base_utils
self.fake_plugin = self.patch(self.dummy_plugin, 'DummyVMPlugin') self.fake_plugin = self.patch(self.dummy_plugin, 'DummyVMPlugin')
@ -115,6 +117,7 @@ class ServiceTestCase(tests.TestCase):
self.ctx = self.patch(self.context, 'ClimateContext') self.ctx = self.patch(self.context, 'ClimateContext')
self.trust_ctx = self.patch(self.trusts, 'create_ctx_from_trust') self.trust_ctx = self.patch(self.trusts, 'create_ctx_from_trust')
self.trust_create = self.patch(self.trusts, 'create_trust')
self.lease_get = self.patch(self.db_api, 'lease_get') self.lease_get = self.patch(self.db_api, 'lease_get')
self.lease_get.return_value = self.lease self.lease_get.return_value = self.lease
self.lease_list = self.patch(self.db_api, 'lease_list') self.lease_list = self.patch(self.db_api, 'lease_list')
@ -128,6 +131,8 @@ class ServiceTestCase(tests.TestCase):
{'virtual:instance': {'virtual:instance':
{'on_start': self.fake_plugin.on_start, {'on_start': self.fake_plugin.on_start,
'on_end': self.fake_plugin.on_end}} 'on_end': self.fake_plugin.on_end}}
self.patch(
self.base_utils, 'url_for').return_value = 'http://www.foo.fake'
self.addCleanup(self.cfg.CONF.clear_override, self.addCleanup(self.cfg.CONF.clear_override,
'notify_hours_before_lease_end', 'notify_hours_before_lease_end',
@ -239,6 +244,7 @@ class ServiceTestCase(tests.TestCase):
self.lease_list.assert_called_once_with() self.lease_list.assert_called_once_with()
def test_create_lease_now(self): def test_create_lease_now(self):
trust_id = 'exxee111qwwwwe'
lease_values = { lease_values = {
'id': self.lease_id, 'id': self.lease_id,
'reservations': [{'id': '111', 'reservations': [{'id': '111',
@ -246,10 +252,12 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance', 'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': 'now', 'start_date': 'now',
'end_date': '2026-12-13 13:13'} 'end_date': '2026-12-13 13:13',
'trust_id': trust_id}
lease = self.manager.create_lease(lease_values) lease = self.manager.create_lease(lease_values)
self.trust_ctx.assert_called_once_with(trust_id)
self.lease_create.assert_called_once_with(lease_values) self.lease_create.assert_called_once_with(lease_values)
self.assertEqual(lease, self.lease) self.assertEqual(lease, self.lease)
expected_context = self.trust_ctx.return_value expected_context = self.trust_ctx.return_value
@ -259,6 +267,7 @@ class ServiceTestCase(tests.TestCase):
'lease.create') 'lease.create')
def test_create_lease_some_time(self): def test_create_lease_some_time(self):
trust_id = 'exxee111qwwwwe'
lease_values = { lease_values = {
'id': self.lease_id, 'id': self.lease_id,
'reservations': [{'id': '111', 'reservations': [{'id': '111',
@ -266,11 +275,14 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance', 'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-12-13 13:13'} 'end_date': '2026-12-13 13:13',
'trust_id': trust_id}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
lease = self.manager.create_lease(lease_values) lease = self.manager.create_lease(lease_values)
self.trust_ctx.assert_called_once_with(trust_id)
self.lease_create.assert_called_once_with(lease_values) self.lease_create.assert_called_once_with(lease_values)
self.assertEqual(lease, self.lease) self.assertEqual(lease, self.lease)
@ -282,7 +294,8 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance', 'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-12-13 13:13'} 'end_date': '2026-12-13 13:13',
'trust_id': 'exxee111qwwwwe'}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
lease = self.manager.create_lease(lease_values) lease = self.manager.create_lease(lease_values)
@ -319,7 +332,8 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance', 'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13'} 'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe'}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
self.cfg.CONF.set_override('notify_hours_before_lease_end', 36, self.cfg.CONF.set_override('notify_hours_before_lease_end', 36,
@ -360,6 +374,7 @@ class ServiceTestCase(tests.TestCase):
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': start_date, 'start_date': start_date,
'end_date': '2026-11-14 13:13', 'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe',
'before_end_notification': before_end_notification} 'before_end_notification': before_end_notification}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
@ -376,6 +391,7 @@ class ServiceTestCase(tests.TestCase):
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13', 'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe',
'before_end_notification': before_end_notification} 'before_end_notification': before_end_notification}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
@ -390,7 +406,8 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'virtual:instance', 'resource_type': 'virtual:instance',
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13'} 'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe'}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
self.cfg.CONF.set_override('notify_hours_before_lease_end', 0, self.cfg.CONF.set_override('notify_hours_before_lease_end', 0,
@ -424,6 +441,7 @@ class ServiceTestCase(tests.TestCase):
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13', 'end_date': '2026-11-14 13:13',
'trust_id': 'exxee111qwwwwe',
'before_end_notification': before_end_notification} 'before_end_notification': before_end_notification}
self.lease['start_date'] = '2026-11-13 13:13' self.lease['start_date'] = '2026-11-13 13:13'
@ -455,7 +473,8 @@ class ServiceTestCase(tests.TestCase):
def test_create_lease_wrong_date(self): def test_create_lease_wrong_date(self):
lease_values = {'start_date': '2025-13-35 13:13', lease_values = {'start_date': '2025-13-35 13:13',
'end_date': '2025-12-31 13:13'} 'end_date': '2025-12-31 13:13',
'trust_id': 'exxee111qwwwwe'}
self.assertRaises( self.assertRaises(
manager_ex.InvalidDate, self.manager.create_lease, lease_values) manager_ex.InvalidDate, self.manager.create_lease, lease_values)
@ -464,7 +483,8 @@ class ServiceTestCase(tests.TestCase):
before_end_notification = '2026-14 10:13' before_end_notification = '2026-14 10:13'
lease_values = {'start_date': '2026-11-13 13:13', lease_values = {'start_date': '2026-11-13 13:13',
'end_date': '2026-11-14 13:13', 'end_date': '2026-11-14 13:13',
'before_end_notification': before_end_notification} 'before_end_notification': before_end_notification,
'trust_id': 'exxee111qwwwwe'}
self.assertRaises( self.assertRaises(
manager_ex.InvalidDate, self.manager.create_lease, lease_values) manager_ex.InvalidDate, self.manager.create_lease, lease_values)
@ -475,7 +495,8 @@ class ServiceTestCase(tests.TestCase):
datetime.datetime.strftime( datetime.datetime.strftime(
datetime.datetime.utcnow() - datetime.timedelta(days=1), datetime.datetime.utcnow() - datetime.timedelta(days=1),
service.LEASE_DATE_FORMAT), service.LEASE_DATE_FORMAT),
'end_date': '2025-12-31 13:13'} 'end_date': '2025-12-31 13:13',
'trust_id': 'exxee111qwwwwe'}
self.assertRaises( self.assertRaises(
exceptions.NotAuthorized, self.manager.create_lease, lease_values) exceptions.NotAuthorized, self.manager.create_lease, lease_values)
@ -488,7 +509,8 @@ class ServiceTestCase(tests.TestCase):
'resource_type': 'unsupported:type', 'resource_type': 'unsupported:type',
'status': 'FAKE PROGRESS'}], 'status': 'FAKE PROGRESS'}],
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-12-13 13:13'} 'end_date': '2026-12-13 13:13',
'trust_id': 'exxee111qwwwwe'}
self.assertRaises(manager_ex.UnsupportedResourceType, self.assertRaises(manager_ex.UnsupportedResourceType,
self.manager.create_lease, lease_values) self.manager.create_lease, lease_values)
@ -497,13 +519,23 @@ class ServiceTestCase(tests.TestCase):
lease_values = { lease_values = {
'name': 'duplicated_name', 'name': 'duplicated_name',
'start_date': '2026-11-13 13:13', 'start_date': '2026-11-13 13:13',
'end_date': '2026-12-13 13:13'} 'end_date': '2026-12-13 13:13',
'trust_id': 'exxee111qwwwwe'}
self.patch(self.db_api, self.patch(self.db_api,
'lease_create').side_effect = db_ex.ClimateDBDuplicateEntry 'lease_create').side_effect = db_ex.ClimateDBDuplicateEntry
self.assertRaises(manager_ex.LeaseNameAlreadyExists, self.assertRaises(manager_ex.LeaseNameAlreadyExists,
self.manager.create_lease, lease_values) self.manager.create_lease, lease_values)
def test_create_lease_without_trust_id(self):
lease_values = {
'name': 'name',
'start_date': '2026-11-13 13:13',
'end_date': '2026-12-13 13:13'}
self.assertRaises(manager_ex.MissingTrustId,
self.manager.create_lease, lease_values)
def test_update_lease_completed_lease_rename(self): def test_update_lease_completed_lease_rename(self):
lease_values = {'name': 'renamed'} lease_values = {'name': 'renamed'}
target = datetime.datetime(2015, 1, 1) target = datetime.datetime(2015, 1, 1)

View File

@ -14,6 +14,8 @@
# limitations under the License. # limitations under the License.
import sys import sys
import eventlet
import testtools import testtools
from climate import exceptions as climate_exceptions from climate import exceptions as climate_exceptions
@ -28,25 +30,31 @@ from novaclient import exceptions as nova_exceptions
class VMPluginTestCase(tests.TestCase): class VMPluginTestCase(tests.TestCase):
def setUp(self): def setUp(self):
super(VMPluginTestCase, self).setUp() super(VMPluginTestCase, self).setUp()
self.plugin = vm_plugin.VMPlugin()
self.nova = nova self.nova = nova
self.exc = climate_exceptions self.exc = climate_exceptions
self.logging = logging self.logging = logging
self.sys = sys self.sys = sys
self.client = self.patch(self.nova, 'ClimateNovaClient') # To speed up the test run
self.eventlet = eventlet
self.eventlet_sleep = self.patch(self.eventlet, 'sleep')
self.fake_id = '1' self.fake_id = '1'
self.nova_wrapper = self.patch(self.nova.NovaClientWrapper, 'nova')
self.plugin = vm_plugin.VMPlugin()
def test_on_start_ok(self): def test_on_start_ok(self):
self.plugin.on_start(self.fake_id) self.plugin.on_start(self.fake_id)
self.client.return_value.servers.unshelve.assert_called_once_with('1') self.nova_wrapper.servers.unshelve.assert_called_once_with('1')
@testtools.skip('Will be released later') @testtools.skip('Will be released later')
def test_on_start_fail(self): def test_on_start_fail(self):
self.client.side_effect = \ def raise_exception(resource_id):
self.nova.ClimateNovaClient.exceptions.Conflict raise climate_exceptions.Conflict(409)
self.nova_wrapper.servers.unshelve.side_effect = raise_exception
self.plugin.on_start(self.fake_id) self.plugin.on_start(self.fake_id)
def test_on_end_create_image_ok(self): def test_on_end_create_image_ok(self):
@ -57,36 +65,39 @@ class VMPluginTestCase(tests.TestCase):
self.plugin.on_end(self.fake_id) self.plugin.on_end(self.fake_id)
self.client.return_value.servers.create_image.assert_called_once_with( self.nova_wrapper.servers.create_image.assert_called_once_with('1')
'1')
def test_on_end_suspend_ok(self): def test_on_end_suspend_ok(self):
self.patch(self.plugin, '_split_actions').return_value =\ self.patch(self.plugin, '_split_actions').return_value =\
['suspend'] ['suspend']
self.plugin.on_end(self.fake_id) self.plugin.on_end(self.fake_id)
self.client.return_value.servers.suspend.assert_called_once_with('1') self.nova_wrapper.servers.suspend.assert_called_once_with('1')
def test_on_end_delete_ok(self): def test_on_end_delete_ok(self):
self.patch(self.plugin, '_split_actions').return_value =\ self.patch(self.plugin, '_split_actions').return_value =\
['delete'] ['delete']
self.plugin.on_end(self.fake_id) self.plugin.on_end(self.fake_id)
self.client.return_value.servers.delete.assert_called_once_with('1') self.nova_wrapper.servers.delete.assert_called_once_with('1')
def test_on_end_create_image_instance_or_not_found(self): def test_on_end_create_image_instance_or_not_found(self):
self.client.return_value.servers.create_image.side_effect = \ def raise_exception(resource_id):
nova_exceptions.NotFound(404) raise nova_exceptions.NotFound(404)
self.nova_wrapper.servers.create_image.side_effect = raise_exception
self.plugin.on_end(self.fake_id) self.plugin.on_end(self.fake_id)
self.client.return_value.servers.delete.assert_called_once_with('1') self.nova_wrapper.servers.delete.assert_called_once_with('1')
def test_on_end_create_image_ko_invalid_vm_state(self): def test_on_end_create_image_ko_invalid_vm_state(self):
self.client.return_value.servers.create_image.side_effect = \ def raise_exception(resource_id):
nova_exceptions.Conflict(409) raise nova_exceptions.Conflict(409)
self.nova_wrapper.servers.create_image.side_effect = raise_exception
self.plugin.on_end(self.fake_id) self.plugin.on_end(self.fake_id)
self.client.return_value.servers.delete.assert_called_once_with('1') self.nova_wrapper.servers.delete.assert_called_once_with('1')
@testtools.skip('Will be released later') @testtools.skip('Will be released later')
def test_on_end_timeout(self): def test_on_end_timeout(self):

View File

@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from novaclient import client
from novaclient import exceptions as nova_exceptions from novaclient import exceptions as nova_exceptions
from novaclient.v1_1 import hypervisors
from climate import context from climate import context
from climate.manager import exceptions as manager_exceptions from climate.manager import exceptions as manager_exceptions
@ -77,15 +77,12 @@ class NovaInventoryTestCase(tests.TestCase):
self.context = context self.context = context
self.patch(self.context, 'ClimateContext') self.patch(self.context, 'ClimateContext')
self.nova_inventory = nova_inventory self.nova_inventory = nova_inventory
self.client = client
self.client = self.patch(self.client, 'Client').return_value
self.patch(base, 'url_for').return_value = 'http://foo.bar' self.patch(base, 'url_for').return_value = 'http://foo.bar'
self.inventory = self.nova_inventory.NovaInventory() self.inventory = self.nova_inventory.NovaInventory()
self.hypervisors_get = self.patch(self.inventory.nova.hypervisors, self.hypervisors_get = self.patch(hypervisors.HypervisorManager, 'get')
'get')
self.hypervisors_get.side_effect = FakeNovaHypervisors.get self.hypervisors_get.side_effect = FakeNovaHypervisors.get
self.hypervisors_search = self.patch(self.inventory.nova.hypervisors, self.hypervisors_search = self.patch(hypervisors.HypervisorManager,
'search') 'search')
self.hypervisors_search.side_effect = FakeNovaHypervisors.search self.hypervisors_search.side_effect = FakeNovaHypervisors.search

View File

@ -23,6 +23,7 @@ from climate.plugins import oshosts as host_plugin
from climate.plugins.oshosts import reservation_pool as rp from climate.plugins.oshosts import reservation_pool as rp
from climate import tests from climate import tests
from climate.utils.openstack import base from climate.utils.openstack import base
from climate.utils.openstack import nova
from novaclient import client as nova_client from novaclient import client as nova_client
from novaclient import exceptions as nova_exceptions from novaclient import exceptions as nova_exceptions
@ -128,6 +129,20 @@ class ReservationPoolTestCase(tests.TestCase):
self.nova.aggregates.create.assert_called_once_with(self.pool_name, self.nova.aggregates.create.assert_called_once_with(self.pool_name,
None) None)
def test_create_no_project_id(self):
self.patch(self.nova.aggregates, 'create').return_value = \
self.fake_aggregate
self.nova_wrapper = self.patch(nova.NovaClientWrapper, 'nova')
def raiseRuntimeError():
raise RuntimeError()
self.context_mock.side_effect = raiseRuntimeError
self.assertRaises(manager_exceptions.ProjectIdNotFound,
self.pool.create)
def test_delete_with_host(self): def test_delete_with_host(self):
self._patch_get_aggregate_from_name_or_id() self._patch_get_aggregate_from_name_or_id()
agg = self.pool.get('foo') agg = self.pool.get('foo')

View File

@ -31,6 +31,7 @@ from climate.plugins.oshosts import nova_inventory
from climate.plugins.oshosts import reservation_pool as rp from climate.plugins.oshosts import reservation_pool as rp
from climate import tests from climate import tests
from climate.utils.openstack import base from climate.utils.openstack import base
from climate.utils import trusts
from novaclient import client as nova_client from novaclient import client as nova_client
@ -58,15 +59,6 @@ class PhysicalHostPlugingSetupOnlyTestCase(tests.TestCase):
self.db_host_extra_capability_get_all_per_host = ( self.db_host_extra_capability_get_all_per_host = (
self.patch(self.db_api, 'host_extra_capability_get_all_per_host')) self.patch(self.db_api, 'host_extra_capability_get_all_per_host'))
def test_setup(self):
pool = self.patch(self.rp.ReservationPool, '__init__')
pool.return_value = None
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()
def test__get_extra_capabilities_with_values(self): def test__get_extra_capabilities_with_values(self):
self.db_host_extra_capability_get_all_per_host.return_value = [ self.db_host_extra_capability_get_all_per_host.return_value = [
{'id': 1, {'id': 1,
@ -114,6 +106,7 @@ class PhysicalHostPluginTestCase(tests.TestCase):
'hypervisor_version': 1, 'hypervisor_version': 1,
'memory_mb': 8192, 'memory_mb': 8192,
'local_gb': 10, 'local_gb': 10,
'trust_id': 'exxee111qwwwwe',
} }
self.patch(base, 'url_for').return_value = 'http://foo.bar' self.patch(base, 'url_for').return_value = 'http://foo.bar'
@ -164,6 +157,10 @@ class PhysicalHostPluginTestCase(tests.TestCase):
} }
self.fake_phys_plugin.setup(None) self.fake_phys_plugin.setup(None)
self.trusts = trusts
self.trust_ctx = self.patch(self.trusts, 'create_ctx_from_trust')
self.trust_create = self.patch(self.trusts, 'create_trust')
def test_get_host(self): def test_get_host(self):
host = self.fake_phys_plugin.get_computehost(self.fake_host_id) host = self.fake_phys_plugin.get_computehost(self.fake_host_id)
self.db_host_get.assert_called_once_with('1') self.db_host_get.assert_called_once_with('1')
@ -205,10 +202,15 @@ class PhysicalHostPluginTestCase(tests.TestCase):
self.db_host_extra_capability_create.assert_called_once_with(fake_capa) self.db_host_extra_capability_create.assert_called_once_with(fake_capa)
self.assertEqual(host, fake_host) self.assertEqual(host, fake_host)
def test_create_host_with_invalid_values(self): def test_create_host_without_trust_id(self):
self.assertRaises(manager_exceptions.InvalidHost, self.assertRaises(manager_exceptions.MissingTrustId,
self.fake_phys_plugin.create_computehost, {}) self.fake_phys_plugin.create_computehost, {})
def test_create_host_without_host_id(self):
self.assertRaises(manager_exceptions.InvalidHost,
self.fake_phys_plugin.create_computehost,
{'trust_id': 'exxee111qwwwwe'})
def test_create_host_with_existing_vms(self): def test_create_host_with_existing_vms(self):
self.get_servers_per_host.return_value = ['server1', 'server2'] self.get_servers_per_host.return_value = ['server1', 'server2']
self.assertRaises(manager_exceptions.HostHavingServers, self.assertRaises(manager_exceptions.HostHavingServers,

View File

@ -31,11 +31,10 @@ class TestTrusts(tests.TestCase):
self.keystone = keystone self.keystone = keystone
self.client = self.patch(self.keystone, 'ClimateKeystoneClient') self.client = self.patch(self.keystone, 'ClimateKeystoneClient')
def test_create_trust(self):
self.patch(self.context, 'current') self.patch(self.context, 'current')
self.patch(self.base, 'url_for').return_value = 'http://www.foo.fake' self.patch(self.base, 'url_for').return_value = 'http://www.foo.fake'
def test_create_trust(self):
correct_trust = self.client().trusts.create() correct_trust = self.client().trusts.create()
trust = self.trusts.create_trust() trust = self.trusts.create_trust()
@ -62,3 +61,35 @@ class TestTrusts(tests.TestCase):
ctx = self.trusts.create_ctx_from_trust('1') ctx = self.trusts.create_ctx_from_trust('1')
self.assertEqual(fake_ctx_dict, ctx.__dict__) self.assertEqual(fake_ctx_dict, ctx.__dict__)
def test_use_trust_auth_dict(self):
def to_wrap(self, arg_to_update):
return arg_to_update
correct_trust = self.client().trusts.create()
fill_with_trust_id = {}
updated_arg = self.trusts.use_trust_auth()(to_wrap)(self,
fill_with_trust_id)
self.assertIn('trust_id', updated_arg)
self.assertEqual(correct_trust.id, updated_arg['trust_id'])
def test_use_trust_auth_object(self):
class AsDict(object):
def __init__(self, value):
self.value = value
def as_dict(self):
to_return = {}
for key in dir(self):
to_return[key] = getattr(self, key)
return to_return
def to_wrap(self, arg_to_update):
return arg_to_update
correct_trust = self.client().trusts.create()
fill_with_trust_id = AsDict(1)
updated_arg = self.trusts.use_trust_auth()(to_wrap)(self,
fill_with_trust_id)
self.assertIn('trust_id', updated_arg.as_dict())
self.assertEqual(correct_trust.id, updated_arg.trust_id)

View File

@ -131,15 +131,11 @@ class ServerManager(servers.ServerManager):
class NovaClientWrapper(object): class NovaClientWrapper(object):
def __init__(self):
self._nova = None
self.username = None
self.api_key = None
self.project_id = None
@property @property
def nova(self): def nova(self):
return ClimateNovaClient( ctx = context.current()
username=self.username, nova = ClimateNovaClient(username=ctx.user_name,
api_key=self.api_key, api_key=None,
project_id=self.project_id) project_id=ctx.project_id,
ctx=ctx)
return nova

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import functools
from oslo.config import cfg from oslo.config import cfg
from climate import context from climate import context
@ -69,3 +70,23 @@ def create_ctx_from_trust(trust_id):
service_catalog=client.service_catalog.catalog['catalog'], service_catalog=client.service_catalog.catalog['catalog'],
project_id=client.tenant_id, project_id=client.tenant_id,
) )
def use_trust_auth():
"""This decorator creates a keystone trust, and adds the trust_id to the
parameter of the decorated method.
"""
def decorator(func):
@functools.wraps(func)
def wrapped(self, to_update):
if to_update is not None:
trust = create_trust()
if isinstance(to_update, dict):
to_update.update({'trust_id': trust.id})
elif isinstance(to_update, object):
setattr(to_update, 'trust_id', trust.id)
return func(self, to_update)
return wrapped
return decorator

View File

@ -763,20 +763,6 @@
[physical:host] [physical:host]
#
# Options defined in climate.plugins.oshosts
#
# Name of the user for write operations (string value)
#climate_username=climate_admin
# Password of the user for write operations (string value)
#climate_password=climate_password
# Project of the user for write operations (string value)
#climate_project_name=admin
# #
# Options defined in climate.plugins.oshosts.host_plugin # Options defined in climate.plugins.oshosts.host_plugin
# #