Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 1 | # |
| 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 | # |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 16 | from __future__ import absolute_import |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 17 | import copy |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 18 | from .mib_db_api import * |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 19 | import json |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 20 | import six |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 21 | |
| 22 | |
| 23 | class MibDbVolatileDict(MibDbApi): |
| 24 | """ |
| 25 | A very simple in-memory database for ME storage. Data is not persistent |
| 26 | across reboots. |
| 27 | |
| 28 | In Phase 2, this DB will be instantiated on a per-ONU basis but act as if |
| 29 | it is shared for all ONUs. This class will be updated with and external |
| 30 | key-value store (or other appropriate database) in Voltha 1.3 Sprint 3 |
| 31 | |
| 32 | This class can be used for unit tests |
| 33 | """ |
| 34 | CURRENT_VERSION = 1 |
| 35 | |
| 36 | def __init__(self, omci_agent): |
| 37 | """ |
| 38 | Class initializer |
| 39 | :param omci_agent: (OpenOMCIAgent) OpenOMCI Agent |
| 40 | """ |
| 41 | super(MibDbVolatileDict, self).__init__(omci_agent) |
| 42 | self._data = dict() # device_id -> ME ID -> Inst ID -> Attr Name -> Values |
| 43 | |
| 44 | def start(self): |
| 45 | """ |
| 46 | Start up/restore the database. For in-memory, will be a nop. For external |
| 47 | DB, may need to create the DB and fetch create/modified values |
| 48 | """ |
| 49 | super(MibDbVolatileDict, self).start() |
| 50 | # TODO: Delete this method if nothing else is done except calling the base class |
| 51 | |
| 52 | def stop(self): |
| 53 | """ |
| 54 | Start up the database. For in-memory, will be a nop. For external |
| 55 | DB, may need to create the DB and fetch create/modified values |
| 56 | """ |
| 57 | super(MibDbVolatileDict, self).stop() |
| 58 | # TODO: Delete this method if nothing else is done except calling the base class |
| 59 | |
| 60 | def add(self, device_id, overwrite=False): |
| 61 | """ |
| 62 | Add a new ONU to database |
| 63 | |
| 64 | :param device_id: (str) Device ID of ONU to add |
| 65 | :param overwrite: (bool) Overwrite existing entry if found. |
| 66 | |
| 67 | :raises KeyError: If device already exist and 'overwrite' is False |
| 68 | """ |
| 69 | self.log.debug('add-device', device_id=device_id, overwrite=overwrite) |
| 70 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 71 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 72 | raise TypeError('Device ID should be an string') |
| 73 | |
| 74 | if not self._started: |
| 75 | raise DatabaseStateError('The Database is not currently active') |
| 76 | |
| 77 | if not overwrite and device_id in self._data: |
| 78 | raise KeyError('Device {} already exists in the database' |
| 79 | .format(device_id)) |
| 80 | |
| 81 | now = datetime.utcnow() |
| 82 | self._data[device_id] = { |
| 83 | DEVICE_ID_KEY: device_id, |
| 84 | CREATED_KEY: now, |
| 85 | LAST_SYNC_KEY: None, |
| 86 | MDS_KEY: 0, |
| 87 | VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION, |
| 88 | ME_KEY: dict(), |
| 89 | MSG_TYPE_KEY: set() |
| 90 | } |
| 91 | |
| 92 | def remove(self, device_id): |
| 93 | """ |
| 94 | Remove an ONU from the database |
| 95 | |
| 96 | :param device_id: (str) Device ID of ONU to remove from database |
| 97 | """ |
| 98 | self.log.debug('remove-device', device_id=device_id) |
| 99 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 100 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 101 | raise TypeError('Device ID should be an string') |
| 102 | |
| 103 | if not self._started: |
| 104 | raise DatabaseStateError('The Database is not currently active') |
| 105 | |
| 106 | if device_id in self._data: |
| 107 | del self._data[device_id] |
| 108 | self._modified = datetime.utcnow() |
| 109 | |
| 110 | def on_mib_reset(self, device_id): |
| 111 | """ |
| 112 | Reset/clear the database for a specific Device |
| 113 | |
| 114 | :param device_id: (str) ONU Device ID |
| 115 | :raises DatabaseStateError: If the database is not enabled |
| 116 | :raises KeyError: If the device does not exist in the database |
| 117 | """ |
| 118 | if not self._started: |
| 119 | raise DatabaseStateError('The Database is not currently active') |
| 120 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 121 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 122 | raise TypeError('Device ID should be an string') |
| 123 | |
| 124 | device_db = self._data[device_id] |
| 125 | self._modified = datetime.utcnow() |
| 126 | |
| 127 | self._data[device_id] = { |
| 128 | DEVICE_ID_KEY: device_id, |
| 129 | CREATED_KEY: device_db[CREATED_KEY], |
| 130 | LAST_SYNC_KEY: device_db[LAST_SYNC_KEY], |
| 131 | MDS_KEY: 0, |
| 132 | VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION, |
| 133 | ME_KEY: device_db[ME_KEY], |
| 134 | MSG_TYPE_KEY: device_db[MSG_TYPE_KEY] |
| 135 | } |
| 136 | |
| 137 | def save_mib_data_sync(self, device_id, value): |
| 138 | """ |
| 139 | Save the MIB Data Sync to the database in an easy location to access |
| 140 | |
| 141 | :param device_id: (str) ONU Device ID |
| 142 | :param value: (int) Value to save |
| 143 | """ |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 144 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 145 | raise TypeError('Device ID should be an string') |
| 146 | |
| 147 | if not isinstance(value, int): |
| 148 | raise TypeError('MIB Data Sync is an integer') |
| 149 | |
| 150 | if not 0 <= value <= 255: |
| 151 | raise ValueError('Invalid MIB-data-sync value {}. Must be 0..255'. |
| 152 | format(value)) |
| 153 | |
| 154 | self._data[device_id][MDS_KEY] = value |
| 155 | self._modified = datetime.utcnow() |
| 156 | |
| 157 | def get_mib_data_sync(self, device_id): |
| 158 | """ |
| 159 | Get the MIB Data Sync value last saved to the database for a device |
| 160 | |
| 161 | :param device_id: (str) ONU Device ID |
| 162 | :return: (int) The Value or None if not found |
| 163 | """ |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 164 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 165 | raise TypeError('Device ID should be an string') |
| 166 | |
| 167 | if device_id not in self._data: |
| 168 | return None |
| 169 | |
| 170 | return self._data[device_id].get(MDS_KEY) |
| 171 | |
| 172 | def save_last_sync(self, device_id, value): |
| 173 | """ |
| 174 | Save the Last Sync time to the database in an easy location to access |
| 175 | |
| 176 | :param device_id: (str) ONU Device ID |
| 177 | :param value: (DateTime) Value to save |
| 178 | """ |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 179 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 180 | raise TypeError('Device ID should be an string') |
| 181 | |
| 182 | if not isinstance(value, datetime): |
| 183 | raise TypeError('Expected a datetime object, got {}'. |
| 184 | format(type(datetime))) |
| 185 | |
| 186 | self._data[device_id][LAST_SYNC_KEY] = value |
| 187 | self._modified = datetime.utcnow() |
| 188 | |
| 189 | def get_last_sync(self, device_id): |
| 190 | """ |
| 191 | Get the Last SYnc Time saved to the database for a device |
| 192 | |
| 193 | :param device_id: (str) ONU Device ID |
| 194 | :return: (int) The Value or None if not found |
| 195 | """ |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 196 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 197 | raise TypeError('Device ID should be an string') |
| 198 | |
| 199 | if device_id not in self._data: |
| 200 | return None |
| 201 | |
| 202 | return self._data[device_id].get(LAST_SYNC_KEY) |
| 203 | |
| 204 | def set(self, device_id, class_id, instance_id, attributes): |
| 205 | """ |
| 206 | Set a database value. This should only be called by the MIB synchronizer |
| 207 | and its related tasks |
| 208 | |
| 209 | :param device_id: (str) ONU Device ID |
| 210 | :param class_id: (int) ME Class ID |
| 211 | :param instance_id: (int) ME Entity ID |
| 212 | :param attributes: (dict) Attribute dictionary |
| 213 | |
| 214 | :returns: (bool) True if the value was saved to the database. False if the |
| 215 | value was identical to the current instance |
| 216 | |
| 217 | :raises KeyError: If device does not exist |
| 218 | :raises DatabaseStateError: If the database is not enabled |
| 219 | """ |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 220 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 221 | raise TypeError('Device ID should be a string') |
| 222 | |
| 223 | if not 0 <= class_id <= 0xFFFF: |
| 224 | raise ValueError("Invalid Class ID: {}, should be 0..65535".format(class_id)) |
| 225 | |
| 226 | if not 0 <= instance_id <= 0xFFFF: |
| 227 | raise ValueError("Invalid Instance ID: {}, should be 0..65535".format(instance_id)) |
| 228 | |
| 229 | if not isinstance(attributes, dict): |
| 230 | raise TypeError("Attributes should be a dictionary") |
| 231 | |
| 232 | if not self._started: |
| 233 | raise DatabaseStateError('The Database is not currently active') |
| 234 | |
| 235 | now = datetime.utcnow() |
| 236 | try: |
| 237 | device_db = self._data[device_id] |
| 238 | class_db = device_db.get(class_id) |
| 239 | created = False |
| 240 | |
| 241 | if class_db is None: |
| 242 | device_db[class_id] = {CLASS_ID_KEY: class_id} |
| 243 | |
| 244 | class_db = device_db[class_id] |
| 245 | self._modified = now |
| 246 | created = True |
| 247 | |
| 248 | instance_db = class_db.get(instance_id) |
| 249 | if instance_db is None: |
| 250 | class_db[instance_id] = { |
| 251 | INSTANCE_ID_KEY: instance_id, |
| 252 | CREATED_KEY: now, |
| 253 | MODIFIED_KEY: now, |
| 254 | ATTRIBUTES_KEY: dict() |
| 255 | } |
| 256 | instance_db = class_db[instance_id] |
| 257 | self._modified = now |
| 258 | created = True |
| 259 | |
| 260 | changed = False |
| 261 | |
| 262 | me_map = self._omci_agent.get_device(device_id).me_map |
| 263 | entity = me_map.get(class_id) |
| 264 | |
| 265 | for attribute, value in attributes.items(): |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 266 | assert isinstance(attribute, six.string_types) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 267 | assert value is not None, "Attribute '{}' value cannot be 'None'".\ |
| 268 | format(attribute) |
| 269 | |
| 270 | db_value = instance_db[ATTRIBUTES_KEY].get(attribute) \ |
| 271 | if ATTRIBUTES_KEY in instance_db else None |
| 272 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 273 | if entity is not None and isinstance(value, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 274 | from scapy.fields import StrFixedLenField |
| 275 | attr_index = entity.attribute_name_to_index_map[attribute] |
| 276 | eca = entity.attributes[attr_index] |
| 277 | field = eca.field |
| 278 | |
| 279 | if isinstance(field, StrFixedLenField): |
| 280 | from scapy.base_classes import Packet_metaclass |
| 281 | if isinstance(field.default, Packet_metaclass) \ |
| 282 | and hasattr(field.default, 'json_from_value'): |
| 283 | # Value/hex of Packet Class to string |
| 284 | value = field.default.json_from_value(value) |
| 285 | |
| 286 | if entity is not None and attribute in entity.attribute_name_to_index_map: |
| 287 | attr_index = entity.attribute_name_to_index_map[attribute] |
| 288 | eca = entity.attributes[attr_index] |
| 289 | field = eca.field |
| 290 | |
| 291 | if hasattr(field, 'to_json'): |
| 292 | value = field.to_json(value, db_value) |
| 293 | |
| 294 | # Complex packet types may have an attribute encoded as an object, this |
| 295 | # can be check by seeing if there is a to_json() conversion callable |
| 296 | # defined |
| 297 | if hasattr(value, 'to_json'): |
| 298 | value = value.to_json() |
| 299 | |
| 300 | # Other complex packet types may be a repeated list field (FieldListField) |
| 301 | elif isinstance(value, (list, dict)): |
| 302 | value = json.dumps(value, separators=(',', ':')) |
| 303 | |
Matt Jeanneret | 40f2839 | 2019-12-04 18:21:46 -0500 | [diff] [blame] | 304 | if isinstance(value, six.string_types): |
| 305 | value = value.rstrip('\x00') |
| 306 | |
| 307 | if isinstance(value, six.binary_type): |
| 308 | value = value.decode('ascii').rstrip('\x00') |
| 309 | |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 310 | assert db_value is None or isinstance(value, type(db_value)), \ |
| 311 | "New value type for attribute '{}' type is changing from '{}' to '{}'".\ |
| 312 | format(attribute, type(db_value), type(value)) |
| 313 | |
| 314 | if db_value is None or db_value != value: |
| 315 | instance_db[ATTRIBUTES_KEY][attribute] = value |
| 316 | changed = True |
| 317 | |
| 318 | if changed: |
| 319 | instance_db[MODIFIED_KEY] = now |
| 320 | self._modified = now |
| 321 | |
| 322 | return changed or created |
| 323 | |
| 324 | except Exception as e: |
| 325 | self.log.error('set-failure', e=e, class_id=class_id, |
| 326 | instance_id=instance_id, attributes=attributes) |
| 327 | raise |
| 328 | |
| 329 | def delete(self, device_id, class_id, instance_id): |
| 330 | """ |
| 331 | Delete an entity from the database if it exists. If all instances |
| 332 | of a class are deleted, the class is deleted as well. |
| 333 | |
| 334 | :param device_id: (str) ONU Device ID |
| 335 | :param class_id: (int) ME Class ID |
| 336 | :param instance_id: (int) ME Entity ID |
| 337 | |
| 338 | :returns: (bool) True if the instance was found and deleted. False |
| 339 | if it did not exist. |
| 340 | |
| 341 | :raises KeyError: If device does not exist |
| 342 | :raises DatabaseStateError: If the database is not enabled |
| 343 | """ |
| 344 | if not self._started: |
| 345 | raise DatabaseStateError('The Database is not currently active') |
| 346 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 347 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 348 | raise TypeError('Device ID should be an string') |
| 349 | |
| 350 | if not 0 <= class_id <= 0xFFFF: |
| 351 | raise ValueError('class-id is 0..0xFFFF') |
| 352 | |
| 353 | if not 0 <= instance_id <= 0xFFFF: |
| 354 | raise ValueError('instance-id is 0..0xFFFF') |
| 355 | |
| 356 | try: |
| 357 | device_db = self._data[device_id] |
| 358 | class_db = device_db.get(class_id) |
| 359 | |
| 360 | if class_db is None: |
| 361 | return False |
| 362 | |
| 363 | instance_db = class_db.get(instance_id) |
| 364 | if instance_db is None: |
| 365 | return False |
| 366 | |
| 367 | now = datetime.utcnow() |
| 368 | del class_db[instance_id] |
| 369 | |
| 370 | if len(class_db) == 1: # Is only 'CLASS_ID_KEY' remaining |
| 371 | del device_db[class_id] |
| 372 | |
| 373 | self._modified = now |
| 374 | return True |
| 375 | |
| 376 | except Exception as e: |
| 377 | self.log.error('delete-failure', e=e) |
| 378 | raise |
| 379 | |
| 380 | def query(self, device_id, class_id=None, instance_id=None, attributes=None): |
| 381 | """ |
| 382 | Get database information. |
| 383 | |
| 384 | This method can be used to request information from the database to the detailed |
| 385 | level requested |
| 386 | |
| 387 | :param device_id: (str) ONU Device ID |
| 388 | :param class_id: (int) Managed Entity class ID |
| 389 | :param instance_id: (int) Managed Entity instance |
| 390 | :param attributes: (list/set or str) Managed Entity instance's attributes |
| 391 | |
| 392 | :return: (dict) The value(s) requested. If class/inst/attribute is |
| 393 | not found, an empty dictionary is returned |
| 394 | :raises KeyError: If the requested device does not exist |
| 395 | :raises DatabaseStateError: If the database is not enabled |
| 396 | """ |
| 397 | self.log.debug('query', device_id=device_id, class_id=class_id, |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 398 | entity_instance_id=instance_id, attributes=attributes) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 399 | |
| 400 | if not self._started: |
| 401 | raise DatabaseStateError('The Database is not currently active') |
| 402 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 403 | if not isinstance(device_id, six.string_types): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 404 | raise TypeError('Device ID is a string') |
| 405 | |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 406 | device_db = self._data.get(device_id, dict()) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 407 | if class_id is None: |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 408 | return self._fix_dev_json_attributes(copy.deepcopy(device_db), device_id) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 409 | |
| 410 | if not isinstance(class_id, int): |
| 411 | raise TypeError('Class ID is an integer') |
| 412 | |
| 413 | me_map = self._omci_agent.get_device(device_id).me_map |
| 414 | entity = me_map.get(class_id) |
| 415 | |
| 416 | class_db = device_db.get(class_id, dict()) |
| 417 | if instance_id is None or len(class_db) == 0: |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 418 | return self._fix_cls_json_attributes(copy.deepcopy(class_db), entity) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 419 | |
| 420 | if not isinstance(instance_id, int): |
| 421 | raise TypeError('Instance ID is an integer') |
| 422 | |
| 423 | instance_db = class_db.get(instance_id, dict()) |
| 424 | if attributes is None or len(instance_db) == 0: |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 425 | return self._fix_inst_json_attributes(copy.deepcopy(instance_db), entity) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 426 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 427 | if not isinstance(attributes, (six.string_types, list, set)): |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 428 | raise TypeError('Attributes should be a string or list/set of strings') |
| 429 | |
| 430 | if not isinstance(attributes, (list, set)): |
| 431 | attributes = [attributes] |
| 432 | |
Zack Williams | 84a71e9 | 2019-11-15 09:00:19 -0700 | [diff] [blame] | 433 | results = {attr: val for attr, val in six.iteritems(instance_db[ATTRIBUTES_KEY]) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 434 | if attr in attributes} |
| 435 | |
| 436 | for attr, attr_data in results.items(): |
| 437 | attr_index = entity.attribute_name_to_index_map[attr] |
| 438 | eca = entity.attributes[attr_index] |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 439 | results[attr] = self._fix_attr_json_attribute(copy.deepcopy(attr_data), eca) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 440 | |
| 441 | return results |
| 442 | |
| 443 | ######################################################################### |
| 444 | # Following routines are used to fix-up JSON encoded complex data. A |
| 445 | # nice side effect is that the values returned will be a deep-copy of |
| 446 | # the class/instance/attribute data of what is in the database. Note |
| 447 | # That other database values (created, modified, ...) will still reference |
| 448 | # back to the original DB. |
| 449 | |
| 450 | def _fix_dev_json_attributes(self, dev_data, device_id): |
| 451 | for cls_id, cls_data in dev_data.items(): |
| 452 | if isinstance(cls_id, int): |
| 453 | me_map = self._omci_agent.get_device(device_id).me_map |
| 454 | entity = me_map.get(cls_id) |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 455 | dev_data[cls_id] = self._fix_cls_json_attributes(copy.deepcopy(cls_data), entity) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 456 | return dev_data |
| 457 | |
| 458 | def _fix_cls_json_attributes(self, cls_data, entity): |
| 459 | for inst_id, inst_data in cls_data.items(): |
| 460 | if isinstance(inst_id, int): |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 461 | cls_data[inst_id] = self._fix_inst_json_attributes(copy.deepcopy(inst_data), entity) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 462 | return cls_data |
| 463 | |
| 464 | def _fix_inst_json_attributes(self, inst_data, entity): |
| 465 | if ATTRIBUTES_KEY in inst_data: |
| 466 | for attr, attr_data in inst_data[ATTRIBUTES_KEY].items(): |
| 467 | attr_index = entity.attribute_name_to_index_map[attr] \ |
| 468 | if entity is not None and attr in entity.attribute_name_to_index_map else None |
| 469 | eca = entity.attributes[attr_index] if attr_index is not None else None |
Matt Jeanneret | 2bcbab0 | 2019-09-23 07:28:49 -0400 | [diff] [blame] | 470 | inst_data[ATTRIBUTES_KEY][attr] = self._fix_attr_json_attribute(copy.deepcopy(attr_data), eca) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 471 | return inst_data |
| 472 | |
| 473 | def _fix_attr_json_attribute(self, attr_data, eca): |
| 474 | |
| 475 | try: |
Matt Jeanneret | 40f2839 | 2019-12-04 18:21:46 -0500 | [diff] [blame] | 476 | if eca is not None and hasattr(eca.field, 'load_json'): |
| 477 | try: |
| 478 | value = eca.field.load_json(attr_data) |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 479 | return value |
Matt Jeanneret | 40f2839 | 2019-12-04 18:21:46 -0500 | [diff] [blame] | 480 | except ValueError: |
| 481 | pass |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 482 | |
Matt Jeanneret | 40f2839 | 2019-12-04 18:21:46 -0500 | [diff] [blame] | 483 | if isinstance(attr_data, six.string_types): |
| 484 | try: |
| 485 | value = json.loads(attr_data) |
| 486 | return value |
| 487 | except ValueError: |
| 488 | pass |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 489 | |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 490 | return attr_data |
| 491 | |
| 492 | except Exception as e: |
Matt Jeanneret | 40f2839 | 2019-12-04 18:21:46 -0500 | [diff] [blame] | 493 | self.log.error('could-not-parse-attribute-returning-as-is', field=eca.field, attr_data=attr_data, e=e) |
| 494 | return attr_data |
Chip Boling | 67b674a | 2019-02-08 11:42:18 -0600 | [diff] [blame] | 495 | |
| 496 | def update_supported_managed_entities(self, device_id, managed_entities): |
| 497 | """ |
| 498 | Update the supported OMCI Managed Entities for this device |
| 499 | |
| 500 | :param device_id: (str) ONU Device ID |
| 501 | :param managed_entities: (set) Managed Entity class IDs |
| 502 | """ |
| 503 | now = datetime.utcnow() |
| 504 | try: |
| 505 | device_db = self._data[device_id] |
| 506 | |
| 507 | entities = {class_id: self._managed_entity_to_name(device_id, class_id) |
| 508 | for class_id in managed_entities} |
| 509 | |
| 510 | device_db[ME_KEY] = entities |
| 511 | self._modified = now |
| 512 | |
| 513 | except Exception as e: |
| 514 | self.log.error('set-me-failure', e=e) |
| 515 | raise |
| 516 | |
| 517 | def _managed_entity_to_name(self, device_id, class_id): |
| 518 | me_map = self._omci_agent.get_device(device_id).me_map |
| 519 | entity = me_map.get(class_id) |
| 520 | |
| 521 | return entity.__name__ if entity is not None else 'UnknownManagedEntity' |
| 522 | |
| 523 | def update_supported_message_types(self, device_id, msg_types): |
| 524 | """ |
| 525 | Update the supported OMCI Managed Entities for this device |
| 526 | |
| 527 | :param device_id: (str) ONU Device ID |
| 528 | :param msg_types: (set) Message Type values (ints) |
| 529 | """ |
| 530 | now = datetime.utcnow() |
| 531 | try: |
| 532 | msg_type_set = {msg_type.value for msg_type in msg_types} |
| 533 | self._data[device_id][MSG_TYPE_KEY] = msg_type_set |
| 534 | self._modified = now |
| 535 | |
| 536 | except Exception as e: |
| 537 | self.log.error('set-me-failure', e=e) |
| 538 | raise |
Matt Jeanneret | 40f2839 | 2019-12-04 18:21:46 -0500 | [diff] [blame] | 539 | |
| 540 | def load_from_template(self, device_id, template): |
| 541 | now = datetime.utcnow() |
| 542 | headerdata = { |
| 543 | DEVICE_ID_KEY: device_id, |
| 544 | CREATED_KEY: now, |
| 545 | LAST_SYNC_KEY: None, |
| 546 | MDS_KEY: 0, |
| 547 | VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION, |
| 548 | ME_KEY: dict(), |
| 549 | MSG_TYPE_KEY: set() |
| 550 | } |
| 551 | template.update(headerdata) |
| 552 | self._data[device_id] = template |
| 553 | |
| 554 | def dump_to_json(self, device_id): |
| 555 | device_db = self._data.get(device_id, dict()) |
| 556 | device_db = self._fix_dev_json_attributes(device_db, device_id) |
| 557 | |
| 558 | def json_converter(o): |
| 559 | if isinstance(o, datetime): |
| 560 | return o.__str__() |
| 561 | if isinstance(o, six.binary_type): |
| 562 | return o.decode('ascii') |
| 563 | |
| 564 | json_string = json.dumps(device_db, default=json_converter, indent=4) |
| 565 | |
| 566 | return json_string |