377 lines
15 KiB
Python
377 lines
15 KiB
Python
# Copyright 2016 NTT DATA
|
|
#
|
|
# 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 datetime
|
|
import traceback
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import strutils
|
|
from oslo_utils import uuidutils
|
|
|
|
from masakari.api import utils as api_utils
|
|
from masakari.compute import nova
|
|
import masakari.conf
|
|
from masakari.engine import rpcapi as engine_rpcapi
|
|
from masakari import exception
|
|
from masakari.i18n import _
|
|
from masakari import objects
|
|
from masakari.objects import fields
|
|
|
|
|
|
CONF = masakari.conf.CONF
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def is_failover_segment_under_recovery(segment):
|
|
filters = {
|
|
'status': [fields.NotificationStatus.NEW,
|
|
fields.NotificationStatus.RUNNING,
|
|
fields.NotificationStatus.ERROR]
|
|
}
|
|
|
|
return segment.is_under_recovery(filters=filters)
|
|
|
|
|
|
class FailoverSegmentAPI(object):
|
|
|
|
def get_segment(self, context, segment_uuid):
|
|
"""Get a single failover segment with the given segment_uuid."""
|
|
if uuidutils.is_uuid_like(segment_uuid):
|
|
LOG.debug("Fetching failover segment by "
|
|
"UUID", segment_uuid=segment_uuid)
|
|
|
|
segment = objects.FailoverSegment.get_by_uuid(context, segment_uuid
|
|
)
|
|
else:
|
|
LOG.debug("Failed to fetch failover "
|
|
"segment by uuid %s", segment_uuid)
|
|
raise exception.FailoverSegmentNotFound(id=segment_uuid)
|
|
|
|
return segment
|
|
|
|
def get_all(self, context, filters=None, sort_keys=None,
|
|
sort_dirs=None, limit=None, marker=None):
|
|
"""Get all failover segments filtered by one of the given parameters.
|
|
|
|
If there is no filter it will retrieve all segments in the system.
|
|
|
|
The results will be sorted based on the list of sort keys in the
|
|
'sort_keys' parameter (first value is primary sort key, second value is
|
|
secondary sort ket, etc.). For each sort key, the associated sort
|
|
direction is based on the list of sort directions in the 'sort_dirs'
|
|
parameter.
|
|
"""
|
|
|
|
LOG.debug("Searching by: %s", str(filters))
|
|
|
|
limited_segments = (objects.FailoverSegmentList.
|
|
get_all(context, filters=filters,
|
|
sort_keys=sort_keys,
|
|
sort_dirs=sort_dirs, limit=limit,
|
|
marker=marker))
|
|
|
|
return limited_segments
|
|
|
|
def create_segment(self, context, segment_data):
|
|
"""Create segment"""
|
|
segment = objects.FailoverSegment(context=context)
|
|
# Populate segment object for create
|
|
segment.name = segment_data.get('name')
|
|
segment.description = segment_data.get('description')
|
|
segment.recovery_method = segment_data.get('recovery_method')
|
|
segment.service_type = segment_data.get('service_type')
|
|
|
|
try:
|
|
segment.create()
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_segment_api(context, segment,
|
|
action=fields.EventNotificationAction.SEGMENT_CREATE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
return segment
|
|
|
|
def update_segment(self, context, uuid, segment_data):
|
|
"""Update the properties of a failover segment."""
|
|
segment = objects.FailoverSegment.get_by_uuid(context, uuid)
|
|
if is_failover_segment_under_recovery(segment):
|
|
msg = _("Failover segment %s can't be updated as "
|
|
"it is in-use to process notifications.") % uuid
|
|
LOG.error(msg)
|
|
raise exception.FailoverSegmentInUse(msg)
|
|
|
|
try:
|
|
segment.update(segment_data)
|
|
segment.save()
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_segment_api(context, segment,
|
|
action=fields.EventNotificationAction.SEGMENT_UPDATE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
return segment
|
|
|
|
def delete_segment(self, context, uuid):
|
|
"""Deletes the segment."""
|
|
segment = objects.FailoverSegment.get_by_uuid(context, uuid)
|
|
if is_failover_segment_under_recovery(segment):
|
|
msg = _("Failover segment (%s) can't be deleted as "
|
|
"it is in-use to process notifications.") % uuid
|
|
LOG.error(msg)
|
|
raise exception.FailoverSegmentInUse(msg)
|
|
|
|
try:
|
|
segment.destroy()
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_segment_api(context, segment,
|
|
action=fields.EventNotificationAction.SEGMENT_DELETE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
|
|
|
|
class HostAPI(object):
|
|
"""The Host API to manage hosts"""
|
|
|
|
def _is_valid_host_name(self, context, name):
|
|
novaclient = nova.API()
|
|
novaclient.hypervisor_search(context, name)
|
|
|
|
def get_host(self, context, segment_uuid, host_uuid):
|
|
"""Get a host by id"""
|
|
objects.FailoverSegment.get_by_uuid(context, segment_uuid)
|
|
if uuidutils.is_uuid_like(host_uuid):
|
|
LOG.debug("Fetching host by "
|
|
"UUID", host_uuid=host_uuid)
|
|
|
|
host = objects.Host.get_by_uuid(context, host_uuid)
|
|
else:
|
|
LOG.debug("Failed to fetch host by uuid %s", host_uuid)
|
|
raise exception.HostNotFound(id=host_uuid)
|
|
|
|
return host
|
|
|
|
def get_all(self, context, filters=None, sort_keys=None,
|
|
sort_dirs=None, limit=None, marker=None):
|
|
"""Get all hosts by filter"""
|
|
|
|
LOG.debug("Searching by: %s", str(filters))
|
|
|
|
limited_hosts = objects.HostList.get_all(context,
|
|
filters=filters,
|
|
sort_keys=sort_keys,
|
|
sort_dirs=sort_dirs,
|
|
limit=limit,
|
|
marker=marker)
|
|
|
|
return limited_hosts
|
|
|
|
def create_host(self, context, segment_uuid, host_data):
|
|
"""Create host"""
|
|
segment = objects.FailoverSegment.get_by_uuid(context, segment_uuid)
|
|
host = objects.Host(context=context)
|
|
|
|
# Populate host object for create
|
|
host.name = host_data.get('name')
|
|
host.failover_segment_id = segment.uuid
|
|
host.type = host_data.get('type')
|
|
host.control_attributes = host_data.get('control_attributes')
|
|
host.on_maintenance = strutils.bool_from_string(
|
|
host_data.get('on_maintenance', False), strict=True)
|
|
host.reserved = strutils.bool_from_string(
|
|
host_data.get('reserved', False), strict=True)
|
|
|
|
self._is_valid_host_name(context, host.name)
|
|
|
|
try:
|
|
host.create()
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_host_api(context, host,
|
|
action=fields.EventNotificationAction.HOST_CREATE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
|
|
return host
|
|
|
|
def update_host(self, context, segment_uuid, id, host_data):
|
|
"""Update the host"""
|
|
segment = objects.FailoverSegment.get_by_uuid(context, segment_uuid)
|
|
|
|
host = objects.Host.get_by_uuid(context, id)
|
|
|
|
if is_failover_segment_under_recovery(segment):
|
|
msg = _("Host %s can't be updated as "
|
|
"it is in-use to process notifications.") % host.uuid
|
|
LOG.error(msg)
|
|
raise exception.HostInUse(msg)
|
|
|
|
if 'name' in host_data:
|
|
self._is_valid_host_name(context, host_data.get('name'))
|
|
|
|
if 'on_maintenance' in host_data:
|
|
host_data['on_maintenance'] = strutils.bool_from_string(
|
|
host_data['on_maintenance'], strict=True)
|
|
if 'reserved' in host_data:
|
|
host_data['reserved'] = strutils.bool_from_string(
|
|
host_data['reserved'], strict=True)
|
|
|
|
try:
|
|
host.update(host_data)
|
|
host.save()
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_host_api(context, host,
|
|
action=fields.EventNotificationAction.HOST_UPDATE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
return host
|
|
|
|
def delete_host(self, context, segment_uuid, id):
|
|
"""Delete the host"""
|
|
|
|
segment = objects.FailoverSegment.get_by_uuid(context, segment_uuid)
|
|
host = objects.Host.get_by_uuid(context, id, segment_uuid=segment_uuid)
|
|
if is_failover_segment_under_recovery(segment):
|
|
msg = _("Host %s can't be deleted as "
|
|
"it is in-use to process notifications.") % host.uuid
|
|
LOG.error(msg)
|
|
raise exception.HostInUse(msg)
|
|
|
|
try:
|
|
host.destroy()
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_host_api(context, host,
|
|
action=fields.EventNotificationAction.HOST_DELETE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
|
|
|
|
class NotificationAPI(object):
|
|
|
|
def __init__(self):
|
|
self.engine_rpcapi = engine_rpcapi.EngineAPI()
|
|
|
|
@staticmethod
|
|
def _is_duplicate_notification(context, notification):
|
|
# Get all the notifications by filters
|
|
|
|
filters = {
|
|
'type': notification.type,
|
|
'status': [fields.NotificationStatus.NEW,
|
|
fields.NotificationStatus.RUNNING],
|
|
'source_host_uuid': notification.source_host_uuid,
|
|
'generated-since': (notification.generated_time -
|
|
datetime.timedelta(
|
|
seconds=CONF.duplicate_notification_detection_interval))
|
|
}
|
|
notifications_list = objects.NotificationList.get_all(context,
|
|
filters=filters)
|
|
for db_notification in notifications_list:
|
|
# if payload is same notification should be considered as
|
|
# duplicate
|
|
if db_notification.payload == notification.payload:
|
|
return True
|
|
|
|
return False
|
|
|
|
def create_notification(self, context, notification_data):
|
|
"""Create notification"""
|
|
|
|
# Check whether host from which the notification came is already
|
|
# present in failover segment or not
|
|
host_name = notification_data.get('hostname')
|
|
host_object = objects.Host.get_by_name(context, host_name)
|
|
host_on_maintenance = host_object.on_maintenance
|
|
|
|
if host_on_maintenance:
|
|
message = (_("Notification received from host %(host)s of type "
|
|
"'%(type)s' is ignored as the host is already under "
|
|
"maintenance.") % {
|
|
'host': host_name,
|
|
'type': notification_data.get('type')
|
|
})
|
|
raise exception.HostOnMaintenanceError(message=message)
|
|
|
|
notification = objects.Notification(context=context)
|
|
|
|
# Populate notification object for create
|
|
notification.type = notification_data.get('type')
|
|
notification.generated_time = notification_data.get('generated_time')
|
|
notification.source_host_uuid = host_object.uuid
|
|
notification.payload = notification_data.get('payload')
|
|
notification.status = fields.NotificationStatus.NEW
|
|
|
|
if self._is_duplicate_notification(context, notification):
|
|
message = (_("Notification received from host %(host)s of "
|
|
" type '%(type)s' is duplicate.") %
|
|
{'host': host_name, 'type': notification.type})
|
|
raise exception.DuplicateNotification(message=message)
|
|
|
|
try:
|
|
notification.create()
|
|
self.engine_rpcapi.process_notification(context, notification)
|
|
except Exception as e:
|
|
with excutils.save_and_reraise_exception():
|
|
tb = traceback.format_exc()
|
|
api_utils.notify_about_notification_api(context, notification,
|
|
action=fields.EventNotificationAction.NOTIFICATION_CREATE,
|
|
phase=fields.EventNotificationPhase.ERROR, exception=e,
|
|
tb=tb)
|
|
return notification
|
|
|
|
def get_all(self, context, filters=None, sort_keys=None,
|
|
sort_dirs=None, limit=None, marker=None):
|
|
"""Get all notifications filtered by one of the given parameters.
|
|
|
|
If there is no filter it will retrieve all notifications in the system.
|
|
|
|
The results will be sorted based on the list of sort keys in the
|
|
'sort_keys' parameter (first value is primary sort key, second value is
|
|
secondary sort ket, etc.). For each sort key, the associated sort
|
|
direction is based on the list of sort directions in the 'sort_dirs'
|
|
parameter.
|
|
"""
|
|
LOG.debug("Searching by: %s", str(filters))
|
|
|
|
limited_notifications = (objects.NotificationList.
|
|
get_all(context, filters, sort_keys,
|
|
sort_dirs, limit, marker))
|
|
|
|
return limited_notifications
|
|
|
|
def get_notification(self, context, notification_uuid):
|
|
"""Get a single notification with the given notification_uuid."""
|
|
if uuidutils.is_uuid_like(notification_uuid):
|
|
LOG.debug("Fetching notification by "
|
|
"UUID", notification_uuid=notification_uuid)
|
|
|
|
notification = objects.Notification.get_by_uuid(context,
|
|
notification_uuid)
|
|
else:
|
|
LOG.debug("Failed to fetch notification by "
|
|
"uuid %s", notification_uuid)
|
|
raise exception.NotificationNotFound(id=notification_uuid)
|
|
|
|
return notification
|