Coverage for nova/notifications/objects/base.py: 96%

81 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +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 

17 

18from nova import exception 

19from nova.objects import base 

20from nova.objects import fields 

21from nova import rpc 

22 

23LOG = logging.getLogger(__name__) 

24 

25 

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' 

31 

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) 

39 

40 

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' 

79 

80 fields = { 

81 'object': fields.StringField(nullable=False), 

82 'action': fields.NotificationActionField(nullable=False), 

83 'phase': fields.NotificationPhaseField(nullable=True), 

84 } 

85 

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 

91 

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 

98 

99 

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' 

124 

125 def __init__(self): 

126 super(NotificationPayloadBase, self).__init__() 

127 self.populated = not self.SCHEMA 

128 

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 

132 

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) 

161 

162 self.populated = True 

163 

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) 

167 

168 

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' 

178 

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 } 

185 

186 def __init__(self, host, source): 

187 super(NotificationPublisher, self).__init__() 

188 self.host = host 

189 self.source = source 

190 

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) 

195 

196 

197@base.NovaObjectRegistry.register_if(False) 

198class NotificationBase(NotificationObject): 

199 """Base class for versioned notifications. 

200 

201 Every subclass shall define a 'payload' field. 

202 """ 

203 # Version 1.0: Initial version 

204 VERSION = '1.0' 

205 

206 fields = { 

207 'priority': fields.NotificationPriorityField(), 

208 'event_type': fields.ObjectField('EventType'), 

209 'publisher': fields.ObjectField('NotificationPublisher'), 

210 } 

211 

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) 

216 

217 @rpc.if_notifications_enabled 

218 def emit(self, context): 

219 """Send the notification.""" 

220 assert self.payload.populated 

221 

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) 

226 

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()) 

234 

235 

236def notification_sample(sample): 

237 """Class decorator to attach the notification sample information 

238 to the notification object for documentation generation purposes. 

239 

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