Transform instance.delete notifications
The instance.delete.start and instance.delete.end notifications are transformed to the versioned framework using the generic InstanceActionNotification and InstanceActionPayload class. There is no instance.delete.error notification in legacy so it is not added here but considered as a future improvement. Change-Id: Iddbe50ce0ad3c14562df800bbc09ec5a7e840485 Implements: bp versioned-notification-transformation-newton
This commit is contained in:
parent
23153952a9
commit
998235da63
49
doc/notification_samples/instance-delete-end.json
Normal file
49
doc/notification_samples/instance-delete-end.json
Normal file
@ -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"
|
||||
}
|
62
doc/notification_samples/instance-delete-start.json
Normal file
62
doc/notification_samples/instance-delete-start.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"event_type":"instance.delete.start",
|
||||
"payload":{
|
||||
"nova_object.data":{
|
||||
"architecture":"x86_64",
|
||||
"availability_zone":null,
|
||||
"created_at":"2012-10-29T13:42:11Z",
|
||||
"deleted_at":null,
|
||||
"display_name":"some-server",
|
||||
"fault":null,
|
||||
"host":"compute",
|
||||
"host_name":"some-server",
|
||||
"ip_addresses": [{
|
||||
"nova_object.name": "IpPayload",
|
||||
"nova_object.namespace": "nova",
|
||||
"nova_object.version": "1.0",
|
||||
"nova_object.data": {
|
||||
"mac": "fa:16:3e:4c:2c:30",
|
||||
"address": "192.168.1.3",
|
||||
"port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
|
||||
"meta": {},
|
||||
"version": 4,
|
||||
"label": "private-network",
|
||||
"device_name": "tapce531f90-19"
|
||||
}
|
||||
}],
|
||||
"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":"active",
|
||||
"task_state":"deleting",
|
||||
"power_state":"running",
|
||||
"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":"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"
|
||||
}
|
@ -85,6 +85,7 @@ from nova.network import model as network_model
|
||||
from nova.network.security_group import openstack_driver
|
||||
from nova import objects
|
||||
from nova.objects import base as obj_base
|
||||
from nova.objects import fields
|
||||
from nova.objects import instance as obj_instance
|
||||
from nova.objects import migrate_data as migrate_data_obj
|
||||
from nova import rpc
|
||||
@ -727,7 +728,9 @@ class ComputeManager(manager.Manager):
|
||||
self._update_resource_tracker(context, instance)
|
||||
self._notify_about_instance_usage(context, instance, "delete.end",
|
||||
system_metadata=system_meta)
|
||||
|
||||
compute_utils.notify_about_instance_action(context, instance,
|
||||
self.host, action=fields.NotificationAction.DELETE,
|
||||
phase=fields.NotificationPhase.END)
|
||||
self._clean_instance_console_tokens(context, instance)
|
||||
self._delete_scheduler_instance_info(context, instance.uuid)
|
||||
|
||||
@ -2279,6 +2282,10 @@ class ComputeManager(manager.Manager):
|
||||
instance=instance)
|
||||
self._notify_about_instance_usage(context, instance,
|
||||
"delete.start")
|
||||
compute_utils.notify_about_instance_action(context, instance,
|
||||
self.host, action=fields.NotificationAction.DELETE,
|
||||
phase=fields.NotificationPhase.START)
|
||||
|
||||
self._shutdown_instance(context, instance, bdms)
|
||||
# NOTE(dims): instance.info_cache.delete() should be called after
|
||||
# _shutdown_instance in the compute manager as shutdown calls
|
||||
|
@ -32,7 +32,10 @@ from nova import exception
|
||||
from nova.i18n import _LW
|
||||
from nova.network import model as network_model
|
||||
from nova import notifications
|
||||
from nova.notifications.objects import base as notification_base
|
||||
from nova.notifications.objects import instance as instance_notification
|
||||
from nova import objects
|
||||
from nova.objects import fields
|
||||
from nova import rpc
|
||||
from nova import safe_utils
|
||||
from nova import utils
|
||||
@ -318,6 +321,49 @@ def notify_about_instance_usage(notifier, context, instance, event_suffix,
|
||||
method(context, 'compute.instance.%s' % event_suffix, usage_info)
|
||||
|
||||
|
||||
def notify_about_instance_action(context, instance, host, action, phase=None,
|
||||
binary='nova-compute'):
|
||||
"""Send versioned notification about the action made on the instance
|
||||
:param instance: the instance which the action performed on
|
||||
:param host: the host emitting the notification
|
||||
:param action: the name of the action
|
||||
:param phase: the phase of the action
|
||||
:param binary: the binary emitting the notification
|
||||
"""
|
||||
network_info = get_nw_info_for_instance(instance)
|
||||
ips = []
|
||||
if network_info is not None:
|
||||
for vif in network_info:
|
||||
for ip in vif.fixed_ips():
|
||||
ips.append(instance_notification.IpPayload(
|
||||
label=vif["network"]["label"],
|
||||
mac=vif["address"],
|
||||
meta=vif["meta"],
|
||||
port_uuid=vif["id"],
|
||||
version=ip["version"],
|
||||
address=ip["address"],
|
||||
device_name=vif["devname"]))
|
||||
flavor = instance_notification.FlavorPayload(instance=instance)
|
||||
# TODO(gibi): handle fault during the transformation of the first error
|
||||
# notifications
|
||||
payload = instance_notification.InstanceActionPayload(
|
||||
instance=instance,
|
||||
fault=None,
|
||||
ip_addresses=ips,
|
||||
flavor=flavor)
|
||||
notification = instance_notification.InstanceActionNotification(
|
||||
context=context,
|
||||
priority=fields.NotificationPriority.INFO,
|
||||
publisher=notification_base.NotificationPublisher(
|
||||
context=context, host=host, binary=binary),
|
||||
event_type=notification_base.EventType(
|
||||
object='instance',
|
||||
action=action,
|
||||
phase=phase),
|
||||
payload=payload)
|
||||
notification.emit(context)
|
||||
|
||||
|
||||
def notify_about_server_group_update(context, event_suffix, sg_payload):
|
||||
"""Send a notification about server group update.
|
||||
|
||||
|
@ -23,13 +23,22 @@ class NotificationObject(base.NovaObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(NotificationObject, self).__init__(**kwargs)
|
||||
# The notification objects are created on the fly when nova emits the
|
||||
# notification. This causes that every object shows every field as
|
||||
# changed. We don't want to send this meaningless information so we
|
||||
# reset the object after creation.
|
||||
self.obj_reset_changes(recursive=False)
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register_notification
|
||||
class EventType(NotificationObject):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: New valid actions values are added to the
|
||||
# NotificationActionField enum
|
||||
VERSION = '1.1'
|
||||
# Version 1.2: DELETE value is added to the NotificationActionField enum
|
||||
VERSION = '1.2'
|
||||
|
||||
fields = {
|
||||
'object': fields.StringField(nullable=False),
|
||||
@ -70,8 +79,8 @@ class NotificationPayloadBase(NotificationObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NotificationPayloadBase, self).__init__(*args, **kwargs)
|
||||
def __init__(self, **kwargs):
|
||||
super(NotificationPayloadBase, self).__init__(**kwargs)
|
||||
self.populated = not self.SCHEMA
|
||||
|
||||
def populate_schema(self, **kwargs):
|
||||
@ -86,6 +95,10 @@ class NotificationPayloadBase(NotificationObject):
|
||||
setattr(self, key, getattr(source, field))
|
||||
self.populated = True
|
||||
|
||||
# the schema population will create changed fields but we don't need
|
||||
# this information in the notification
|
||||
self.obj_reset_changes(recursive=False)
|
||||
|
||||
|
||||
@base.NovaObjectRegistry.register_notification
|
||||
class NotificationPublisher(NotificationObject):
|
||||
|
158
nova/notifications/objects/instance.py
Normal file
158
nova/notifications/objects/instance.py
Normal file
@ -0,0 +1,158 @@
|
||||
# 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 nova.notifications.objects import base
|
||||
from nova.objects import base as nova_base
|
||||
from nova.objects import fields
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class InstancePayload(base.NotificationPayloadBase):
|
||||
SCHEMA = {
|
||||
'uuid': ('instance', 'uuid'),
|
||||
'user_id': ('instance', 'user_id'),
|
||||
'tenant_id': ('instance', 'project_id'),
|
||||
'reservation_id': ('instance', 'reservation_id'),
|
||||
'display_name': ('instance', 'display_name'),
|
||||
'host_name': ('instance', 'hostname'),
|
||||
'host': ('instance', 'host'),
|
||||
'node': ('instance', 'node'),
|
||||
'os_type': ('instance', 'os_type'),
|
||||
'architecture': ('instance', 'architecture'),
|
||||
'availability_zone': ('instance', 'availability_zone'),
|
||||
|
||||
'image_uuid': ('instance', 'image_ref'),
|
||||
|
||||
'kernel_id': ('instance', 'kernel_id'),
|
||||
'ramdisk_id': ('instance', 'ramdisk_id'),
|
||||
|
||||
'created_at': ('instance', 'created_at'),
|
||||
'launched_at': ('instance', 'launched_at'),
|
||||
'terminated_at': ('instance', 'terminated_at'),
|
||||
'deleted_at': ('instance', 'deleted_at'),
|
||||
|
||||
'state': ('instance', 'vm_state'),
|
||||
'power_state': ('instance', 'power_state'),
|
||||
'task_state': ('instance', 'task_state'),
|
||||
'progress': ('instance', 'progress'),
|
||||
|
||||
'metadata': ('instance', 'metadata'),
|
||||
}
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'uuid': fields.UUIDField(),
|
||||
'user_id': fields.StringField(nullable=True),
|
||||
'tenant_id': fields.StringField(nullable=True),
|
||||
'reservation_id': fields.StringField(nullable=True),
|
||||
'display_name': fields.StringField(nullable=True),
|
||||
'host_name': fields.StringField(nullable=True),
|
||||
'host': fields.StringField(nullable=True),
|
||||
'node': fields.StringField(nullable=True),
|
||||
'os_type': fields.StringField(nullable=True),
|
||||
'architecture': fields.StringField(nullable=True),
|
||||
'availability_zone': fields.StringField(nullable=True),
|
||||
|
||||
'flavor': fields.ObjectField('FlavorPayload'),
|
||||
'image_uuid': fields.StringField(nullable=True),
|
||||
|
||||
'kernel_id': fields.StringField(nullable=True),
|
||||
'ramdisk_id': fields.StringField(nullable=True),
|
||||
|
||||
'created_at': fields.DateTimeField(nullable=True),
|
||||
'launched_at': fields.DateTimeField(nullable=True),
|
||||
'terminated_at': fields.DateTimeField(nullable=True),
|
||||
'deleted_at': fields.DateTimeField(nullable=True),
|
||||
|
||||
'state': fields.InstanceStateField(nullable=True),
|
||||
'power_state': fields.InstancePowerStateField(nullable=True),
|
||||
'task_state': fields.InstanceTaskStateField(nullable=True),
|
||||
'progress': fields.IntegerField(nullable=True),
|
||||
|
||||
'ip_addresses': fields.ListOfObjectsField('IpPayload'),
|
||||
|
||||
'metadata': fields.DictOfStringsField(),
|
||||
}
|
||||
|
||||
def __init__(self, instance, **kwargs):
|
||||
super(InstancePayload, self).__init__(**kwargs)
|
||||
self.populate_schema(instance=instance)
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class InstanceActionPayload(InstancePayload):
|
||||
# No SCHEMA as all the additional fields are calculated
|
||||
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'fault': fields.ObjectField('ExceptionPayload', nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, instance, fault, ip_addresses, flavor):
|
||||
super(InstanceActionPayload, self).__init__(
|
||||
instance=instance,
|
||||
fault=fault,
|
||||
ip_addresses=ip_addresses,
|
||||
flavor=flavor)
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class IpPayload(base.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
fields = {
|
||||
'label': fields.StringField(),
|
||||
'mac': fields.MACAddressField(),
|
||||
'meta': fields.DictOfStringsField(),
|
||||
'port_uuid': fields.UUIDField(nullable=True),
|
||||
'version': fields.IntegerField(),
|
||||
'address': fields.IPV4AndV6AddressField(),
|
||||
'device_name': fields.StringField(nullable=True)
|
||||
}
|
||||
|
||||
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class FlavorPayload(base.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
SCHEMA = {
|
||||
'flavorid': ('flavor', 'flavorid'),
|
||||
'memory_mb': ('instance', 'memory_mb'),
|
||||
'vcpus': ('instance', 'vcpus'),
|
||||
'root_gb': ('instance', 'root_gb'),
|
||||
'ephemeral_gb': ('instance', 'ephemeral_gb'),
|
||||
}
|
||||
|
||||
fields = {
|
||||
'flavorid': fields.StringField(nullable=True),
|
||||
'memory_mb': fields.IntegerField(nullable=True),
|
||||
'vcpus': fields.IntegerField(nullable=True),
|
||||
'root_gb': fields.IntegerField(nullable=True),
|
||||
'ephemeral_gb': fields.IntegerField(nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, instance, **kwargs):
|
||||
super(FlavorPayload, self).__init__(**kwargs)
|
||||
self.populate_schema(instance=instance, flavor=instance.flavor)
|
||||
|
||||
|
||||
@base.notification_sample('instance-delete-start.json')
|
||||
@base.notification_sample('instance-delete-end.json')
|
||||
@nova_base.NovaObjectRegistry.register_notification
|
||||
class InstanceActionNotification(base.NotificationBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': fields.ObjectField('InstanceActionPayload')
|
||||
}
|
@ -609,14 +609,136 @@ class NotificationPhase(Enum):
|
||||
class NotificationAction(Enum):
|
||||
UPDATE = 'update'
|
||||
EXCEPTION = 'exception'
|
||||
DELETE = 'delete'
|
||||
|
||||
ALL = (UPDATE, EXCEPTION)
|
||||
ALL = (UPDATE, EXCEPTION, DELETE)
|
||||
|
||||
def __init__(self):
|
||||
super(NotificationAction, self).__init__(
|
||||
valid_values=NotificationAction.ALL)
|
||||
|
||||
|
||||
class InstanceState(Enum):
|
||||
# TODO(gibi): this is currently a copy of nova.compute.vm_states, remove
|
||||
# the duplication
|
||||
ACTIVE = 'active'
|
||||
BUILDING = 'building'
|
||||
PAUSED = 'paused'
|
||||
SUSPENDED = 'suspended'
|
||||
STOPPED = 'stopped'
|
||||
RESCUED = 'rescued'
|
||||
RESIZED = 'resized'
|
||||
SOFT_DELETED = 'soft-delete'
|
||||
DELETED = 'deleted'
|
||||
ERROR = 'error'
|
||||
SHELVED = 'shelved'
|
||||
SHELVED_OFFLOADED = 'shelved_offloaded'
|
||||
|
||||
ALL = (ACTIVE, BUILDING, PAUSED, SUSPENDED, STOPPED, RESCUED, RESIZED,
|
||||
SOFT_DELETED, DELETED, ERROR, SHELVED, SHELVED_OFFLOADED)
|
||||
|
||||
def __init__(self):
|
||||
super(InstanceState, self).__init__(
|
||||
valid_values=InstanceState.ALL)
|
||||
|
||||
|
||||
class InstanceTaskState(Enum):
|
||||
# TODO(gibi): this is currently a copy of nova.compute.task_states, remove
|
||||
# the duplication
|
||||
SCHEDULING = 'scheduling'
|
||||
BLOCK_DEVICE_MAPPING = 'block_device_mapping'
|
||||
NETWORKING = 'networking'
|
||||
SPAWNING = 'spawning'
|
||||
IMAGE_SNAPSHOT = 'image_snapshot'
|
||||
IMAGE_SNAPSHOT_PENDING = 'image_snapshot_pending'
|
||||
IMAGE_PENDING_UPLOAD = 'image_pending_upload'
|
||||
IMAGE_UPLOADING = 'image_uploading'
|
||||
IMAGE_BACKUP = 'image_backup'
|
||||
UPDATING_PASSWORD = 'updating_password'
|
||||
RESIZE_PREP = 'resize_prep'
|
||||
RESIZE_MIGRATING = 'resize_migrating'
|
||||
RESIZE_MIGRATED = 'resize_migrated'
|
||||
RESIZE_FINISH = 'resize_finish'
|
||||
RESIZE_REVERTING = 'resize_reverting'
|
||||
RESIZE_CONFIRMING = 'resize_confirming'
|
||||
REBOOTING = 'rebooting'
|
||||
REBOOT_PENDING = 'reboot_pending'
|
||||
REBOOT_STARTED = 'reboot_started'
|
||||
REBOOTING_HARD = 'rebooting_hard'
|
||||
REBOOT_PENDING_HARD = 'reboot_pending_hard'
|
||||
REBOOT_STARTED_HARD = 'reboot_started_hard'
|
||||
PAUSING = 'pausing'
|
||||
UNPAUSING = 'unpausing'
|
||||
SUSPENDING = 'suspending'
|
||||
RESUMING = 'resuming'
|
||||
POWERING_OFF = 'powering-off'
|
||||
POWERING_ON = 'powering-on'
|
||||
RESCUING = 'rescuing'
|
||||
UNRESCUING = 'unrescuing'
|
||||
REBUILDING = 'rebuilding'
|
||||
REBUILD_BLOCK_DEVICE_MAPPING = "rebuild_block_device_mapping"
|
||||
REBUILD_SPAWNING = 'rebuild_spawning'
|
||||
MIGRATING = "migrating"
|
||||
DELETING = 'deleting'
|
||||
SOFT_DELETING = 'soft-deleting'
|
||||
RESTORING = 'restoring'
|
||||
SHELVING = 'shelving'
|
||||
SHELVING_IMAGE_PENDING_UPLOAD = 'shelving_image_pending_upload'
|
||||
SHELVING_IMAGE_UPLOADING = 'shelving_image_uploading'
|
||||
SHELVING_OFFLOADING = 'shelving_offloading'
|
||||
UNSHELVING = 'unshelving'
|
||||
|
||||
ALL = (SCHEDULING, BLOCK_DEVICE_MAPPING, NETWORKING, SPAWNING,
|
||||
IMAGE_SNAPSHOT, IMAGE_SNAPSHOT_PENDING, IMAGE_PENDING_UPLOAD,
|
||||
IMAGE_UPLOADING, IMAGE_BACKUP, UPDATING_PASSWORD, RESIZE_PREP,
|
||||
RESIZE_MIGRATING, RESIZE_MIGRATED, RESIZE_FINISH, RESIZE_REVERTING,
|
||||
RESIZE_CONFIRMING, REBOOTING, REBOOT_PENDING, REBOOT_STARTED,
|
||||
REBOOTING_HARD, REBOOT_PENDING_HARD, REBOOT_STARTED_HARD, PAUSING,
|
||||
UNPAUSING, SUSPENDING, RESUMING, POWERING_OFF, POWERING_ON,
|
||||
RESCUING, UNRESCUING, REBUILDING, REBUILD_BLOCK_DEVICE_MAPPING,
|
||||
REBUILD_SPAWNING, MIGRATING, DELETING, SOFT_DELETING, RESTORING,
|
||||
SHELVING, SHELVING_IMAGE_PENDING_UPLOAD, SHELVING_IMAGE_UPLOADING,
|
||||
SHELVING_OFFLOADING, UNSHELVING)
|
||||
|
||||
def __init__(self):
|
||||
super(InstanceTaskState, self).__init__(
|
||||
valid_values=InstanceTaskState.ALL)
|
||||
|
||||
|
||||
class InstancePowerState(Enum):
|
||||
# TODO(gibi): this is currently a copy of nova.compute.power_state, remove
|
||||
# the duplication
|
||||
NOSTATE = 'pending'
|
||||
RUNNING = 'running'
|
||||
PAUSED = 'paused'
|
||||
SHUTDOWN = 'shutdown'
|
||||
CRASHED = 'crashed'
|
||||
SUSPENDED = 'suspended'
|
||||
|
||||
VALUE_MAP = {
|
||||
0x00: NOSTATE,
|
||||
0x01: RUNNING,
|
||||
0x03: PAUSED,
|
||||
0x04: SHUTDOWN,
|
||||
0x06: CRASHED,
|
||||
0x07: SUSPENDED
|
||||
}
|
||||
|
||||
ALL = (NOSTATE, RUNNING, PAUSED, SHUTDOWN, CRASHED, SUSPENDED)
|
||||
|
||||
def __init__(self):
|
||||
super(InstancePowerState, self).__init__(
|
||||
valid_values=InstancePowerState.ALL)
|
||||
|
||||
def coerce(self, obj, attr, value):
|
||||
try:
|
||||
value = int(value)
|
||||
value = InstancePowerState.VALUE_MAP[value]
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
return super(InstancePowerState, self).coerce(obj, attr, value)
|
||||
|
||||
|
||||
class IPV4AndV6Address(IPAddress):
|
||||
@staticmethod
|
||||
def coerce(obj, attr, value):
|
||||
@ -864,6 +986,18 @@ class NotificationActionField(BaseEnumField):
|
||||
AUTO_TYPE = NotificationAction()
|
||||
|
||||
|
||||
class InstanceStateField(BaseEnumField):
|
||||
AUTO_TYPE = InstanceState()
|
||||
|
||||
|
||||
class InstanceTaskStateField(BaseEnumField):
|
||||
AUTO_TYPE = InstanceTaskState()
|
||||
|
||||
|
||||
class InstancePowerStateField(BaseEnumField):
|
||||
AUTO_TYPE = InstancePowerState()
|
||||
|
||||
|
||||
class IPV4AndV6AddressField(AutoTypedField):
|
||||
AUTO_TYPE = IPV4AndV6Address()
|
||||
|
||||
|
@ -665,3 +665,90 @@ class AllServicesCurrent(fixtures.Fixture):
|
||||
|
||||
def _fake_minimum(self, *args, **kwargs):
|
||||
return service_obj.SERVICE_VERSION
|
||||
|
||||
|
||||
class NeutronFixture(fixtures.Fixture):
|
||||
"""A fixture to boot instances with neutron ports"""
|
||||
|
||||
# the default project_id in OsaAPIFixtures
|
||||
tenant_id = '6f70656e737461636b20342065766572'
|
||||
network_1 = {
|
||||
'status': 'ACTIVE',
|
||||
'subnets': [],
|
||||
'name': 'private-network',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': tenant_id,
|
||||
'id': '3cb9bc59-5699-4588-a4b1-b87f96708bc6',
|
||||
}
|
||||
subnet_1 = {
|
||||
'name': 'private-subnet',
|
||||
'enable_dhcp': True,
|
||||
'network_id': network_1['id'],
|
||||
'tenant_id': tenant_id,
|
||||
'dns_nameservers': [],
|
||||
'allocation_pools': [
|
||||
{
|
||||
'start': '192.168.1.1',
|
||||
'end': '192.168.1.254'
|
||||
}
|
||||
],
|
||||
'host_routes': [],
|
||||
'ip_version': 4,
|
||||
'gateway_ip': '192.168.1.1',
|
||||
'cidr': '192.168.1.1/24',
|
||||
'id': 'f8a6e8f8-c2ec-497c-9f23-da9616de54ef'
|
||||
}
|
||||
network_1['subnets'] = [subnet_1['id']]
|
||||
|
||||
port_1 = {
|
||||
'id': 'ce531f90-199f-48c0-816c-13e38010b442',
|
||||
'network_id': network_1['id'],
|
||||
'admin_state_up': True,
|
||||
'status': 'ACTIVE',
|
||||
'mac_address': 'fa:16:3e:4c:2c:30',
|
||||
'fixed_ips': [
|
||||
{
|
||||
'ip_address': '192.168.1.3',
|
||||
'subnet_id': subnet_1['id']
|
||||
}
|
||||
],
|
||||
'tenant_id': tenant_id
|
||||
}
|
||||
|
||||
def __init__(self, test):
|
||||
super(NeutronFixture, self).__init__()
|
||||
self.test = test
|
||||
|
||||
def setUp(self):
|
||||
super(NeutronFixture, self).setUp()
|
||||
|
||||
self.test.stub_out(
|
||||
'nova.network.neutronv2.api.API.'
|
||||
'validate_networks',
|
||||
lambda *args, **kwargs: 1)
|
||||
self.test.stub_out(
|
||||
'nova.network.neutronv2.api.API.'
|
||||
'create_pci_requests_for_sriov_ports',
|
||||
lambda *args, **kwargs: None)
|
||||
self.test.stub_out(
|
||||
'nova.network.security_group.neutron_driver.SecurityGroupAPI.'
|
||||
'get_instances_security_groups_bindings',
|
||||
lambda *args, **kwargs: {})
|
||||
|
||||
mock_neutron_client = mock.Mock()
|
||||
mock_neutron_client.list_extensions.return_value = {'extensions': []}
|
||||
mock_neutron_client.show_port.return_value = {
|
||||
'port': NeutronFixture.port_1}
|
||||
mock_neutron_client.list_networks.return_value = {
|
||||
'networks': [NeutronFixture.network_1]}
|
||||
mock_neutron_client.list_ports.return_value = {
|
||||
'ports': [NeutronFixture.port_1]}
|
||||
mock_neutron_client.list_subnets.return_value = {
|
||||
'subnets': [NeutronFixture.subnet_1]}
|
||||
mock_neutron_client.list_floatingips.return_value = {'floatingips': []}
|
||||
mock_neutron_client.update_port.return_value = {
|
||||
'port': NeutronFixture.port_1}
|
||||
|
||||
self.test.stub_out(
|
||||
'nova.network.neutronv2.api.get_client',
|
||||
lambda *args, **kwargs: mock_neutron_client)
|
||||
|
@ -239,22 +239,27 @@ class InstanceHelperMixin(object):
|
||||
|
||||
return server
|
||||
|
||||
def _build_minimal_create_server_request(self, api, name):
|
||||
def _build_minimal_create_server_request(self, api, name, image_uuid=None,
|
||||
flavor_id=None):
|
||||
server = {}
|
||||
|
||||
image = api.get_images()[0]
|
||||
|
||||
if 'imageRef' in image:
|
||||
image_href = image['imageRef']
|
||||
if image_uuid:
|
||||
image_href = 'http://fake.server/%s' % image_uuid
|
||||
else:
|
||||
image_href = image['id']
|
||||
image_href = 'http://fake.server/%s' % image_href
|
||||
image = api.get_images()[0]
|
||||
|
||||
if 'imageRef' in image:
|
||||
image_href = image['imageRef']
|
||||
else:
|
||||
image_href = image['id']
|
||||
image_href = 'http://fake.server/%s' % image_href
|
||||
|
||||
# We now have a valid imageId
|
||||
server['imageRef'] = image_href
|
||||
|
||||
# Set a valid flavorId
|
||||
flavor = api.get_flavors()[1]
|
||||
server['flavorRef'] = ('http://fake.server/%s' % flavor['id'])
|
||||
if not flavor_id:
|
||||
# Set a valid flavorId
|
||||
flavor_id = api.get_flavors()[1]['id']
|
||||
server['flavorRef'] = ('http://fake.server/%s' % flavor_id)
|
||||
server['name'] = name
|
||||
return server
|
||||
|
@ -12,16 +12,27 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import os
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import fixture as utils_fixture
|
||||
|
||||
from nova import test
|
||||
from nova.tests import fixtures as nova_fixtures
|
||||
from nova.tests.functional.api import client as api_client
|
||||
from nova.tests.functional import integrated_helpers
|
||||
from nova.tests.unit.api.openstack.compute import test_services
|
||||
from nova.tests.unit import fake_notifier
|
||||
import nova.tests.unit.image.fake
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class NotificationSampleTestBase(test.TestCase):
|
||||
class NotificationSampleTestBase(test.TestCase,
|
||||
integrated_helpers.InstanceHelperMixin):
|
||||
"""Base class for notification sample testing.
|
||||
|
||||
To add tests for a versioned notification you have to store a sample file
|
||||
@ -43,6 +54,10 @@ class NotificationSampleTestBase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NotificationSampleTestBase, self).setUp()
|
||||
# Needs to mock this to avoid REQUIRES_LOCKING to be set to True
|
||||
patcher = mock.patch('oslo_concurrency.lockutils.lock')
|
||||
self.addCleanup(patcher.stop)
|
||||
patcher.start()
|
||||
|
||||
api_fixture = self.useFixture(nova_fixtures.OSAPIFixture(
|
||||
api_version='v2.1'))
|
||||
@ -52,6 +67,18 @@ class NotificationSampleTestBase(test.TestCase):
|
||||
fake_notifier.stub_notifier(self)
|
||||
self.addCleanup(fake_notifier.reset)
|
||||
|
||||
self.useFixture(utils_fixture.TimeFixture(test_services.fake_utcnow()))
|
||||
|
||||
self.flags(scheduler_driver='nova.scheduler.chance.ChanceScheduler')
|
||||
# the image fake backend needed for image discovery
|
||||
nova.tests.unit.image.fake.stub_out_image_service(self)
|
||||
self.addCleanup(nova.tests.unit.image.fake.FakeImageService_reset)
|
||||
|
||||
self.start_service('conductor', manager=CONF.conductor.manager)
|
||||
self.start_service('scheduler')
|
||||
self.start_service('network')
|
||||
self.start_service('compute')
|
||||
|
||||
def _get_notification_sample(self, sample):
|
||||
sample_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sample_dir = os.path.normpath(os.path.join(
|
||||
@ -106,3 +133,47 @@ class NotificationSampleTestBase(test.TestCase):
|
||||
self._apply_replacements(replacements, sample_obj, notification)
|
||||
|
||||
self.assertJsonEqual(sample_obj, notification)
|
||||
|
||||
def _boot_a_server(self, expected_status='ACTIVE', extra_params=None):
|
||||
|
||||
# We have to depend on a specific image and flavor to fix the content
|
||||
# of the notification that will be emitted
|
||||
flavor_body = {'flavor': {'name': 'test_flavor',
|
||||
'ram': 512,
|
||||
'vcpus': 1,
|
||||
'disk': 1,
|
||||
'id': 'a22d5517-147c-4147-a0d1-e698df5cd4e3'
|
||||
}}
|
||||
|
||||
flavor_id = self.api.post_flavor(flavor_body)['id']
|
||||
|
||||
server = self._build_minimal_create_server_request(
|
||||
self.api, 'some-server',
|
||||
image_uuid='155d900f-4e14-4e4c-a73d-069cbf4541e6',
|
||||
flavor_id=flavor_id)
|
||||
|
||||
if extra_params:
|
||||
server.update(extra_params)
|
||||
|
||||
post = {'server': server}
|
||||
created_server = self.api.post_server(post)
|
||||
self.assertTrue(created_server['id'])
|
||||
|
||||
# Wait for it to finish being created
|
||||
found_server = self._wait_for_state_change(self.api, created_server,
|
||||
expected_status)
|
||||
|
||||
return found_server
|
||||
|
||||
def _wait_until_deleted(self, server):
|
||||
try:
|
||||
for i in range(40):
|
||||
server = self.api.get_server(server['id'])
|
||||
if server['status'] == 'ERROR':
|
||||
self.fail('Server went to error state instead of'
|
||||
'disappearing.')
|
||||
time.sleep(0.5)
|
||||
|
||||
self.fail('Server failed to delete.')
|
||||
except api_client.OpenStackApiNotFoundException:
|
||||
return
|
||||
|
@ -0,0 +1,49 @@
|
||||
# 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 nova.tests import fixtures
|
||||
from nova.tests.functional.notification_sample_tests \
|
||||
import notification_sample_base
|
||||
from nova.tests.unit import fake_notifier
|
||||
|
||||
|
||||
class TestInstanceNotificationSample(
|
||||
notification_sample_base.NotificationSampleTestBase):
|
||||
|
||||
def setUp(self):
|
||||
self.flags(use_neutron=True)
|
||||
super(TestInstanceNotificationSample, self).setUp()
|
||||
self.neutron = fixtures.NeutronFixture(self)
|
||||
self.useFixture(self.neutron)
|
||||
|
||||
def test_create_delete_server(self):
|
||||
server = self._boot_a_server(
|
||||
extra_params={'networks': [{'port': self.neutron.port_1['id']}]})
|
||||
|
||||
# TODO(gibi) verify instance.create notifications here when transformed
|
||||
self.api.delete_server(server['id'])
|
||||
self._wait_until_deleted(server)
|
||||
|
||||
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
self._verify_notification(
|
||||
'instance-delete-start',
|
||||
replacements={
|
||||
'reservation_id':
|
||||
notification_sample_base.NotificationSampleTestBase.ANY,
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
|
||||
self._verify_notification(
|
||||
'instance-delete-end',
|
||||
replacements={
|
||||
'reservation_id':
|
||||
notification_sample_base.NotificationSampleTestBase.ANY,
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
@ -119,7 +119,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
event_pwr_state=power_state.SHUTDOWN,
|
||||
current_pwr_state=power_state.RUNNING)
|
||||
|
||||
def test_delete_instance_info_cache_delete_ordering(self):
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_action')
|
||||
def test_delete_instance_info_cache_delete_ordering(self, mock_notify):
|
||||
call_tracker = mock.Mock()
|
||||
call_tracker.clear_events_for_instance.return_value = None
|
||||
mgr_class = self.compute.__class__
|
||||
@ -128,6 +129,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
# spec out everything except for the method we really want
|
||||
# to test, then use call_tracker to verify call sequence
|
||||
specd_compute._delete_instance = orig_delete
|
||||
specd_compute.host = 'compute'
|
||||
|
||||
mock_inst = mock.Mock()
|
||||
mock_inst.uuid = uuids.instance
|
||||
@ -157,6 +159,11 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
'_notify_about_instance_usage',
|
||||
'_shutdown_instance', 'delete'],
|
||||
methods_called)
|
||||
mock_notify.assert_called_once_with(self.context,
|
||||
mock_inst,
|
||||
specd_compute.host,
|
||||
action='delete',
|
||||
phase='start')
|
||||
|
||||
def _make_compute_node(self, hyp_hostname, cn_id):
|
||||
cn = mock.Mock(spec_set=['hypervisor_hostname', 'id',
|
||||
@ -245,7 +252,8 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
else:
|
||||
self.assertFalse(db_node.destroy.called)
|
||||
|
||||
def test_delete_instance_without_info_cache(self):
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_action')
|
||||
def test_delete_instance_without_info_cache(self, mock_notify):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.context,
|
||||
uuid=uuids.instance,
|
||||
@ -267,6 +275,12 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
||||
instance.info_cache = None
|
||||
self.compute._delete_instance(self.context, instance, [], quotas)
|
||||
|
||||
mock_notify.assert_has_calls([
|
||||
mock.call(self.context, instance, 'fake-mini',
|
||||
action='delete', phase='start'),
|
||||
mock.call(self.context, instance, 'fake-mini',
|
||||
action='delete', phase='end')])
|
||||
|
||||
def test_check_device_tagging_no_tagging(self):
|
||||
bdms = objects.BlockDeviceMappingList(objects=[
|
||||
objects.BlockDeviceMapping(source_type='volume',
|
||||
|
@ -486,6 +486,39 @@ class UsageInfoTestCase(test.TestCase):
|
||||
uuids.fake_image_ref)
|
||||
self.assertEqual(payload['image_ref_url'], image_ref_url)
|
||||
|
||||
def test_notify_about_instance_action(self):
|
||||
instance = create_instance(self.context)
|
||||
|
||||
compute_utils.notify_about_instance_action(
|
||||
self.context,
|
||||
instance,
|
||||
host='fake-compute',
|
||||
action='delete',
|
||||
phase='start')
|
||||
|
||||
self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1)
|
||||
notification = fake_notifier.VERSIONED_NOTIFICATIONS[0]
|
||||
|
||||
self.assertEqual(notification['priority'], 'INFO')
|
||||
self.assertEqual(notification['event_type'], 'instance.delete.start')
|
||||
self.assertEqual(notification['publisher_id'],
|
||||
'nova-compute:fake-compute')
|
||||
|
||||
payload = notification['payload']['nova_object.data']
|
||||
self.assertEqual(payload['tenant_id'], self.project_id)
|
||||
self.assertEqual(payload['user_id'], self.user_id)
|
||||
self.assertEqual(payload['uuid'], instance['uuid'])
|
||||
|
||||
flavorid = flavors.get_flavor_by_name('m1.tiny')['flavorid']
|
||||
flavor = payload['flavor']['nova_object.data']
|
||||
self.assertEqual(str(flavor['flavorid']), flavorid)
|
||||
|
||||
for attr in ('display_name', 'created_at', 'launched_at',
|
||||
'state', 'task_state'):
|
||||
self.assertIn(attr, payload, "Key %s not in payload" % attr)
|
||||
|
||||
self.assertEqual(payload['image_uuid'], uuids.fake_image_ref)
|
||||
|
||||
def test_notify_usage_exists_instance_not_found(self):
|
||||
# Ensure 'exists' notification generates appropriate usage data.
|
||||
instance = create_instance(self.context)
|
||||
|
@ -255,9 +255,14 @@ class TestNotificationBase(test.NoDBTestCase):
|
||||
|
||||
|
||||
notification_object_data = {
|
||||
'EventType': '1.1-8291570eed00192197c7fa02ac677cd4',
|
||||
'EventType': '1.2-b81c9f80d9344a24df0b1ecce376b515',
|
||||
'ExceptionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'ExceptionPayload': '1.0-4516ae282a55fe2fd5c754967ee6248b',
|
||||
'FlavorPayload': '1.0-8ad962ab0bafc7270f474c7dda0b7c20',
|
||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionPayload': '1.0-aa6a322cf1a3a19d090259fee65d1094',
|
||||
'InstancePayload': '1.0-878bbc5a7a20bdeac7c6570f438a53aa',
|
||||
'IpPayload': '1.0-26b40117c41ed95a61ae104f0fcb5fdc',
|
||||
'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
|
||||
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'ServiceStatusPayload': '1.0-a5e7b4fd6cc5581be45b31ff1f3a3f7f',
|
||||
|
Loading…
Reference in New Issue
Block a user