blob: 17241009bbe4d5cdc8aa8dd4067de32d3d93fb45 [file] [log] [blame]
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -05001#
2# Copyright 2017 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16"""
17OMCI Managed Entity Message support base class
18"""
19from voltha.extensions.omci.omci import *
20
21# abbreviations
22OP = EntityOperations
23AA = AttributeAccess
24
25
26class MEFrame(object):
27 """Base class to help simplify Frame Creation"""
28 def __init__(self, entity_class, entity_id, data):
29 assert issubclass(entity_class, EntityClass), \
30 "'{}' must be a subclass of MEFrame".format(entity_class)
31 self.check_type(entity_id, int)
32
33 if not 0 <= entity_id <= 0xFFFF:
34 raise ValueError('entity_id should be 0..65535')
35
36 self.log = structlog.get_logger()
37 self._class = entity_class
38 self._entity_id = entity_id
39 self.data = data
40
41 def __str__(self):
42 return '{}: Entity_ID: {}, Data: {}'.\
43 format(self.entity_class_name, self._entity_id, self.data)
44
45 def __repr__(self):
46 return str(self)
47
48 @property
49 def entity_class(self):
50 """
51 The Entity Class for this ME
52 :return: (EntityClass) Entity class
53 """
54 return self._class
55
56 @property
57 def entity_class_name(self):
58 return self._class.__name__
59
60 @property
61 def entity_id(self):
62 """
63 The Entity ID for this ME frame
64 :return: (int) Entity ID (0..0xFFFF)
65 """
66 return self._entity_id
67
68 @staticmethod
69 def check_type(param, types):
70 if not isinstance(param, types):
71 raise TypeError("Parameter '{}' should be a {}".format(param, types))
72
73 def _check_operation(self, operation):
74 allowed = self.entity_class.mandatory_operations | self.entity_class.optional_operations
75 assert operation in allowed, "{} not allowed for '{}'".format(operation.name,
76 self.entity_class_name)
77
78 def _check_attributes(self, attributes, access):
79 keys = attributes.keys() if isinstance(attributes, dict) else attributes
80 for attr_name in keys:
81 # Bad attribute name (invalid or spelling error)?
82 index = self.entity_class.attribute_name_to_index_map.get(attr_name)
83 if index is None:
84 raise KeyError("Attribute '{}' is not valid for '{}'".
85 format(attr_name, self.entity_class_name))
86 # Invalid access?
87 assert access in self.entity_class.attributes[index].access, \
88 "Access '{}' for attribute '{}' is not valid for '{}'".format(access.name,
89 attr_name,
90 self.entity_class_name)
91
92 if access.value in [AA.W.value, AA.SBC.value] and isinstance(attributes, dict):
93 for attr_name, value in attributes.iteritems():
94 index = self.entity_class.attribute_name_to_index_map.get(attr_name)
95 attribute = self.entity_class.attributes[index]
96 if not attribute.valid(value):
97 raise ValueError("Invalid value '{}' for attribute '{}' of '{}".
98 format(value, attr_name, self.entity_class_name))
99
100 @staticmethod
101 def _attr_to_data(attributes):
102 """
103 Convert an object into the 'data' set or dictionary for get/set/create/delete
104 requests.
105
106 This method takes a 'string', 'list', or 'set' for get requests and
107 converts it to a 'set' of attributes.
108
109 For create/set requests a dictionary of attribute/value pairs is required
110
111 :param attributes: (basestring, list, set, dict) attributes. For gets
112 a string, list, set, or dict can be provided. For create/set
113 operations, a dictionary should be provided. For delete
114 the attributes may be None since they are ignored.
115
116 :return: (set, dict) set for get/deletes, dict for create/set
117 """
118 if isinstance(attributes, basestring):
119 # data = [str(attributes)]
120 data = set()
121 data.add(str(attributes))
122
123 elif isinstance(attributes, list):
124 assert all(isinstance(attr, basestring) for attr in attributes),\
125 'attribute list must be strings'
126 data = {str(attr) for attr in attributes}
127 assert len(data) == len(attributes), 'Attributes were not unique'
128
129 elif isinstance(attributes, set):
130 assert all(isinstance(attr, basestring) for attr in attributes),\
131 'attribute set must be strings'
132 data = {str(attr) for attr in attributes}
133
134 elif isinstance(attributes, (dict, type(None))):
135 data = attributes
136
137 else:
138 raise TypeError("Unsupported attributes type '{}'".format(type(attributes)))
139
140 return data
141
142 def create(self):
143 """
144 Create a Create request frame for this ME
145 :return: (OmciFrame) OMCI Frame
146 """
147 assert hasattr(self.entity_class, 'class_id'), 'class_id required for Create actions'
148 assert hasattr(self, 'entity_id'), 'entity_id required for Create actions'
149 assert hasattr(self, 'data'), 'data required for Create actions'
150
151 data = getattr(self, 'data')
152 MEFrame.check_type(data, dict)
153 assert len(data) > 0, 'No attributes supplied'
154
155 self._check_operation(OP.Create)
156 self._check_attributes(data, AA.Writable)
157
158 return OmciFrame(
159 transaction_id=None,
160 message_type=OmciCreate.message_id,
161 omci_message=OmciCreate(
162 entity_class=getattr(self.entity_class, 'class_id'),
163 entity_id=getattr(self, 'entity_id'),
164 data=data
165 ))
166
167 def delete(self):
168 """
169 Create a Delete request frame for this ME
170 :return: (OmciFrame) OMCI Frame
171 """
172 self._check_operation(OP.Delete)
173
174 return OmciFrame(
175 transaction_id=None,
176 message_type=OmciDelete.message_id,
177 omci_message=OmciDelete(
178 entity_class=getattr(self.entity_class, 'class_id'),
179 entity_id=getattr(self, 'entity_id')
180 ))
181
182 def set(self):
183 """
184 Create a Set request frame for this ME
185 :return: (OmciFrame) OMCI Frame
186 """
187 assert hasattr(self, 'data'), 'data required for Set actions'
188 data = getattr(self, 'data')
189 MEFrame.check_type(data, dict)
190 assert len(data) > 0, 'No attributes supplied'
191
192 self._check_operation(OP.Set)
193 self._check_attributes(data, AA.Writable)
194
195 return OmciFrame(
196 transaction_id=None,
197 message_type=OmciSet.message_id,
198 omci_message=OmciSet(
199 entity_class=getattr(self.entity_class, 'class_id'),
200 entity_id=getattr(self, 'entity_id'),
201 attributes_mask=self.entity_class.mask_for(*data.keys()),
202 data=data
203 ))
204
205 def get(self):
206 """
207 Create a Get request frame for this ME
208 :return: (OmciFrame) OMCI Frame
209 """
210 assert hasattr(self, 'data'), 'data required for Get actions'
211 data = getattr(self, 'data')
212 MEFrame.check_type(data, (list, set, dict))
213 assert len(data) > 0, 'No attributes supplied'
214
215 mask_set = data.keys() if isinstance(data, dict) else data
216
217 self._check_operation(OP.Get)
218 self._check_attributes(mask_set, AA.Readable)
219
220 return OmciFrame(
221 transaction_id=None,
222 message_type=OmciGet.message_id,
223 omci_message=OmciGet(
224 entity_class=getattr(self.entity_class, 'class_id'),
225 entity_id=getattr(self, 'entity_id'),
226 attributes_mask=self.entity_class.mask_for(*mask_set)
227 ))
228
229 def reboot(self, reboot_code=0):
230 """
231 Create a Reboot request from for this ME
232 :return: (OmciFrame) OMCI Frame
233 """
234 self._check_operation(OP.Reboot)
235 assert 0 <= reboot_code <= 2, 'Reboot code must be 0..2'
236
237 return OmciFrame(
238 transaction_id=None,
239 message_type=OmciReboot.message_id,
240 omci_message=OmciReboot(
241 entity_class=getattr(self.entity_class, 'class_id'),
242 entity_id=getattr(self, 'entity_id'),
243 reboot_code=reboot_code
244 ))
245
246 def mib_reset(self):
247 """
248 Create a MIB Reset request from for this ME
249 :return: (OmciFrame) OMCI Frame
250 """
251 self._check_operation(OP.MibReset)
252
253 return OmciFrame(
254 transaction_id=None,
255 message_type=OmciMibReset.message_id,
256 omci_message=OmciMibReset(
257 entity_class=getattr(self.entity_class, 'class_id'),
258 entity_id=getattr(self, 'entity_id')
259 ))
260
261 def mib_upload(self):
262 """
263 Create a MIB Upload request from for this ME
264 :return: (OmciFrame) OMCI Frame
265 """
266 self._check_operation(OP.MibUpload)
267
268 return OmciFrame(
269 transaction_id=None,
270 message_type=OmciMibUpload.message_id,
271 omci_message=OmciMibUpload(
272 entity_class=getattr(self.entity_class, 'class_id'),
273 entity_id=getattr(self, 'entity_id')
274 ))
275
276 def mib_upload_next(self):
277 """
278 Create a MIB Upload Next request from for this ME
279 :return: (OmciFrame) OMCI Frame
280 """
281 assert hasattr(self, 'data'), 'data required for Set actions'
282 data = getattr(self, 'data')
283 MEFrame.check_type(data, dict)
284 assert len(data) > 0, 'No attributes supplied'
285 assert 'mib_data_sync' in data, "'mib_data_sync' not in attributes list"
286
287 self._check_operation(OP.MibUploadNext)
288 self._check_attributes(data, AA.Writable)
289
290 return OmciFrame(
291 transaction_id=None,
292 message_type=OmciMibUploadNext.message_id,
293 omci_message=OmciMibUploadNext(
294 entity_class=getattr(self.entity_class, 'class_id'),
295 entity_id=getattr(self, 'entity_id'),
296 command_sequence_number=data['mib_data_sync']
297 ))
298
299 def get_next(self):
300 """
301 Create a Get Next request frame for this ME
302 :return: (OmciFrame) OMCI Frame
303 """
304 assert hasattr(self, 'data'), 'data required for Get Next actions'
305 data = getattr(self, 'data')
306 MEFrame.check_type(data, dict)
307 assert len(data) == 1, 'Only one attribute should be specified'
308
309 mask_set = data.keys() if isinstance(data, dict) else data
310
311 self._check_operation(OP.GetNext)
312 self._check_attributes(mask_set, AA.Readable)
313
314 return OmciFrame(
315 transaction_id=None,
316 message_type=OmciGetNext.message_id,
317 omci_message=OmciGetNext(
318 entity_class=getattr(self.entity_class, 'class_id'),
319 entity_id=getattr(self, 'entity_id'),
320 attributes_mask=self.entity_class.mask_for(*mask_set),
321 command_sequence_number=data.values()[0]
322 ))
323
324 def synchronize_time(self, time=None):
325 """
326 Create a Synchronize Time request from for this ME
327 :param time: (DateTime) Time to set to. If none, use UTC
328 :return: (OmciFrame) OMCI Frame
329 """
330 from datetime import datetime
331 self._check_operation(OP.SynchronizeTime)
332 dt = time or datetime.utcnow()
333
334 return OmciFrame(
335 transaction_id=None,
336 message_type=OmciSynchronizeTime.message_id,
337 omci_message=OmciSynchronizeTime(
338 entity_class=getattr(self.entity_class, 'class_id'),
339 entity_id=getattr(self, 'entity_id'),
340 year=dt.year,
341 month=dt.month,
342 hour=dt.hour,
343 minute=dt.minute,
344 second=dt.second,
345 ))
346
347 def get_all_alarm(self, alarm_retrieval_mode):
348 """
349 Create a Alarm request from for this ME
350 :return: (OmciFrame) OMCI Frame
351 """
352 self._check_operation(OP.GetAllAlarms)
353 assert 0 <= alarm_retrieval_mode <= 1, 'Alarm retrieval mode must be 0..1'
354
355 return OmciFrame(
356 transaction_id=None,
357 message_type=OmciGetAllAlarms.message_id,
358 omci_message=OmciGetAllAlarms(
359 entity_class=getattr(self.entity_class, 'class_id'),
360 entity_id=getattr(self, 'entity_id'),
361 alarm_retrieval_mode=alarm_retrieval_mode
362 ))
363
364 def get_all_alarm_next(self, command_sequence_number):
365 """
366 Create a Alarm request from for this ME
367 :return: (OmciFrame) OMCI Frame
368 """
369 self._check_operation(OP.GetAllAlarmsNext)
370
371 return OmciFrame(
372 transaction_id=None,
373 message_type=OmciGetAllAlarmsNext.message_id,
374 omci_message=OmciGetAllAlarmsNext(
375 entity_class=getattr(self.entity_class, 'class_id'),
376 entity_id=getattr(self, 'entity_id'),
377 command_sequence_number=command_sequence_number
378 ))
379
380 def start_software_download(self, image_size, window_size):
381 """
382 Create Start Software Download message
383 :return: (OmciFrame) OMCI Frame
384 """
385 self.log.debug("--> start_software_download")
386 self._check_operation(OP.StartSoftwareDownload)
387 return OmciFrame(
388 transaction_id=None,
389 message_type=OmciStartSoftwareDownload.message_id,
390 omci_message=OmciStartSoftwareDownload(
391 entity_class=getattr(self.entity_class, 'class_id'),
392 entity_id=getattr(self, 'entity_id'),
393 window_size=window_size,
394 image_size=image_size,
395 instance_id=getattr(self, 'entity_id')
396 ))
397
398 def end_software_download(self, crc32, image_size):
399 """
400 Create End Software Download message
401 :return: (OmciFrame) OMCI Frame
402 """
403 self._check_operation(OP.EndSoftwareDownload)
404 return OmciFrame(
405 transaction_id=None,
406 message_type=OmciEndSoftwareDownload.message_id,
407 omci_message=OmciEndSoftwareDownload(
408 entity_class=getattr(self.entity_class, 'class_id'),
409 entity_id=getattr(self, 'entity_id'),
410 crc32=crc32,
411 image_size=image_size,
412 instance_id=getattr(self, 'entity_id')
413 ))
414
415 def download_section(self, is_last_section, section_number, data):
416 """
417 Create Download Section message
418 :is_last_section: (bool) indicate the last section in the window
419 :section_num : (int) current section number
420 :data : (byte) data to be sent in the section
421 :return: (OmciFrame) OMCI Frame
422 """
423 self.log.debug("--> download_section: ", section_number=section_number)
424
425 self._check_operation(OP.DownloadSection)
426 if is_last_section:
427 return OmciFrame(
428 transaction_id=None,
429 message_type=OmciDownloadSectionLast.message_id,
430 omci_message=OmciDownloadSectionLast(
431 entity_class=getattr(self.entity_class, 'class_id'),
432 entity_id=getattr(self, 'entity_id'),
433 section_number=section_number,
434 data=data
435 ))
436 else:
437 return OmciFrame(
438 transaction_id=None,
439 message_type=OmciDownloadSection.message_id,
440 omci_message=OmciDownloadSection(
441 entity_class=getattr(self.entity_class, 'class_id'),
442 entity_id=getattr(self, 'entity_id'),
443 section_number=section_number,
444 data=data
445 ))
446
447 def activate_image(self, activate_flag=0):
448 """
449 Activate Image message
450 :activate_flag: 00 Activate image unconditionally
451 01 Activate image only if no POTS/VoIP calls are in progress
452 10 Activate image only if no emergency call is in progress
453 :return: (OmciFrame) OMCI Frame
454 """
455 self.log.debug("--> activate_image", entity=self.entity_id, flag=activate_flag)
456 return OmciFrame(
457 transaction_id=None,
458 message_type=OmciActivateImage.message_id,
459 omci_message=OmciActivateImage(
460 entity_class=getattr(self.entity_class, 'class_id'),
461 entity_id=getattr(self, 'entity_id'),
462 activate_flag=activate_flag
463 ))
464
465 def commit_image(self):
466 """
467 Commit Image message
468 :return: (OmciFrame) OMCI Frame
469 """
470 self.log.debug("--> commit_image", entity=self.entity_id)
471 return OmciFrame(
472 transaction_id=None,
473 message_type=OmciCommitImage.message_id,
474 omci_message=OmciCommitImage(
475 entity_class=getattr(self.entity_class, 'class_id'),
476 entity_id=getattr(self, 'entity_id'),
477 ))
478