Merge "Notification and CDM partial update"
This commit is contained in:
@@ -44,6 +44,9 @@ WATCHER_CONF_DIR=/etc/watcher
|
|||||||
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
WATCHER_CONF=$WATCHER_CONF_DIR/watcher.conf
|
||||||
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
WATCHER_POLICY_JSON=$WATCHER_CONF_DIR/policy.json
|
||||||
|
|
||||||
|
NOVA_CONF_DIR=/etc/nova
|
||||||
|
NOVA_CONF=$NOVA_CONF_DIR/nova.conf
|
||||||
|
|
||||||
if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
|
if is_ssl_enabled_service "watcher" || is_service_enabled tls-proxy; then
|
||||||
WATCHER_SERVICE_PROTOCOL="https"
|
WATCHER_SERVICE_PROTOCOL="https"
|
||||||
fi
|
fi
|
||||||
@@ -123,6 +126,8 @@ function create_watcher_conf {
|
|||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||||
|
|
||||||
|
iniset $NOVA_CONF oslo_messaging_notifications topics "notifications,watcher_notifications"
|
||||||
|
|
||||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||||
def __init__(self, context, applier_manager, action_plan_uuid):
|
def __init__(self, context, service, action_plan_uuid):
|
||||||
super(DefaultActionPlanHandler, self).__init__()
|
super(DefaultActionPlanHandler, self).__init__()
|
||||||
self.ctx = context
|
self.ctx = context
|
||||||
|
self.service = service
|
||||||
self.action_plan_uuid = action_plan_uuid
|
self.action_plan_uuid = action_plan_uuid
|
||||||
self.applier_manager = applier_manager
|
|
||||||
|
|
||||||
def notify(self, uuid, event_type, state):
|
def notify(self, uuid, event_type, state):
|
||||||
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||||
@@ -43,8 +43,7 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
ev.data = {}
|
ev.data = {}
|
||||||
payload = {'action_plan__uuid': uuid,
|
payload = {'action_plan__uuid': uuid,
|
||||||
'action_plan_state': state}
|
'action_plan_state': state}
|
||||||
self.applier_manager.status_topic_handler.publish_event(
|
self.service.publish_status_event(ev.type.name, payload)
|
||||||
ev.type.name, payload)
|
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
try:
|
try:
|
||||||
@@ -52,10 +51,9 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
|||||||
self.notify(self.action_plan_uuid,
|
self.notify(self.action_plan_uuid,
|
||||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||||
ap_objects.State.ONGOING)
|
ap_objects.State.ONGOING)
|
||||||
applier = default.DefaultApplier(self.ctx, self.applier_manager)
|
applier = default.DefaultApplier(self.ctx, self.service)
|
||||||
applier.execute(self.action_plan_uuid)
|
applier.execute(self.action_plan_uuid)
|
||||||
state = ap_objects.State.SUCCEEDED
|
state = ap_objects.State.SUCCEEDED
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
state = ap_objects.State.FAILED
|
state = ap_objects.State.FAILED
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ class ApplierManager(object):
|
|||||||
|
|
||||||
conductor_endpoints = [trigger.TriggerActionPlan]
|
conductor_endpoints = [trigger.TriggerActionPlan]
|
||||||
status_endpoints = []
|
status_endpoints = []
|
||||||
|
notification_endpoints = []
|
||||||
|
notification_topics = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.publisher_id = CONF.watcher_applier.publisher_id
|
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from oslo_log import log
|
|||||||
|
|
||||||
from watcher.applier import manager
|
from watcher.applier import manager
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common.messaging import notification_handler as notification
|
|
||||||
from watcher.common import service
|
from watcher.common import service
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
@@ -51,7 +50,9 @@ class ApplierAPIManager(object):
|
|||||||
API_VERSION = '1.0'
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
conductor_endpoints = []
|
conductor_endpoints = []
|
||||||
status_endpoints = [notification.NotificationHandler]
|
status_endpoints = []
|
||||||
|
notification_endpoints = []
|
||||||
|
notification_topics = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.publisher_id = CONF.watcher_applier.publisher_id
|
self.publisher_id = CONF.watcher_applier.publisher_id
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ class BaseWorkFlowEngine(loadable.Loadable):
|
|||||||
ev.data = {}
|
ev.data = {}
|
||||||
payload = {'action_uuid': action.uuid,
|
payload = {'action_uuid': action.uuid,
|
||||||
'action_state': state}
|
'action_state': state}
|
||||||
self.applier_manager.status_topic_handler.publish_event(
|
self.applier_manager.publish_status_event(ev.type.name, payload)
|
||||||
ev.type.name, payload)
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def execute(self, actions):
|
def execute(self, actions):
|
||||||
|
|||||||
@@ -11,16 +11,24 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_context import context
|
from oslo_context import context
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
import six
|
||||||
|
|
||||||
|
from watcher._i18n import _LW
|
||||||
|
from watcher.common import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RequestContext(context.RequestContext):
|
class RequestContext(context.RequestContext):
|
||||||
"""Extends security contexts from the OpenStack common library."""
|
"""Extends security contexts from the OpenStack common library."""
|
||||||
|
|
||||||
def __init__(self, auth_token=None, auth_url=None, domain_id=None,
|
def __init__(self, user_id=None, project_id=None, is_admin=None,
|
||||||
domain_name=None, user=None, user_id=None, project=None,
|
roles=None, timestamp=None, request_id=None, auth_token=None,
|
||||||
project_id=None, is_admin=False, is_public_api=False,
|
auth_url=None, overwrite=True, user_name=None,
|
||||||
read_only=False, show_deleted=False, request_id=None,
|
project_name=None, domain_name=None, domain_id=None,
|
||||||
trust_id=None, auth_token_info=None, roles=None):
|
auth_token_info=None, **kwargs):
|
||||||
"""Stores several additional request parameters:
|
"""Stores several additional request parameters:
|
||||||
|
|
||||||
:param domain_id: The ID of the domain.
|
:param domain_id: The ID of the domain.
|
||||||
@@ -29,46 +37,84 @@ class RequestContext(context.RequestContext):
|
|||||||
without authentication.
|
without authentication.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(RequestContext, self).__init__(auth_token=auth_token,
|
user = kwargs.pop('user', None)
|
||||||
user=user, tenant=project,
|
tenant = kwargs.pop('tenant', None)
|
||||||
is_admin=is_admin,
|
super(RequestContext, self).__init__(
|
||||||
read_only=read_only,
|
auth_token=auth_token,
|
||||||
show_deleted=show_deleted,
|
user=user_id or user,
|
||||||
request_id=request_id,
|
tenant=project_id or tenant,
|
||||||
roles=roles)
|
domain=kwargs.pop('domain', None) or domain_name or domain_id,
|
||||||
|
user_domain=kwargs.pop('user_domain', None),
|
||||||
|
project_domain=kwargs.pop('project_domain', None),
|
||||||
|
is_admin=is_admin,
|
||||||
|
read_only=kwargs.pop('read_only', False),
|
||||||
|
show_deleted=kwargs.pop('show_deleted', False),
|
||||||
|
request_id=request_id,
|
||||||
|
resource_uuid=kwargs.pop('resource_uuid', None),
|
||||||
|
is_admin_project=kwargs.pop('is_admin_project', None),
|
||||||
|
overwrite=overwrite,
|
||||||
|
roles=roles)
|
||||||
|
|
||||||
self.is_public_api = is_public_api
|
self.remote_address = kwargs.pop('remote_address', None)
|
||||||
self.user_id = user_id
|
self.instance_lock_checked = kwargs.pop('instance_lock_checked', None)
|
||||||
self.project = project
|
self.read_deleted = kwargs.pop('read_deleted', None)
|
||||||
self.project_id = project_id
|
self.service_catalog = kwargs.pop('service_catalog', None)
|
||||||
self.domain_id = domain_id
|
self.quota_class = kwargs.pop('quota_class', None)
|
||||||
self.domain_name = domain_name
|
|
||||||
|
# oslo_context's RequestContext.to_dict() generates this field, we can
|
||||||
|
# safely ignore this as we don't use it.
|
||||||
|
kwargs.pop('user_identity', None)
|
||||||
|
if kwargs:
|
||||||
|
LOG.warning(_LW('Arguments dropped when creating context: %s'),
|
||||||
|
str(kwargs))
|
||||||
|
|
||||||
|
# FIXME(dims): user_id and project_id duplicate information that is
|
||||||
|
# already present in the oslo_context's RequestContext. We need to
|
||||||
|
# get rid of them.
|
||||||
self.auth_url = auth_url
|
self.auth_url = auth_url
|
||||||
|
self.domain_name = domain_name
|
||||||
|
self.domain_id = domain_id
|
||||||
self.auth_token_info = auth_token_info
|
self.auth_token_info = auth_token_info
|
||||||
self.trust_id = trust_id
|
self.user_id = user_id
|
||||||
|
self.project_id = project_id
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = timeutils.utcnow()
|
||||||
|
if isinstance(timestamp, six.string_types):
|
||||||
|
timestamp = timeutils.parse_isotime(timestamp)
|
||||||
|
self.timestamp = timestamp
|
||||||
|
self.user_name = user_name
|
||||||
|
self.project_name = project_name
|
||||||
|
self.is_admin = is_admin
|
||||||
|
# if self.is_admin is None:
|
||||||
|
# self.is_admin = policy.check_is_admin(self)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {'auth_token': self.auth_token,
|
values = super(RequestContext, self).to_dict()
|
||||||
'auth_url': self.auth_url,
|
# FIXME(dims): defensive hasattr() checks need to be
|
||||||
'domain_id': self.domain_id,
|
# removed once we figure out why we are seeing stack
|
||||||
'domain_name': self.domain_name,
|
# traces
|
||||||
'user': self.user,
|
values.update({
|
||||||
'user_id': self.user_id,
|
'user_id': getattr(self, 'user_id', None),
|
||||||
'project': self.project,
|
'user_name': getattr(self, 'user_name', None),
|
||||||
'project_id': self.project_id,
|
'project_id': getattr(self, 'project_id', None),
|
||||||
'is_admin': self.is_admin,
|
'project_name': getattr(self, 'project_name', None),
|
||||||
'is_public_api': self.is_public_api,
|
'domain_id': getattr(self, 'domain_id', None),
|
||||||
'read_only': self.read_only,
|
'domain_name': getattr(self, 'domain_name', None),
|
||||||
'show_deleted': self.show_deleted,
|
'auth_token_info': getattr(self, 'auth_token_info', None),
|
||||||
'request_id': self.request_id,
|
'is_admin': getattr(self, 'is_admin', None),
|
||||||
'trust_id': self.trust_id,
|
'timestamp': utils.strtime(self.timestamp) if hasattr(
|
||||||
'auth_token_info': self.auth_token_info,
|
self, 'timestamp') else None,
|
||||||
'roles': self.roles}
|
'request_id': getattr(self, 'request_id', None),
|
||||||
|
})
|
||||||
|
return values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, values):
|
def from_dict(cls, values):
|
||||||
return cls(**values)
|
return cls(**values)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "<Context %s>" % self.to_dict()
|
||||||
|
|
||||||
|
|
||||||
def make_context(*args, **kwargs):
|
def make_context(*args, **kwargs):
|
||||||
return RequestContext(*args, **kwargs)
|
return RequestContext(*args, **kwargs)
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ class InstanceNotFound(WatcherException):
|
|||||||
|
|
||||||
|
|
||||||
class ComputeNodeNotFound(WatcherException):
|
class ComputeNodeNotFound(WatcherException):
|
||||||
msg_fmt = _("The compute node %s could not be found")
|
msg_fmt = _("The compute node %(name)s could not be found")
|
||||||
|
|
||||||
|
|
||||||
class LoadingError(WatcherException):
|
class LoadingError(WatcherException):
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ class MessagingHandler(threading.Thread):
|
|||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
LOG.debug('Stopped server')
|
LOG.debug('Stopped server')
|
||||||
self.__server.wait()
|
|
||||||
self.__server.stop()
|
self.__server.stop()
|
||||||
|
|
||||||
def publish_event(self, event_type, payload, request_id=None):
|
def publish_event(self, event_type, payload, request_id=None):
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ class NovaHelper(object):
|
|||||||
used as the name of the intermediate image used for migration.
|
used as the name of the intermediate image used for migration.
|
||||||
If this flag is False, a temporary image name is built
|
If this flag is False, a temporary image name is built
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new_image_name = ""
|
new_image_name = ""
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
@@ -298,7 +297,6 @@ class NovaHelper(object):
|
|||||||
:param dest_hostname: the name of the destination compute node.
|
:param dest_hostname: the name of the destination compute node.
|
||||||
:param block_migration: No shared storage is required.
|
:param block_migration: No shared storage is required.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug("Trying a live migrate of instance %s to host '%s'" % (
|
LOG.debug("Trying a live migrate of instance %s to host '%s'" % (
|
||||||
instance_id, dest_hostname))
|
instance_id, dest_hostname))
|
||||||
|
|
||||||
@@ -456,7 +454,6 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
:param instance_id: the unique id of the instance to delete.
|
:param instance_id: the unique id of the instance to delete.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug("Trying to remove instance %s ..." % instance_id)
|
LOG.debug("Trying to remove instance %s ..." % instance_id)
|
||||||
|
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
@@ -474,7 +471,6 @@ class NovaHelper(object):
|
|||||||
|
|
||||||
:param instance_id: the unique id of the instance to stop.
|
:param instance_id: the unique id of the instance to stop.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug("Trying to stop instance %s ..." % instance_id)
|
LOG.debug("Trying to stop instance %s ..." % instance_id)
|
||||||
|
|
||||||
instance = self.find_instance(instance_id)
|
instance = self.find_instance(instance_id)
|
||||||
@@ -502,7 +498,6 @@ class NovaHelper(object):
|
|||||||
:param retry: how many times to retry
|
:param retry: how many times to retry
|
||||||
:param sleep: seconds to sleep between the retries
|
:param sleep: seconds to sleep between the retries
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not server:
|
if not server:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -524,7 +519,6 @@ class NovaHelper(object):
|
|||||||
:param retry: how many times to retry
|
:param retry: how many times to retry
|
||||||
:param sleep: seconds to sleep between the retries
|
:param sleep: seconds to sleep between the retries
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not instance:
|
if not instance:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -548,7 +542,6 @@ class NovaHelper(object):
|
|||||||
it with the new instance
|
it with the new instance
|
||||||
It returns the unique id of the created instance.
|
It returns the unique id of the created instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to create new instance '%s' "
|
"Trying to create new instance '%s' "
|
||||||
"from image '%s' with flavor '%s' ..." % (
|
"from image '%s' with flavor '%s' ..." % (
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
import oslo_messaging as messaging
|
import oslo_messaging as messaging
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
|
from watcher._i18n import _LE
|
||||||
from watcher.common import context as watcher_context
|
from watcher.common import context as watcher_context
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
|
|
||||||
@@ -36,7 +36,9 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
TRANSPORT = None
|
TRANSPORT = None
|
||||||
|
NOTIFICATION_TRANSPORT = None
|
||||||
NOTIFIER = None
|
NOTIFIER = None
|
||||||
|
|
||||||
ALLOWED_EXMODS = [
|
ALLOWED_EXMODS = [
|
||||||
@@ -55,23 +57,36 @@ TRANSPORT_ALIASES = {
|
|||||||
'watcher.rpc.impl_zmq': 'zmq',
|
'watcher.rpc.impl_zmq': 'zmq',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonPayloadSerializer = messaging.JsonPayloadSerializer
|
||||||
|
|
||||||
|
|
||||||
def init(conf):
|
def init(conf):
|
||||||
global TRANSPORT, NOTIFIER
|
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
||||||
exmods = get_allowed_exmods()
|
exmods = get_allowed_exmods()
|
||||||
TRANSPORT = messaging.get_transport(conf,
|
TRANSPORT = messaging.get_transport(conf,
|
||||||
allowed_remote_exmods=exmods,
|
allowed_remote_exmods=exmods,
|
||||||
aliases=TRANSPORT_ALIASES)
|
aliases=TRANSPORT_ALIASES)
|
||||||
|
NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
|
||||||
|
conf,
|
||||||
|
allowed_remote_exmods=exmods,
|
||||||
|
aliases=TRANSPORT_ALIASES)
|
||||||
|
|
||||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||||
NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer)
|
NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
|
||||||
|
serializer=serializer)
|
||||||
|
|
||||||
|
|
||||||
|
def initialized():
|
||||||
|
return None not in [TRANSPORT, NOTIFIER]
|
||||||
|
|
||||||
|
|
||||||
def cleanup():
|
def cleanup():
|
||||||
global TRANSPORT, NOTIFIER
|
global TRANSPORT, NOTIFICATION_TRANSPORT, NOTIFIER
|
||||||
assert TRANSPORT is not None
|
if NOTIFIER is None:
|
||||||
assert NOTIFIER is not None
|
LOG.exception(_LE("RPC cleanup: NOTIFIER is None"))
|
||||||
TRANSPORT.cleanup()
|
TRANSPORT.cleanup()
|
||||||
TRANSPORT = NOTIFIER = None
|
NOTIFICATION_TRANSPORT.cleanup()
|
||||||
|
TRANSPORT = NOTIFICATION_TRANSPORT = NOTIFIER = None
|
||||||
|
|
||||||
|
|
||||||
def set_defaults(control_exchange):
|
def set_defaults(control_exchange):
|
||||||
@@ -90,12 +105,6 @@ def get_allowed_exmods():
|
|||||||
return ALLOWED_EXMODS + EXTRA_EXMODS
|
return ALLOWED_EXMODS + EXTRA_EXMODS
|
||||||
|
|
||||||
|
|
||||||
class JsonPayloadSerializer(messaging.NoOpSerializer):
|
|
||||||
@staticmethod
|
|
||||||
def serialize_entity(context, entity):
|
|
||||||
return jsonutils.to_primitive(entity, convert_instances=True)
|
|
||||||
|
|
||||||
|
|
||||||
class RequestContextSerializer(messaging.Serializer):
|
class RequestContextSerializer(messaging.Serializer):
|
||||||
|
|
||||||
def __init__(self, base):
|
def __init__(self, base):
|
||||||
@@ -118,10 +127,6 @@ class RequestContextSerializer(messaging.Serializer):
|
|||||||
return watcher_context.RequestContext.from_dict(context)
|
return watcher_context.RequestContext.from_dict(context)
|
||||||
|
|
||||||
|
|
||||||
def get_transport_url(url_str=None):
|
|
||||||
return messaging.TransportURL.parse(CONF, url_str, TRANSPORT_ALIASES)
|
|
||||||
|
|
||||||
|
|
||||||
def get_client(target, version_cap=None, serializer=None):
|
def get_client(target, version_cap=None, serializer=None):
|
||||||
assert TRANSPORT is not None
|
assert TRANSPORT is not None
|
||||||
serializer = RequestContextSerializer(serializer)
|
serializer = RequestContextSerializer(serializer)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from oslo_reports import opts as gmr_opts
|
|||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
from oslo_service import wsgi
|
from oslo_service import wsgi
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _, _LI
|
||||||
from watcher.api import app
|
from watcher.api import app
|
||||||
from watcher.common import config
|
from watcher.common import config
|
||||||
from watcher.common.messaging.events import event_dispatcher as dispatcher
|
from watcher.common.messaging.events import event_dispatcher as dispatcher
|
||||||
@@ -111,8 +111,10 @@ class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
|||||||
|
|
||||||
self.publisher_id = self.manager.publisher_id
|
self.publisher_id = self.manager.publisher_id
|
||||||
self.api_version = self.manager.API_VERSION
|
self.api_version = self.manager.API_VERSION
|
||||||
|
|
||||||
self.conductor_topic = self.manager.conductor_topic
|
self.conductor_topic = self.manager.conductor_topic
|
||||||
self.status_topic = self.manager.status_topic
|
self.status_topic = self.manager.status_topic
|
||||||
|
self.notification_topics = self.manager.notification_topics
|
||||||
|
|
||||||
self.conductor_endpoints = [
|
self.conductor_endpoints = [
|
||||||
ep(self) for ep in self.manager.conductor_endpoints
|
ep(self) for ep in self.manager.conductor_endpoints
|
||||||
@@ -120,28 +122,52 @@ class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
|||||||
self.status_endpoints = [
|
self.status_endpoints = [
|
||||||
ep(self.publisher_id) for ep in self.manager.status_endpoints
|
ep(self.publisher_id) for ep in self.manager.status_endpoints
|
||||||
]
|
]
|
||||||
|
self.notification_endpoints = self.manager.notification_endpoints
|
||||||
|
|
||||||
self.serializer = rpc.RequestContextSerializer(
|
self.serializer = rpc.RequestContextSerializer(
|
||||||
base.WatcherObjectSerializer())
|
base.WatcherObjectSerializer())
|
||||||
|
|
||||||
self.conductor_topic_handler = self.build_topic_handler(
|
self._transport = None
|
||||||
self.conductor_topic, self.conductor_endpoints)
|
self._notification_transport = None
|
||||||
self.status_topic_handler = self.build_topic_handler(
|
|
||||||
self.status_topic, self.status_endpoints)
|
|
||||||
|
|
||||||
self._conductor_client = None
|
self._conductor_client = None
|
||||||
self._status_client = None
|
self._status_client = None
|
||||||
|
|
||||||
|
self.conductor_topic_handler = None
|
||||||
|
self.status_topic_handler = None
|
||||||
|
self.notification_handler = None
|
||||||
|
|
||||||
|
if self.conductor_topic and self.conductor_endpoints:
|
||||||
|
self.conductor_topic_handler = self.build_topic_handler(
|
||||||
|
self.conductor_topic, self.conductor_endpoints)
|
||||||
|
if self.status_topic and self.status_endpoints:
|
||||||
|
self.status_topic_handler = self.build_topic_handler(
|
||||||
|
self.status_topic, self.status_endpoints)
|
||||||
|
if self.notification_topics and self.notification_endpoints:
|
||||||
|
self.notification_handler = self.build_notification_handler(
|
||||||
|
self.notification_topics, self.notification_endpoints
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transport(self):
|
||||||
|
if self._transport is None:
|
||||||
|
self._transport = om.get_transport(CONF)
|
||||||
|
return self._transport
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notification_transport(self):
|
||||||
|
if self._notification_transport is None:
|
||||||
|
self._notification_transport = om.get_notification_transport(CONF)
|
||||||
|
return self._notification_transport
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def conductor_client(self):
|
def conductor_client(self):
|
||||||
if self._conductor_client is None:
|
if self._conductor_client is None:
|
||||||
transport = om.get_transport(CONF)
|
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=self.conductor_topic,
|
topic=self.conductor_topic,
|
||||||
version=self.API_VERSION,
|
version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
self._conductor_client = om.RPCClient(
|
self._conductor_client = om.RPCClient(
|
||||||
transport, target, serializer=self.serializer)
|
self.transport, target, serializer=self.serializer)
|
||||||
return self._conductor_client
|
return self._conductor_client
|
||||||
|
|
||||||
@conductor_client.setter
|
@conductor_client.setter
|
||||||
@@ -151,13 +177,12 @@ class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
|||||||
@property
|
@property
|
||||||
def status_client(self):
|
def status_client(self):
|
||||||
if self._status_client is None:
|
if self._status_client is None:
|
||||||
transport = om.get_transport(CONF)
|
|
||||||
target = om.Target(
|
target = om.Target(
|
||||||
topic=self.status_topic,
|
topic=self.status_topic,
|
||||||
version=self.API_VERSION,
|
version=self.API_VERSION,
|
||||||
)
|
)
|
||||||
self._status_client = om.RPCClient(
|
self._status_client = om.RPCClient(
|
||||||
transport, target, serializer=self.serializer)
|
self.transport, target, serializer=self.serializer)
|
||||||
return self._status_client
|
return self._status_client
|
||||||
|
|
||||||
@status_client.setter
|
@status_client.setter
|
||||||
@@ -169,17 +194,33 @@ class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
|||||||
self.publisher_id, topic_name, [self.manager] + list(endpoints),
|
self.publisher_id, topic_name, [self.manager] + list(endpoints),
|
||||||
self.api_version, self.serializer)
|
self.api_version, self.serializer)
|
||||||
|
|
||||||
|
def build_notification_handler(self, topic_names, endpoints=()):
|
||||||
|
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
|
||||||
|
targets = [om.Target(topic=topic_name) for topic_name in topic_names]
|
||||||
|
return om.get_notification_listener(
|
||||||
|
self.notification_transport, targets, endpoints,
|
||||||
|
executor='eventlet', serializer=serializer,
|
||||||
|
allow_requeue=False)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
LOG.debug("Connecting to '%s' (%s)",
|
LOG.debug("Connecting to '%s' (%s)",
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
self.conductor_topic_handler.start()
|
if self.conductor_topic_handler:
|
||||||
self.status_topic_handler.start()
|
self.conductor_topic_handler.start()
|
||||||
|
if self.status_topic_handler:
|
||||||
|
self.status_topic_handler.start()
|
||||||
|
if self.notification_handler:
|
||||||
|
self.notification_handler.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
LOG.debug("Disconnecting from '%s' (%s)",
|
LOG.debug("Disconnecting from '%s' (%s)",
|
||||||
CONF.transport_url, CONF.rpc_backend)
|
CONF.transport_url, CONF.rpc_backend)
|
||||||
self.conductor_topic_handler.stop()
|
if self.conductor_topic_handler:
|
||||||
self.status_topic_handler.stop()
|
self.conductor_topic_handler.stop()
|
||||||
|
if self.status_topic_handler:
|
||||||
|
self.status_topic_handler.stop()
|
||||||
|
if self.notification_handler:
|
||||||
|
self.notification_handler.stop()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Reset a service in case it received a SIGHUP."""
|
"""Reset a service in case it received a SIGHUP."""
|
||||||
@@ -190,9 +231,14 @@ class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
|||||||
def publish_control(self, event, payload):
|
def publish_control(self, event, payload):
|
||||||
return self.conductor_topic_handler.publish_event(event, payload)
|
return self.conductor_topic_handler.publish_event(event, payload)
|
||||||
|
|
||||||
def publish_status(self, event, payload, request_id=None):
|
def publish_status_event(self, event, payload, request_id=None):
|
||||||
return self.status_topic_handler.publish_event(
|
if self.status_topic_handler:
|
||||||
event, payload, request_id)
|
return self.status_topic_handler.publish_event(
|
||||||
|
event, payload, request_id)
|
||||||
|
else:
|
||||||
|
LOG.info(
|
||||||
|
_LI("No status notifier declared: notification '%s' not sent"),
|
||||||
|
event)
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
return self.api_version
|
return self.api_version
|
||||||
@@ -208,7 +254,7 @@ class Service(service.ServiceBase, dispatcher.EventDispatcher):
|
|||||||
'request_id': ctx['request_id'],
|
'request_id': ctx['request_id'],
|
||||||
'msg': message
|
'msg': message
|
||||||
}
|
}
|
||||||
self.publish_status(evt, payload)
|
self.publish_status_event(evt, payload)
|
||||||
|
|
||||||
|
|
||||||
def launch(conf, service_, workers=1, restart_method='reload'):
|
def launch(conf, service_, workers=1, restart_method='reload'):
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ def get_cls_import_path(cls):
|
|||||||
return module + '.' + cls.__name__
|
return module + '.' + cls.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def strtime(at):
|
||||||
|
return at.strftime("%Y-%m-%dT%H:%M:%S.%f")
|
||||||
|
|
||||||
|
|
||||||
# Default value feedback extension as jsonschema doesn't support it
|
# Default value feedback extension as jsonschema doesn't support it
|
||||||
def extend_with_default(validator_class):
|
def extend_with_default(validator_class):
|
||||||
validate_properties = validator_class.VALIDATORS["properties"]
|
validate_properties = validator_class.VALIDATORS["properties"]
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ class AuditHandler(BaseAuditHandler):
|
|||||||
event.data = {}
|
event.data = {}
|
||||||
payload = {'audit_uuid': audit_uuid,
|
payload = {'audit_uuid': audit_uuid,
|
||||||
'audit_status': status}
|
'audit_status': status}
|
||||||
self.messaging.status_topic_handler.publish_event(
|
self.messaging.publish_status_event(event.type.name, payload)
|
||||||
event.type.name, payload)
|
|
||||||
|
|
||||||
def update_audit_state(self, request_context, audit, state):
|
def update_audit_state(self, request_context, audit, state):
|
||||||
LOG.debug("Update audit state: %s", state)
|
LOG.debug("Update audit state: %s", state)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# 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.
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This component is responsible for computing a set of potential optimization
|
This component is responsible for computing a set of potential optimization
|
||||||
@@ -40,6 +39,7 @@ See :doc:`../architecture` for more details on this component.
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from watcher.decision_engine.messaging import audit_endpoint
|
from watcher.decision_engine.messaging import audit_endpoint
|
||||||
|
from watcher.decision_engine.model.collector import manager
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@@ -57,6 +57,10 @@ WATCHER_DECISION_ENGINE_OPTS = [
|
|||||||
'is used so as to notify'
|
'is used so as to notify'
|
||||||
'the others components '
|
'the others components '
|
||||||
'of the system'),
|
'of the system'),
|
||||||
|
cfg.ListOpt('notification_topics',
|
||||||
|
default=['versioned_notifications', 'watcher_notifications'],
|
||||||
|
help='The topic names from which notification events '
|
||||||
|
'will be listened to'),
|
||||||
cfg.StrOpt('publisher_id',
|
cfg.StrOpt('publisher_id',
|
||||||
default='watcher.decision.api',
|
default='watcher.decision.api',
|
||||||
help='The identifier used by the Watcher '
|
help='The identifier used by the Watcher '
|
||||||
@@ -65,8 +69,7 @@ WATCHER_DECISION_ENGINE_OPTS = [
|
|||||||
default=2,
|
default=2,
|
||||||
required=True,
|
required=True,
|
||||||
help='The maximum number of threads that can be used to '
|
help='The maximum number of threads that can be used to '
|
||||||
'execute strategies',
|
'execute strategies'),
|
||||||
),
|
|
||||||
]
|
]
|
||||||
decision_engine_opt_group = cfg.OptGroup(name='watcher_decision_engine',
|
decision_engine_opt_group = cfg.OptGroup(name='watcher_decision_engine',
|
||||||
title='Defines the parameters of '
|
title='Defines the parameters of '
|
||||||
@@ -79,11 +82,19 @@ class DecisionEngineManager(object):
|
|||||||
|
|
||||||
API_VERSION = '1.0'
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
conductor_endpoints = [audit_endpoint.AuditEndpoint]
|
|
||||||
status_endpoints = []
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.api_version = self.API_VERSION
|
||||||
|
|
||||||
self.publisher_id = CONF.watcher_decision_engine.publisher_id
|
self.publisher_id = CONF.watcher_decision_engine.publisher_id
|
||||||
self.conductor_topic = CONF.watcher_decision_engine.conductor_topic
|
self.conductor_topic = CONF.watcher_decision_engine.conductor_topic
|
||||||
self.status_topic = CONF.watcher_decision_engine.status_topic
|
self.status_topic = CONF.watcher_decision_engine.status_topic
|
||||||
self.api_version = self.API_VERSION
|
self.notification_topics = (
|
||||||
|
CONF.watcher_decision_engine.notification_topics)
|
||||||
|
|
||||||
|
self.conductor_endpoints = [audit_endpoint.AuditEndpoint]
|
||||||
|
|
||||||
|
self.status_endpoints = []
|
||||||
|
|
||||||
|
self.collector_manager = manager.CollectorManager()
|
||||||
|
self.notification_endpoints = (
|
||||||
|
self.collector_manager.get_notification_endpoints())
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ LOG = log.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class AuditEndpoint(object):
|
class AuditEndpoint(object):
|
||||||
|
|
||||||
def __init__(self, messaging):
|
def __init__(self, messaging):
|
||||||
self._messaging = messaging
|
self._messaging = messaging
|
||||||
self._executor = futures.ThreadPoolExecutor(
|
self._executor = futures.ThreadPoolExecutor(
|
||||||
|
|||||||
@@ -139,6 +139,15 @@ class BaseClusterDataModelCollector(loadable.LoadableSingleton):
|
|||||||
self._cluster_data_model = model
|
self._cluster_data_model = model
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def notification_endpoints(self):
|
||||||
|
"""Associated notification endpoints
|
||||||
|
|
||||||
|
:return: Associated notification endpoints
|
||||||
|
:rtype: List of :py:class:`~.EventsNotificationEndpoint` instances
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_cluster_data_model_as_stale(self):
|
def set_cluster_data_model_as_stale(self):
|
||||||
self.cluster_data_model = self.STALE_MODEL
|
self.cluster_data_model = self.STALE_MODEL
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class CollectorManager(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.collector_loader = default.ClusterDataModelCollectorLoader()
|
self.collector_loader = default.ClusterDataModelCollectorLoader()
|
||||||
self._collectors = None
|
self._collectors = None
|
||||||
|
self._notification_endpoints = None
|
||||||
|
|
||||||
def get_collectors(self):
|
def get_collectors(self):
|
||||||
if self._collectors is None:
|
if self._collectors is None:
|
||||||
@@ -43,6 +44,15 @@ class CollectorManager(object):
|
|||||||
|
|
||||||
return self._collectors
|
return self._collectors
|
||||||
|
|
||||||
|
def get_notification_endpoints(self):
|
||||||
|
if self._notification_endpoints is None:
|
||||||
|
endpoints = []
|
||||||
|
for collector in self.get_collectors().values():
|
||||||
|
endpoints.extend(collector.notification_endpoints)
|
||||||
|
self._notification_endpoints = endpoints
|
||||||
|
|
||||||
|
return self._notification_endpoints
|
||||||
|
|
||||||
def get_cluster_model_collector(self, name, osc=None):
|
def get_cluster_model_collector(self, name, osc=None):
|
||||||
"""Retrieve cluster data model collector
|
"""Retrieve cluster data model collector
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from watcher.common import nova_helper
|
|||||||
from watcher.decision_engine.model.collector import base
|
from watcher.decision_engine.model.collector import base
|
||||||
from watcher.decision_engine.model import element
|
from watcher.decision_engine.model import element
|
||||||
from watcher.decision_engine.model import model_root
|
from watcher.decision_engine.model import model_root
|
||||||
|
from watcher.decision_engine.model.notification import nova
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -43,6 +44,26 @@ class NovaClusterDataModelCollector(base.BaseClusterDataModelCollector):
|
|||||||
super(NovaClusterDataModelCollector, self).__init__(config, osc)
|
super(NovaClusterDataModelCollector, self).__init__(config, osc)
|
||||||
self.wrapper = nova_helper.NovaHelper(osc=self.osc)
|
self.wrapper = nova_helper.NovaHelper(osc=self.osc)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notification_endpoints(self):
|
||||||
|
"""Associated notification endpoints
|
||||||
|
|
||||||
|
:return: Associated notification endpoints
|
||||||
|
:rtype: List of :py:class:`~.EventsNotificationEndpoint` instances
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
nova.ServiceUpdated(self),
|
||||||
|
|
||||||
|
nova.InstanceCreated(self),
|
||||||
|
nova.InstanceUpdated(self),
|
||||||
|
nova.InstanceDeletedEnd(self),
|
||||||
|
|
||||||
|
nova.LegacyInstanceCreatedEnd(self),
|
||||||
|
nova.LegacyInstanceUpdated(self),
|
||||||
|
nova.LegacyInstanceDeletedEnd(self),
|
||||||
|
nova.LegacyLiveMigratedEnd(self),
|
||||||
|
]
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""Build the compute cluster data model"""
|
"""Build the compute cluster data model"""
|
||||||
LOG.debug("Building latest Nova cluster data model")
|
LOG.debug("Building latest Nova cluster data model")
|
||||||
@@ -87,7 +108,6 @@ class NovaClusterDataModelCollector(base.BaseClusterDataModelCollector):
|
|||||||
disk.set_capacity(instance, v.flavor['disk'])
|
disk.set_capacity(instance, v.flavor['disk'])
|
||||||
num_cores.set_capacity(instance, v.flavor['vcpus'])
|
num_cores.set_capacity(instance, v.flavor['vcpus'])
|
||||||
|
|
||||||
model.get_mapping().map(node, instance)
|
model.map_instance(instance, node)
|
||||||
model.add_instance(instance)
|
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from watcher.decision_engine.model.element import node
|
|||||||
from watcher.decision_engine.model.element import resource
|
from watcher.decision_engine.model.element import resource
|
||||||
|
|
||||||
ServiceState = node.ServiceState
|
ServiceState = node.ServiceState
|
||||||
PowerState = node.PowerState
|
|
||||||
ComputeNode = node.ComputeNode
|
ComputeNode = node.ComputeNode
|
||||||
|
|
||||||
InstanceState = instance.InstanceState
|
InstanceState = instance.InstanceState
|
||||||
@@ -35,5 +34,5 @@ Resource = resource.Resource
|
|||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ServiceState', 'PowerState', 'ComputeNode', 'InstanceState', 'Instance',
|
'ServiceState', 'ComputeNode', 'InstanceState', 'Instance',
|
||||||
'DiskInfo', 'ResourceType', 'Resource']
|
'DiskInfo', 'ResourceType', 'Resource']
|
||||||
|
|||||||
@@ -26,31 +26,12 @@ class ServiceState(enum.Enum):
|
|||||||
DISABLED = 'disabled'
|
DISABLED = 'disabled'
|
||||||
|
|
||||||
|
|
||||||
class PowerState(enum.Enum):
|
|
||||||
# away mode
|
|
||||||
g0 = "g0"
|
|
||||||
# power on suspend (processor caches are flushed)
|
|
||||||
# The power to the CPU(s) and RAM is maintained
|
|
||||||
g1_S1 = "g1_S1"
|
|
||||||
# CPU powered off. Dirty cache is flushed to RAM
|
|
||||||
g1_S2 = "g1_S2"
|
|
||||||
# Suspend to RAM
|
|
||||||
g1_S3 = "g1_S3"
|
|
||||||
# Suspend to Disk
|
|
||||||
g1_S4 = "g1_S4"
|
|
||||||
# switch outlet X OFF on the PDU (Power Distribution Unit)
|
|
||||||
switch_off = "switch_off"
|
|
||||||
# switch outlet X ON on the PDU (Power Distribution Unit)
|
|
||||||
switch_on = "switch_on"
|
|
||||||
|
|
||||||
|
|
||||||
class ComputeNode(compute_resource.ComputeResource):
|
class ComputeNode(compute_resource.ComputeResource):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ComputeNode, self).__init__()
|
super(ComputeNode, self).__init__()
|
||||||
self._state = ServiceState.ONLINE
|
self._state = ServiceState.ONLINE.value
|
||||||
self._status = ServiceState.ENABLED
|
self._status = ServiceState.ENABLED.value
|
||||||
self._power_state = PowerState.g0
|
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -70,11 +51,3 @@ class ComputeNode(compute_resource.ComputeResource):
|
|||||||
@status.setter
|
@status.setter
|
||||||
def status(self, s):
|
def status(self, s):
|
||||||
self._status = s
|
self._status = s
|
||||||
|
|
||||||
@property
|
|
||||||
def powerstate(self):
|
|
||||||
return self._power_state
|
|
||||||
|
|
||||||
@powerstate.setter
|
|
||||||
def powerstate(self, p):
|
|
||||||
self._power_state = p
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class Resource(object):
|
|||||||
def set_capacity(self, element, value):
|
def set_capacity(self, element, value):
|
||||||
self.mapping[element.uuid] = value
|
self.mapping[element.uuid] = value
|
||||||
|
|
||||||
|
def unset_capacity(self, element):
|
||||||
|
del self.mapping[element.uuid]
|
||||||
|
|
||||||
def get_capacity_from_id(self, uuid):
|
def get_capacity_from_id(self, uuid):
|
||||||
if str(uuid) in self.mapping.keys():
|
if str(uuid) in self.mapping.keys():
|
||||||
return self.mapping[str(uuid)]
|
return self.mapping[str(uuid)]
|
||||||
|
|||||||
@@ -36,17 +36,15 @@ class Mapping(object):
|
|||||||
:param node: the node
|
:param node: the node
|
||||||
:param instance: the virtual machine or instance
|
:param instance: the virtual machine or instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
|
||||||
# init first
|
# init first
|
||||||
if node.uuid not in self.compute_node_mapping.keys():
|
if node.uuid not in self.compute_node_mapping.keys():
|
||||||
self.compute_node_mapping[node.uuid] = []
|
self.compute_node_mapping[node.uuid] = set()
|
||||||
|
|
||||||
# map node => instances
|
# map node => instances
|
||||||
self.compute_node_mapping[node.uuid].append(
|
self.compute_node_mapping[node.uuid].add(instance.uuid)
|
||||||
instance.uuid)
|
|
||||||
|
|
||||||
# map instance => node
|
# map instance => node
|
||||||
self.instance_mapping[instance.uuid] = node.uuid
|
self.instance_mapping[instance.uuid] = node.uuid
|
||||||
@@ -60,7 +58,6 @@ class Mapping(object):
|
|||||||
:param node: the node
|
:param node: the node
|
||||||
:param instance: the virtual machine or instance
|
:param instance: the virtual machine or instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.unmap_from_id(node.uuid, instance.uuid)
|
self.unmap_from_id(node.uuid, instance.uuid)
|
||||||
|
|
||||||
def unmap_from_id(self, node_uuid, instance_uuid):
|
def unmap_from_id(self, node_uuid, instance_uuid):
|
||||||
@@ -68,7 +65,6 @@ class Mapping(object):
|
|||||||
|
|
||||||
:rtype : object
|
:rtype : object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
if str(node_uuid) in self.compute_node_mapping:
|
if str(node_uuid) in self.compute_node_mapping:
|
||||||
@@ -77,9 +73,9 @@ class Mapping(object):
|
|||||||
# remove instance
|
# remove instance
|
||||||
self.instance_mapping.pop(instance_uuid)
|
self.instance_mapping.pop(instance_uuid)
|
||||||
else:
|
else:
|
||||||
LOG.warning(_LW(
|
LOG.warning(
|
||||||
"Trying to delete the instance %(instance)s but it was "
|
_LW("Trying to delete the instance %(instance)s but it "
|
||||||
"not found on node %(node)s"),
|
"was not found on node %(node)s") %
|
||||||
{'instance': instance_uuid, 'node': node_uuid})
|
{'instance': instance_uuid, 'node': node_uuid})
|
||||||
finally:
|
finally:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
@@ -96,7 +92,6 @@ class Mapping(object):
|
|||||||
:param instance: the uuid of the instance
|
:param instance: the uuid of the instance
|
||||||
:return: node
|
:return: node
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.model.get_node_from_id(
|
return self.model.get_node_from_id(
|
||||||
self.instance_mapping[str(instance_uuid)])
|
self.instance_mapping[str(instance_uuid)])
|
||||||
|
|
||||||
@@ -113,21 +108,4 @@ class Mapping(object):
|
|||||||
return self.compute_node_mapping[str(node_uuid)]
|
return self.compute_node_mapping[str(node_uuid)]
|
||||||
else:
|
else:
|
||||||
# empty
|
# empty
|
||||||
return []
|
return set()
|
||||||
|
|
||||||
def migrate_instance(self, instance, source_node, destination_node):
|
|
||||||
"""Migrate single instance from source_node to destination_node
|
|
||||||
|
|
||||||
:param instance:
|
|
||||||
:param source_node:
|
|
||||||
:param destination_node:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
if source_node == destination_node:
|
|
||||||
return False
|
|
||||||
# unmap
|
|
||||||
self.unmap(source_node, instance)
|
|
||||||
# map
|
|
||||||
self.map(destination_node, instance)
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -14,6 +14,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.
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from watcher._i18n import _
|
from watcher._i18n import _
|
||||||
from watcher.common import exception
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
@@ -51,7 +53,7 @@ class ModelRoot(object):
|
|||||||
def remove_node(self, node):
|
def remove_node(self, node):
|
||||||
self.assert_node(node)
|
self.assert_node(node)
|
||||||
if str(node.uuid) not in self._nodes:
|
if str(node.uuid) not in self._nodes:
|
||||||
raise exception.ComputeNodeNotFound(node.uuid)
|
raise exception.ComputeNodeNotFound(name=node.uuid)
|
||||||
else:
|
else:
|
||||||
del self._nodes[node.uuid]
|
del self._nodes[node.uuid]
|
||||||
|
|
||||||
@@ -59,12 +61,75 @@ class ModelRoot(object):
|
|||||||
self.assert_instance(instance)
|
self.assert_instance(instance)
|
||||||
self._instances[instance.uuid] = instance
|
self._instances[instance.uuid] = instance
|
||||||
|
|
||||||
|
def remove_instance(self, instance):
|
||||||
|
self.assert_instance(instance)
|
||||||
|
del self._instances[instance.uuid]
|
||||||
|
|
||||||
|
def map_instance(self, instance, node):
|
||||||
|
"""Map a newly created instance to a node
|
||||||
|
|
||||||
|
:param instance: :py:class:`~.Instance` object or instance UUID
|
||||||
|
:type instance: str or :py:class:`~.Instance`
|
||||||
|
:param node: :py:class:`~.ComputeNode` object or node UUID
|
||||||
|
:type node: str or :py:class:`~.Instance`
|
||||||
|
"""
|
||||||
|
if isinstance(instance, six.string_types):
|
||||||
|
instance = self.get_instance_from_id(instance)
|
||||||
|
if isinstance(node, six.string_types):
|
||||||
|
node = self.get_node_from_id(node)
|
||||||
|
|
||||||
|
self.add_instance(instance)
|
||||||
|
self.mapping.map(node, instance)
|
||||||
|
|
||||||
|
def unmap_instance(self, instance, node):
|
||||||
|
"""Unmap an instance from a node
|
||||||
|
|
||||||
|
:param instance: :py:class:`~.Instance` object or instance UUID
|
||||||
|
:type instance: str or :py:class:`~.Instance`
|
||||||
|
:param node: :py:class:`~.ComputeNode` object or node UUID
|
||||||
|
:type node: str or :py:class:`~.Instance`
|
||||||
|
"""
|
||||||
|
if isinstance(instance, six.string_types):
|
||||||
|
instance = self.get_instance_from_id(instance)
|
||||||
|
if isinstance(node, six.string_types):
|
||||||
|
node = self.get_node_from_id(node)
|
||||||
|
|
||||||
|
self.add_instance(instance)
|
||||||
|
self.mapping.unmap(node, instance)
|
||||||
|
|
||||||
|
def delete_instance(self, instance, node):
|
||||||
|
self.remove_instance(instance)
|
||||||
|
|
||||||
|
self.mapping.unmap(node, instance)
|
||||||
|
|
||||||
|
for resource in self.resource.values():
|
||||||
|
try:
|
||||||
|
resource.unset_capacity(instance)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def migrate_instance(self, instance, source_node, destination_node):
|
||||||
|
"""Migrate single instance from source_node to destination_node
|
||||||
|
|
||||||
|
:param instance:
|
||||||
|
:param source_node:
|
||||||
|
:param destination_node:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if source_node == destination_node:
|
||||||
|
return False
|
||||||
|
# unmap
|
||||||
|
self.mapping.unmap(source_node, instance)
|
||||||
|
# map
|
||||||
|
self.mapping.map(destination_node, instance)
|
||||||
|
return True
|
||||||
|
|
||||||
def get_all_compute_nodes(self):
|
def get_all_compute_nodes(self):
|
||||||
return self._nodes
|
return self._nodes
|
||||||
|
|
||||||
def get_node_from_id(self, node_uuid):
|
def get_node_from_id(self, node_uuid):
|
||||||
if str(node_uuid) not in self._nodes:
|
if str(node_uuid) not in self._nodes:
|
||||||
raise exception.ComputeNodeNotFound(node_uuid)
|
raise exception.ComputeNodeNotFound(name=node_uuid)
|
||||||
return self._nodes[str(node_uuid)]
|
return self._nodes[str(node_uuid)]
|
||||||
|
|
||||||
def get_instance_from_id(self, uuid):
|
def get_instance_from_id(self, uuid):
|
||||||
@@ -72,6 +137,17 @@ class ModelRoot(object):
|
|||||||
raise exception.InstanceNotFound(name=uuid)
|
raise exception.InstanceNotFound(name=uuid)
|
||||||
return self._instances[str(uuid)]
|
return self._instances[str(uuid)]
|
||||||
|
|
||||||
|
def get_node_from_instance_id(self, instance_uuid):
|
||||||
|
"""Getting host information from the guest instance
|
||||||
|
|
||||||
|
:param instance_uuid: the uuid of the instance
|
||||||
|
:return: node
|
||||||
|
"""
|
||||||
|
if str(instance_uuid) not in self.mapping.instance_mapping:
|
||||||
|
raise exception.InstanceNotFound(name=instance_uuid)
|
||||||
|
return self.get_node_from_id(
|
||||||
|
self.mapping.instance_mapping[str(instance_uuid)])
|
||||||
|
|
||||||
def get_all_instances(self):
|
def get_all_instances(self):
|
||||||
return self._instances
|
return self._instances
|
||||||
|
|
||||||
|
|||||||
51
watcher/decision_engine/model/notification/base.py
Normal file
51
watcher/decision_engine/model/notification/base.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Vincent FRANCOISE <Vincent.FRANCOISE@b-com.com>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
import six
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from watcher.common import rpc
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class NotificationEndpoint(object):
|
||||||
|
|
||||||
|
def __init__(self, collector):
|
||||||
|
super(NotificationEndpoint, self).__init__()
|
||||||
|
self.collector = collector
|
||||||
|
self._notifier = None
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Notification Filter"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cluster_data_model(self):
|
||||||
|
return self.collector.cluster_data_model
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notifier(self):
|
||||||
|
if self._notifier is None:
|
||||||
|
self._notifier = rpc.get_notifier('decision-engine')
|
||||||
|
|
||||||
|
return self._notifier
|
||||||
91
watcher/decision_engine/model/notification/filtering.py
Normal file
91
watcher/decision_engine/model/notification/filtering.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Vincent FRANCOISE <Vincent.FRANCOISE@b-com.com>
|
||||||
|
#
|
||||||
|
# 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 re
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
import oslo_messaging as om
|
||||||
|
import six
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationFilter(om.NotificationFilter):
|
||||||
|
"""Notification Endpoint base class
|
||||||
|
|
||||||
|
This class is responsible for handling incoming notifications. Depending
|
||||||
|
on the priority level of the incoming, you may need to implement one or
|
||||||
|
more of the following methods:
|
||||||
|
|
||||||
|
.. code: py
|
||||||
|
def audit(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
do_something(payload)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
do_something(payload)
|
||||||
|
|
||||||
|
def warn(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
do_something(payload)
|
||||||
|
|
||||||
|
def error(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
do_something(payload)
|
||||||
|
|
||||||
|
def critical(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
do_something(payload)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _build_regex_dict(self, regex_list):
|
||||||
|
if regex_list is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
regex_mapping = {}
|
||||||
|
for key, value in regex_list.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
regex_mapping[key] = self._build_regex_dict(value)
|
||||||
|
else:
|
||||||
|
if callable(value):
|
||||||
|
regex_mapping[key] = value
|
||||||
|
elif value is not None:
|
||||||
|
regex_mapping[key] = re.compile(value)
|
||||||
|
else:
|
||||||
|
regex_mapping[key] = None
|
||||||
|
|
||||||
|
return regex_mapping
|
||||||
|
|
||||||
|
def _check_for_mismatch(self, data, regex):
|
||||||
|
if isinstance(regex, dict):
|
||||||
|
mismatch_results = [
|
||||||
|
k not in data or not self._check_for_mismatch(data[k], v)
|
||||||
|
for k, v in regex.items()
|
||||||
|
]
|
||||||
|
if not mismatch_results:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return all(mismatch_results)
|
||||||
|
elif callable(regex):
|
||||||
|
# The filter is a callable that should return True
|
||||||
|
# if there is a mismatch
|
||||||
|
return regex(data)
|
||||||
|
elif regex is not None and data is None:
|
||||||
|
return True
|
||||||
|
elif (regex is not None and
|
||||||
|
isinstance(data, six.string_types) and
|
||||||
|
not regex.match(data)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
334
watcher/decision_engine/model/notification/nova.py
Normal file
334
watcher/decision_engine/model/notification/nova.py
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Vincent FRANCOISE <Vincent.FRANCOISE@b-com.com>
|
||||||
|
#
|
||||||
|
# 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_log import log
|
||||||
|
|
||||||
|
from watcher._i18n import _LI
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.decision_engine.model import element
|
||||||
|
from watcher.decision_engine.model.notification import base
|
||||||
|
from watcher.decision_engine.model.notification import filtering
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NovaNotification(base.NotificationEndpoint):
|
||||||
|
|
||||||
|
def get_or_create_instance(self, uuid):
|
||||||
|
try:
|
||||||
|
instance = self.cluster_data_model.get_instance_from_id(uuid)
|
||||||
|
except exception.InstanceNotFound:
|
||||||
|
# The instance didn't exist yet so we create a new instance object
|
||||||
|
LOG.debug("New instance created: %s", uuid)
|
||||||
|
instance = element.Instance()
|
||||||
|
instance.uuid = uuid
|
||||||
|
|
||||||
|
self.cluster_data_model.add_instance(instance)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def update_instance(self, instance, data):
|
||||||
|
instance_data = data['nova_object.data']
|
||||||
|
instance_flavor_data = instance_data['flavor']['nova_object.data']
|
||||||
|
|
||||||
|
instance.state = instance_data['state']
|
||||||
|
instance.hostname = instance_data['host_name']
|
||||||
|
instance.human_id = instance_data['display_name']
|
||||||
|
|
||||||
|
memory_mb = instance_flavor_data['memory_mb']
|
||||||
|
num_cores = instance_flavor_data['vcpus']
|
||||||
|
disk_gb = instance_flavor_data['root_gb']
|
||||||
|
|
||||||
|
self.update_capacity(element.ResourceType.memory, instance, memory_mb)
|
||||||
|
self.update_capacity(
|
||||||
|
element.ResourceType.cpu_cores, instance, num_cores)
|
||||||
|
self.update_capacity(
|
||||||
|
element.ResourceType.disk, instance, disk_gb)
|
||||||
|
|
||||||
|
node = self.get_or_create_node(instance_data['host'])
|
||||||
|
|
||||||
|
self.update_instance_mapping(instance, node)
|
||||||
|
|
||||||
|
def update_capacity(self, resource_id, obj, value):
|
||||||
|
resource = self.cluster_data_model.get_resource_from_id(resource_id)
|
||||||
|
resource.set_capacity(obj, value)
|
||||||
|
|
||||||
|
def legacy_update_instance(self, instance, data):
|
||||||
|
instance.state = data['state']
|
||||||
|
instance.hostname = data['hostname']
|
||||||
|
instance.human_id = data['display_name']
|
||||||
|
|
||||||
|
memory_mb = data['memory_mb']
|
||||||
|
num_cores = data['vcpus']
|
||||||
|
disk_gb = data['root_gb']
|
||||||
|
|
||||||
|
self.update_capacity(element.ResourceType.memory, instance, memory_mb)
|
||||||
|
self.update_capacity(
|
||||||
|
element.ResourceType.cpu_cores, instance, num_cores)
|
||||||
|
self.update_capacity(
|
||||||
|
element.ResourceType.disk, instance, disk_gb)
|
||||||
|
|
||||||
|
node = self.get_or_create_node(data['host'])
|
||||||
|
|
||||||
|
self.update_instance_mapping(instance, node)
|
||||||
|
|
||||||
|
def get_or_create_node(self, uuid):
|
||||||
|
if uuid is None:
|
||||||
|
LOG.debug("Compute node UUID not provided: skipping")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
node = self.cluster_data_model.get_node_from_id(uuid)
|
||||||
|
except exception.ComputeNodeNotFound:
|
||||||
|
# The node didn't exist yet so we create a new node object
|
||||||
|
LOG.debug("New compute node created: %s", uuid)
|
||||||
|
node = element.ComputeNode()
|
||||||
|
node.uuid = uuid
|
||||||
|
|
||||||
|
self.cluster_data_model.add_node(node)
|
||||||
|
|
||||||
|
return node
|
||||||
|
|
||||||
|
def update_instance_mapping(self, instance, node):
|
||||||
|
if not node:
|
||||||
|
LOG.debug("Instance %s not yet attached to any node: skipping",
|
||||||
|
instance.uuid)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
old_node = self.get_or_create_node(node.uuid)
|
||||||
|
LOG.debug("Mapped node %s found", node.uuid)
|
||||||
|
if node and node != old_node:
|
||||||
|
LOG.debug("Unmapping instance %s from %s",
|
||||||
|
instance.uuid, node.uuid)
|
||||||
|
self.cluster_data_model.unmap_instance(instance, old_node)
|
||||||
|
except exception.InstanceNotFound:
|
||||||
|
# The instance didn't exist yet so we map it for the first time
|
||||||
|
LOG.debug("New instance: mapping it to %s", node.uuid)
|
||||||
|
finally:
|
||||||
|
if node:
|
||||||
|
self.cluster_data_model.map_instance(instance, node)
|
||||||
|
LOG.debug("Mapped instance %s to %s", instance.uuid, node.uuid)
|
||||||
|
|
||||||
|
def delete_instance(self, instance, node):
|
||||||
|
try:
|
||||||
|
self.cluster_data_model.delete_instance(instance, node)
|
||||||
|
except Exception as exc:
|
||||||
|
LOG.exception(exc)
|
||||||
|
LOG.info(_LI("Instance %s already deleted"), instance.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionnedNotificationEndpoint(NovaNotification):
|
||||||
|
publisher_id_regex = r'^nova-compute.*'
|
||||||
|
|
||||||
|
|
||||||
|
class UnversionnedNotificationEndpoint(NovaNotification):
|
||||||
|
publisher_id_regex = r'^compute.*'
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceUpdated(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova service.update notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='service.update',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
node_data = payload['nova_object.data']
|
||||||
|
node_uuid = node_data['host']
|
||||||
|
node = self.get_or_create_node(node_uuid)
|
||||||
|
|
||||||
|
node.hostname = node_data['host']
|
||||||
|
node.state = (
|
||||||
|
element.ServiceState.OFFLINE.value
|
||||||
|
if node_data['forced_down'] else element.ServiceState.ONLINE.value)
|
||||||
|
node.status = (
|
||||||
|
element.ServiceState.DISABLED.value
|
||||||
|
if node_data['host'] else element.ServiceState.ENABLED.value)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceCreated(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova instance.update notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='instance.update',
|
||||||
|
# To be "fully" created, an instance transitions
|
||||||
|
# from the 'building' state to the 'active' one.
|
||||||
|
# See http://docs.openstack.org/developer/nova/vmstates.html
|
||||||
|
payload={
|
||||||
|
'nova_object.data': {
|
||||||
|
'state': element.InstanceState.ACTIVE.value,
|
||||||
|
'state_update': {
|
||||||
|
'nova_object.data': {
|
||||||
|
'old_state': element.InstanceState.BUILDING.value,
|
||||||
|
'state': element.InstanceState.ACTIVE.value,
|
||||||
|
},
|
||||||
|
'nova_object.name': 'InstanceStateUpdatePayload',
|
||||||
|
'nova_object.namespace': 'nova',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
instance_data = payload['nova_object.data']
|
||||||
|
|
||||||
|
instance_uuid = instance_data['uuid']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
self.update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceUpdated(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _match_not_new_instance_state(data):
|
||||||
|
is_new_instance = (
|
||||||
|
data['old_state'] == element.InstanceState.BUILDING.value and
|
||||||
|
data['state'] == element.InstanceState.ACTIVE.value)
|
||||||
|
|
||||||
|
return not is_new_instance
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova instance.update notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='instance.update',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
instance_data = payload['nova_object.data']
|
||||||
|
instance_uuid = instance_data['uuid']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
self.update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceDeletedEnd(VersionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova service.update notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='instance.delete.end',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
|
||||||
|
instance_data = payload['nova_object.data']
|
||||||
|
instance_uuid = instance_data['uuid']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
node = self.get_or_create_node(instance_data['host'])
|
||||||
|
|
||||||
|
self.delete_instance(instance, node)
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyInstanceUpdated(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova compute.instance.update notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='compute.instance.update',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
|
||||||
|
instance_uuid = payload['instance_id']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
self.legacy_update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyInstanceCreatedEnd(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova compute.instance.create.end notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='compute.instance.create.end',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
|
||||||
|
instance_uuid = payload['instance_id']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
self.legacy_update_instance(instance, payload)
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyInstanceDeletedEnd(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova compute.instance.delete.end notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='compute.instance.delete.end',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
instance_uuid = payload['instance_id']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
node = self.get_or_create_node(payload['host'])
|
||||||
|
|
||||||
|
self.delete_instance(instance, node)
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyLiveMigratedEnd(UnversionnedNotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
"""Nova *.live_migration.post.dest.end notification filter"""
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=self.publisher_id_regex,
|
||||||
|
event_type='compute.instance.live_migration.post.dest.end',
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
LOG.info(_LI("Event '%(event)s' received from %(publisher)s") %
|
||||||
|
dict(event=event_type, publisher=publisher_id))
|
||||||
|
|
||||||
|
instance_uuid = payload['instance_id']
|
||||||
|
instance = self.get_or_create_instance(instance_uuid)
|
||||||
|
|
||||||
|
self.legacy_update_instance(instance, payload)
|
||||||
@@ -52,6 +52,8 @@ class DecisionEngineAPIManager(object):
|
|||||||
|
|
||||||
conductor_endpoints = []
|
conductor_endpoints = []
|
||||||
status_endpoints = [notification_handler.NotificationHandler]
|
status_endpoints = [notification_handler.NotificationHandler]
|
||||||
|
notification_endpoints = []
|
||||||
|
notification_topics = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.publisher_id = CONF.watcher_decision_engine.publisher_id
|
self.publisher_id = CONF.watcher_decision_engine.publisher_id
|
||||||
|
|||||||
@@ -209,24 +209,24 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
"""
|
"""
|
||||||
return self.migration_attempts
|
return self.migration_attempts
|
||||||
|
|
||||||
def calculate_weight(self, node, total_cores_used, total_disk_used,
|
def calculate_weight(self, compute_resource, total_cores_used,
|
||||||
total_memory_used):
|
total_disk_used, total_memory_used):
|
||||||
"""Calculate weight of every resource
|
"""Calculate weight of every resource
|
||||||
|
|
||||||
:param element:
|
:param compute_resource:
|
||||||
:param total_cores_used:
|
:param total_cores_used:
|
||||||
:param total_disk_used:
|
:param total_disk_used:
|
||||||
:param total_memory_used:
|
:param total_memory_used:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
cpu_capacity = self.compute_model.get_resource_from_id(
|
cpu_capacity = self.compute_model.get_resource_from_id(
|
||||||
element.ResourceType.cpu_cores).get_capacity(node)
|
element.ResourceType.cpu_cores).get_capacity(compute_resource)
|
||||||
|
|
||||||
disk_capacity = self.compute_model.get_resource_from_id(
|
disk_capacity = self.compute_model.get_resource_from_id(
|
||||||
element.ResourceType.disk).get_capacity(node)
|
element.ResourceType.disk).get_capacity(compute_resource)
|
||||||
|
|
||||||
memory_capacity = self.compute_model.get_resource_from_id(
|
memory_capacity = self.compute_model.get_resource_from_id(
|
||||||
element.ResourceType.memory).get_capacity(node)
|
element.ResourceType.memory).get_capacity(compute_resource)
|
||||||
|
|
||||||
score_cores = (1 - (float(cpu_capacity) - float(total_cores_used)) /
|
score_cores = (1 - (float(cpu_capacity) - float(total_cores_used)) /
|
||||||
float(cpu_capacity))
|
float(cpu_capacity))
|
||||||
@@ -261,10 +261,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
if host_avg_cpu_util is None:
|
if host_avg_cpu_util is None:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
_LE("No values returned by %(resource_id)s "
|
_LE("No values returned by %(resource_id)s "
|
||||||
"for %(metric_name)s"),
|
"for %(metric_name)s") % dict(
|
||||||
resource_id=resource_id,
|
resource_id=resource_id,
|
||||||
metric_name=self.HOST_CPU_USAGE_METRIC_NAME,
|
metric_name=self.HOST_CPU_USAGE_METRIC_NAME))
|
||||||
)
|
|
||||||
host_avg_cpu_util = 100
|
host_avg_cpu_util = 100
|
||||||
|
|
||||||
cpu_capacity = self.compute_model.get_resource_from_id(
|
cpu_capacity = self.compute_model.get_resource_from_id(
|
||||||
@@ -302,10 +301,9 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
if instance_cpu_utilization is None:
|
if instance_cpu_utilization is None:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
_LE("No values returned by %(resource_id)s "
|
_LE("No values returned by %(resource_id)s "
|
||||||
"for %(metric_name)s"),
|
"for %(metric_name)s") % dict(
|
||||||
resource_id=instance.uuid,
|
resource_id=instance.uuid,
|
||||||
metric_name=self.INSTANCE_CPU_USAGE_METRIC_NAME,
|
metric_name=self.INSTANCE_CPU_USAGE_METRIC_NAME))
|
||||||
)
|
|
||||||
instance_cpu_utilization = 100
|
instance_cpu_utilization = 100
|
||||||
|
|
||||||
cpu_capacity = self.compute_model.get_resource_from_id(
|
cpu_capacity = self.compute_model.get_resource_from_id(
|
||||||
@@ -335,18 +333,16 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
|
|
||||||
def score_of_nodes(self, score):
|
def score_of_nodes(self, score):
|
||||||
"""Calculate score of nodes based on load by VMs"""
|
"""Calculate score of nodes based on load by VMs"""
|
||||||
for node_id in self.compute_model.get_all_compute_nodes():
|
for node in self.compute_model.get_all_compute_nodes().values():
|
||||||
node = self.compute_model. \
|
count = self.compute_model.mapping.get_node_instances_from_id(
|
||||||
get_node_from_id(node_id)
|
node.uuid)
|
||||||
count = self.compute_model.get_mapping(). \
|
|
||||||
get_node_instances_from_id(node_id)
|
|
||||||
if len(count) > 0:
|
if len(count) > 0:
|
||||||
result = self.calculate_score_node(node)
|
result = self.calculate_score_node(node)
|
||||||
else:
|
else:
|
||||||
# The node has not VMs
|
# The node has not VMs
|
||||||
result = 0
|
result = 0
|
||||||
if len(count) > 0:
|
if len(count) > 0:
|
||||||
score.append((node_id, result))
|
score.append((node.uuid, result))
|
||||||
return score
|
return score
|
||||||
|
|
||||||
def node_and_instance_score(self, sorted_score, score):
|
def node_and_instance_score(self, sorted_score, score):
|
||||||
@@ -368,7 +364,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def create_migration_instance(self, mig_instance, mig_source_node,
|
def create_migration_instance(self, mig_instance, mig_source_node,
|
||||||
mig_destination_node):
|
mig_destination_node):
|
||||||
"""Create migration VM"""
|
"""Create migration VM"""
|
||||||
if self.compute_model.get_mapping().migrate_instance(
|
if self.compute_model.migrate_instance(
|
||||||
mig_instance, mig_source_node, mig_destination_node):
|
mig_instance, mig_source_node, mig_destination_node):
|
||||||
self.add_migration(mig_instance.uuid, 'live',
|
self.add_migration(mig_instance.uuid, 'live',
|
||||||
mig_source_node.uuid,
|
mig_source_node.uuid,
|
||||||
@@ -427,14 +423,14 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
|
|
||||||
self.compute_attempts(size_cluster)
|
self.compute_attempts(size_cluster)
|
||||||
|
|
||||||
for node_id in self.compute_model.get_all_compute_nodes():
|
for node_uuid, node in self.compute_model.get_all_compute_nodes(
|
||||||
node = self.compute_model.get_node_from_id(node_id)
|
).items():
|
||||||
count = self.compute_model.get_mapping(). \
|
node_instances = (self.compute_model.mapping
|
||||||
get_node_instances_from_id(node_id)
|
.get_node_instances_from_id(node_uuid))
|
||||||
if len(count) == 0:
|
if node_instances:
|
||||||
if node.state == element.ServiceState.ENABLED:
|
if node.state == element.ServiceState.ENABLED:
|
||||||
self.add_change_service_state(
|
self.add_change_service_state(
|
||||||
node_id, element.ServiceState.DISABLED.value)
|
node_uuid, element.ServiceState.DISABLED.value)
|
||||||
|
|
||||||
while self.get_allowed_migration_attempts() >= unsuccessful_migration:
|
while self.get_allowed_migration_attempts() >= unsuccessful_migration:
|
||||||
if not first_migration:
|
if not first_migration:
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
|||||||
# always use the host with lowerest outlet temperature
|
# always use the host with lowerest outlet temperature
|
||||||
mig_destination_node = dest_servers[0]['node']
|
mig_destination_node = dest_servers[0]['node']
|
||||||
# generate solution to migrate the instance to the dest server,
|
# generate solution to migrate the instance to the dest server,
|
||||||
if self.compute_model.mapping.migrate_instance(
|
if self.compute_model.migrate_instance(
|
||||||
instance_src, mig_source_node, mig_destination_node):
|
instance_src, mig_source_node, mig_destination_node):
|
||||||
parameters = {'migration_type': 'live',
|
parameters = {'migration_type': 'live',
|
||||||
'source_node': mig_source_node.uuid,
|
'source_node': mig_source_node.uuid,
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ class UniformAirflow(base.BaseStrategy):
|
|||||||
for info in destination_hosts:
|
for info in destination_hosts:
|
||||||
instance = info['instance']
|
instance = info['instance']
|
||||||
destination_node = info['node']
|
destination_node = info['node']
|
||||||
if self.compute_model.mapping.migrate_instance(
|
if self.compute_model.migrate_instance(
|
||||||
instance, source_node, destination_node):
|
instance, source_node, destination_node):
|
||||||
parameters = {'migration_type': 'live',
|
parameters = {'migration_type': 'live',
|
||||||
'source_node': source_node.uuid,
|
'source_node': source_node.uuid,
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
|
|||||||
# always use the host with lowerest CPU utilization
|
# always use the host with lowerest CPU utilization
|
||||||
mig_destination_node = destination_hosts[0]['node']
|
mig_destination_node = destination_hosts[0]['node']
|
||||||
# generate solution to migrate the instance to the dest server,
|
# generate solution to migrate the instance to the dest server,
|
||||||
if self.compute_model.mapping.migrate_instance(
|
if self.compute_model.migrate_instance(
|
||||||
instance_src, source_node, mig_destination_node):
|
instance_src, source_node, mig_destination_node):
|
||||||
parameters = {'migration_type': 'live',
|
parameters = {'migration_type': 'live',
|
||||||
'source_node': source_node.uuid,
|
'source_node': source_node.uuid,
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
def create_migration_instance(self, mig_instance, mig_source_node,
|
def create_migration_instance(self, mig_instance, mig_source_node,
|
||||||
mig_destination_node):
|
mig_destination_node):
|
||||||
"""Create migration VM """
|
"""Create migration VM """
|
||||||
if self.compute_model.get_mapping().migrate_instance(
|
if self.compute_model.migrate_instance(
|
||||||
mig_instance, mig_source_node, mig_destination_node):
|
mig_instance, mig_source_node, mig_destination_node):
|
||||||
self.add_migration(mig_instance.uuid, 'live',
|
self.add_migration(mig_instance.uuid, 'live',
|
||||||
mig_source_node.uuid,
|
mig_source_node.uuid,
|
||||||
|
|||||||
@@ -32,18 +32,17 @@ class TestDefaultActionPlanHandler(base.DbTestCase):
|
|||||||
self.context)
|
self.context)
|
||||||
|
|
||||||
def test_launch_action_plan(self):
|
def test_launch_action_plan(self):
|
||||||
command = default.DefaultActionPlanHandler(self.context,
|
command = default.DefaultActionPlanHandler(
|
||||||
mock.MagicMock(),
|
self.context, mock.MagicMock(), self.action_plan.uuid)
|
||||||
self.action_plan.uuid)
|
|
||||||
command.execute()
|
command.execute()
|
||||||
action_plan = ap_objects.ActionPlan.get_by_uuid(self.context,
|
action_plan = ap_objects.ActionPlan.get_by_uuid(
|
||||||
self.action_plan.uuid)
|
self.context, self.action_plan.uuid)
|
||||||
self.assertEqual(ap_objects.State.SUCCEEDED, action_plan.state)
|
self.assertEqual(ap_objects.State.SUCCEEDED, action_plan.state)
|
||||||
|
|
||||||
def test_trigger_audit_send_notification(self):
|
def test_trigger_audit_send_notification(self):
|
||||||
messaging = mock.MagicMock()
|
messaging = mock.MagicMock()
|
||||||
command = default.DefaultActionPlanHandler(self.context, messaging,
|
command = default.DefaultActionPlanHandler(
|
||||||
self.action_plan.uuid)
|
self.context, messaging, self.action_plan.uuid)
|
||||||
command.execute()
|
command.execute()
|
||||||
|
|
||||||
call_on_going = mock.call(ev.EventTypes.LAUNCH_ACTION_PLAN.name, {
|
call_on_going = mock.call(ev.EventTypes.LAUNCH_ACTION_PLAN.name, {
|
||||||
@@ -54,6 +53,5 @@ class TestDefaultActionPlanHandler(base.DbTestCase):
|
|||||||
'action_plan__uuid': self.action_plan.uuid})
|
'action_plan__uuid': self.action_plan.uuid})
|
||||||
|
|
||||||
calls = [call_on_going, call_succeeded]
|
calls = [call_on_going, call_succeeded]
|
||||||
messaging.status_topic_handler.publish_event.assert_has_calls(calls)
|
messaging.publish_status_event.assert_has_calls(calls)
|
||||||
self.assertEqual(
|
self.assertEqual(2, messaging.publish_status_event.call_count)
|
||||||
2, messaging.status_topic_handler.publish_event.call_count)
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
from mock import patch
|
import mock
|
||||||
|
|
||||||
from watcher.applier import manager as applier_manager
|
from watcher.applier import manager as applier_manager
|
||||||
from watcher.common.messaging import messaging_handler
|
from watcher.common.messaging import messaging_handler
|
||||||
@@ -30,10 +30,10 @@ class TestApplierManager(base.TestCase):
|
|||||||
super(TestApplierManager, self).setUp()
|
super(TestApplierManager, self).setUp()
|
||||||
self.applier = service.Service(applier_manager.ApplierManager)
|
self.applier = service.Service(applier_manager.ApplierManager)
|
||||||
|
|
||||||
@patch.object(messaging_handler.MessagingHandler, "stop")
|
@mock.patch.object(messaging_handler.MessagingHandler, "stop")
|
||||||
@patch.object(messaging_handler.MessagingHandler, "start")
|
@mock.patch.object(messaging_handler.MessagingHandler, "start")
|
||||||
def test_start(self, m_messaging_start, m_messaging_stop):
|
def test_start(self, m_messaging_start, m_messaging_stop):
|
||||||
self.applier.start()
|
self.applier.start()
|
||||||
self.applier.stop()
|
self.applier.stop()
|
||||||
self.assertEqual(2, m_messaging_start.call_count)
|
self.assertEqual(1, m_messaging_start.call_count)
|
||||||
self.assertEqual(2, m_messaging_stop.call_count)
|
self.assertEqual(1, m_messaging_stop.call_count)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import os
|
|||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
from oslo_messaging import conffixture
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
import pecan
|
import pecan
|
||||||
from pecan import testing
|
from pecan import testing
|
||||||
@@ -52,10 +53,19 @@ class TestCase(BaseTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCase, self).setUp()
|
super(TestCase, self).setUp()
|
||||||
self.useFixture(conf_fixture.ConfReloadFixture())
|
self.useFixture(conf_fixture.ConfReloadFixture())
|
||||||
self.app = testing.load_test_app(os.path.join(
|
self.policy = self.useFixture(policy_fixture.PolicyFixture())
|
||||||
os.path.dirname(__file__),
|
self.messaging_conf = self.useFixture(conffixture.ConfFixture(CONF))
|
||||||
'config.py'
|
self.messaging_conf.transport_driver = 'fake'
|
||||||
))
|
|
||||||
|
cfg.CONF.set_override("auth_type", "admin_token",
|
||||||
|
group='keystone_authtoken',
|
||||||
|
enforce_type=True)
|
||||||
|
cfg.CONF.set_override("auth_uri", "http://127.0.0.1/identity",
|
||||||
|
group='keystone_authtoken',
|
||||||
|
enforce_type=True)
|
||||||
|
|
||||||
|
app_config_path = os.path.join(os.path.dirname(__file__), 'config.py')
|
||||||
|
self.app = testing.load_test_app(app_config_path)
|
||||||
token_info = {
|
token_info = {
|
||||||
'token': {
|
'token': {
|
||||||
'project': {
|
'project': {
|
||||||
@@ -71,8 +81,6 @@ class TestCase(BaseTestCase):
|
|||||||
project_id='fake_project',
|
project_id='fake_project',
|
||||||
user_id='fake_user')
|
user_id='fake_user')
|
||||||
|
|
||||||
self.policy = self.useFixture(policy_fixture.PolicyFixture())
|
|
||||||
|
|
||||||
def make_context(*args, **kwargs):
|
def make_context(*args, **kwargs):
|
||||||
# If context hasn't been constructed with token_info
|
# If context hasn't been constructed with token_info
|
||||||
if not kwargs.get('auth_token_info'):
|
if not kwargs.get('auth_token_info'):
|
||||||
@@ -120,11 +128,8 @@ class TestCase(BaseTestCase):
|
|||||||
:param project_file: File whose path to return. Default: None.
|
:param project_file: File whose path to return. Default: None.
|
||||||
:returns: path to the specified file, or path to project root.
|
:returns: path to the specified file, or path to project root.
|
||||||
"""
|
"""
|
||||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
root = os.path.abspath(
|
||||||
'..',
|
os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||||
'..',
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if project_file:
|
if project_file:
|
||||||
return os.path.join(root, project_file)
|
return os.path.join(root, project_file)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -27,13 +27,15 @@ class DummyManager(object):
|
|||||||
|
|
||||||
API_VERSION = '1.0'
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
conductor_endpoints = []
|
conductor_endpoints = [mock.Mock()]
|
||||||
status_endpoints = []
|
status_endpoints = [mock.Mock()]
|
||||||
|
notification_endpoints = [mock.Mock()]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.publisher_id = "pub_id"
|
self.publisher_id = "pub_id"
|
||||||
self.conductor_topic = "conductor_topic"
|
self.conductor_topic = "conductor_topic"
|
||||||
self.status_topic = "status_topic"
|
self.status_topic = "status_topic"
|
||||||
|
self.notification_topics = []
|
||||||
self.api_version = self.API_VERSION
|
self.api_version = self.API_VERSION
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ class TestService(base.TestCase):
|
|||||||
m_handler.publish_event.assert_called_once_with(event, payload)
|
m_handler.publish_event.assert_called_once_with(event, payload)
|
||||||
|
|
||||||
@mock.patch.object(messaging_handler, "MessagingHandler")
|
@mock.patch.object(messaging_handler, "MessagingHandler")
|
||||||
def test_publish_status(self, m_handler_cls):
|
def test_publish_status_event(self, m_handler_cls):
|
||||||
m_handler = mock.Mock()
|
m_handler = mock.Mock()
|
||||||
m_handler_cls.return_value = m_handler
|
m_handler_cls.return_value = m_handler
|
||||||
payload = {
|
payload = {
|
||||||
@@ -92,10 +94,10 @@ class TestService(base.TestCase):
|
|||||||
}
|
}
|
||||||
event = "myevent"
|
event = "myevent"
|
||||||
dummy_service = service.Service(DummyManager)
|
dummy_service = service.Service(DummyManager)
|
||||||
dummy_service.publish_status(event, payload)
|
dummy_service.publish_status_event(event, payload)
|
||||||
m_handler.publish_event.assert_called_once_with(event, payload, None)
|
m_handler.publish_event.assert_called_once_with(event, payload, None)
|
||||||
|
|
||||||
@mock.patch.object(service.Service, 'publish_status')
|
@mock.patch.object(service.Service, 'publish_status_event')
|
||||||
def test_response(self, mock_call):
|
def test_response(self, mock_call):
|
||||||
event = "My event"
|
event = "My event"
|
||||||
context = {'request_id': 12}
|
context = {'request_id': 12}
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ class TestOneShotAuditHandler(base.DbTestCase):
|
|||||||
'audit_uuid': self.audit.uuid})
|
'audit_uuid': self.audit.uuid})
|
||||||
|
|
||||||
calls = [call_on_going, call_succeeded]
|
calls = [call_on_going, call_succeeded]
|
||||||
messaging.status_topic_handler.publish_event.assert_has_calls(calls)
|
messaging.publish_status_event.assert_has_calls(calls)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
2, messaging.status_topic_handler.publish_event.call_count)
|
2, messaging.publish_status_event.call_count)
|
||||||
|
|
||||||
|
|
||||||
class TestContinuousAuditHandler(base.DbTestCase):
|
class TestContinuousAuditHandler(base.DbTestCase):
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ from watcher.tests import base as test_base
|
|||||||
|
|
||||||
class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
|
class DummyClusterDataModelCollector(base.BaseClusterDataModelCollector):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notification_endpoints(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
model = model_root.ModelRoot()
|
model = model_root.ModelRoot()
|
||||||
# Do something here...
|
# Do something here...
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"event_type": "instance.update",
|
||||||
|
"payload": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"audit_period": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"audit_period_beginning": "2012-10-01T00:00:00Z",
|
||||||
|
"audit_period_ending": "2012-10-29T13:42:11Z"
|
||||||
|
},
|
||||||
|
"nova_object.name": "AuditPeriodPayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"availability_zone": null,
|
||||||
|
"bandwidth": [],
|
||||||
|
"created_at": "2012-10-29T13:42:11Z",
|
||||||
|
"deleted_at": null,
|
||||||
|
"display_name": "some-server",
|
||||||
|
"host": "compute",
|
||||||
|
"host_name": "some-server",
|
||||||
|
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": null,
|
||||||
|
"metadata": {},
|
||||||
|
"node": "fake-mini",
|
||||||
|
"old_display_name": null,
|
||||||
|
"os_type": null,
|
||||||
|
"progress": 0,
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-sd3ygfjj",
|
||||||
|
"state": "active",
|
||||||
|
"task_state": "scheduling",
|
||||||
|
"power_state": "pending",
|
||||||
|
"ip_addresses": [],
|
||||||
|
"state_update": {
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.name": "InstanceStateUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.data": {
|
||||||
|
"old_state": "building",
|
||||||
|
"new_task_state": null,
|
||||||
|
"old_task_state": "spawning",
|
||||||
|
"state": "active"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"terminated_at": null,
|
||||||
|
"flavor": {
|
||||||
|
"nova_object.name": "FlavorPayload",
|
||||||
|
"nova_object.data": {
|
||||||
|
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||||
|
"root_gb": 1,
|
||||||
|
"vcpus": 1,
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"memory_mb": 512
|
||||||
|
},
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.namespace": "nova"
|
||||||
|
},
|
||||||
|
"user_id": "fake",
|
||||||
|
"uuid": "c03c0bf9-f46e-4e4f-93f1-817568567ee2"
|
||||||
|
},
|
||||||
|
"nova_object.name": "InstanceUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"priority": "INFO",
|
||||||
|
"publisher_id": "nova-compute:compute"
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"event_type":"instance.delete.end",
|
||||||
|
"payload":{
|
||||||
|
"nova_object.data":{
|
||||||
|
"architecture":"x86_64",
|
||||||
|
"availability_zone":null,
|
||||||
|
"created_at":"2012-10-29T13:42:11Z",
|
||||||
|
"deleted_at":"2012-10-29T13:42:11Z",
|
||||||
|
"display_name":"some-server",
|
||||||
|
"fault":null,
|
||||||
|
"host":"compute",
|
||||||
|
"host_name":"some-server",
|
||||||
|
"ip_addresses":[],
|
||||||
|
"kernel_id":"",
|
||||||
|
"launched_at":"2012-10-29T13:42:11Z",
|
||||||
|
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||||
|
"metadata":{},
|
||||||
|
"node":"fake-mini",
|
||||||
|
"os_type":null,
|
||||||
|
"progress":0,
|
||||||
|
"ramdisk_id":"",
|
||||||
|
"reservation_id":"r-npxv0e40",
|
||||||
|
"state":"deleted",
|
||||||
|
"task_state":null,
|
||||||
|
"power_state":"pending",
|
||||||
|
"tenant_id":"6f70656e737461636b20342065766572",
|
||||||
|
"terminated_at":"2012-10-29T13:42:11Z",
|
||||||
|
"flavor": {
|
||||||
|
"nova_object.name": "FlavorPayload",
|
||||||
|
"nova_object.data": {
|
||||||
|
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||||
|
"root_gb": 1,
|
||||||
|
"vcpus": 1,
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"memory_mb": 512
|
||||||
|
},
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.namespace": "nova"
|
||||||
|
},
|
||||||
|
"user_id":"fake",
|
||||||
|
"uuid":"178b0921-8f85-4257-88b6-2e743b5a975c"
|
||||||
|
},
|
||||||
|
"nova_object.name":"InstanceActionPayload",
|
||||||
|
"nova_object.namespace":"nova",
|
||||||
|
"nova_object.version":"1.0"
|
||||||
|
},
|
||||||
|
"priority":"INFO",
|
||||||
|
"publisher_id":"nova-compute:compute"
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"event_type": "instance.update",
|
||||||
|
"payload": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"audit_period": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"audit_period_beginning": "2012-10-01T00:00:00Z",
|
||||||
|
"audit_period_ending": "2012-10-29T13:42:11Z"},
|
||||||
|
"nova_object.name": "AuditPeriodPayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"availability_zone": null,
|
||||||
|
"bandwidth": [],
|
||||||
|
"created_at": "2012-10-29T13:42:11Z",
|
||||||
|
"deleted_at": null,
|
||||||
|
"display_name": "some-server",
|
||||||
|
"host": "compute",
|
||||||
|
"host_name": "some-server",
|
||||||
|
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": null,
|
||||||
|
"metadata": {},
|
||||||
|
"node": "fake-mini",
|
||||||
|
"old_display_name": null,
|
||||||
|
"os_type": null,
|
||||||
|
"progress": 0,
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-sd3ygfjj",
|
||||||
|
"state": "active",
|
||||||
|
"task_state": "scheduling",
|
||||||
|
"power_state": "pending",
|
||||||
|
"ip_addresses": [],
|
||||||
|
"state_update": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"new_task_state": null,
|
||||||
|
"old_state": null,
|
||||||
|
"old_task_state": null,
|
||||||
|
"state": "active"},
|
||||||
|
"nova_object.name": "InstanceStateUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"},
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"terminated_at": null,
|
||||||
|
"flavor": {
|
||||||
|
"nova_object.name": "FlavorPayload",
|
||||||
|
"nova_object.data": {
|
||||||
|
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||||
|
"root_gb": 1,
|
||||||
|
"vcpus": 1,
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"memory_mb": 512
|
||||||
|
},
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.namespace": "nova"
|
||||||
|
},
|
||||||
|
"user_id": "fake",
|
||||||
|
"uuid": "c03c0bf9-f46e-4e4f-93f1-817568567ee2"},
|
||||||
|
"nova_object.name": "InstanceUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"},
|
||||||
|
"priority": "INFO",
|
||||||
|
"publisher_id": "nova-compute:compute"
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"event_type": "instance.update",
|
||||||
|
"payload": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"audit_period": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"audit_period_beginning": "2012-10-01T00:00:00Z",
|
||||||
|
"audit_period_ending": "2012-10-29T13:42:11Z"
|
||||||
|
},
|
||||||
|
"nova_object.name": "AuditPeriodPayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"availability_zone": null,
|
||||||
|
"bandwidth": [],
|
||||||
|
"created_at": "2012-10-29T13:42:11Z",
|
||||||
|
"deleted_at": null,
|
||||||
|
"display_name": "some-server",
|
||||||
|
"host": "Node_0",
|
||||||
|
"host_name": "some-server",
|
||||||
|
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": null,
|
||||||
|
"metadata": {},
|
||||||
|
"node": "hostname_0",
|
||||||
|
"old_display_name": null,
|
||||||
|
"os_type": null,
|
||||||
|
"progress": 0,
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-sd3ygfjj",
|
||||||
|
"state": "active",
|
||||||
|
"task_state": "scheduling",
|
||||||
|
"power_state": "pending",
|
||||||
|
"ip_addresses": [],
|
||||||
|
"state_update": {
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.name": "InstanceStateUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.data": {
|
||||||
|
"old_state": "building",
|
||||||
|
"new_task_state": null,
|
||||||
|
"old_task_state": "spawning",
|
||||||
|
"state": "active"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"terminated_at": null,
|
||||||
|
"flavor": {
|
||||||
|
"nova_object.name": "FlavorPayload",
|
||||||
|
"nova_object.data": {
|
||||||
|
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||||
|
"root_gb": 1,
|
||||||
|
"vcpus": 1,
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"memory_mb": 512
|
||||||
|
},
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.namespace": "nova"
|
||||||
|
},
|
||||||
|
"user_id": "fake",
|
||||||
|
"uuid": "c03c0bf9-f46e-4e4f-93f1-817568567ee2"
|
||||||
|
},
|
||||||
|
"nova_object.name": "InstanceUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"priority": "INFO",
|
||||||
|
"publisher_id": "nova-compute:Node_0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"event_type":"instance.delete.end",
|
||||||
|
"payload":{
|
||||||
|
"nova_object.data":{
|
||||||
|
"architecture":"x86_64",
|
||||||
|
"availability_zone":null,
|
||||||
|
"created_at":"2012-10-29T13:42:11Z",
|
||||||
|
"deleted_at":"2012-10-29T13:42:11Z",
|
||||||
|
"display_name":"some-server",
|
||||||
|
"fault":null,
|
||||||
|
"host":"Node_0",
|
||||||
|
"host_name":"some-server",
|
||||||
|
"ip_addresses":[],
|
||||||
|
"kernel_id":"",
|
||||||
|
"launched_at":"2012-10-29T13:42:11Z",
|
||||||
|
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||||
|
"metadata":{},
|
||||||
|
"node":"fake-mini",
|
||||||
|
"os_type":null,
|
||||||
|
"progress":0,
|
||||||
|
"ramdisk_id":"",
|
||||||
|
"reservation_id":"r-npxv0e40",
|
||||||
|
"state":"deleted",
|
||||||
|
"task_state":null,
|
||||||
|
"power_state":"pending",
|
||||||
|
"tenant_id":"6f70656e737461636b20342065766572",
|
||||||
|
"terminated_at":"2012-10-29T13:42:11Z",
|
||||||
|
"flavor": {
|
||||||
|
"nova_object.name": "FlavorPayload",
|
||||||
|
"nova_object.data": {
|
||||||
|
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||||
|
"root_gb": 1,
|
||||||
|
"vcpus": 1,
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"memory_mb": 512
|
||||||
|
},
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.namespace": "nova"
|
||||||
|
},
|
||||||
|
"user_id":"fake",
|
||||||
|
"uuid":"73b09e16-35b7-4922-804e-e8f5d9b740fc"
|
||||||
|
},
|
||||||
|
"nova_object.name":"InstanceActionPayload",
|
||||||
|
"nova_object.namespace":"nova",
|
||||||
|
"nova_object.version":"1.0"
|
||||||
|
},
|
||||||
|
"priority":"INFO",
|
||||||
|
"publisher_id":"nova-compute:Node_0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"event_type": "instance.update",
|
||||||
|
"payload": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"audit_period": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"audit_period_beginning": "2012-10-01T00:00:00Z",
|
||||||
|
"audit_period_ending": "2012-10-29T13:42:11Z"},
|
||||||
|
"nova_object.name": "AuditPeriodPayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"
|
||||||
|
},
|
||||||
|
"availability_zone": null,
|
||||||
|
"bandwidth": [],
|
||||||
|
"created_at": "2012-10-29T13:42:11Z",
|
||||||
|
"deleted_at": null,
|
||||||
|
"display_name": "NEW_INSTANCE0",
|
||||||
|
"host": "Node_0",
|
||||||
|
"host_name": "NEW_INSTANCE0",
|
||||||
|
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": null,
|
||||||
|
"metadata": {},
|
||||||
|
"node": "hostname_0",
|
||||||
|
"old_display_name": null,
|
||||||
|
"os_type": null,
|
||||||
|
"progress": 0,
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-sd3ygfjj",
|
||||||
|
"state": "paused",
|
||||||
|
"task_state": "scheduling",
|
||||||
|
"power_state": "pending",
|
||||||
|
"ip_addresses": [],
|
||||||
|
"state_update": {
|
||||||
|
"nova_object.data": {
|
||||||
|
"old_task_state": null,
|
||||||
|
"new_task_state": null,
|
||||||
|
"old_state": "paused",
|
||||||
|
"state": "paused"},
|
||||||
|
"nova_object.name": "InstanceStateUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"},
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"terminated_at": null,
|
||||||
|
"flavor": {
|
||||||
|
"nova_object.name": "FlavorPayload",
|
||||||
|
"nova_object.data": {
|
||||||
|
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||||
|
"root_gb": 1,
|
||||||
|
"vcpus": 1,
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"memory_mb": 512
|
||||||
|
},
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.namespace": "nova"
|
||||||
|
},
|
||||||
|
"user_id": "fake",
|
||||||
|
"uuid": "73b09e16-35b7-4922-804e-e8f5d9b740fc"},
|
||||||
|
"nova_object.name": "InstanceUpdatePayload",
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.version": "1.0"},
|
||||||
|
"priority": "INFO",
|
||||||
|
"publisher_id": "nova-compute:Node_0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"event_type": "compute.instance.create.end",
|
||||||
|
"metadata": {
|
||||||
|
"message_id": "577bfd11-88e0-4044-b8ae-496e3257efe2",
|
||||||
|
"timestamp": "2016-08-19 10:20:59.279903"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"access_ip_v4": null,
|
||||||
|
"access_ip_v6": null,
|
||||||
|
"architecture": null,
|
||||||
|
"availability_zone": "nova",
|
||||||
|
"cell_name": "",
|
||||||
|
"created_at": "2016-08-19 10:20:49+00:00",
|
||||||
|
"deleted_at": "",
|
||||||
|
"disk_gb": 1,
|
||||||
|
"display_name": "INSTANCE_0",
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"address": "192.168.1.197",
|
||||||
|
"floating_ips": [],
|
||||||
|
"label": "demo-net",
|
||||||
|
"meta": {},
|
||||||
|
"type": "fixed",
|
||||||
|
"version": 4,
|
||||||
|
"vif_mac": "fa:16:3e:a3:c0:0f"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"host": "Node_0",
|
||||||
|
"hostname": "INSTANCE_0",
|
||||||
|
"image_meta": {
|
||||||
|
"base_image_ref": "205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"container_format": "bare",
|
||||||
|
"disk_format": "qcow2",
|
||||||
|
"min_disk": "1",
|
||||||
|
"min_ram": "0"
|
||||||
|
},
|
||||||
|
"image_ref_url": "http://127.0.0.1:9292/images/205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"instance_flavor_id": "1",
|
||||||
|
"instance_id": "c03c0bf9-f46e-4e4f-93f1-817568567ee2",
|
||||||
|
"instance_type": "m1.tiny",
|
||||||
|
"instance_type_id": 2,
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": "2016-08-19T10:20:59.135390",
|
||||||
|
"memory_mb": 512,
|
||||||
|
"message": "Success",
|
||||||
|
"metadata": {},
|
||||||
|
"node": "Node_0",
|
||||||
|
"os_type": null,
|
||||||
|
"progress": "",
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-56edz88e",
|
||||||
|
"root_gb": 1,
|
||||||
|
"state": "active",
|
||||||
|
"state_description": "",
|
||||||
|
"tenant_id": "57ab04ad6d3b495789a58258bc00842b",
|
||||||
|
"terminated_at": "",
|
||||||
|
"user_id": "cd7d93be51e4460ab51514b2a925b23a",
|
||||||
|
"vcpus": 1
|
||||||
|
},
|
||||||
|
"publisher_id": "compute.Node_0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"publisher_id": "compute:compute",
|
||||||
|
"event_type": "compute.instance.delete.end",
|
||||||
|
"payload": {
|
||||||
|
"access_ip_v4": null,
|
||||||
|
"access_ip_v6": null,
|
||||||
|
"architecture": null,
|
||||||
|
"availability_zone": "nova",
|
||||||
|
"cell_name": "",
|
||||||
|
"created_at": "2016-08-17 15:10:12+00:00",
|
||||||
|
"deleted_at": "2016-08-17T15:10:33.000000",
|
||||||
|
"disk_gb": 1,
|
||||||
|
"display_name": "some-server",
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"host": "Node_0",
|
||||||
|
"hostname": "some-server",
|
||||||
|
"image_meta": {
|
||||||
|
"base_image_ref": "205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"container_format": "bare",
|
||||||
|
"disk_format": "qcow2",
|
||||||
|
"min_disk": "1",
|
||||||
|
"min_ram": "0"
|
||||||
|
},
|
||||||
|
"image_ref_url": "http://10.50.254.222:9292/images/205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"instance_flavor_id": "1",
|
||||||
|
"instance_id": "73b09e16-35b7-4922-804e-e8f5d9b740fc",
|
||||||
|
"instance_type": "m1.tiny",
|
||||||
|
"instance_type_id": 2,
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": "2016-08-17T15:10:23.000000",
|
||||||
|
"memory_mb": 512,
|
||||||
|
"metadata": {},
|
||||||
|
"node": "Node_0",
|
||||||
|
"os_type": null,
|
||||||
|
"progress": "",
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-z76fnsyy",
|
||||||
|
"root_gb": 1,
|
||||||
|
"state": "deleted",
|
||||||
|
"state_description": "",
|
||||||
|
"tenant_id": "15995ea2694e4268b3631db32e38678b",
|
||||||
|
"terminated_at": "2016-08-17T15:10:33.008164",
|
||||||
|
"user_id": "cd7d93be51e4460ab51514b2a925b23a",
|
||||||
|
"vcpus": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"publisher_id": "compute:Node_0",
|
||||||
|
"event_type": "compute.instance.update",
|
||||||
|
"payload": {
|
||||||
|
"access_ip_v4": null,
|
||||||
|
"access_ip_v6": null,
|
||||||
|
"architecture": null,
|
||||||
|
"audit_period_beginning": "2016-08-17T13:00:00.000000",
|
||||||
|
"audit_period_ending": "2016-08-17T13:56:05.262440",
|
||||||
|
"availability_zone": "nova",
|
||||||
|
"bandwidth": {},
|
||||||
|
"cell_name": "",
|
||||||
|
"created_at": "2016-08-17 13:53:23+00:00",
|
||||||
|
"deleted_at": "",
|
||||||
|
"disk_gb": 1,
|
||||||
|
"display_name": "NEW_INSTANCE0",
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"host": "Node_0",
|
||||||
|
"hostname": "NEW_INSTANCE0",
|
||||||
|
"image_meta": {
|
||||||
|
"base_image_ref": "205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"container_format": "bare",
|
||||||
|
"disk_format": "qcow2",
|
||||||
|
"min_disk": "1",
|
||||||
|
"min_ram": "0"
|
||||||
|
},
|
||||||
|
"image_ref_url": "http://10.50.0.222:9292/images/205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"instance_flavor_id": "1",
|
||||||
|
"instance_id": "73b09e16-35b7-4922-804e-e8f5d9b740fc",
|
||||||
|
"instance_type": "m1.tiny",
|
||||||
|
"instance_type_id": 2,
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": "2016-08-17T13:53:35.000000",
|
||||||
|
"memory_mb": 512,
|
||||||
|
"metadata": {},
|
||||||
|
"new_task_state": null,
|
||||||
|
"node": "hostname_0",
|
||||||
|
"old_state": "paused",
|
||||||
|
"old_task_state": null,
|
||||||
|
"os_type": null,
|
||||||
|
"progress": "",
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-0822ymml",
|
||||||
|
"root_gb": 1,
|
||||||
|
"state": "paused",
|
||||||
|
"state_description": "paused",
|
||||||
|
"tenant_id": "a4b4772d93c74d5e8b7c68cdd2a014e1",
|
||||||
|
"terminated_at": "",
|
||||||
|
"user_id": "ce64facc93354bbfa90f4f9f9a3e1e75",
|
||||||
|
"vcpus": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"event_type": "compute.instance.live_migration.post.dest.end",
|
||||||
|
"metadata": {
|
||||||
|
"message_id": "9f58cad4-ff90-40f8-a8e4-633807f4a995",
|
||||||
|
"timestamp": "2016-08-19 10:13:44.645575"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"access_ip_v4": null,
|
||||||
|
"access_ip_v6": null,
|
||||||
|
"architecture": null,
|
||||||
|
"availability_zone": "nova",
|
||||||
|
"cell_name": "",
|
||||||
|
"created_at": "2016-08-18 09:49:23+00:00",
|
||||||
|
"deleted_at": "",
|
||||||
|
"disk_gb": 1,
|
||||||
|
"display_name": "INSTANCE_0",
|
||||||
|
"ephemeral_gb": 0,
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"address": "192.168.1.196",
|
||||||
|
"floating_ips": [],
|
||||||
|
"label": "demo-net",
|
||||||
|
"meta": {},
|
||||||
|
"type": "fixed",
|
||||||
|
"version": 4,
|
||||||
|
"vif_mac": "fa:16:3e:cc:ba:81"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"host": "Node_1",
|
||||||
|
"hostname": "INSTANCE_0",
|
||||||
|
"image_meta": {
|
||||||
|
"base_image_ref": "205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"container_format": "bare",
|
||||||
|
"disk_format": "qcow2",
|
||||||
|
"min_disk": "1",
|
||||||
|
"min_ram": "0"
|
||||||
|
},
|
||||||
|
"image_ref_url": "http://10.50.254.222:9292/images/205f96f5-91f9-42eb-9138-03fffcea2b97",
|
||||||
|
"instance_flavor_id": "1",
|
||||||
|
"instance_id": "73b09e16-35b7-4922-804e-e8f5d9b740fc",
|
||||||
|
"instance_type": "m1.tiny",
|
||||||
|
"instance_type_id": 2,
|
||||||
|
"kernel_id": "",
|
||||||
|
"launched_at": "2016-08-18T09:49:33.000000",
|
||||||
|
"memory_mb": 512,
|
||||||
|
"metadata": {},
|
||||||
|
"node": "Node_1",
|
||||||
|
"os_type": null,
|
||||||
|
"progress": "",
|
||||||
|
"ramdisk_id": "",
|
||||||
|
"reservation_id": "r-he04tfco",
|
||||||
|
"root_gb": 1,
|
||||||
|
"state": "active",
|
||||||
|
"state_description": "",
|
||||||
|
"tenant_id": "57ab04ad6d3b495789a58258bc00842b",
|
||||||
|
"terminated_at": "",
|
||||||
|
"user_id": "cd7d93be51e4460ab51514b2a925b23a",
|
||||||
|
"vcpus": 1
|
||||||
|
},
|
||||||
|
"publisher_id": "compute.Node_1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"priority": "INFO",
|
||||||
|
"payload": {
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.name": "ServiceStatusPayload",
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.data": {
|
||||||
|
"host": "Node_0",
|
||||||
|
"disabled": true,
|
||||||
|
"last_seen_up": "2012-10-29T13:42:05Z",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"topic": "compute",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"report_count": 1,
|
||||||
|
"forced_down": true,
|
||||||
|
"version": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event_type": "service.update",
|
||||||
|
"publisher_id": "nova-compute:Node_0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"priority": "INFO",
|
||||||
|
"payload": {
|
||||||
|
"nova_object.namespace": "nova",
|
||||||
|
"nova_object.name": "ServiceStatusPayload",
|
||||||
|
"nova_object.version": "1.0",
|
||||||
|
"nova_object.data": {
|
||||||
|
"host": "host1",
|
||||||
|
"disabled": false,
|
||||||
|
"last_seen_up": "2012-10-29T13:42:05Z",
|
||||||
|
"binary": "nova-compute",
|
||||||
|
"topic": "compute",
|
||||||
|
"disabled_reason": null,
|
||||||
|
"report_count": 1,
|
||||||
|
"forced_down": false,
|
||||||
|
"version": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event_type": "service.update",
|
||||||
|
"publisher_id": "nova-compute:host1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
|
||||||
|
#
|
||||||
|
# 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 watcher.decision_engine.model.notification import nova as novanotification
|
||||||
|
from watcher.tests.decision_engine.strategy.strategies \
|
||||||
|
import faker_cluster_state
|
||||||
|
|
||||||
|
|
||||||
|
class FakeManager(object):
|
||||||
|
|
||||||
|
API_VERSION = '1.0'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.api_version = self.API_VERSION
|
||||||
|
|
||||||
|
# fake cluster instead on Nova CDM
|
||||||
|
self.fake_cdmc = faker_cluster_state.FakerModelCollector()
|
||||||
|
|
||||||
|
self.publisher_id = 'test_publisher_id'
|
||||||
|
self.conductor_topic = 'test_conductor_topic'
|
||||||
|
self.status_topic = 'test_status_topic'
|
||||||
|
self.notification_topics = ['nova']
|
||||||
|
|
||||||
|
self.conductor_endpoints = [] # Disable audit endpoint
|
||||||
|
self.status_endpoints = []
|
||||||
|
|
||||||
|
self.notification_endpoints = [
|
||||||
|
novanotification.ServiceUpdated(self.fake_cdmc),
|
||||||
|
|
||||||
|
novanotification.InstanceCreated(self.fake_cdmc),
|
||||||
|
novanotification.InstanceUpdated(self.fake_cdmc),
|
||||||
|
novanotification.InstanceDeletedEnd(self.fake_cdmc),
|
||||||
|
|
||||||
|
novanotification.LegacyInstanceCreatedEnd(self.fake_cdmc),
|
||||||
|
novanotification.LegacyInstanceUpdated(self.fake_cdmc),
|
||||||
|
novanotification.LegacyLiveMigratedEnd(self.fake_cdmc),
|
||||||
|
novanotification.LegacyInstanceDeletedEnd(self.fake_cdmc),
|
||||||
|
]
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from watcher.common import context
|
||||||
|
from watcher.common import service as watcher_service
|
||||||
|
from watcher.decision_engine.model.notification import base
|
||||||
|
from watcher.decision_engine.model.notification import filtering
|
||||||
|
from watcher.tests import base as base_test
|
||||||
|
from watcher.tests.decision_engine.model.notification import fake_managers
|
||||||
|
|
||||||
|
|
||||||
|
class DummyManager(fake_managers.FakeManager):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DummyManager, self).__init__()
|
||||||
|
self.notification_endpoints = [DummyNotification(self.fake_cdmc)]
|
||||||
|
|
||||||
|
|
||||||
|
class DummyNotification(base.NotificationEndpoint):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_rule(self):
|
||||||
|
return filtering.NotificationFilter(
|
||||||
|
publisher_id=r'.*',
|
||||||
|
event_type=r'compute.dummy',
|
||||||
|
payload={'data': {'nested': r'^T.*'}},
|
||||||
|
)
|
||||||
|
|
||||||
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationTestCase(base_test.TestCase):
|
||||||
|
|
||||||
|
def load_message(self, filename):
|
||||||
|
cwd = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
data_folder = os.path.join(cwd, "data")
|
||||||
|
|
||||||
|
with open(os.path.join(data_folder, filename), 'rb') as json_file:
|
||||||
|
json_data = jsonutils.load(json_file)
|
||||||
|
|
||||||
|
return json_data
|
||||||
|
|
||||||
|
|
||||||
|
class TestReceiveNotifications(NotificationTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestReceiveNotifications, self).setUp()
|
||||||
|
|
||||||
|
p_from_dict = mock.patch.object(context.RequestContext, 'from_dict')
|
||||||
|
m_from_dict = p_from_dict.start()
|
||||||
|
m_from_dict.return_value = self.context
|
||||||
|
self.addCleanup(p_from_dict.stop)
|
||||||
|
|
||||||
|
@mock.patch.object(DummyNotification, 'info')
|
||||||
|
def test_receive_dummy_notification(self, m_info):
|
||||||
|
message = {
|
||||||
|
'publisher_id': 'nova-compute',
|
||||||
|
'event_type': 'compute.dummy',
|
||||||
|
'payload': {'data': {'nested': 'TEST'}},
|
||||||
|
'priority': 'INFO',
|
||||||
|
}
|
||||||
|
de_service = watcher_service.Service(DummyManager)
|
||||||
|
incoming = mock.Mock(ctxt=self.context.to_dict(), message=message)
|
||||||
|
|
||||||
|
de_service.notification_handler.dispatcher.dispatch(incoming)
|
||||||
|
|
||||||
|
m_info.assert_called_once_with(
|
||||||
|
self.context, 'nova-compute', 'compute.dummy',
|
||||||
|
{'data': {'nested': 'TEST'}},
|
||||||
|
{'message_id': None, 'timestamp': None})
|
||||||
|
|
||||||
|
@mock.patch.object(DummyNotification, 'info')
|
||||||
|
def test_skip_unwanted_notification(self, m_info):
|
||||||
|
message = {
|
||||||
|
'publisher_id': 'nova-compute',
|
||||||
|
'event_type': 'compute.dummy',
|
||||||
|
'payload': {'data': {'nested': 'unwanted'}},
|
||||||
|
'priority': 'INFO',
|
||||||
|
}
|
||||||
|
de_service = watcher_service.Service(DummyManager)
|
||||||
|
incoming = mock.Mock(ctxt=self.context.to_dict(), message=message)
|
||||||
|
|
||||||
|
de_service.notification_handler.dispatcher.dispatch(incoming)
|
||||||
|
|
||||||
|
self.assertEqual(0, m_info.call_count)
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# Copyright (c) 2016 b<>com
|
||||||
|
#
|
||||||
|
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from watcher.common import context
|
||||||
|
from watcher.common import exception
|
||||||
|
from watcher.common import service as watcher_service
|
||||||
|
from watcher.decision_engine.model import element
|
||||||
|
from watcher.decision_engine.model import model_root
|
||||||
|
from watcher.decision_engine.model.notification import nova as novanotification
|
||||||
|
from watcher.tests import base as base_test
|
||||||
|
from watcher.tests.decision_engine.model.notification import fake_managers
|
||||||
|
from watcher.tests.decision_engine.strategy.strategies \
|
||||||
|
import faker_cluster_state
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationTestCase(base_test.TestCase):
|
||||||
|
|
||||||
|
def load_message(self, filename):
|
||||||
|
cwd = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
data_folder = os.path.join(cwd, "data")
|
||||||
|
|
||||||
|
with open(os.path.join(data_folder, filename), 'rb') as json_file:
|
||||||
|
json_data = jsonutils.load(json_file)
|
||||||
|
|
||||||
|
return json_data
|
||||||
|
|
||||||
|
|
||||||
|
class TestReceiveNovaNotifications(NotificationTestCase):
|
||||||
|
|
||||||
|
FAKE_METADATA = {'message_id': None, 'timestamp': None}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestReceiveNovaNotifications, self).setUp()
|
||||||
|
|
||||||
|
p_from_dict = mock.patch.object(context.RequestContext, 'from_dict')
|
||||||
|
m_from_dict = p_from_dict.start()
|
||||||
|
m_from_dict.return_value = self.context
|
||||||
|
self.addCleanup(p_from_dict.stop)
|
||||||
|
|
||||||
|
@mock.patch.object(novanotification.ServiceUpdated, 'info')
|
||||||
|
def test_nova_receive_service_update(self, m_info):
|
||||||
|
message = self.load_message('service-update.json')
|
||||||
|
expected_message = message['payload']
|
||||||
|
|
||||||
|
de_service = watcher_service.Service(fake_managers.FakeManager)
|
||||||
|
incoming = mock.Mock(ctxt=self.context.to_dict(), message=message)
|
||||||
|
|
||||||
|
de_service.notification_handler.dispatcher.dispatch(incoming)
|
||||||
|
m_info.assert_called_once_with(
|
||||||
|
self.context, 'nova-compute:host1', 'service.update',
|
||||||
|
expected_message, self.FAKE_METADATA)
|
||||||
|
|
||||||
|
@mock.patch.object(novanotification.InstanceCreated, 'info')
|
||||||
|
def test_nova_receive_instance_create(self, m_info):
|
||||||
|
message = self.load_message('instance-create.json')
|
||||||
|
expected_message = message['payload']
|
||||||
|
|
||||||
|
de_service = watcher_service.Service(fake_managers.FakeManager)
|
||||||
|
incoming = mock.Mock(ctxt=self.context.to_dict(), message=message)
|
||||||
|
|
||||||
|
de_service.notification_handler.dispatcher.dispatch(incoming)
|
||||||
|
m_info.assert_called_once_with(
|
||||||
|
self.context, 'nova-compute:compute', 'instance.update',
|
||||||
|
expected_message, self.FAKE_METADATA)
|
||||||
|
|
||||||
|
@mock.patch.object(novanotification.InstanceUpdated, 'info')
|
||||||
|
def test_nova_receive_instance_update(self, m_info):
|
||||||
|
message = self.load_message('instance-update.json')
|
||||||
|
expected_message = message['payload']
|
||||||
|
|
||||||
|
de_service = watcher_service.Service(fake_managers.FakeManager)
|
||||||
|
incoming = mock.Mock(ctxt=self.context.to_dict(), message=message)
|
||||||
|
|
||||||
|
de_service.notification_handler.dispatcher.dispatch(incoming)
|
||||||
|
m_info.assert_called_once_with(
|
||||||
|
self.context, 'nova-compute:compute', 'instance.update',
|
||||||
|
expected_message, self.FAKE_METADATA)
|
||||||
|
|
||||||
|
@mock.patch.object(novanotification.InstanceDeletedEnd, 'info')
|
||||||
|
def test_nova_receive_instance_delete_end(self, m_info):
|
||||||
|
message = self.load_message('instance-delete-end.json')
|
||||||
|
expected_message = message['payload']
|
||||||
|
|
||||||
|
de_service = watcher_service.Service(fake_managers.FakeManager)
|
||||||
|
incoming = mock.Mock(ctxt=self.context.to_dict(), message=message)
|
||||||
|
|
||||||
|
de_service.notification_handler.dispatcher.dispatch(incoming)
|
||||||
|
m_info.assert_called_once_with(
|
||||||
|
self.context, 'nova-compute:compute', 'instance.delete.end',
|
||||||
|
expected_message, self.FAKE_METADATA)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNovaNotifications(NotificationTestCase):
|
||||||
|
|
||||||
|
FAKE_METADATA = {'message_id': None, 'timestamp': None}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNovaNotifications, self).setUp()
|
||||||
|
# fake cluster
|
||||||
|
self.fake_cdmc = faker_cluster_state.FakerModelCollector()
|
||||||
|
|
||||||
|
def test_nova_service_update(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.ServiceUpdated(self.fake_cdmc)
|
||||||
|
|
||||||
|
node0_uuid = 'Node_0'
|
||||||
|
node0 = compute_model.get_node_from_id(node0_uuid)
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_service-update.json')
|
||||||
|
|
||||||
|
self.assertEqual('hostname_0', node0.hostname)
|
||||||
|
self.assertEqual(element.ServiceState.ONLINE.value, node0.state)
|
||||||
|
self.assertEqual(element.ServiceState.ENABLED.value, node0.status)
|
||||||
|
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual('Node_0', node0.hostname)
|
||||||
|
self.assertEqual(element.ServiceState.OFFLINE.value, node0.state)
|
||||||
|
self.assertEqual(element.ServiceState.DISABLED.value, node0.status)
|
||||||
|
|
||||||
|
def test_nova_instance_update(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.InstanceUpdated(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_instance-update.json')
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.ACTIVE.value, instance0.state)
|
||||||
|
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.PAUSED.value, instance0.state)
|
||||||
|
|
||||||
|
def test_nova_instance_update_notfound_creates(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.InstanceUpdated(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_instance-update.json')
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
model_root.ModelRoot, 'get_instance_from_id'
|
||||||
|
) as m_get_instance_from_id:
|
||||||
|
m_get_instance_from_id.side_effect = exception.InstanceNotFound(
|
||||||
|
name='TEST')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
cpu_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.cpu_cores)
|
||||||
|
disk_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.disk)
|
||||||
|
memory_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.memory)
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.PAUSED.value, instance0.state)
|
||||||
|
self.assertEqual(1, cpu_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(1, disk_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(512, memory_capacity.get_capacity(instance0))
|
||||||
|
|
||||||
|
def test_nova_instance_create(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.InstanceCreated(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = 'c03c0bf9-f46e-4e4f-93f1-817568567ee2'
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InstanceNotFound,
|
||||||
|
compute_model.get_instance_from_id, instance0_uuid)
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_instance-create.json')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
cpu_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.cpu_cores)
|
||||||
|
disk_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.disk)
|
||||||
|
memory_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.memory)
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.ACTIVE.value, instance0.state)
|
||||||
|
self.assertEqual(1, cpu_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(1, disk_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(512, memory_capacity.get_capacity(instance0))
|
||||||
|
|
||||||
|
def test_nova_instance_delete_end(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.InstanceDeletedEnd(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
|
||||||
|
# Before
|
||||||
|
self.assertTrue(compute_model.get_instance_from_id(instance0_uuid))
|
||||||
|
for resource in compute_model.resource.values():
|
||||||
|
self.assertIn(instance0_uuid, resource.mapping)
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_instance-delete-end.json')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
# After
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InstanceNotFound,
|
||||||
|
compute_model.get_instance_from_id, instance0_uuid)
|
||||||
|
|
||||||
|
for resource in compute_model.resource.values():
|
||||||
|
self.assertNotIn(instance0_uuid, resource.mapping)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLegacyNovaNotifications(NotificationTestCase):
|
||||||
|
|
||||||
|
FAKE_METADATA = {'message_id': None, 'timestamp': None}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestLegacyNovaNotifications, self).setUp()
|
||||||
|
# fake cluster
|
||||||
|
self.fake_cdmc = faker_cluster_state.FakerModelCollector()
|
||||||
|
|
||||||
|
def test_legacy_instance_created_end(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.LegacyInstanceCreatedEnd(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = 'c03c0bf9-f46e-4e4f-93f1-817568567ee2'
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InstanceNotFound,
|
||||||
|
compute_model.get_instance_from_id, instance0_uuid)
|
||||||
|
|
||||||
|
message = self.load_message(
|
||||||
|
'scenario3_legacy_instance-create-end.json')
|
||||||
|
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
cpu_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.cpu_cores)
|
||||||
|
disk_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.disk)
|
||||||
|
memory_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.memory)
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.ACTIVE.value, instance0.state)
|
||||||
|
self.assertEqual(1, cpu_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(1, disk_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(512, memory_capacity.get_capacity(instance0))
|
||||||
|
|
||||||
|
def test_legacy_instance_updated(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.LegacyInstanceUpdated(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_legacy_instance-update.json')
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.ACTIVE.value, instance0.state)
|
||||||
|
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.PAUSED.value, instance0.state)
|
||||||
|
|
||||||
|
def test_legacy_instance_update_notfound_creates(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.LegacyInstanceUpdated(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_legacy_instance-update.json')
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
model_root.ModelRoot, 'get_instance_from_id'
|
||||||
|
) as m_get_instance_from_id:
|
||||||
|
m_get_instance_from_id.side_effect = exception.InstanceNotFound(
|
||||||
|
name='TEST')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
self.assertEqual(element.InstanceState.PAUSED.value, instance0.state)
|
||||||
|
|
||||||
|
def test_legacy_instance_update_node_notfound_stil_creates(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.LegacyInstanceUpdated(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
|
||||||
|
message = self.load_message('scenario3_legacy_instance-update.json')
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
model_root.ModelRoot, 'get_instance_from_id'
|
||||||
|
) as m_get_instance_from_id:
|
||||||
|
m_get_instance_from_id.side_effect = exception.InstanceNotFound(
|
||||||
|
name='TEST')
|
||||||
|
with mock.patch.object(
|
||||||
|
model_root.ModelRoot, 'get_node_from_id'
|
||||||
|
) as m_get_node_from_id:
|
||||||
|
m_get_node_from_id.side_effect = exception.ComputeNodeNotFound(
|
||||||
|
name='TEST')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
cpu_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.cpu_cores)
|
||||||
|
disk_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.disk)
|
||||||
|
memory_capacity = compute_model.get_resource_from_id(
|
||||||
|
element.ResourceType.memory)
|
||||||
|
|
||||||
|
self.assertEqual(element.InstanceState.PAUSED.value, instance0.state)
|
||||||
|
self.assertEqual(1, cpu_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(1, disk_capacity.get_capacity(instance0))
|
||||||
|
self.assertEqual(512, memory_capacity.get_capacity(instance0))
|
||||||
|
|
||||||
|
def test_legacy_live_migrated_end(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.LegacyLiveMigratedEnd(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
instance0 = compute_model.get_instance_from_id(instance0_uuid)
|
||||||
|
|
||||||
|
node = compute_model.get_node_from_instance_id(instance0_uuid)
|
||||||
|
self.assertEqual('Node_0', node.uuid)
|
||||||
|
|
||||||
|
message = self.load_message(
|
||||||
|
'scenario3_legacy_livemigration-post-dest-end.json')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
node = compute_model.get_node_from_instance_id(instance0_uuid)
|
||||||
|
self.assertEqual('Node_1', node.uuid)
|
||||||
|
self.assertEqual(element.InstanceState.ACTIVE.value, instance0.state)
|
||||||
|
|
||||||
|
def test_legacy_instance_deleted_end(self):
|
||||||
|
compute_model = self.fake_cdmc.generate_scenario_3_with_2_nodes()
|
||||||
|
self.fake_cdmc.cluster_data_model = compute_model
|
||||||
|
handler = novanotification.LegacyInstanceDeletedEnd(self.fake_cdmc)
|
||||||
|
|
||||||
|
instance0_uuid = '73b09e16-35b7-4922-804e-e8f5d9b740fc'
|
||||||
|
|
||||||
|
# Before
|
||||||
|
self.assertTrue(compute_model.get_instance_from_id(instance0_uuid))
|
||||||
|
for resource in compute_model.resource.values():
|
||||||
|
self.assertIn(instance0_uuid, resource.mapping)
|
||||||
|
|
||||||
|
message = self.load_message(
|
||||||
|
'scenario3_legacy_instance-delete-end.json')
|
||||||
|
handler.info(
|
||||||
|
ctxt=self.context,
|
||||||
|
publisher_id=message['publisher_id'],
|
||||||
|
event_type=message['event_type'],
|
||||||
|
payload=message['payload'],
|
||||||
|
metadata=self.FAKE_METADATA,
|
||||||
|
)
|
||||||
|
|
||||||
|
# After
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InstanceNotFound,
|
||||||
|
compute_model.get_instance_from_id, instance0_uuid)
|
||||||
|
|
||||||
|
for resource in compute_model.resource.values():
|
||||||
|
self.assertNotIn(instance0_uuid, resource.mapping)
|
||||||
@@ -80,16 +80,16 @@ class TestMapping(base.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
False,
|
False,
|
||||||
model.mapping.migrate_instance(instance1, node1, node1))
|
model.migrate_instance(instance1, node1, node1))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
False,
|
False,
|
||||||
model.mapping.migrate_instance(instance1, node0, node0))
|
model.migrate_instance(instance1, node0, node0))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
True,
|
True,
|
||||||
model.mapping.migrate_instance(instance1, node1, node0))
|
model.migrate_instance(instance1, node1, node0))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
True,
|
True,
|
||||||
model.mapping.migrate_instance(instance1, node0, node1))
|
model.migrate_instance(instance1, node0, node1))
|
||||||
|
|
||||||
def test_unmap_from_id_log_warning(self):
|
def test_unmap_from_id_log_warning(self):
|
||||||
model = self.fake_cluster.generate_scenario_3_with_2_nodes()
|
model = self.fake_cluster.generate_scenario_3_with_2_nodes()
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ class TestModel(base.TestCase):
|
|||||||
node.uuid = id_
|
node.uuid = id_
|
||||||
model.add_node(node)
|
model.add_node(node)
|
||||||
|
|
||||||
self.assertIsInstance(node.state, element.ServiceState)
|
self.assertIn(node.state, [el.value for el in element.ServiceState])
|
||||||
|
|
||||||
node = model.get_node_from_id(id_)
|
node = model.get_node_from_id(id_)
|
||||||
node.state = element.ServiceState.OFFLINE
|
node.state = element.ServiceState.OFFLINE.value
|
||||||
self.assertIsInstance(node.state, element.ServiceState)
|
self.assertIn(node.state, [el.value for el in element.ServiceState])
|
||||||
|
|
||||||
def test_node_from_id_raise(self):
|
def test_node_from_id_raise(self):
|
||||||
model = model_root.ModelRoot()
|
model = model_root.ModelRoot()
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ class FakerModelCollector(base.BaseClusterDataModelCollector):
|
|||||||
config = mock.Mock()
|
config = mock.Mock()
|
||||||
super(FakerModelCollector, self).__init__(config)
|
super(FakerModelCollector, self).__init__(config)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notification_endpoints(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
return self.generate_scenario_1()
|
return self.generate_scenario_1()
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ class FakerModelCollector(base.BaseClusterDataModelCollector):
|
|||||||
config = mock.Mock(period=777)
|
config = mock.Mock(period=777)
|
||||||
super(FakerModelCollector, self).__init__(config)
|
super(FakerModelCollector, self).__init__(config)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notification_endpoints(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
return self.generate_scenario_1()
|
return self._cluster_data_model or self.generate_scenario_1()
|
||||||
|
|
||||||
def generate_scenario_1(self):
|
def generate_scenario_1(self):
|
||||||
instances = []
|
instances = []
|
||||||
|
|||||||
@@ -95,10 +95,12 @@ class TestUniformAirflow(base.TestCase):
|
|||||||
self.strategy.threshold_inlet_t = 22
|
self.strategy.threshold_inlet_t = 22
|
||||||
n1, n2 = self.strategy.group_hosts_by_airflow()
|
n1, n2 = self.strategy.group_hosts_by_airflow()
|
||||||
instance_to_mig = self.strategy.choose_instance_to_migrate(n1)
|
instance_to_mig = self.strategy.choose_instance_to_migrate(n1)
|
||||||
|
|
||||||
self.assertEqual(instance_to_mig[0].uuid, 'Node_0')
|
self.assertEqual(instance_to_mig[0].uuid, 'Node_0')
|
||||||
self.assertEqual(len(instance_to_mig[1]), 1)
|
self.assertEqual(len(instance_to_mig[1]), 1)
|
||||||
self.assertEqual(instance_to_mig[1][0].uuid,
|
self.assertIn(instance_to_mig[1][0].uuid,
|
||||||
"cae81432-1631-4d4e-b29c-6f3acdcde906")
|
{'cae81432-1631-4d4e-b29c-6f3acdcde906',
|
||||||
|
'73b09e16-35b7-4922-804e-e8f5d9b740fc'})
|
||||||
|
|
||||||
def test_choose_instance_to_migrate_all(self):
|
def test_choose_instance_to_migrate_all(self):
|
||||||
model = self.fake_cluster.generate_scenario_7_with_2_nodes()
|
model = self.fake_cluster.generate_scenario_7_with_2_nodes()
|
||||||
@@ -107,10 +109,12 @@ class TestUniformAirflow(base.TestCase):
|
|||||||
self.strategy.threshold_inlet_t = 25
|
self.strategy.threshold_inlet_t = 25
|
||||||
n1, n2 = self.strategy.group_hosts_by_airflow()
|
n1, n2 = self.strategy.group_hosts_by_airflow()
|
||||||
instance_to_mig = self.strategy.choose_instance_to_migrate(n1)
|
instance_to_mig = self.strategy.choose_instance_to_migrate(n1)
|
||||||
|
|
||||||
self.assertEqual(instance_to_mig[0].uuid, 'Node_0')
|
self.assertEqual(instance_to_mig[0].uuid, 'Node_0')
|
||||||
self.assertEqual(len(instance_to_mig[1]), 2)
|
self.assertEqual(len(instance_to_mig[1]), 2)
|
||||||
self.assertEqual(instance_to_mig[1][1].uuid,
|
self.assertEqual({'cae81432-1631-4d4e-b29c-6f3acdcde906',
|
||||||
"73b09e16-35b7-4922-804e-e8f5d9b740fc")
|
'73b09e16-35b7-4922-804e-e8f5d9b740fc'},
|
||||||
|
{inst.uuid for inst in instance_to_mig[1]})
|
||||||
|
|
||||||
def test_choose_instance_notfound(self):
|
def test_choose_instance_notfound(self):
|
||||||
model = self.fake_cluster.generate_scenario_7_with_2_nodes()
|
model = self.fake_cluster.generate_scenario_7_with_2_nodes()
|
||||||
@@ -132,10 +136,12 @@ class TestUniformAirflow(base.TestCase):
|
|||||||
instance_to_mig = self.strategy.choose_instance_to_migrate(n1)
|
instance_to_mig = self.strategy.choose_instance_to_migrate(n1)
|
||||||
dest_hosts = self.strategy.filter_destination_hosts(
|
dest_hosts = self.strategy.filter_destination_hosts(
|
||||||
n2, instance_to_mig[1])
|
n2, instance_to_mig[1])
|
||||||
|
|
||||||
self.assertEqual(len(dest_hosts), 1)
|
self.assertEqual(len(dest_hosts), 1)
|
||||||
self.assertEqual(dest_hosts[0]['node'].uuid, 'Node_1')
|
self.assertEqual(dest_hosts[0]['node'].uuid, 'Node_1')
|
||||||
self.assertEqual(dest_hosts[0]['instance'].uuid,
|
self.assertIn(instance_to_mig[1][0].uuid,
|
||||||
'cae81432-1631-4d4e-b29c-6f3acdcde906')
|
{'cae81432-1631-4d4e-b29c-6f3acdcde906',
|
||||||
|
'73b09e16-35b7-4922-804e-e8f5d9b740fc'})
|
||||||
|
|
||||||
def test_exception_model(self):
|
def test_exception_model(self):
|
||||||
self.m_model.return_value = None
|
self.m_model.return_value = None
|
||||||
|
|||||||
Reference in New Issue
Block a user