blob: d0cf98d0c13ac660d2d9a925256e0a223623e92f [file] [log] [blame]
#
# Copyright 2017 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import copy
from mib_db_api import *
class MibDbVolatileDict(MibDbApi):
"""
A very simple in-memory database for ME storage. Data is not persistent
across reboots.
In Phase 2, this DB will be instantiated on a per-ONU basis but act as if
it is shared for all ONUs. This class will be updated with and external
key-value store (or other appropriate database) in Voltha 1.3 Sprint 3
This class can be used for unit tests
"""
CURRENT_VERSION = 1
def __init__(self, omci_agent):
"""
Class initializer
:param omci_agent: (OpenOMCIAgent) OpenOMCI Agent
"""
super(MibDbVolatileDict, self).__init__(omci_agent)
self._data = dict() # device_id -> ME ID -> Inst ID -> Attr Name -> Values
def start(self):
"""
Start up/restore the database. For in-memory, will be a nop. For external
DB, may need to create the DB and fetch create/modified values
"""
super(MibDbVolatileDict, self).start()
# TODO: Delete this method if nothing else is done except calling the base class
def stop(self):
"""
Start up the database. For in-memory, will be a nop. For external
DB, may need to create the DB and fetch create/modified values
"""
super(MibDbVolatileDict, self).stop()
# TODO: Delete this method if nothing else is done except calling the base class
def add(self, device_id, overwrite=False):
"""
Add a new ONU to database
:param device_id: (str) Device ID of ONU to add
:param overwrite: (bool) Overwrite existing entry if found.
:raises KeyError: If device already exist and 'overwrite' is False
"""
self.log.debug('add-device', device_id=device_id, overwrite=overwrite)
if not isinstance(device_id, basestring):
raise TypeError('Device ID should be an string')
if not self._started:
raise DatabaseStateError('The Database is not currently active')
if not overwrite and device_id in self._data:
raise KeyError('Device {} already exists in the database'
.format(device_id))
now = datetime.utcnow()
self._data[device_id] = {
DEVICE_ID_KEY: device_id,
CREATED_KEY: now,
MODIFIED_KEY: now,
MDS_KEY: 0,
LAST_SYNC_KEY: None,
VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION
}
def remove(self, device_id):
"""
Remove an ONU from the database
:param device_id: (str) Device ID of ONU to remove from database
"""
self.log.debug('remove-device', device_id=device_id)
if not isinstance(device_id, basestring):
raise TypeError('Device ID should be an string')
if not self._started:
raise DatabaseStateError('The Database is not currently active')
if device_id in self._data:
del self._data[device_id]
def on_mib_reset(self, device_id):
"""
Reset/clear the database for a specific Device
:param device_id: (str) ONU Device ID
:raises DatabaseStateError: If the database is not enabled
"""
if not self._started:
raise DatabaseStateError('The Database is not currently active')
now = datetime.utcnow()
device_db = self._data.get(device_id)
if device_db is None:
self._data[device_id] = {
CREATED_KEY: now,
LAST_SYNC_KEY: None
}
device_db = self._data[device_id]
else:
created = device_db[CREATED_KEY]
last_sync = device_db[LAST_SYNC_KEY]
self._data[device_id] = {
CREATED_KEY: created,
LAST_SYNC_KEY: last_sync
}
device_db[DEVICE_ID_KEY] = device_id
device_db[MODIFIED_KEY] = now
device_db[MDS_KEY] = 0
device_db[VERSION_KEY] = MibDbVolatileDict.CURRENT_VERSION
self._modified = now
def save_mib_data_sync(self, device_id, value):
"""
Save the MIB Data Sync to the database in an easy location to access
:param device_id: (str) ONU Device ID
:param value: (int) Value to save
"""
if device_id in self._data:
assert 0 <= value <= 255,\
'Invalid MIB Data Sync Value: {}'.format(value)
self._data[device_id][MODIFIED_KEY] = datetime.utcnow()
self._data[device_id][MDS_KEY] = value
def get_mib_data_sync(self, device_id):
"""
Get the MIB Data Sync value last saved to the database for a device
:param device_id: (str) ONU Device ID
:return: (int) The Value or None if not found
"""
if device_id not in self._data:
return None
return self._data[device_id].get(MDS_KEY)
def save_last_sync(self, device_id, value):
"""
Save the Last Sync time to the database in an easy location to access
:param device_id: (str) ONU Device ID
:param value: (DateTime) Value to save
"""
if device_id in self._data:
self._data[device_id][MODIFIED_KEY] = datetime.utcnow()
self._data[device_id][LAST_SYNC_KEY] = value
def get_last_sync(self, device_id):
"""
Get the Last SYnc Time saved to the database for a device
:param device_id: (str) ONU Device ID
:return: (int) The Value or None if not found
"""
if device_id not in self._data:
return None
return self._data[device_id].get(LAST_SYNC_KEY)
def set(self, device_id, class_id, instance_id, attributes):
"""
Set a database value. This should only be called by the MIB synchronizer
and its related tasks
:param device_id: (str) ONU Device ID
:param class_id: (int) ME Class ID
:param instance_id: (int) ME Entity ID
:param attributes: (dict) Attribute dictionary
:returns: (bool) True if the value was saved to the database. False if the
value was identical to the current instance
:raises KeyError: If device does not exist
:raises DatabaseStateError: If the database is not enabled
"""
if not self._started:
raise DatabaseStateError('The Database is not currently active')
now = datetime.utcnow()
try:
device_db = self._data[device_id]
class_db = device_db.get(class_id)
if class_db is None:
device_db[class_id] = {
CLASS_ID_KEY: class_id,
CREATED_KEY: now,
MODIFIED_KEY: now
}
class_db = device_db[class_id]
self._modified = now
instance_db = class_db.get(instance_id)
if instance_db is None:
class_db[instance_id] = {
INSTANCE_ID_KEY: instance_id,
CREATED_KEY: now,
MODIFIED_KEY: now,
ATTRIBUTES_KEY: dict()
}
instance_db = class_db[instance_id]
class_db[MODIFIED_KEY] = now
device_db[MODIFIED_KEY] = now
self._modified = now
changed = False
for attribute, value in attributes.items():
assert isinstance(attribute, basestring)
assert value is not None, "Attribute '{}' value cannot be 'None'".\
format(attribute)
db_value = instance_db.get(attribute)
assert db_value is None or isinstance(value, type(db_value)), \
"New value for attribute '{}' type is changing from '{}' to '{}'".\
format(attribute, type(db_value), type(value))
if db_value is None or db_value != value:
instance_db[ATTRIBUTES_KEY][attribute] = value
changed = True
if changed:
instance_db[MODIFIED_KEY] = now
class_db[MODIFIED_KEY] = now
device_db[MODIFIED_KEY] = now
self._modified = now
return changed
except Exception as e:
self.log.error('set-failure', e=e)
raise
def delete(self, device_id, class_id, instance_id):
"""
Delete an entity from the database if it exists. If all instances
of a class are deleted, the class is deleted as well.
:param device_id: (str) ONU Device ID
:param class_id: (int) ME Class ID
:param instance_id: (int) ME Entity ID
:returns: (bool) True if the instance was found and deleted. False
if it did not exist.
:raises KeyError: If device does not exist
:raises DatabaseStateError: If the database is not enabled
"""
if not self._started:
raise DatabaseStateError('The Database is not currently active')
try:
device_db = self._data[device_id]
class_db = device_db.get(class_id)
if class_db is None:
return False
instance_db = class_db.get(instance_id)
if instance_db is None:
return False
now = datetime.utcnow()
del class_db[instance_id]
if len(class_db) == len([CREATED_KEY, MODIFIED_KEY]):
del device_db[class_id]
else:
class_db[MODIFIED_KEY] = now
device_db[MODIFIED_KEY] = now
self._modified = now
return True
except Exception as e:
self.log.error('delete-failure', e=e)
raise
def query(self, device_id, class_id=None, instance_id=None, attributes=None):
"""
Get database information.
This method can be used to request information from the database to the detailed
level requested
:param device_id: (str) ONU Device ID
:param class_id: (int) Managed Entity class ID
:param instance_id: (int) Managed Entity instance
:param attributes: (list/set or str) Managed Entity instance's attributes
:return: (dict) The value(s) requested. If class/inst/attribute is
not found, an empty dictionary is returned
:raises KeyError: If the requested device does not exist
:raises DatabaseStateError: If the database is not enabled
"""
self.log.debug('query', device_id=device_id, class_id=class_id,
instance_id=instance_id, attributes=attributes)
if not self._started:
raise DatabaseStateError('The Database is not currently active')
if not isinstance(device_id, basestring):
raise TypeError('Device ID is a string')
device_db = self._data[device_id]
if class_id is None:
return device_db # TODO: copy.deepcopy(device_db)
if not isinstance(class_id, int):
raise TypeError('Class ID is an integer')
class_db = device_db.get(class_id, dict())
if instance_id is None or len(class_db) == 0:
return class_db # TODO: copy.deepcopy(class_db)
if not isinstance(instance_id, int):
raise TypeError('Instance ID is an integer')
instance_db = class_db.get(instance_id, dict())
if attributes is None or len(instance_db) == 0:
return instance_db # TODO: copy.deepcopy(instance_db)
if not isinstance(attributes, (basestring, list, set)):
raise TypeError('Attributes should be a string or list/set of strings')
if not isinstance(attributes, (list, set)):
attributes = [attributes]
return {attr: val for attr, val in instance_db[ATTRIBUTES_KEY].iteritems()
if attr in attributes}