trove/trove/common/notification.py
Lingxian Kong ba046b2a14 Support online volume resize
Trove now supports to resize volume without downtime. To use this
feature, the version of Nova and Cinder needs to be at least Pike, the
config option ``cinder_service_type`` needs to be set to ``volumev3``.
The cloud admin can disable this feature by setting
``online_volume_resize=False``, default is enabled.

Change-Id: I000a4e90800454972dd39f2f82d286571bc0b96c
2020-07-24 13:55:51 +12:00

849 lines
23 KiB
Python

# Copyright 2015 Tesora Inc.
#
# 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 copy
import traceback
from oslo_log import log as logging
from trove.common import cfg
from trove.common.exception import TroveError
from trove.common.i18n import _
from trove.common import timeutils
from trove.conductor import api as conductor_api
from trove import rpc
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class EndNotification(object):
@property
def _notifier(self):
'''
Returns the notification for Trove API or TaskManager, otherwise
returns an API to the conductor to whom to forward the notification
'''
return (self.context.notification
if self.context.notification.server_type in ['api',
'taskmanager']
else conductor_api.API(self.context))
def __init__(self, context, **kwargs):
self.context = context
self.context.notification.payload.update(kwargs)
def __enter__(self):
return self.context.notification
def __exit__(self, etype, value, tb):
if etype:
message = str(value)
exception = traceback.format_exception(etype, value, tb)
self._notifier.notify_exc_info(message, exception)
else:
self._notifier.notify_end()
class StartNotification(EndNotification):
def __enter__(self):
self.context.notification.notify_start()
return super(StartNotification, self).__enter__()
class NotificationCastWrapper(object):
def __init__(self, context, api):
self.context = context
self.api = api
self.has_notification = hasattr(context, 'notification')
def __enter__(self):
if self.has_notification:
self.old_server_type = self.context.notification.server_type
self.context.notification.server_type = self.api
def __exit__(self, etype, value, traceback):
if self.has_notification:
self.context.notification.server_type = self.old_server_type
self.context.notification.needs_end_notification = False
class TroveBaseTraits(object):
'''
The base traits of all trove.* notifications.
This class should correspond to trove_base_traits in
ceilometer/event_definitions.yaml
'''
event_type_format = 'trove.instance.%s'
def __init__(self, **kwargs):
self.payload = {}
instance = kwargs.pop('instance', None)
if instance:
self.instance = instance
self.context = instance.context
created_time = timeutils.isotime(instance.db_info.created)
self.payload.update({
'created_at': created_time,
'name': instance.name,
'instance_id': instance.id,
'instance_name': instance.name,
'instance_type_id': instance.flavor_id,
'launched_at': created_time,
'nova_instance_id': instance.server_id,
'region': CONF.region,
'state_description': instance.status.lower(),
'state': instance.status.lower(),
'tenant_id': instance.tenant_id,
'user_id': instance.context.user,
})
self.payload.update(kwargs)
def serialize(self, ctxt):
if hasattr(self, 'instance'):
if 'instance_type' not in self.payload:
flavor_id = self.instance.flavor_id
flavor = self.instance.nova_client.flavors.get(flavor_id)
self.payload['instance_type'] = flavor.name
self.payload['service_id'] = self.instance._get_service_id(
self.instance.datastore_version.manager,
CONF.notification_service_id)
return self.payload
def deserialize(self, ctxt, payload):
self.payload = payload
self.context = ctxt
return self
def notify(self, event_type, publisher_id=None):
publisher_id = publisher_id or CONF.host
event_type = self.event_type_format % event_type
event_payload = self.serialize(self.context)
LOG.debug('Sending event: %(event_type)s, %(payload)s',
{'event_type': event_type, 'payload': event_payload})
notifier = rpc.get_notifier(
service='taskmanager', publisher_id=publisher_id)
notifier.info(self.context, event_type, event_payload)
class TroveCommonTraits(TroveBaseTraits):
'''
Additional traits for trove.* notifications that describe
instance action events
This class should correspond to trove_common_traits in
ceilometer/event_definitions.yaml
'''
def __init__(self, **kwargs):
self.server = kwargs.pop('server', None)
super(TroveCommonTraits, self).__init__(**kwargs)
def serialize(self, ctxt):
if hasattr(self, 'instance'):
instance = self.instance
if 'instance_type' not in self.payload:
flavor = instance.nova_client.flavors.get(instance.flavor_id)
self.payload['instance_size'] = flavor.ram
if self.server is None and instance.server_id:
try:
self.server = instance.nova_client.servers.get(
instance.server_id)
except Exception:
pass
if self.server:
self.payload['availability_zone'] = getattr(
self.server, 'OS-EXT-AZ:availability_zone', None)
if CONF.get(instance.datastore_version.manager).volume_support:
self.payload.update({
'volume_size': instance.volume_size,
'nova_volume_id': instance.volume_id
})
return TroveBaseTraits.serialize(self, ctxt)
class TroveInstanceCreate(TroveCommonTraits):
'''
Additional traits for trove.instance.create notifications that describe
instance action events
This class should correspond to trove_instance_create in
ceilometer/event_definitions.yaml
'''
def __init__(self, **kwargs):
super(TroveInstanceCreate, self).__init__(**kwargs)
def notify(self):
super(TroveInstanceCreate, self).notify('create')
class TroveInstanceModifyFlavor(TroveCommonTraits):
'''
Additional traits for trove.instance.create notifications that describe
instance action events
This class should correspond to trove_instance_modify_flavor in
ceilometer/event_definitions.yaml
'''
def __init__(self, **kwargs):
super(TroveInstanceModifyFlavor, self).__init__(**kwargs)
def notify(self):
super(TroveInstanceModifyFlavor, self).notify('modify_flavor')
class TroveInstanceDelete(TroveCommonTraits):
'''
Additional traits for trove.instance.create notifications that describe
instance action events
This class should correspond to trove_instance_delete in
ceilometer/event_definitions.yaml
'''
def __init__(self, **kwargs):
super(TroveInstanceDelete, self).__init__(**kwargs)
def notify(self):
super(TroveInstanceDelete, self).notify('delete')
class DBaaSQuotas(object):
'''
The traits of dbaas.quotas notifications.
This class should correspond to dbaas.quotas in
ceilometer/event_definitions.yaml
'''
event_type = 'dbaas.quota'
def __init__(self, context, quota, usage):
self.context = context
self.payload = {
'resource': quota.resource,
'in_use': usage.in_use,
'reserved': usage.reserved,
'limit': quota.hard_limit,
'updated': usage.updated
}
def notify(self):
LOG.debug('Sending event: %(event_type)s, %(payload)s',
{'event_type': DBaaSQuotas.event_type,
'payload': self.payload})
notifier = rpc.get_notifier(
service='taskmanager', publisher_id=CONF.host)
notifier.info(self.context, DBaaSQuotas.event_type, self.payload)
class DBaaSAPINotification(object):
'''
The traits of dbaas.* notifications (except quotas).
This class should correspond to dbaas_base_traits in
ceilometer/event_definitions.yaml
'''
event_type_format = 'dbaas.%s.%s'
notify_callback = None
@classmethod
def register_notify_callback(cls, callback):
"""A callback registered here will be fired whenever
a notification is sent out. The callback should
take a notification object, and event_qualifier.
"""
cls.notify_callback = callback
@abc.abstractmethod
def event_type(self):
'Returns the event type (like "create" for dbaas.create.start)'
pass
@abc.abstractmethod
def required_start_traits(self):
'Returns list of required traits for start notification'
pass
def optional_start_traits(self):
'Returns list of optional traits for start notification'
return []
def required_end_traits(self):
'Returns list of required traits for end notification'
return []
def optional_end_traits(self):
'Returns list of optional traits for end notification'
return []
def required_error_traits(self):
'Returns list of required traits for error notification'
return ['message', 'exception']
def optional_error_traits(self):
'Returns list of optional traits for error notification'
return ['instance_id']
def required_base_traits(self):
return ['tenant_id', 'client_ip', 'server_ip', 'server_type',
'request_id']
@property
def server_type(self):
return self.payload['server_type']
@server_type.setter
def server_type(self, server_type):
self.payload['server_type'] = server_type
@property
def request_id(self):
return self.payload['request_id']
def __init__(self, context, **kwargs):
self.context = context
self.needs_end_notification = True
self.payload = {}
if 'request' in kwargs:
request = kwargs.pop('request')
self.payload.update({
'request_id': context.request_id,
'server_type': 'api',
'client_ip': request.remote_addr,
'server_ip': request.host,
'tenant_id': context.project_id,
})
elif 'request_id' not in kwargs:
raise TroveError(_("Notification %s must include 'request'"
" property") % self.__class__.__name__)
self.payload.update(kwargs)
def serialize(self, context):
return self.payload
def validate(self, required_traits):
required_keys = set(required_traits)
provided_keys = set(self.payload.keys())
if not required_keys.issubset(provided_keys):
raise TroveError(_("The following required keys not defined for"
" notification %(name)s: %(keys)s")
% {'name': self.__class__.__name__,
'keys': list(required_keys - provided_keys)})
if 'server_type' not in self.payload:
raise TroveError(_("Notification %s must include a"
" 'server_type' for correct routing")
% self.__class__.__name__)
def _notify(self, event_qualifier, required_traits, optional_traits,
**kwargs):
self.payload.update(kwargs)
self.validate(self.required_base_traits() + required_traits)
available_values = self.serialize(self.context)
payload = {k: available_values[k]
for k in self.required_base_traits() + required_traits}
for k in optional_traits:
if k in available_values:
payload[k] = available_values[k]
qualified_event_type = (DBaaSAPINotification.event_type_format
% (self.event_type(), event_qualifier))
LOG.debug('Sending event: %(event_type)s, %(payload)s',
{'event_type': qualified_event_type, 'payload': payload})
context = copy.copy(self.context)
del context.notification
notifier = rpc.get_notifier(service=self.payload['server_type'])
notifier.info(context, qualified_event_type, self.payload)
if self.notify_callback:
self.notify_callback(event_qualifier)
def notify_start(self, **kwargs):
self._notify('start', self.required_start_traits(),
self.optional_start_traits(), **kwargs)
def notify_end(self, **kwargs):
if self.needs_end_notification:
self._notify('end', self.required_end_traits(),
self.optional_end_traits(), **kwargs)
def notify_exc_info(self, message, exception):
self.payload.update({
'message': message,
'exception': exception
})
self._notify('error', self.required_error_traits(),
self.optional_error_traits())
class DBaaSInstanceCreate(DBaaSAPINotification):
def event_type(self):
return 'instance_create'
def required_start_traits(self):
return ['name', 'flavor_id', 'datastore', 'datastore_version',
'image_id', 'availability_zone', 'region_name']
def optional_start_traits(self):
return ['databases', 'users', 'volume_size', 'restore_point',
'replica_of', 'replica_count', 'cluster_id', 'backup_id',
'nics']
def required_end_traits(self):
return ['instance_id']
class DBaaSInstanceReboot(DBaaSAPINotification):
def event_type(self):
return 'instance_reboot'
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceRestart(DBaaSAPINotification):
def event_type(self):
return 'instance_restart'
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceResizeVolume(DBaaSAPINotification):
def event_type(self):
return 'instance_resize_volume'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'new_size']
class DBaaSInstanceResizeInstance(DBaaSAPINotification):
def event_type(self):
return 'instance_resize_instance'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'new_flavor_id']
class DBaaSInstancePromote(DBaaSAPINotification):
def event_type(self):
return 'instance_promote'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceEject(DBaaSAPINotification):
def event_type(self):
return 'instance_eject'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceDelete(DBaaSAPINotification):
def event_type(self):
return 'instance_delete'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceResetStatus(DBaaSAPINotification):
def event_type(self):
return 'instance_reset_status'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceDetach(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'instance_detach'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id']
class DBaaSInstanceAttachConfiguration(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'instance_attach_configuration'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'configuration_id']
class DBaaSInstanceDetachConfiguration(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'instance_detach_configuration'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id']
class DBaaSClusterAttachConfiguration(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_attach_configuration'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id', 'configuration_id']
class DBaaSClusterDetachConfiguration(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_detach_configuration'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id']
class DBaaSClusterCreate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_create'
@abc.abstractmethod
def required_start_traits(self):
return ['name', 'datastore', 'datastore_version']
@abc.abstractmethod
def required_end_traits(self):
return ['cluster_id']
class DBaaSClusterRestart(DBaaSAPINotification):
def event_type(self):
return 'cluster_restart'
def required_start_traits(self):
return ['cluster_id']
class DBaaSClusterUpgrade(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_upgrade'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id', 'datastore_version']
class DBaaSClusterDelete(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_delete'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id']
class DBaaSClusterResetStatus(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_reset_status'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id']
class DBaaSClusterAddShard(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_add_shard'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id']
class DBaaSClusterGrow(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_grow'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id']
@abc.abstractmethod
def required_end_traits(self):
return ['cluster_id']
class DBaaSClusterShrink(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'cluster_shrink'
@abc.abstractmethod
def required_start_traits(self):
return ['cluster_id']
@abc.abstractmethod
def required_end_traits(self):
return ['cluster_id']
class DBaaSBackupCreate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'backup_create'
@abc.abstractmethod
def required_start_traits(self):
return ['name', 'instance_id', 'description', 'parent_id']
@abc.abstractmethod
def required_end_traits(self):
return ['backup_id']
class DBaaSBackupDelete(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'backup_delete'
@abc.abstractmethod
def required_start_traits(self):
return ['backup_id']
class DBaaSDatabaseCreate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'database_create'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'dbname']
class DBaaSDatabaseDelete(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'database_delete'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'dbname']
class DBaaSUserCreate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'user_create'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'username']
class DBaaSUserDelete(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'user_delete'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'username']
class DBaaSUserUpdateAttributes(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'user_update_attributes'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'username']
class DBaaSUserGrant(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'user_grant'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'username', 'database']
class DBaaSUserRevoke(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'user_revoke'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'username', 'database']
class DBaaSUserChangePassword(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'user_change_password'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'username']
class DBaaSConfigurationCreate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'configuration_create'
@abc.abstractmethod
def required_start_traits(self):
return ['name', 'datastore', 'datastore_version']
def required_end_traits(self):
return ['configuration_id']
class DBaaSConfigurationDelete(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'configuration_delete'
@abc.abstractmethod
def required_start_traits(self):
return ['configuration_id']
class DBaaSConfigurationUpdate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'configuration_update'
@abc.abstractmethod
def required_start_traits(self):
return ['configuration_id', 'name', 'description']
class DBaaSConfigurationEdit(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'configuration_edit'
@abc.abstractmethod
def required_start_traits(self):
return ['configuration_id']
class DBaaSInstanceUpgrade(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'upgrade'
@abc.abstractmethod
def required_start_traits(self):
return ['instance_id', 'datastore_version_id']
class DBaaSInstanceMigrate(DBaaSAPINotification):
@abc.abstractmethod
def event_type(self):
return 'migrate'
@abc.abstractmethod
def required_start_traits(self):
return ['host']