Coverage for nova/notifications/objects/base.py: 96%
81 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14from oslo_log import log as logging
15from oslo_utils import excutils
16from oslo_versionedobjects import exception as ovo_exception
18from nova import exception
19from nova.objects import base
20from nova.objects import fields
21from nova import rpc
23LOG = logging.getLogger(__name__)
26@base.NovaObjectRegistry.register_if(False)
27class NotificationObject(base.NovaObject):
28 """Base class for every notification related versioned object."""
29 # Version 1.0: Initial version
30 VERSION = '1.0'
32 def __init__(self, **kwargs):
33 super(NotificationObject, self).__init__(**kwargs)
34 # The notification objects are created on the fly when nova emits the
35 # notification. This causes that every object shows every field as
36 # changed. We don't want to send this meaningless information so we
37 # reset the object after creation.
38 self.obj_reset_changes(recursive=False)
41@base.NovaObjectRegistry.register_notification
42class EventType(NotificationObject):
43 # Version 1.0: Initial version
44 # Version 1.1: New valid actions values are added to the
45 # NotificationActionField enum
46 # Version 1.2: DELETE value is added to the NotificationActionField enum
47 # Version 1.3: Set of new values are added to NotificationActionField enum
48 # Version 1.4: Another set of new values are added to
49 # NotificationActionField enum
50 # Version 1.5: Aggregate related values have been added to
51 # NotificationActionField enum
52 # Version 1.6: ADD_FIX_IP replaced with INTERFACE_ATTACH in
53 # NotificationActionField enum
54 # Version 1.7: REMOVE_FIXED_IP replaced with INTERFACE_DETACH in
55 # NotificationActionField enum
56 # Version 1.8: IMPORT value is added to NotificationActionField enum
57 # Version 1.9: ADD_MEMBER value is added to NotificationActionField enum
58 # Version 1.10: UPDATE_METADATA value is added to the
59 # NotificationActionField enum
60 # Version 1.11: LOCK is added to NotificationActionField enum
61 # Version 1.12: UNLOCK is added to NotificationActionField enum
62 # Version 1.13: REBUILD_SCHEDULED value is added to the
63 # NotificationActionField enum
64 # Version 1.14: UPDATE_PROP value is added to the NotificationActionField
65 # enum
66 # Version 1.15: LIVE_MIGRATION_FORCE_COMPLETE is added to the
67 # NotificationActionField enum
68 # Version 1.16: CONNECT is added to NotificationActionField enum
69 # Version 1.17: USAGE is added to NotificationActionField enum
70 # Version 1.18: ComputeTask related values have been added to
71 # NotificationActionField enum
72 # Version 1.19: SELECT_DESTINATIONS is added to the NotificationActionField
73 # enum
74 # Version 1.20: IMAGE_CACHE is added to the NotificationActionField enum
75 # Version 1.21: PROGRESS added to NotificationPhase enum
76 # Version 1.22: SHARE_ATTACH SHARE_DETACH are added to the
77 # NotificationActionField enum
78 VERSION = '1.22'
80 fields = {
81 'object': fields.StringField(nullable=False),
82 'action': fields.NotificationActionField(nullable=False),
83 'phase': fields.NotificationPhaseField(nullable=True),
84 }
86 def __init__(self, object, action, phase=None):
87 super(EventType, self).__init__()
88 self.object = object
89 self.action = action
90 self.phase = phase
92 def to_notification_event_type_field(self):
93 """Serialize the object to the wire format."""
94 s = '%s.%s' % (self.object, self.action)
95 if self.phase:
96 s += '.%s' % self.phase
97 return s
100@base.NovaObjectRegistry.register_if(False)
101class NotificationPayloadBase(NotificationObject):
102 """Base class for the payload of versioned notifications."""
103 # SCHEMA defines how to populate the payload fields. It is a dictionary
104 # where every key value pair has the following format:
105 # <payload_field_name>: (<data_source_name>,
106 # <field_of_the_data_source>)
107 # The <payload_field_name> is the name where the data will be stored in the
108 # payload object, this field has to be defined as a field of the payload.
109 # The <data_source_name> shall refer to name of the parameter passed as
110 # kwarg to the payload's populate_schema() call and this object will be
111 # used as the source of the data. The <field_of_the_data_source> shall be
112 # a valid field of the passed argument.
113 # The SCHEMA needs to be applied with the populate_schema() call before the
114 # notification can be emitted.
115 # The value of the payload.<payload_field_name> field will be set by the
116 # <data_source_name>.<field_of_the_data_source> field. The
117 # <data_source_name> will not be part of the payload object internal or
118 # external representation.
119 # Payload fields that are not set by the SCHEMA can be filled in the same
120 # way as in any versioned object.
121 SCHEMA = {}
122 # Version 1.0: Initial version
123 VERSION = '1.0'
125 def __init__(self):
126 super(NotificationPayloadBase, self).__init__()
127 self.populated = not self.SCHEMA
129 @rpc.if_notifications_enabled
130 def populate_schema(self, set_none=True, **kwargs):
131 """Populate the object based on the SCHEMA and the source objects
133 :param kwargs: A dict contains the source object at the key defined in
134 the SCHEMA
135 """
136 for key, (obj, field) in self.SCHEMA.items():
137 source = kwargs[obj]
138 # trigger lazy-load if possible
139 try:
140 setattr(self, key, getattr(source, field))
141 # ObjectActionError - not lazy loadable field
142 # NotImplementedError - obj_load_attr() is not even defined
143 # OrphanedObjectError - lazy loadable field but context is None
144 except (exception.ObjectActionError,
145 NotImplementedError,
146 exception.OrphanedObjectError,
147 ovo_exception.OrphanedObjectError):
148 if set_none: 148 ↛ 136line 148 didn't jump to line 136 because the condition on line 148 was always true
149 # If it is unset or non lazy loadable in the source object
150 # then we cannot do anything else but try to default it
151 # in the payload object we are generating here.
152 # NOTE(gibi): This will fail if the payload field is not
153 # nullable, but that means that either the source object
154 # is not properly initialized or the payload field needs
155 # to be defined as nullable
156 setattr(self, key, None)
157 except Exception:
158 with excutils.save_and_reraise_exception():
159 LOG.error('Failed trying to populate attribute "%s" '
160 'using field: %s', key, field)
162 self.populated = True
164 # the schema population will create changed fields but we don't need
165 # this information in the notification
166 self.obj_reset_changes(recursive=True)
169@base.NovaObjectRegistry.register_notification
170class NotificationPublisher(NotificationObject):
171 # Version 1.0: Initial version
172 # 2.0: The binary field has been renamed to source
173 # 2.1: The type of the source field changed from string to enum.
174 # This only needs a minor bump as the enum uses the possible
175 # values of the previous string field
176 # 2.2: New enum for source fields added
177 VERSION = '2.2'
179 # TODO(stephenfin): Remove 'nova-cells' from 'NotificationSourceField' enum
180 # when bumping this object to version 3.0
181 fields = {
182 'host': fields.StringField(nullable=False),
183 'source': fields.NotificationSourceField(nullable=False),
184 }
186 def __init__(self, host, source):
187 super(NotificationPublisher, self).__init__()
188 self.host = host
189 self.source = source
191 @classmethod
192 def from_service_obj(cls, service):
193 source = fields.NotificationSource.get_source_by_binary(service.binary)
194 return cls(host=service.host, source=source)
197@base.NovaObjectRegistry.register_if(False)
198class NotificationBase(NotificationObject):
199 """Base class for versioned notifications.
201 Every subclass shall define a 'payload' field.
202 """
203 # Version 1.0: Initial version
204 VERSION = '1.0'
206 fields = {
207 'priority': fields.NotificationPriorityField(),
208 'event_type': fields.ObjectField('EventType'),
209 'publisher': fields.ObjectField('NotificationPublisher'),
210 }
212 def _emit(self, context, event_type, publisher_id, payload):
213 notifier = rpc.get_versioned_notifier(publisher_id)
214 notify = getattr(notifier, self.priority)
215 notify(context, event_type=event_type, payload=payload)
217 @rpc.if_notifications_enabled
218 def emit(self, context):
219 """Send the notification."""
220 assert self.payload.populated
222 # Note(gibi): notification payload will be a newly populated object
223 # therefore every field of it will look changed so this does not carry
224 # any extra information so we drop this from the payload.
225 self.payload.obj_reset_changes(recursive=True)
227 self._emit(context,
228 event_type=
229 self.event_type.to_notification_event_type_field(),
230 publisher_id='%s:%s' %
231 (self.publisher.source,
232 self.publisher.host),
233 payload=self.payload.obj_to_primitive())
236def notification_sample(sample):
237 """Class decorator to attach the notification sample information
238 to the notification object for documentation generation purposes.
240 :param sample: the path of the sample json file relative to the
241 doc/notification_samples/ directory in the nova repository
242 root.
243 """
244 def wrap(cls):
245 if not getattr(cls, 'samples', None):
246 cls.samples = [sample]
247 else:
248 cls.samples.append(sample)
249 return cls
250 return wrap