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