blob: 6b49e2b19518aed2bcd836b5814ab2794abc5f0d [file] [log] [blame]
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -05001#
2# Copyright 2018 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#
16from mib_db_api import *
17from voltha.protos.omci_mib_db_pb2 import MibInstanceData, MibClassData, \
18 MibDeviceData, MibAttributeData, MessageType, ManagedEntity
19from voltha.extensions.omci.omci_entities import *
20from voltha.extensions.omci.omci_fields import *
21from scapy.fields import StrField, FieldListField, PacketField
22
23
24class MibDbStatistic(object):
25 """
26 For debug/tuning purposes.
27
28 With etcd around the v1.5 time frame, seeing the following:
29
30 o Creates: Avg: 57.1 mS, Min: 76 mS, Max: 511 mS (146 samples)
31 o Sets: Avg: 303.9 mS, Min: 126 mS, Max: 689 mS (103 samples)
32 o Gets: Avg: 3.3 mS, Min: 0 mS, Max: 8 mS ( 9 samples)
33 o Deletes: No samples
34 """
35 def __init__(self, name):
36 self._name = name
37 self._count = 0
38 self._total_time = 0 # Total milliseconds
39 self._min_time = 99999999
40 self._max_time = 0
41
42 def get_statistics(self):
43 return {
44 'name': self._name,
45 'count': self._count,
46 'total_time': self._total_time,
47 'min_time': self._min_time,
48 'max_time': self._max_time,
49 'avg_time': self._total_time / self._count if self._count > 0 else 0
50 }
51
52 def clear_statistics(self):
53 self._count = 0
54 self._total_time = 0 # Total milliseconds
55 self._min_time = 99999999
56 self._max_time = 0
57
58 def increment(self, time):
59 self._count += 1
60 self._total_time += time # Total milliseconds
61 if self._min_time > time:
62 self._min_time = time
63 if self._max_time < time:
64 self._max_time = time
65
66
67class MibDbExternal(MibDbApi):
68 """
69 A persistent external OpenOMCI MIB Database
70 """
71 CURRENT_VERSION = 1 # VOLTHA v1.3.0 release
72
73 _TIME_FORMAT = '%Y%m%d-%H%M%S.%f'
74
75 # Paths from root proxy
76 MIB_PATH = '/omci_mibs'
77 DEVICE_PATH = MIB_PATH + '/{}' # .format(device_id)
78
79 # Classes, Instances, and Attributes as lists from root proxy
80 CLASSES_PATH = DEVICE_PATH + '/classes' # .format(device_id)
81 INSTANCES_PATH = DEVICE_PATH + '/classes/{}/instances' # .format(device_id, class_id)
82 ATTRIBUTES_PATH = DEVICE_PATH + '/classes/{}/instances/{}/attributes' # .format(device_id, class_id, instance_id)
83
84 # Single Class, Instance, and Attribute as objects from device proxy
85 CLASS_PATH = '/classes/{}' # .format(class_id)
86 INSTANCE_PATH = '/classes/{}/instances/{}' # .format(class_id, instance_id)
87 ATTRIBUTE_PATH = '/classes/{}/instances/{}/attributes/{}' # .format(class_id, instance_id
88 # attribute_name)
89
90 def __init__(self, omci_agent):
91 """
92 Class initializer
93 :param omci_agent: (OpenOMCIAgent) OpenOMCI Agent
94 """
95 super(MibDbExternal, self).__init__(omci_agent)
96 self._core = omci_agent.core
97 # Some statistics to help with debug/tuning/...
98 self._statistics = {
99 'get': MibDbStatistic('get'),
100 'set': MibDbStatistic('set'),
101 'create': MibDbStatistic('create'),
102 'delete': MibDbStatistic('delete')
103 }
104
105 def start(self):
106 """
107 Start up/restore the database
108 """
109 self.log.debug('start')
110
111 if not self._started:
112 super(MibDbExternal, self).start()
113 root_proxy = self._core.get_proxy('/')
114
115 try:
116 base = root_proxy.get(MibDbExternal.MIB_PATH)
117 self.log.info('db-exists', num_devices=len(base))
118
119 except Exception as e:
120 self.log.exception('start-failure', e=e)
121 raise
122
123 def stop(self):
124 """
125 Start up the database
126 """
127 self.log.debug('stop')
128
129 if self._started:
130 super(MibDbExternal, self).stop()
131 # TODO: Delete this method if nothing else is done except calling the base class
132
133 def _time_to_string(self, time):
134 return time.strftime(MibDbExternal._TIME_FORMAT) if time is not None else ''
135
136 def _string_to_time(self, time):
137 return datetime.strptime(time, MibDbExternal._TIME_FORMAT) if len(time) else None
138
139 def _attribute_to_string(self, device_id, class_id, attr_name, value, old_value = None):
140 """
141 Convert an ME's attribute value to string representation
142
143 :param device_id: (str) ONU Device ID
144 :param class_id: (int) Class ID
145 :param attr_name: (str) Attribute Name (see EntityClasses)
146 :param value: (various) Attribute Value
147
148 :return: (str) String representation of the value
149 :raises KeyError: Device, Class ID, or Attribute does not exist
150 """
151 try:
152 me_map = self._omci_agent.get_device(device_id).me_map
153
154 if class_id in me_map:
155 entity = me_map[class_id]
156 attr_index = entity.attribute_name_to_index_map[attr_name]
157 eca = entity.attributes[attr_index]
158 field = eca.field
159 else:
160 # Here for auto-defined MEs (ones not defined in ME Map)
161 from voltha.extensions.omci.omci_cc import UNKNOWN_CLASS_ATTRIBUTE_KEY
162 field = StrFixedLenField(UNKNOWN_CLASS_ATTRIBUTE_KEY, None, 24)
163
164 if isinstance(field, StrFixedLenField):
165 from scapy.base_classes import Packet_metaclass
166 if hasattr(value, 'to_json') and not isinstance(value, basestring):
167 # Packet Class to string
168 str_value = value.to_json()
169 elif isinstance(field.default, Packet_metaclass) \
170 and hasattr(field.default, 'json_from_value'):
171 #and not isinstance(value, basestring):
172 # Value/hex of Packet Class to string
173 str_value = field.default.json_from_value(value)
174 else:
175 str_value = str(value)
176
177 elif isinstance(field, (StrField, MACField, IPField)):
178 # For StrField, value is an str already
179 # For MACField, value is a string in ':' delimited form
180 # For IPField, value is a string in '.' delimited form
181 str_value = str(value)
182
183 elif isinstance(field, (ByteField, ShortField, IntField, LongField)):
184 # For ByteField, ShortField, IntField, and LongField value is an int
185 str_value = str(value)
186
187 elif isinstance(field, BitField):
188 # For BitField, value is a long
189 #
190 str_value = str(value)
191
192 elif hasattr(field, 'to_json'):
193 str_value = field.to_json(value, old_value)
194
195 elif isinstance(field, FieldListField):
196 str_value = json.dumps(value, separators=(',', ':'))
197
198 else:
199 self.log.warning('default-conversion', type=type(field),
200 class_id=class_id, attribute=attr_name, value=str(value))
201 str_value = str(value)
202
203 return str_value
204
205 except Exception as e:
206 self.log.exception('attr-to-string', device_id=device_id,
207 class_id=class_id, attr=attr_name,
208 value=value, e=e)
209 raise
210
211 def _string_to_attribute(self, device_id, class_id, attr_name, str_value):
212 """
213 Convert an ME's attribute value-string to its Scapy decode equivalent
214
215 :param device_id: (str) ONU Device ID
216 :param class_id: (int) Class ID
217 :param attr_name: (str) Attribute Name (see EntityClasses)
218 :param str_value: (str) Attribute Value in string form
219
220 :return: (various) String representation of the value
221 :raises KeyError: Device, Class ID, or Attribute does not exist
222 """
223 try:
224 me_map = self._omci_agent.get_device(device_id).me_map
225
226 if class_id in me_map:
227 entity = me_map[class_id]
228 attr_index = entity.attribute_name_to_index_map[attr_name]
229 eca = entity.attributes[attr_index]
230 field = eca.field
231 else:
232 # Here for auto-defined MEs (ones not defined in ME Map)
233 from voltha.extensions.omci.omci_cc import UNKNOWN_CLASS_ATTRIBUTE_KEY
234 field = StrFixedLenField(UNKNOWN_CLASS_ATTRIBUTE_KEY, None, 24)
235
236 if isinstance(field, StrFixedLenField):
237 from scapy.base_classes import Packet_metaclass
238 default = field.default
239 if isinstance(default, Packet_metaclass) and \
240 hasattr(default, 'to_json'):
241 value = json.loads(str_value)
242 else:
243 value = str_value
244
245 elif isinstance(field, MACField):
246 value = str_value
247
248 elif isinstance(field, IPField):
249 value = str_value
250
251 elif isinstance(field, (ByteField, ShortField, IntField, LongField)):
252 if str_value.lower() in ('true', 'false'):
253 str_value = '1' if str_value.lower() == 'true' else '0'
254 value = int(str_value)
255
256 elif isinstance(field, BitField):
257 value = long(str_value)
258
259 elif hasattr(field, 'load_json'):
260 value = field.load_json(str_value)
261
262 elif isinstance(field, FieldListField):
263 value = json.loads(str_value)
264
265 else:
266 self.log.warning('default-conversion', type=type(field),
267 class_id=class_id, attribute=attr_name, value=str_value)
268 value = None
269
270 return value
271
272 except Exception as e:
273 self.log.exception('attr-to-string', device_id=device_id,
274 class_id=class_id, attr=attr_name,
275 value=str_value, e=e)
276 raise
277
278 def add(self, device_id, overwrite=False):
279 """
280 Add a new ONU to database
281
282 :param device_id: (str) Device ID of ONU to add
283 :param overwrite: (bool) Overwrite existing entry if found.
284
285 :raises KeyError: If device already exists and 'overwrite' is False
286 """
287 self.log.debug('add-device', device_id=device_id, overwrite=overwrite)
288
289 now = datetime.utcnow()
290 found = False
291 root_proxy = self._core.get_proxy('/')
292
293 data = MibDeviceData(device_id=device_id,
294 created=self._time_to_string(now),
295 last_sync_time='',
296 mib_data_sync=0,
297 version=MibDbExternal.CURRENT_VERSION)
298 try:
299 dev_proxy = self._device_proxy(device_id)
300 found = True
301
302 if not overwrite:
303 # Device already exists
304 raise KeyError('Device with ID {} already exists in MIB database'.
305 format(device_id))
306
307 # Overwrite with new data
308 data = dev_proxy.get('/', depth=0)
309 self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id), data)
310 self._modified = now
311
312 except KeyError:
313 if found:
314 raise
315 # Did not exist, add it now
316 root_proxy.add(MibDbExternal.MIB_PATH, data)
317 self._created = now
318 self._modified = now
319
320 def remove(self, device_id):
321 """
322 Remove an ONU from the database
323
324 :param device_id: (str) Device ID of ONU to remove from database
325 """
326 self.log.debug('remove-device', device_id=device_id)
327
328 if not self._started:
329 raise DatabaseStateError('The Database is not currently active')
330
331 if not isinstance(device_id, basestring):
332 raise TypeError('Device ID should be an string')
333
334 try:
335 # self._root_proxy.get(MibDbExternal.DEVICE_PATH.format(device_id))
336 self._root_proxy.remove(MibDbExternal.DEVICE_PATH.format(device_id))
337 self._modified = datetime.utcnow()
338
339 except KeyError:
340 # Did not exists, which is not a failure
341 pass
342
343 except Exception as e:
344 self.log.exception('remove-exception', device_id=device_id, e=e)
345 raise
346
347 @property
348 def _root_proxy(self):
349 return self._core.get_proxy('/')
350
351 def _device_proxy(self, device_id):
352 """
353 Return a config proxy to the OMCI MIB_DB leaf for a given device
354
355 :param device_id: (str) ONU Device ID
356 :return: (ConfigProxy) Configuration proxy rooted at OMCI MIB DB
357 :raises KeyError: If the device does not exist in the database
358 """
359 if not isinstance(device_id, basestring):
360 raise TypeError('Device ID should be an string')
361
362 if not self._started:
363 raise DatabaseStateError('The Database is not currently active')
364
365 return self._core.get_proxy(MibDbExternal.DEVICE_PATH.format(device_id))
366
367 def _class_proxy(self, device_id, class_id, create=False):
368 """
369 Get a config proxy to a specific managed entity class
370 :param device_id: (str) ONU Device ID
371 :param class_id: (int) Class ID
372 :param create: (bool) If true, create default instance (and class)
373 :return: (ConfigProxy) Class configuration proxy
374
375 :raises DatabaseStateError: If database is not started
376 :raises KeyError: If Instance does not exist and 'create' is False
377 """
378 if not self._started:
379 raise DatabaseStateError('The Database is not currently active')
380
381 if not 0 <= class_id <= 0xFFFF:
382 raise ValueError('class-id is 0..0xFFFF')
383
384 fmt = MibDbExternal.DEVICE_PATH + MibDbExternal.CLASS_PATH
385 path = fmt.format(device_id, class_id)
386
387 try:
388 return self._core.get_proxy(path)
389
390 except KeyError:
391 if not create:
392 # This can occur right after a MIB Reset if the ONU publishes AVCs right away
393 # and during the MIB audit resync for ONU created MEs in response to an OLT
394 # created ME. Fail since for these test cases they occur during a verification
395 # 'query' and not the ME creation during resync. Calling code should handle
396 # they exception if it is expected to occur on occasion.
397 self.log.debug('class-proxy-does-not-exist', device_id=device_id,
398 class_id=class_id)
399 raise
400
401 # Create class
402 data = MibClassData(class_id=class_id)
403 root_path = MibDbExternal.CLASSES_PATH.format(device_id)
404 self._root_proxy.add(root_path, data)
405
406 return self._core.get_proxy(path)
407
408 def _instance_proxy(self, device_id, class_id, instance_id, create=False):
409 """
410 Get a config proxy to a specific managed entity instance
411 :param device_id: (str) ONU Device ID
412 :param class_id: (int) Class ID
413 :param instance_id: (int) Instance ID
414 :param create: (bool) If true, create default instance (and class)
415 :return: (ConfigProxy) Instance configuration proxy
416
417 :raises DatabaseStateError: If database is not started
418 :raises KeyError: If Instance does not exist and 'create' is False
419 """
420 if not self._started:
421 raise DatabaseStateError('The Database is not currently active')
422
423 if not isinstance(device_id, basestring):
424 raise TypeError('Device ID is a string')
425
426 if not 0 <= class_id <= 0xFFFF:
427 raise ValueError('class-id is 0..0xFFFF')
428
429 if not 0 <= instance_id <= 0xFFFF:
430 raise ValueError('instance-id is 0..0xFFFF')
431
432 fmt = MibDbExternal.DEVICE_PATH + MibDbExternal.INSTANCE_PATH
433 path = fmt.format(device_id, class_id, instance_id)
434
435 try:
436 return self._core.get_proxy(path)
437
438 except KeyError:
439 if not create:
440 # This can occur right after a MIB Reset if the ONU publishes AVCs right away
441 # and during the MIB audit resync for ONU created MEs in response to an OLT
442 # created ME. Fail since for these test cases they occur during a verification
443 # 'query' and not the ME creation during resync. Calling code should handle
444 # they exception if it is expected to occur on occasion.
445 self.log.info('instance-proxy-does-not-exist', device_id=device_id,
446 class_id=class_id, instance_id=instance_id)
447 raise
448
449 # Create instance, first make sure class exists
450 self._class_proxy(device_id, class_id, create=True)
451
452 now = self._time_to_string(datetime.utcnow())
453 data = MibInstanceData(instance_id=instance_id, created=now, modified=now)
454 root_path = MibDbExternal.INSTANCES_PATH.format(device_id, class_id)
455 self._root_proxy.add(root_path, data)
456
457 return self._core.get_proxy(path)
458
459 def on_mib_reset(self, device_id):
460 """
461 Reset/clear the database for a specific Device
462
463 :param device_id: (str) ONU Device ID
464 :raises DatabaseStateError: If the database is not enabled
465 :raises KeyError: If the device does not exist in the database
466 """
467 self.log.debug('on-mib-reset', device_id=device_id)
468
469 try:
470 device_proxy = self._device_proxy(device_id)
471 data = device_proxy.get(depth=2)
472
473 # Wipe out any existing class IDs
474 class_ids = [c.class_id for c in data.classes]
475
476 if len(class_ids):
477 for class_id in class_ids:
478 device_proxy.remove(MibDbExternal.CLASS_PATH.format(class_id))
479
480 # Reset MIB Data Sync to zero
481 now = datetime.utcnow()
482 data = MibDeviceData(device_id=device_id,
483 created=data.created,
484 last_sync_time=data.last_sync_time,
485 mib_data_sync=0,
486 version=MibDbExternal.CURRENT_VERSION)
487 # Update
488 self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
489 data)
490 self._modified = now
491 self.log.debug('mib-reset-complete', device_id=device_id)
492
493 except Exception as e:
494 self.log.exception('mib-reset-exception', device_id=device_id, e=e)
495 raise
496
497 def save_mib_data_sync(self, device_id, value):
498 """
499 Save the MIB Data Sync to the database in an easy location to access
500
501 :param device_id: (str) ONU Device ID
502 :param value: (int) Value to save
503 """
504 self.log.debug('save-mds', device_id=device_id, value=value)
505
506 try:
507 if not isinstance(value, int):
508 raise TypeError('MIB Data Sync is an integer')
509
510 if not 0 <= value <= 255:
511 raise ValueError('Invalid MIB-data-sync value {}. Must be 0..255'.
512 format(value))
513 device_proxy = self._device_proxy(device_id)
514 data = device_proxy.get(depth=0)
515
516 now = datetime.utcnow()
517 data.mib_data_sync = value
518
519 # Update
520 self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
521 data)
522 self._modified = now
523 self.log.debug('save-mds-complete', device_id=device_id)
524
525 except Exception as e:
526 self.log.exception('save-mds-exception', device_id=device_id, e=e)
527 raise
528
529 def get_mib_data_sync(self, device_id):
530 """
531 Get the MIB Data Sync value last saved to the database for a device
532
533 :param device_id: (str) ONU Device ID
534 :return: (int) The Value or None if not found
535 """
536 self.log.debug('get-mds', device_id=device_id)
537
538 try:
539 device_proxy = self._device_proxy(device_id)
540 data = device_proxy.get(depth=0)
541 return int(data.mib_data_sync)
542
543 except KeyError:
544 return None # OMCI MIB_DB entry has not yet been created
545
546 except Exception as e:
547 self.log.exception('get-mds-exception', device_id=device_id, e=e)
548 raise
549
550 def save_last_sync(self, device_id, value):
551 """
552 Save the Last Sync time to the database in an easy location to access
553
554 :param device_id: (str) ONU Device ID
555 :param value: (DateTime) Value to save
556 """
557 self.log.debug('save-last-sync', device_id=device_id, time=str(value))
558
559 try:
560 if not isinstance(value, datetime):
561 raise TypeError('Expected a datetime object, got {}'.
562 format(type(datetime)))
563
564 device_proxy = self._device_proxy(device_id)
565 data = device_proxy.get(depth=0)
566
567 now = datetime.utcnow()
568 data.last_sync_time = self._time_to_string(value)
569
570 # Update
571 self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
572 data)
573 self._modified = now
574 self.log.debug('save-mds-complete', device_id=device_id)
575
576 except Exception as e:
577 self.log.exception('save-last-sync-exception', device_id=device_id, e=e)
578 raise
579
580 def get_last_sync(self, device_id):
581 """
582 Get the Last Sync Time saved to the database for a device
583
584 :param device_id: (str) ONU Device ID
585 :return: (int) The Value or None if not found
586 """
587 self.log.debug('get-last-sync', device_id=device_id)
588
589 try:
590 device_proxy = self._device_proxy(device_id)
591 data = device_proxy.get(depth=0)
592 return self._string_to_time(data.last_sync_time)
593
594 except KeyError:
595 return None # OMCI MIB_DB entry has not yet been created
596
597 except Exception as e:
598 self.log.exception('get-last-sync-exception', e=e)
599 raise
600
601 def _add_new_class(self, device_id, class_id, instance_id, attributes):
602 """
603 Create an entry for a new class in the external database
604
605 :param device_id: (str) ONU Device ID
606 :param class_id: (int) ME Class ID
607 :param instance_id: (int) ME Entity ID
608 :param attributes: (dict) Attribute dictionary
609
610 :returns: (bool) True if the value was saved to the database. False if the
611 value was identical to the current instance
612 """
613 self.log.debug('add', device_id=device_id, class_id=class_id,
614 instance_id=instance_id, attributes=attributes)
615
616 now = self._time_to_string(datetime.utcnow())
617 attrs = []
618 for k, v in attributes.items():
619 if k == 'serial_number':
620 vendor_id = str(v[0:4])
621 vendor_specific = v[4:]
622 vendor_specific = str(vendor_specific.encode('hex'))
623 str_value = vendor_id + vendor_specific
624 attrs.append(MibAttributeData(name=k, value=str_value))
625 else:
626 str_value = self._attribute_to_string(device_id, class_id, k, v)
627 attrs.append(MibAttributeData(name=k, value=str_value))
628
629 class_data = MibClassData(class_id=class_id,
630 instances=[MibInstanceData(instance_id=instance_id,
631 created=now,
632 modified=now,
633 attributes=attrs)])
634
635 self._root_proxy.add(MibDbExternal.CLASSES_PATH.format(device_id), class_data)
636 self.log.debug('set-complete', device_id=device_id, class_id=class_id,
637 entity_id=instance_id, attributes=attributes)
638 return True
639
640 def _add_new_instance(self, device_id, class_id, instance_id, attributes):
641 """
642 Create an entry for a instance of an existing class in the external database
643
644 :param device_id: (str) ONU Device ID
645 :param class_id: (int) ME Class ID
646 :param instance_id: (int) ME Entity ID
647 :param attributes: (dict) Attribute dictionary
648
649 :returns: (bool) True if the value was saved to the database. False if the
650 value was identical to the current instance
651 """
652 self.log.debug('add', device_id=device_id, class_id=class_id,
653 instance_id=instance_id, attributes=attributes)
654
655 now = self._time_to_string(datetime.utcnow())
656 attrs = []
657 for k, v in attributes.items():
658 if k == 'serial_number':
659 vendor_id = str(v[0:4])
660 vendor_specific = v[4:]
661 vendor_specific = str(vendor_specific.encode('hex'))
662 str_value = vendor_id+vendor_specific
663 attrs.append(MibAttributeData(name=k, value=str_value))
664 else:
665 str_value = self._attribute_to_string(device_id, class_id, k, v)
666 attrs.append(MibAttributeData(name=k, value=str_value))
667
668 instance_data = MibInstanceData(instance_id=instance_id,
669 created=now,
670 modified=now,
671 attributes=attrs)
672
673 self._root_proxy.add(MibDbExternal.INSTANCES_PATH.format(device_id, class_id),
674 instance_data)
675
676 self.log.debug('set-complete', device_id=device_id, class_id=class_id,
677 entity_id=instance_id, attributes=attributes)
678 return True
679
680 def set(self, device_id, class_id, instance_id, attributes):
681 """
682 Set a database value. This should only be called by the MIB synchronizer
683 and its related tasks
684
685 :param device_id: (str) ONU Device ID
686 :param class_id: (int) ME Class ID
687 :param instance_id: (int) ME Entity ID
688 :param attributes: (dict) Attribute dictionary
689
690 :returns: (bool) True if the value was saved to the database. False if the
691 value was identical to the current instance
692
693 :raises KeyError: If device does not exist
694 :raises DatabaseStateError: If the database is not enabled
695 """
696 self.log.debug('set', device_id=device_id, class_id=class_id,
697 instance_id=instance_id, attributes=attributes)
698 try:
699 if not isinstance(device_id, basestring):
700 raise TypeError('Device ID should be a string')
701
702 if not 0 <= class_id <= 0xFFFF:
703 raise ValueError("Invalid Class ID: {}, should be 0..65535".format(class_id))
704
705 if not 0 <= instance_id <= 0xFFFF:
706 raise ValueError("Invalid Instance ID: {}, should be 0..65535".format(instance_id))
707
708 if not isinstance(attributes, dict):
709 raise TypeError("Attributes should be a dictionary")
710
711 if not self._started:
712 raise DatabaseStateError('The Database is not currently active')
713
714 # Determine the best strategy to add the information
715 dev_proxy = self._device_proxy(device_id)
716
717 operation = 'set'
718 start_time = None
719 try:
720 class_data = dev_proxy.get(MibDbExternal.CLASS_PATH.format(class_id), deep=True)
721
722 inst_data = next((inst for inst in class_data.instances
723 if inst.instance_id == instance_id), None)
724
725 if inst_data is None:
726 operation = 'create'
727 start_time = datetime.utcnow()
728 return self._add_new_instance(device_id, class_id, instance_id, attributes)
729
730 # Possibly adding to or updating an existing instance
731 # Get instance proxy, creating it if needed
732
733 modified = False
734 new_attributes = []
735 exist_attr_indexes = dict()
736 attr_len = len(inst_data.attributes)
737
738 for index in xrange(0, attr_len):
739 name = inst_data.attributes[index].name
740 value = inst_data.attributes[index].value
741 exist_attr_indexes[name] = index
742 new_attributes.append(MibAttributeData(name=name, value=value))
743
744 for k, v in attributes.items():
745 try:
746 old_value = None if k not in exist_attr_indexes \
747 else new_attributes[exist_attr_indexes[k]].value
748
749 str_value = self._attribute_to_string(device_id, class_id, k, v, old_value)
750
751 if k not in exist_attr_indexes:
752 new_attributes.append(MibAttributeData(name=k, value=str_value))
753 modified = True
754
755 elif new_attributes[exist_attr_indexes[k]].value != str_value:
756 new_attributes[exist_attr_indexes[k]].value = str_value
757 modified = True
758
759 except Exception as e:
760 self.log.exception('save-error', e=e, class_id=class_id,
761 attr=k, value_type=type(v))
762
763 if modified:
764 now = datetime.utcnow()
765 start_time = now
766 new_data = MibInstanceData(instance_id=instance_id,
767 created=inst_data.created,
768 modified=self._time_to_string(now),
769 attributes=new_attributes)
770 dev_proxy.remove(MibDbExternal.INSTANCE_PATH.format(class_id, instance_id))
771 self._root_proxy.add(MibDbExternal.INSTANCES_PATH.format(device_id,
772 class_id), new_data)
773 return modified
774
775 except KeyError:
776 # Here if the class-id does not yet exist in the database
777 self.log.debug("adding-key-not-found", class_id=class_id)
778 return self._add_new_class(device_id, class_id, instance_id,
779 attributes)
780 finally:
781 if start_time is not None:
782 diff = datetime.utcnow() - start_time
783 # NOTE: Change to 'debug' when checked in, manually change to 'info'
784 # for development testing.
785 self.log.debug('db-{}-time'.format(operation), milliseconds=diff.microseconds/1000)
786 self._statistics[operation].increment(diff.microseconds/1000)
787
788 except Exception as e:
789 self.log.exception('set-exception', device_id=device_id, class_id=class_id,
790 instance_id=instance_id, attributes=attributes, e=e)
791 raise
792
793 def delete(self, device_id, class_id, entity_id):
794 """
795 Delete an entity from the database if it exists. If all instances
796 of a class are deleted, the class is deleted as well.
797
798 :param device_id: (str) ONU Device ID
799 :param class_id: (int) ME Class ID
800 :param entity_id: (int) ME Entity ID
801
802 :returns: (bool) True if the instance was found and deleted. False
803 if it did not exist.
804
805 :raises KeyError: If device does not exist
806 :raises DatabaseStateError: If the database is not enabled
807 """
808 self.log.debug('delete', device_id=device_id, class_id=class_id,
809 entity_id=entity_id)
810
811 if not self._started:
812 raise DatabaseStateError('The Database is not currently active')
813
814 if not isinstance(device_id, basestring):
815 raise TypeError('Device ID should be an string')
816
817 if not 0 <= class_id <= 0xFFFF:
818 raise ValueError('class-id is 0..0xFFFF')
819
820 if not 0 <= entity_id <= 0xFFFF:
821 raise ValueError('instance-id is 0..0xFFFF')
822
823 start_time = datetime.utcnow()
824 try:
825 # Remove instance
826 self._instance_proxy(device_id, class_id, entity_id).remove('/')
827 now = datetime.utcnow()
828
829 # If resulting class has no instance, remove it as well
830 class_proxy = self._class_proxy(device_id, class_id)
831 class_data = class_proxy.get('/', depth=1)
832
833 if len(class_data.instances) == 0:
834 class_proxy.remove('/')
835
836 self._modified = now
837 return True
838
839 except KeyError:
840 return False # Not found
841
842 except Exception as e:
843 self.log.exception('get-last-sync-exception', device_id=device_id, e=e)
844 raise
845
846 finally:
847 diff = datetime.utcnow() - start_time
848 # NOTE: Change to 'debug' when checked in, manually change to 'info'
849 # for development testing.
850 self.log.debug('db-delete-time', milliseconds=diff.microseconds/1000)
851 self._statistics['delete'].increment(diff.microseconds/1000)
852
853 def query(self, device_id, class_id=None, instance_id=None, attributes=None):
854 """
855 Get database information.
856
857 This method can be used to request information from the database to the detailed
858 level requested
859
860 :param device_id: (str) ONU Device ID
861 :param class_id: (int) Managed Entity class ID
862 :param instance_id: (int) Managed Entity instance
863 :param attributes: (list/set or str) Managed Entity instance's attributes
864
865 :return: (dict) The value(s) requested. If class/inst/attribute is
866 not found, an empty dictionary is returned
867 :raises KeyError: If the requested device does not exist
868 :raises DatabaseStateError: If the database is not enabled
869 """
870 self.log.debug('query', device_id=device_id, class_id=class_id,
871 instance_id=instance_id, attributes=attributes)
872
873 start_time = datetime.utcnow()
874 end_time = None
875 try:
876 if class_id is None:
877 # Get full device info
878 dev_data = self._device_proxy(device_id).get('/', depth=-1)
879 end_time = datetime.utcnow()
880 data = self._device_to_dict(dev_data)
881
882 elif instance_id is None:
883 # Get all instances of the class
884 try:
885 cls_data = self._class_proxy(device_id, class_id).get('/', depth=-1)
886 end_time = datetime.utcnow()
887 data = self._class_to_dict(device_id, cls_data)
888
889 except KeyError:
890 data = dict()
891
892 else:
893 # Get all attributes of a specific ME
894 try:
895 inst_data = self._instance_proxy(device_id, class_id, instance_id).\
896 get('/', depth=-1)
897 end_time = datetime.utcnow()
898
899 if attributes is None:
900 # All Attributes
901 data = self._instance_to_dict(device_id, class_id, inst_data)
902
903 else:
904 # Specific attribute(s)
905 if isinstance(attributes, basestring):
906 attributes = {attributes}
907
908 data = {
909 attr.name: self._string_to_attribute(device_id,
910 class_id,
911 attr.name,
912 attr.value)
913 for attr in inst_data.attributes if attr.name in attributes}
914
915 except KeyError:
916 data = dict()
917
918 return data
919
920 except KeyError:
921 self.log.warn('query-no-device', device_id=device_id)
922 raise
923
924 except Exception as e:
925 self.log.exception('get-last-sync-exception', device_id=device_id, e=e)
926 raise
927
928 finally:
929 if end_time is not None:
930 diff = end_time.utcnow() - start_time
931 # NOTE: Change to 'debug' when checked in, manually change to 'info'
932 # for development testing.
933 self.log.debug('db-get-time', milliseconds=diff.microseconds/1000, class_id=class_id,
934 instance_id=instance_id)
935 self._statistics['get'].increment(diff.microseconds/1000)
936
937 def _instance_to_dict(self, device_id, class_id, instance):
938 if not isinstance(instance, MibInstanceData):
939 raise TypeError('{} is not of type MibInstanceData'.format(type(instance)))
940
941 data = {
942 INSTANCE_ID_KEY: instance.instance_id,
943 CREATED_KEY: self._string_to_time(instance.created),
944 MODIFIED_KEY: self._string_to_time(instance.modified),
945 ATTRIBUTES_KEY: dict()
946 }
947 for attribute in instance.attributes:
948 data[ATTRIBUTES_KEY][attribute.name] = self._string_to_attribute(device_id,
949 class_id,
950 attribute.name,
951 attribute.value)
952 return data
953
954 def _class_to_dict(self, device_id, val):
955 if not isinstance(val, MibClassData):
956 raise TypeError('{} is not of type MibClassData'.format(type(val)))
957
958 data = {
959 CLASS_ID_KEY: val.class_id,
960 }
961 for instance in val.instances:
962 data[instance.instance_id] = self._instance_to_dict(device_id,
963 val.class_id,
964 instance)
965 return data
966
967 def _device_to_dict(self, val):
968 if not isinstance(val, MibDeviceData):
969 raise TypeError('{} is not of type MibDeviceData'.format(type(val)))
970
971 data = {
972 DEVICE_ID_KEY: val.device_id,
973 CREATED_KEY: self._string_to_time(val.created),
974 LAST_SYNC_KEY: self._string_to_time(val.last_sync_time),
975 MDS_KEY: val.mib_data_sync,
976 VERSION_KEY: val.version,
977 ME_KEY: dict(),
978 MSG_TYPE_KEY: set()
979 }
980 for class_data in val.classes:
981 data[class_data.class_id] = self._class_to_dict(val.device_id,
982 class_data)
983 for managed_entity in val.managed_entities:
984 data[ME_KEY][managed_entity.class_id] = managed_entity.name
985
986 for msg_type in val.message_types:
987 data[MSG_TYPE_KEY].add(msg_type.message_type)
988
989 return data
990
991 def _managed_entity_to_name(self, device_id, class_id):
992 me_map = self._omci_agent.get_device(device_id).me_map
993 entity = me_map.get(class_id)
994
995 return entity.__name__ if entity is not None else 'UnknownManagedEntity'
996
997 def update_supported_managed_entities(self, device_id, managed_entities):
998 """
999 Update the supported OMCI Managed Entities for this device
1000 :param device_id: (str) ONU Device ID
1001 :param managed_entities: (set) Managed Entity class IDs
1002 """
1003 try:
1004 me_list = [ManagedEntity(class_id=class_id,
1005 name=self._managed_entity_to_name(device_id,
1006 class_id))
1007 for class_id in managed_entities]
1008
1009 device_proxy = self._device_proxy(device_id)
1010 data = device_proxy.get(depth=0)
1011
1012 now = datetime.utcnow()
1013 data.managed_entities.extend(me_list)
1014
1015 # Update
1016 self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
1017 data)
1018 self._modified = now
1019 self.log.debug('save-me-list-complete', device_id=device_id)
1020
1021 except Exception as e:
1022 self.log.exception('add-me-failure', e=e, me_list=managed_entities)
1023 raise
1024
1025 def update_supported_message_types(self, device_id, msg_types):
1026 """
1027 Update the supported OMCI Managed Entities for this device
1028 :param device_id: (str) ONU Device ID
1029 :param msg_types: (set) Message Type values (ints)
1030 """
1031 try:
1032 msg_type_list = [MessageType(message_type=msg_type.value)
1033 for msg_type in msg_types]
1034
1035 device_proxy = self._device_proxy(device_id)
1036 data = device_proxy.get(depth=0)
1037
1038 now = datetime.utcnow()
1039 data.message_types.extend(msg_type_list)
1040
1041 # Update
1042 self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
1043 data)
1044 self._modified = now
1045 self.log.debug('save-msg-types-complete', device_id=device_id)
1046
1047 except Exception as e:
1048 self.log.exception('add-msg-types-failure', e=e, msg_types=msg_types)
1049 raise