blob: b6a4133aa7e13fa43e3062ed8c2bf6ff20ab5366 [file] [log] [blame]
Chip Boling72bbcfe2018-02-14 14:27:59 -06001#
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
lcui33d6a8e2018-08-28 12:51:38 -070036 self.log = structlog.get_logger()
Chip Boling72bbcfe2018-02-14 14:27:59 -060037 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
Matt Jeanneret49b764b2019-01-22 17:50:36 -050045 def __repr__(self):
46 return str(self)
47
Chip Boling72bbcfe2018-02-14 14:27:59 -060048 @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,
Chip Boling2a059952018-03-12 15:50:00 -0500176 message_type=OmciDelete.message_id,
177 omci_message=OmciDelete(
Chip Boling72bbcfe2018-02-14 14:27:59 -0600178 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
Chip Boling21f88222018-07-17 13:25:11 -0500229 def reboot(self, reboot_code=0):
Chip Boling72bbcfe2018-02-14 14:27:59 -0600230 """
231 Create a Reboot request from for this ME
232 :return: (OmciFrame) OMCI Frame
233 """
234 self._check_operation(OP.Reboot)
Chip Boling21f88222018-07-17 13:25:11 -0500235 assert 0 <= reboot_code <= 2, 'Reboot code must be 0..2'
Chip Boling72bbcfe2018-02-14 14:27:59 -0600236
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'),
Chip Boling21f88222018-07-17 13:25:11 -0500242 entity_id=getattr(self, 'entity_id'),
243 reboot_code=reboot_code
Chip Boling72bbcfe2018-02-14 14:27:59 -0600244 ))
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 ))
Chip Boling28155862018-06-07 11:13:39 -0500298
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 ))
Chip Bolingac7b5622018-07-12 18:53:55 -0500323
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,
Chip Bolingcb8b5972019-03-04 13:33:22 -0600342 day=dt.day,
Chip Bolingac7b5622018-07-12 18:53:55 -0500343 hour=dt.hour,
344 minute=dt.minute,
345 second=dt.second,
346 ))
jasonhuang5f3e63b2018-07-27 01:32:48 +0800347
348 def get_all_alarm(self, alarm_retrieval_mode):
349 """
350 Create a Alarm request from for this ME
351 :return: (OmciFrame) OMCI Frame
352 """
353 self._check_operation(OP.GetAllAlarms)
Chip Boling86221f62018-08-09 15:24:48 -0500354 assert 0 <= alarm_retrieval_mode <= 1, 'Alarm retrieval mode must be 0..1'
jasonhuang5f3e63b2018-07-27 01:32:48 +0800355
356 return OmciFrame(
357 transaction_id=None,
358 message_type=OmciGetAllAlarms.message_id,
359 omci_message=OmciGetAllAlarms(
360 entity_class=getattr(self.entity_class, 'class_id'),
361 entity_id=getattr(self, 'entity_id'),
Chip Boling86221f62018-08-09 15:24:48 -0500362 alarm_retrieval_mode=alarm_retrieval_mode
jasonhuang5f3e63b2018-07-27 01:32:48 +0800363 ))
364
365 def get_all_alarm_next(self, command_sequence_number):
366 """
367 Create a Alarm request from for this ME
368 :return: (OmciFrame) OMCI Frame
369 """
370 self._check_operation(OP.GetAllAlarmsNext)
371
372 return OmciFrame(
373 transaction_id=None,
374 message_type=OmciGetAllAlarmsNext.message_id,
375 omci_message=OmciGetAllAlarmsNext(
376 entity_class=getattr(self.entity_class, 'class_id'),
377 entity_id=getattr(self, 'entity_id'),
378 command_sequence_number=command_sequence_number
lcui33d6a8e2018-08-28 12:51:38 -0700379 ))
380
381 def start_software_download(self, image_size, window_size):
382 """
383 Create Start Software Download message
384 :return: (OmciFrame) OMCI Frame
385 """
386 self.log.debug("--> start_software_download")
387 self._check_operation(OP.StartSoftwareDownload)
388 return OmciFrame(
389 transaction_id=None,
390 message_type=OmciStartSoftwareDownload.message_id,
391 omci_message=OmciStartSoftwareDownload(
392 entity_class=getattr(self.entity_class, 'class_id'),
393 entity_id=getattr(self, 'entity_id'),
394 window_size=window_size,
395 image_size=image_size,
396 instance_id=getattr(self, 'entity_id')
397 ))
398
399 def end_software_download(self, crc32, image_size):
400 """
401 Create End Software Download message
402 :return: (OmciFrame) OMCI Frame
403 """
404 self._check_operation(OP.EndSoftwareDownload)
405 return OmciFrame(
406 transaction_id=None,
407 message_type=OmciEndSoftwareDownload.message_id,
408 omci_message=OmciEndSoftwareDownload(
409 entity_class=getattr(self.entity_class, 'class_id'),
410 entity_id=getattr(self, 'entity_id'),
411 crc32=crc32,
412 image_size=image_size,
413 instance_id=getattr(self, 'entity_id')
414 ))
415
416 def download_section(self, is_last_section, section_number, data):
417 """
418 Create Download Section message
419 :is_last_section: (bool) indicate the last section in the window
420 :section_num : (int) current section number
421 :data : (byte) data to be sent in the section
422 :return: (OmciFrame) OMCI Frame
423 """
424 self.log.debug("--> download_section: ", section_number=section_number)
425
426 self._check_operation(OP.DownloadSection)
427 if is_last_section:
428 return OmciFrame(
429 transaction_id=None,
430 message_type=OmciDownloadSectionLast.message_id,
431 omci_message=OmciDownloadSectionLast(
432 entity_class=getattr(self.entity_class, 'class_id'),
433 entity_id=getattr(self, 'entity_id'),
434 section_number=section_number,
435 data=data
436 ))
437 else:
438 return OmciFrame(
439 transaction_id=None,
440 message_type=OmciDownloadSection.message_id,
441 omci_message=OmciDownloadSection(
442 entity_class=getattr(self.entity_class, 'class_id'),
443 entity_id=getattr(self, 'entity_id'),
444 section_number=section_number,
445 data=data
446 ))
447
448 def activate_image(self, activate_flag=0):
449 """
450 Activate Image message
451 :activate_flag: 00 Activate image unconditionally
452 01 Activate image only if no POTS/VoIP calls are in progress
453 10 Activate image only if no emergency call is in progress
454 :return: (OmciFrame) OMCI Frame
455 """
456 self.log.debug("--> activate_image", entity=self.entity_id, flag=activate_flag)
457 return OmciFrame(
458 transaction_id=None,
459 message_type=OmciActivateImage.message_id,
460 omci_message=OmciActivateImage(
461 entity_class=getattr(self.entity_class, 'class_id'),
462 entity_id=getattr(self, 'entity_id'),
463 activate_flag=activate_flag
464 ))
465
466 def commit_image(self):
467 """
468 Commit Image message
469 :return: (OmciFrame) OMCI Frame
470 """
471 self.log.debug("--> commit_image", entity=self.entity_id)
472 return OmciFrame(
473 transaction_id=None,
474 message_type=OmciCommitImage.message_id,
475 omci_message=OmciCommitImage(
476 entity_class=getattr(self.entity_class, 'class_id'),
477 entity_id=getattr(self, 'entity_id'),
478 ))
479