diff --git a/etc/reddwarf/reddwarf-guestagent.conf.sample b/etc/reddwarf/reddwarf-guestagent.conf.sample index 9dab76def6..da473a4f9b 100644 --- a/etc/reddwarf/reddwarf-guestagent.conf.sample +++ b/etc/reddwarf/reddwarf-guestagent.conf.sample @@ -40,9 +40,9 @@ api_extensions_path = reddwarf/extensions # These options are for an admin user in your keystone config. # It proxy's the token received from the user to send to nova via this admin users creds, # basically acting like the client via that proxy token. -reddwarf_proxy_admin_user = admin -reddwarf_proxy_admin_pass = 3de4922d8b6ac5a1aad9 -reddwarf_proxy_admin_tenant_name = admin +nova_proxy_admin_user = admin +nova_proxy_admin_pass = 3de4922d8b6ac5a1aad9 +nova_proxy_admin_tenant_name = admin reddwarf_auth_url = http://0.0.0.0:5000/v2.0 swift_url = http://10.0.0.1:8080/v1/AUTH_ diff --git a/etc/reddwarf/reddwarf-taskmanager.conf.sample b/etc/reddwarf/reddwarf-taskmanager.conf.sample index b9d858915e..6068077b73 100644 --- a/etc/reddwarf/reddwarf-taskmanager.conf.sample +++ b/etc/reddwarf/reddwarf-taskmanager.conf.sample @@ -44,14 +44,20 @@ server_delete_time_out=480 # These options are for an admin user in your keystone config. # It proxy's the token received from the user to send to nova via this admin users creds, # basically acting like the client via that proxy token. -reddwarf_proxy_admin_user = admin -reddwarf_proxy_admin_pass = 3de4922d8b6ac5a1aad9 -reddwarf_proxy_admin_tenant_name = admin +nova_proxy_admin_user = admin +nova_proxy_admin_pass = 3de4922d8b6ac5a1aad9 +nova_proxy_admin_tenant_name = admin reddwarf_auth_url = http://0.0.0.0:5000/v2.0 # Manager impl for the taskmanager taskmanager_manager=reddwarf.taskmanager.manager.Manager +# Manager sends Exists Notifications +taskmanager_exists_notification = True +exists_notification_transformer = reddwarf.extensions.mgmt.instances.models.NovaNotificationTransformer +exists_notification_ticks = 30 +notification_service_id = 2f3ff068-2bfb-4f70-9a9d-a6bb65bc084b + # Reddwarf DNS reddwarf_dns_support = False @@ -63,9 +69,6 @@ agent_call_high_timeout = 150 # Whether to use nova's contrib api for create server with volume use_nova_server_volume = False -# usage notifications -notification_driver = reddwarf.tests.util.usage - # ============ notifer queue kombu connection options ======================== notifier_queue_hostname = localhost @@ -76,6 +79,10 @@ notifier_queue_port = 5672 notifier_queue_virtual_host = / notifier_queue_transport = memory +# usage notifications +notification_driver=reddwarf.openstack.common.notifier.rpc_notifier +control_exchange=reddwarf + # ============ Logging information ============================= #log_dir = /integration/report #log_file = reddwarf-taskmanager.log diff --git a/etc/reddwarf/reddwarf.conf.sample b/etc/reddwarf/reddwarf.conf.sample index fb87ac4780..83fd70c3ba 100644 --- a/etc/reddwarf/reddwarf.conf.sample +++ b/etc/reddwarf/reddwarf.conf.sample @@ -105,6 +105,8 @@ notifier_queue_port = 5672 notifier_queue_virtual_host = / notifier_queue_transport = memory +control_exchange = reddwarf + # ============ Logging information ============================= #log_dir = /integration/report #log_file = reddwarf-api.log diff --git a/etc/reddwarf/reddwarf.conf.test b/etc/reddwarf/reddwarf.conf.test index 3eca9efed0..b68d6480ea 100644 --- a/etc/reddwarf/reddwarf.conf.test +++ b/etc/reddwarf/reddwarf.conf.test @@ -55,9 +55,9 @@ api_extensions_path = reddwarf/extensions # These options are for an admin user in your keystone config. # It proxy's the token received from the user to send to nova via this admin users creds, # basically acting like the client via that proxy token. -reddwarf_proxy_admin_user = admin -reddwarf_proxy_admin_pass = 3de4922d8b6ac5a1aad9 -reddwarf_proxy_admin_tenant_name = admin +nova_proxy_admin_user = admin +nova_proxy_admin_pass = 3de4922d8b6ac5a1aad9 +nova_proxy_admin_tenant_name = admin reddwarf_auth_url = http://0.0.0.0:5000/v2.0 nova_region_name = RegionOne @@ -116,6 +116,8 @@ notifier_queue_port = 5672 notifier_queue_virtual_host = / notifier_queue_transport = memory +control_exchange = reddwarf + paste_config_file=api-paste.ini.test [composite:reddwarf] diff --git a/reddwarf/common/cfg.py b/reddwarf/common/cfg.py index a28889a938..c5994b4459 100644 --- a/reddwarf/common/cfg.py +++ b/reddwarf/common/cfg.py @@ -45,7 +45,8 @@ common_opts = [ cfg.StrOpt('swift_url', default='http://localhost:8080/v1/AUTH_'), cfg.StrOpt('reddwarf_auth_url', default='http://0.0.0.0:5000/v2.0'), cfg.StrOpt('host', default='0.0.0.0'), - cfg.IntOpt('report_interval', default=10), + cfg.IntOpt('report_interval', default=10, + help='The interval in seconds which periodic tasks are run'), cfg.IntOpt('periodic_interval', default=60), cfg.BoolOpt('reddwarf_dns_support', default=False), cfg.StrOpt('db_api_implementation', default='reddwarf.db.sqlalchemy.api'), @@ -116,33 +117,33 @@ common_opts = [ cfg.IntOpt('reddwarf_security_group_rule_port', default=3306), cfg.IntOpt('reddwarf_api_workers', default=None), cfg.IntOpt('usage_sleep_time', default=1, - help="Time to sleep during the check active guest"), + help='Time to sleep during the check active guest'), cfg.IntOpt('usage_timeout', default=300, - help="Timeout to wait for an guest to become active"), + help='Timeout to wait for an guest to become active'), cfg.StrOpt('region', default='LOCAL_DEV', - help="The region this service is located."), + help='The region this service is located.'), cfg.StrOpt('backup_runner', default='reddwarf.guestagent.backup.backup_types.InnoBackupEx'), cfg.StrOpt('backup_strategy', default='InnoBackupEx', - help="Default strategy to perform backups"), + help='Default strategy to perform backups'), cfg.StrOpt('backup_namespace', default='reddwarf.guestagent.strategies.backup.impl', - help="Namespace to load backup strategies from"), + help='Namespace to load backup strategies from'), cfg.StrOpt('restore_namespace', default='reddwarf.guestagent.strategies.restore.impl', - help="Namespace to load restore strategies from"), + help='Namespace to load restore strategies from'), cfg.StrOpt('storage_strategy', default='SwiftStorage', help="Default strategy to store backups"), cfg.StrOpt('storage_namespace', default='reddwarf.guestagent.strategies.storage.swift', - help="Namespace to load the default storage strategy from"), + help='Namespace to load the default storage strategy from'), cfg.StrOpt('backup_swift_container', default='database_backups'), cfg.BoolOpt('backup_use_gzip_compression', default=True, - help="Compress backups using gzip."), + help='Compress backups using gzip.'), cfg.BoolOpt('backup_use_snet', default=False, - help="Send backup files over snet."), + help='Send backup files over snet.'), cfg.IntOpt('backup_chunk_size', default=2 ** 16, - help="Chunk size to stream to swift container."), + help='Chunk size to stream to swift container'), cfg.IntOpt('backup_segment_max_size', default=2 * (1024 ** 3), help="Maximum size of each segment of the backup file."), cfg.StrOpt('remote_dns_client', @@ -155,6 +156,21 @@ common_opts = [ default='reddwarf.common.remote.nova_volume_client'), cfg.StrOpt('remote_swift_client', default='reddwarf.common.remote.swift_client'), + cfg.BoolOpt('taskmanager_exists_notification', default=False, + help='Toggles Task Manager to send out exists notifications'), + cfg.StrOpt('exists_notification_transformer', default=None, + help='Transformer for exists notifications'), + cfg.IntOpt('exists_notification_ticks', default=360, + help='Number of report_intevals to wait between pushing events ' + '(see report_interval)'), + cfg.StrOpt('notification_service_id', default='', + help='Unique ID to tag notification events'), + cfg.StrOpt('nova_proxy_admin_user', default='', + help="Admin username used to connect to Nova"), + cfg.StrOpt('nova_proxy_admin_pass', default='', + help="Admin password used to connect to Nova"), + cfg.StrOpt('nova_proxy_admin_tenant_name', default='', + help="Admin tenant used to connect to Nova") ] diff --git a/reddwarf/common/remote.py b/reddwarf/common/remote.py index 06cab00fd9..bc9c7d351a 100644 --- a/reddwarf/common/remote.py +++ b/reddwarf/common/remote.py @@ -48,6 +48,16 @@ def nova_client(context): return client +def create_admin_nova_client(context): + """ + Creates client that uses reddwarf admin credentials + :return: a client for nova for the reddwarf admin + """ + client = create_nova_client(context) + client.client.auth_token = None + return client + + def nova_volume_client(context): # Quite annoying but due to a paste config loading bug. # TODO(hub-cap): talk to the openstack-common people about this diff --git a/reddwarf/extensions/mgmt/instances/models.py b/reddwarf/extensions/mgmt/instances/models.py index 2d747271e6..dc0bcbf6b1 100644 --- a/reddwarf/extensions/mgmt/instances/models.py +++ b/reddwarf/extensions/mgmt/instances/models.py @@ -11,39 +11,44 @@ # 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 datetime +from reddwarf.common import cfg +from reddwarf.common import remote +from reddwarf.common import utils from reddwarf.openstack.common import log as logging - -from reddwarf.common.remote import create_nova_client -from reddwarf.common.remote import create_nova_volume_client +from reddwarf.openstack.common.notifier import api as notifier from reddwarf.instance import models as imodels -from reddwarf.instance.models import load_instance +from reddwarf.instance.models import load_instance, InstanceServiceStatus from reddwarf.instance import models as instance_models from reddwarf.extensions.mysql import models as mysql_models - LOG = logging.getLogger(__name__) +CONF = cfg.CONF -def load_mgmt_instances(context, deleted=None): - client = create_nova_client(context) - mgmt_servers = client.rdservers.list() - db_infos = None +def load_mgmt_instances(context, deleted=None, client=None): + if not client: + client = remote.create_nova_client(context) + try: + mgmt_servers = client.rdservers.list() + except AttributeError: + mgmt_servers = client.servers.list(search_opts={'all_tenants': 1}) + LOG.info("Found %d servers in Nova" % + len(mgmt_servers if mgmt_servers else [])) if deleted is not None: db_infos = instance_models.DBInstance.find_all(deleted=deleted) else: db_infos = instance_models.DBInstance.find_all() - instances = MgmtInstances.load_status_from_existing( - context, - db_infos, - mgmt_servers) + instances = MgmtInstances.load_status_from_existing(context, db_infos, + mgmt_servers) return instances def load_mgmt_instance(cls, context, id): try: instance = load_instance(cls, context, id, needs_server=True) - client = create_nova_client(context) + client = remote.create_nova_client(context) server = client.rdservers.get(instance.server_id) instance.server.host = server.host instance.server.deleted = server.deleted @@ -57,7 +62,6 @@ def load_mgmt_instance(cls, context, id): class SimpleMgmtInstance(imodels.BaseInstance): - def __init__(self, context, db_info, server, service_status): super(SimpleMgmtInstance, self).__init__(context, db_info, server, service_status) @@ -86,7 +90,6 @@ class SimpleMgmtInstance(imodels.BaseInstance): class DetailedMgmtInstance(SimpleMgmtInstance): - def __init__(self, *args, **kwargs): super(DetailedMgmtInstance, self).__init__(*args, **kwargs) self.volume = None @@ -96,12 +99,12 @@ class DetailedMgmtInstance(SimpleMgmtInstance): @classmethod def load(cls, context, id): instance = load_mgmt_instance(cls, context, id) - client = create_nova_volume_client(context) + client = remote.create_nova_volume_client(context) try: instance.volume = client.volumes.get(instance.volume_id) - except Exception as ex: + except Exception: instance.volume = None - # Populate the volume_used attribute from the guest agent. + # Populate the volume_used attribute from the guest agent. instance_models.load_guest_info(instance, context, id) instance.root_history = mysql_models.RootHistory.load(context=context, instance_id=id) @@ -109,7 +112,6 @@ class DetailedMgmtInstance(SimpleMgmtInstance): class MgmtInstance(imodels.Instance): - def get_diagnostics(self): return self.get_guest().get_diagnostics() @@ -121,10 +123,8 @@ class MgmtInstance(imodels.Instance): class MgmtInstances(imodels.Instances): - @staticmethod def load_status_from_existing(context, db_infos, servers): - def load_instance(context, db, status, server=None): return SimpleMgmtInstance(context, db, server, status) @@ -132,7 +132,8 @@ class MgmtInstances(imodels.Instances): raise TypeError("Argument context not defined.") find_server = imodels.create_server_list_matcher(servers) instances = imodels.Instances._load_servers_status(load_instance, - context, db_infos, + context, + db_infos, find_server) _load_servers(instances, find_server) return instances @@ -148,3 +149,91 @@ def _load_servers(instances, find_server): except Exception as ex: LOG.error(ex) return instances + + +def publish_exist_events(transformer, admin_context): + notifications = transformer() + for notification in notifications: + notifier.notify(admin_context, + CONF.host, + "reddwarf.instance.exists", + 'INFO', + notification) + + +class NotificationTransformer(object): + def __init__(self, **kwargs): + pass + + @staticmethod + def _get_audit_period(): + now = datetime.datetime.now() + audit_start = utils.isotime(now, subsecond=True) + audit_end = utils.isotime( + now + datetime.timedelta( + seconds=CONF.exists_notification_ticks * CONF.report_interval), + subsecond=True) + return audit_start, audit_end + + def transform_instance(self, instance, audit_start, audit_end): + return {'audit-period-beginning': audit_start, + 'audit-period-ending': audit_end, + 'created_at': instance.created, + 'display_name': instance.name, + 'instance_id': instance.id, + 'instance_name': instance.name, + 'instance_type_id': instance.flavor_id, + 'launched_at': instance.created, + 'nova_instance_id': instance.server_id, + 'region': CONF.region, + 'state_description': instance.status.lower(), + 'state': instance.status.lower(), + 'tenant_id': instance.tenant_id, + 'service_id': CONF.notification_service_id} + + def __call__(self): + audit_start, audit_end = NotificationTransformer._get_audit_period() + messages = [] + db_infos = instance_models.DBInstance.find_all(deleted=False) + for db_info in db_infos: + service_status = InstanceServiceStatus.find_by( + instance_id=db_info.id) + instance = SimpleMgmtInstance(None, db_info, None, service_status) + message = self.transform_instance(instance, audit_start, audit_end) + messages.append(message) + return messages + + +class NovaNotificationTransformer(NotificationTransformer): + def __init__(self, **kwargs): + super(NovaNotificationTransformer, self).__init__(**kwargs) + self.context = kwargs['context'] + self.nova_client = remote.create_admin_nova_client(self.context) + self._flavor_cache = {} + + def _lookup_flavor(self, flavor_id): + if flavor_id in self._flavor_cache: + LOG.debug("Flavor cache hit for %s" % flavor_id) + return self._flavor_cache[flavor_id] + # fetch flavor resource from nova + LOG.info("Flavor cache miss for %s" % flavor_id) + flavor = self.nova_client.flavors.get(flavor_id) + self._flavor_cache[flavor_id] = flavor.name if flavor else 'unknown' + return self._flavor_cache[flavor_id] + + def __call__(self): + audit_start, audit_end = NotificationTransformer._get_audit_period() + instances = load_mgmt_instances(self.context, deleted=False, + client=self.nova_client) + messages = [] + for instance in filter( + lambda inst: inst.status != 'SHUTDOWN' and inst.server, + instances): + message = { + 'instance_type': self._lookup_flavor(instance.flavor_id), + 'user_id': instance.server.user_id} + message.update(self.transform_instance(instance, + audit_start, + audit_end)) + messages.append(message) + return messages diff --git a/reddwarf/instance/models.py b/reddwarf/instance/models.py index ce7467dd09..61633afbd8 100644 --- a/reddwarf/instance/models.py +++ b/reddwarf/instance/models.py @@ -45,7 +45,7 @@ def load_server(context, instance_id, server_id): client = create_nova_client(context) try: server = client.servers.get(server_id) - except nova_exceptions.NotFound as e: + except nova_exceptions.NotFound: LOG.debug("Could not find nova server_id(%s)" % server_id) raise exception.ComputeInstanceNotFound(instance_id=instance_id, server_id=server_id) @@ -89,7 +89,7 @@ def load_simple_instance_server_status(context, db_info): server = client.servers.get(db_info.compute_instance_id) db_info.server_status = server.status db_info.addresses = server.addresses - except nova_exceptions.NotFound, e: + except nova_exceptions.NotFound: db_info.server_status = "SHUTDOWN" db_info.addresses = {} @@ -250,8 +250,6 @@ def get_db_info(context, id): db_info = DBInstance.find_by(id=id, deleted=False) except exception.NotFound: raise exception.NotFound(uuid=id) - except exception.ModelNotFoundError: - raise exception.NotFound(uuid=id) if not context.is_admin and db_info.tenant_id != context.tenant: LOG.error("Tenant %s tried to access instance %s, owned by %s." % (context.tenant, id, db_info.tenant_id)) @@ -264,7 +262,7 @@ def load_any_instance(context, id): # If that fails, try to load it without the server. try: return load_instance(BuiltInstance, context, id, needs_server=True) - except exception.UnprocessableEntity as upe: + except exception.UnprocessableEntity: LOG.warn("Could not load instance %s." % id) return load_instance(FreshInstance, context, id, needs_server=False) @@ -572,7 +570,6 @@ class Instance(BuiltInstance): """ Raises exception if an instance action cannot currently be performed. """ - status = None if self.db_info.server_status != 'ACTIVE': status = self.db_info.server_status elif self.db_info.task_status != InstanceTasks.NONE: @@ -616,7 +613,7 @@ class Instances(object): @staticmethod def load(context): - def load_simple_instance(context, db, status): + def load_simple_instance(context, db, status, **kwargs): return SimpleInstance(context, db, status) if context is None: @@ -662,16 +659,16 @@ class Instances(object): #volumes = find_volumes(server.id) status = InstanceServiceStatus.find_by(instance_id=db.id) LOG.info(_("Server api_status(%s)") % - (status.status.api_status)) + status.status.api_status) if not status.status: # This should never happen. LOG.error(_("Server status could not be read for " - "instance id(%s)") % (db.id)) + "instance id(%s)") % db.id) continue except exception.ModelNotFoundError: LOG.error(_("Server status could not be read for " - "instance id(%s)") % (db.id)) + "instance id(%s)") % db.id) continue - ret.append(load_instance(context, db, status)) + ret.append(load_instance(context, db, status, server=server)) return ret @@ -684,7 +681,7 @@ class DBInstance(dbmodels.DatabaseModelBase): 'task_id', 'task_description', 'task_start_time', 'volume_id', 'deleted', 'tenant_id'] - def __init__(self, task_status=None, **kwargs): + def __init__(self, task_status, **kwargs): kwargs["task_id"] = task_status.code kwargs["task_description"] = task_status.db_text kwargs["deleted"] = False @@ -717,7 +714,7 @@ class InstanceServiceStatus(dbmodels.DatabaseModelBase): _data_fields = ['instance_id', 'status_id', 'status_description'] - def __init__(self, status=None, **kwargs): + def __init__(self, status, **kwargs): kwargs["status_id"] = status.code kwargs["status_description"] = status.description super(InstanceServiceStatus, self).__init__(**kwargs) diff --git a/reddwarf/taskmanager/manager.py b/reddwarf/taskmanager/manager.py index a944c0c14b..1b28f71e96 100644 --- a/reddwarf/taskmanager/manager.py +++ b/reddwarf/taskmanager/manager.py @@ -14,19 +14,35 @@ # 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 reddwarf.common.context import ReddwarfContext +import reddwarf.extensions.mgmt.instances.models as mgmtmodels +import reddwarf.common.cfg as cfg from reddwarf.common import exception from reddwarf.openstack.common import log as logging +from reddwarf.openstack.common import importutils from reddwarf.openstack.common import periodic_task from reddwarf.taskmanager import models from reddwarf.taskmanager.models import FreshInstanceTasks LOG = logging.getLogger(__name__) RPC_API_VERSION = "1.0" +CONF = cfg.CONF class Manager(periodic_task.PeriodicTasks): + def __init__(self): + super(Manager, self).__init__() + self.admin_context = ReddwarfContext( + user=CONF.nova_proxy_admin_user, + auth_token=CONF.nova_proxy_admin_pass, + tenant=CONF.nova_proxy_admin_tenant_name) + if CONF.exists_notification_transformer: + self.exists_transformer = importutils.import_object( + CONF.exists_notification_transformer, + context=self.admin_context) + def resize_volume(self, context, instance_id, new_size): instance_tasks = models.BuiltInstanceTasks.load(context, instance_id) instance_tasks.resize_volume(new_size) @@ -74,3 +90,14 @@ class Manager(periodic_task.PeriodicTasks): databases, users, service_type, volume_size, security_groups, backup_id) + + if CONF.exists_notification_transformer: + @periodic_task.periodic_task( + ticks_between_runs=CONF.exists_notification_ticks) + def publish_exists_event(self, context): + """ + Push this in Instance Tasks to fetch a report/collection + :param context: currently None as specied in bin script + """ + mgmtmodels.publish_exist_events(self.exists_transformer, + self.admin_context) diff --git a/reddwarf/tests/unittests/mgmt/__init__.py b/reddwarf/tests/unittests/mgmt/__init__.py new file mode 100644 index 0000000000..487d0c8c67 --- /dev/null +++ b/reddwarf/tests/unittests/mgmt/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# diff --git a/reddwarf/tests/unittests/mgmt/test_models.py b/reddwarf/tests/unittests/mgmt/test_models.py new file mode 100644 index 0000000000..b04ac6e01c --- /dev/null +++ b/reddwarf/tests/unittests/mgmt/test_models.py @@ -0,0 +1,293 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +from mockito import mock, when, verify, unstub, any +from testtools import TestCase +from testtools.matchers import Equals, Is, Not + +from novaclient.v1_1 import Client +from novaclient.v1_1.flavors import FlavorManager, Flavor +from novaclient.v1_1.servers import Server, ServerManager +from reddwarf.backup.models import Backup + +from reddwarf.common.context import ReddwarfContext +from reddwarf.db.models import DatabaseModelBase +from reddwarf.extensions.mgmt.instances.models import NotificationTransformer +from reddwarf.extensions.mgmt.instances.models import \ + NovaNotificationTransformer +from reddwarf.extensions.mgmt.instances.models import SimpleMgmtInstance +from reddwarf.instance.models import DBInstance +from reddwarf.instance.models import InstanceServiceStatus +from reddwarf.instance.models import ServiceStatuses +from reddwarf.instance.tasks import InstanceTasks +import reddwarf.extensions.mgmt.instances.models as mgmtmodels +from reddwarf.openstack.common.notifier import api as notifier +from reddwarf.common import remote + + +class MockMgmtInstanceTest(TestCase): + def setUp(self): + super(MockMgmtInstanceTest, self).setUp() + self.context = ReddwarfContext() + self.client = mock(Client) + self.server_mgr = mock(ServerManager) + self.client.servers = self.server_mgr + self.flavor_mgr = mock(FlavorManager) + self.client.flavors = self.flavor_mgr + when(remote).create_admin_nova_client(self.context).thenReturn( + self.client) + + def tearDown(self): + super(MockMgmtInstanceTest, self).tearDown() + unstub() + + +class TestNotificationTransformer(MockMgmtInstanceTest): + def test_tranformer(self): + transformer = NotificationTransformer(context=self.context) + status = ServiceStatuses.BUILDING.api_status + db_instance = DBInstance(InstanceTasks.BUILDING, + created='xyz', + name='test_name', + id='1', + flavor_id='flavor_1', + compute_instance_id='compute_id_1', + server_id='server_id_1', + tenant_id='tenant_id_1', + server_status=status) + when(DatabaseModelBase).find_all(deleted=False).thenReturn( + [db_instance]) + when(DatabaseModelBase).find_by(instance_id='1').thenReturn( + InstanceServiceStatus(ServiceStatuses.BUILDING)) + + payloads = transformer() + self.assertIsNotNone(payloads) + self.assertThat(len(payloads), Equals(1)) + payload = payloads[0] + self.assertThat(payload['audit-period-beginning'], Not(Is(None))) + self.assertThat(payload['audit-period-ending'], Not(Is(None))) + self.assertThat(payload['state'], Equals(status.lower())) + + +class TestNovaNotificationTransformer(MockMgmtInstanceTest): + def test_transformer_cache(self): + flavor = mock(Flavor) + flavor.name = 'db.small' + when(self.flavor_mgr).get('flavor_1').thenReturn(flavor) + transformer = NovaNotificationTransformer(context=self.context) + transformer2 = NovaNotificationTransformer(context=self.context) + self.assertThat(transformer._flavor_cache, + Not(Is(transformer2._flavor_cache))) + + def test_lookup_flavor(self): + flavor = mock(Flavor) + flavor.name = 'flav_1' + when(self.flavor_mgr).get('1').thenReturn(flavor) + transformer = NovaNotificationTransformer(context=self.context) + self.assertThat(transformer._lookup_flavor('1'), Equals(flavor.name)) + self.assertThat(transformer._lookup_flavor('2'), Equals('unknown')) + + def test_tranformer(self): + status = ServiceStatuses.BUILDING.api_status + db_instance = DBInstance(InstanceTasks.BUILDING, + created='xyz', + name='test_name', + id='1', + flavor_id='flavor_1', + compute_instance_id='compute_id_1', + server_id='server_id_1', + tenant_id='tenant_id_1', + server_status=status) + + server = mock(Server) + server.user_id = 'test_user_id' + mgmt_instance = SimpleMgmtInstance(self.context, + db_instance, + server, + None) + when(mgmtmodels).load_mgmt_instances( + self.context, + deleted=False, + client=self.client).thenReturn( + [mgmt_instance]) + flavor = mock(Flavor) + flavor.name = 'db.small' + when(self.flavor_mgr).get('flavor_1').thenReturn(flavor) + # invocation + transformer = NovaNotificationTransformer(context=self.context) + payloads = transformer() + # assertions + self.assertIsNotNone(payloads) + self.assertThat(len(payloads), Equals(1)) + payload = payloads[0] + self.assertThat(payload['audit-period-beginning'], Not(Is(None))) + self.assertThat(payload['audit-period-ending'], Not(Is(None))) + self.assertThat(payload['state'], Equals(status.lower())) + self.assertThat(payload['instance_type'], Equals('db.small')) + self.assertThat(payload['instance_type_id'], Equals('flavor_1')) + self.assertThat(payload['user_id'], Equals('test_user_id')) + + def test_tranformer_shutdown_instance(self): + status = ServiceStatuses.SHUTDOWN.api_status + db_instance = DBInstance(InstanceTasks.DELETING, + created='xyz', + name='test_name', + id='1', + flavor_id='flavor_1', + compute_instance_id='compute_id_1', + server_id='server_id_1', + tenant_id='tenant_id_1', + server_status=status) + + server = mock(Server) + server.user_id = 'test_user_id' + mgmt_instance = SimpleMgmtInstance(self.context, + db_instance, + server, + None) + when(Backup).running('1').thenReturn(None) + self.assertThat(mgmt_instance.status, Equals('SHUTDOWN')) + when(mgmtmodels).load_mgmt_instances( + self.context, + deleted=False, + client=self.client).thenReturn( + [mgmt_instance]) + flavor = mock(Flavor) + flavor.name = 'db.small' + when(self.flavor_mgr).get('flavor_1').thenReturn(flavor) + # invocation + transformer = NovaNotificationTransformer(context=self.context) + payloads = transformer() + # assertion that SHUTDOWN instances are not reported + self.assertIsNotNone(payloads) + self.assertThat(len(payloads), Equals(0)) + + def test_tranformer_no_nova_instance(self): + status = ServiceStatuses.SHUTDOWN.api_status + db_instance = DBInstance(InstanceTasks.DELETING, + created='xyz', + name='test_name', + id='1', + flavor_id='flavor_1', + compute_instance_id='compute_id_1', + server_id='server_id_1', + tenant_id='tenant_id_1', + server_status=status) + + mgmt_instance = SimpleMgmtInstance(self.context, + db_instance, + None, + None) + when(Backup).running('1').thenReturn(None) + self.assertThat(mgmt_instance.status, Equals('SHUTDOWN')) + when(mgmtmodels).load_mgmt_instances( + self.context, + deleted=False, + client=self.client).thenReturn( + [mgmt_instance]) + flavor = mock(Flavor) + flavor.name = 'db.small' + when(self.flavor_mgr).get('flavor_1').thenReturn(flavor) + # invocation + transformer = NovaNotificationTransformer(context=self.context) + payloads = transformer() + # assertion that SHUTDOWN instances are not reported + self.assertIsNotNone(payloads) + self.assertThat(len(payloads), Equals(0)) + + def test_tranformer_flavor_cache(self): + status = ServiceStatuses.BUILDING.api_status + db_instance = DBInstance(InstanceTasks.BUILDING, + created='xyz', + name='test_name', + id='1', + flavor_id='flavor_1', + compute_instance_id='compute_id_1', + server_id='server_id_1', + tenant_id='tenant_id_1', + server_status=status) + + server = mock(Server) + server.user_id = 'test_user_id' + mgmt_instance = SimpleMgmtInstance(self.context, + db_instance, + server, + None) + when(mgmtmodels).load_mgmt_instances( + self.context, + deleted=False, + client=self.client).thenReturn( + [mgmt_instance]) + flavor = mock(Flavor) + flavor.name = 'db.small' + when(self.flavor_mgr).get('flavor_1').thenReturn(flavor) + transformer = NovaNotificationTransformer(context=self.context) + transformer() + # call twice ensure client.flavor invoked once + payloads = transformer() + self.assertIsNotNone(payloads) + self.assertThat(len(payloads), Equals(1)) + payload = payloads[0] + self.assertThat(payload['audit-period-beginning'], Not(Is(None))) + self.assertThat(payload['audit-period-ending'], Not(Is(None))) + self.assertThat(payload['state'], Equals(status.lower())) + self.assertThat(payload['instance_type'], Equals('db.small')) + self.assertThat(payload['instance_type_id'], Equals('flavor_1')) + self.assertThat(payload['user_id'], Equals('test_user_id')) + # ensure cache was used to get flavor second time + verify(self.flavor_mgr).get('flavor_1') + + +class TestMgmtInstanceTasks(MockMgmtInstanceTest): + def test_public_exists_events(self): + status = ServiceStatuses.BUILDING.api_status + db_instance = DBInstance(InstanceTasks.BUILDING, + created='xyz', + name='test_name', + id='1', + flavor_id='flavor_1', + compute_instance_id='compute_id_1', + server_id='server_id_1', + tenant_id='tenant_id_1', + server_status=status) + + server = mock(Server) + server.user_id = 'test_user_id' + mgmt_instance = SimpleMgmtInstance(self.context, + db_instance, + server, + None) + when(mgmtmodels).load_mgmt_instances( + self.context, + deleted=False, + client=self.client).thenReturn( + [mgmt_instance, mgmt_instance]) + flavor = mock(Flavor) + flavor.name = 'db.small' + when(self.flavor_mgr).get('flavor_1').thenReturn(flavor) + when(notifier).notify(self.context, + any(str), + 'reddwarf.instance.exists', + 'INFO', + any(dict)).thenReturn(None) + # invocation + mgmtmodels.publish_exist_events( + NovaNotificationTransformer(context=self.context), self.context) + # assertion + verify(notifier, times=2).notify(self.context, + any(str), + 'reddwarf.instance.exists', + 'INFO', + any(dict))