From 0e9b173381fce28fded543095360dca5198c4810 Mon Sep 17 00:00:00 2001 From: Eric Young Date: Mon, 11 Sep 2017 09:46:11 -0400 Subject: [PATCH] ScaleIO Driver - adding cache and refactoring tests Changing static lists to a simple cache. Refactoring some of the unit tests to simplify maintenance. Related-Bug: #1699573 Change-Id: Idff127801da9e286a6b634594e5577eeb9782571 (cherry picked from commit aa8b87a83cc5a8cfcaa3080c9a6080d8289716a4) --- .../drivers/dell_emc/scaleio/__init__.py | 9 +- .../dell_emc/scaleio/test_create_volume.py | 6 + .../dell_emc/scaleio/test_get_manageable.py | 22 +- .../drivers/dell_emc/scaleio/test_misc.py | 85 ++++---- .../volume/drivers/dell_emc/scaleio/driver.py | 188 +++++++++++++----- .../drivers/dell_emc/scaleio/simplecache.py | 122 ++++++++++++ 6 files changed, 329 insertions(+), 103 deletions(-) create mode 100644 cinder/volume/drivers/dell_emc/scaleio/simplecache.py diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py index ad24802ef72..c6bdeafdcfa 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/__init__.py @@ -109,6 +109,8 @@ class TestScaleIODriver(test.TestCase): PROT_DOMAIN_ID = six.text_type('1') PROT_DOMAIN_NAME = 'PD1' + STORAGE_POOLS = ['{}:{}'.format(PROT_DOMAIN_NAME, STORAGE_POOL_NAME)] + def setUp(self): """Setup a test case environment. @@ -135,13 +137,14 @@ class TestScaleIODriver(test.TestCase): group=conf.SHARED_CONF_GROUP) self.override_config('san_password', override='pass', group=conf.SHARED_CONF_GROUP) - self.override_config('sio_storage_pool_id', override='test_pool', + self.override_config('sio_storage_pool_id', + override=self.STORAGE_POOL_ID, group=conf.SHARED_CONF_GROUP) self.override_config('sio_protection_domain_id', - override='test_domain', + override=self.PROT_DOMAIN_ID, group=conf.SHARED_CONF_GROUP) self.override_config('sio_storage_pools', - override='test_domain:test_pool', + override='PD1:SP1', group=conf.SHARED_CONF_GROUP) self.override_config('max_over_subscription_ratio', override=5.0, group=conf.SHARED_CONF_GROUP) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_create_volume.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_create_volume.py index 17fd77afe39..207b257a7e8 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_create_volume.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_create_volume.py @@ -51,6 +51,12 @@ class TestCreateVolume(scaleio.TestScaleIODriver): self.PROT_DOMAIN_ID, self.STORAGE_POOL_NAME ): '"{}"'.format(self.STORAGE_POOL_ID), + 'instances/ProtectionDomain::{}'.format( + self.PROT_DOMAIN_ID + ): {'id': self.PROT_DOMAIN_ID}, + 'instances/StoragePool::{}'.format( + self.STORAGE_POOL_ID + ): {'id': self.STORAGE_POOL_ID}, }, self.RESPONSE_MODE.Invalid: { 'types/Domain/instances/getByName::' + diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_get_manageable.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_get_manageable.py index ca290975f5e..65a52bf7e65 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_get_manageable.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_get_manageable.py @@ -117,17 +117,21 @@ class ScaleIOManageableCase(scaleio.TestScaleIODriver): self.HTTPS_MOCK_RESPONSES = { self.RESPONSE_MODE.Valid: { - 'instances/StoragePool::test_pool/relationships/Volume': - scaleio_objects, + 'instances/StoragePool::{}/relationships/Volume'.format( + self.STORAGE_POOL_ID + ): scaleio_objects, 'types/Pool/instances/getByName::{},{}'.format( - "test_domain", - "test_pool" - ): '"{}"'.format("test_pool").encode('ascii', 'ignore'), + self.PROT_DOMAIN_ID, + self.STORAGE_POOL_NAME + ): '"{}"'.format(self.STORAGE_POOL_ID), + 'instances/ProtectionDomain::{}'.format( + self.PROT_DOMAIN_ID + ): {'id': self.PROT_DOMAIN_ID}, + 'instances/StoragePool::{}'.format( + self.STORAGE_POOL_ID + ): {'id': self.STORAGE_POOL_ID}, 'types/Domain/instances/getByName::' + - "test_domain": '"{}"'.format("test_domain").encode( - 'ascii', - 'ignore' - ), + self.PROT_DOMAIN_NAME: '"{}"'.format(self.PROT_DOMAIN_ID), }, } diff --git a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py index 41ebd71abcd..2f28123905a 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/scaleio/test_misc.py @@ -15,7 +15,6 @@ import ddt import mock -from six.moves import urllib from cinder import context from cinder import exception @@ -28,9 +27,9 @@ from cinder.volume import configuration @ddt.ddt class TestMisc(scaleio.TestScaleIODriver): - DOMAIN_NAME = 'PD1' - POOL_NAME = 'SP1' - STORAGE_POOLS = ['{}:{}'.format(DOMAIN_NAME, POOL_NAME)] + + DOMAIN_ID = '1' + POOL_ID = '1' def setUp(self): """Set up the test case environment. @@ -38,8 +37,6 @@ class TestMisc(scaleio.TestScaleIODriver): Defines the mock HTTPS responses for the REST API calls. """ super(TestMisc, self).setUp() - self.domain_name_enc = urllib.parse.quote(self.DOMAIN_NAME) - self.pool_name_enc = urllib.parse.quote(self.POOL_NAME) self.ctx = context.RequestContext('fake', 'fake', auth_token=True) self.volume = fake_volume.fake_volume_obj( @@ -51,17 +48,15 @@ class TestMisc(scaleio.TestScaleIODriver): self.HTTPS_MOCK_RESPONSES = { self.RESPONSE_MODE.Valid: { - 'types/Domain/instances/getByName::' + - self.domain_name_enc: '"{}"'.format(self.DOMAIN_NAME).encode( - 'ascii', - 'ignore' - ), + 'types/Domain/instances/getByName::{}'.format( + self.PROT_DOMAIN_NAME + ): '"{}"'.format(self.PROT_DOMAIN_ID), 'types/Pool/instances/getByName::{},{}'.format( - self.DOMAIN_NAME, - self.POOL_NAME - ): '"{}"'.format(self.POOL_NAME).encode('ascii', 'ignore'), + self.PROT_DOMAIN_ID, + self.STORAGE_POOL_NAME + ): '"{}"'.format(self.STORAGE_POOL_ID), 'types/StoragePool/instances/action/querySelectedStatistics': { - '"{}"'.format(self.POOL_NAME): { + '"{}"'.format(self.STORAGE_POOL_NAME): { 'capacityAvailableForVolumeAllocationInKb': 5000000, 'capacityLimitInKb': 16000000, 'spareCapacityInKb': 6000000, @@ -78,20 +73,23 @@ class TestMisc(scaleio.TestScaleIODriver): self.volume['provider_id'], 'version': '"{}"'.format('2.0.1'), 'instances/StoragePool::{}'.format( - "test_pool" + self.STORAGE_POOL_ID ): { - 'name': 'test_pool', - 'protectionDomainId': 'test_domain', + 'name': self.STORAGE_POOL_NAME, + 'id': self.STORAGE_POOL_ID, + 'protectionDomainId': self.PROT_DOMAIN_ID, + 'zeroPaddingEnabled': 'true', }, 'instances/ProtectionDomain::{}'.format( - "test_domain" + self.PROT_DOMAIN_ID ): { - 'name': 'test_domain', + 'name': self.PROT_DOMAIN_NAME, + 'id': self.PROT_DOMAIN_ID }, }, self.RESPONSE_MODE.BadStatus: { 'types/Domain/instances/getByName::' + - self.domain_name_enc: self.BAD_STATUS_RESPONSE, + self.PROT_DOMAIN_NAME: self.BAD_STATUS_RESPONSE, 'instances/Volume::{}/action/setVolumeName'.format( self.volume['provider_id']): mocks.MockHTTPSResponse( { @@ -102,7 +100,7 @@ class TestMisc(scaleio.TestScaleIODriver): }, self.RESPONSE_MODE.Invalid: { 'types/Domain/instances/getByName::' + - self.domain_name_enc: None, + self.PROT_DOMAIN_NAME: None, 'instances/Volume::{}/action/setVolumeName'.format( self.volume['provider_id']): mocks.MockHTTPSResponse( { @@ -121,8 +119,10 @@ class TestMisc(scaleio.TestScaleIODriver): INVALID """ - self.driver.configuration.sio_storage_pool_id = "test_pool_id" - self.driver.configuration.sio_storage_pool_name = "test_pool_name" + self.driver.configuration.sio_storage_pool_id = self.STORAGE_POOL_ID + self.driver.configuration.sio_storage_pool_name = ( + self.STORAGE_POOL_NAME + ) self.assertRaises(exception.InvalidInput, self.driver.check_for_setup_error) @@ -141,9 +141,9 @@ class TestMisc(scaleio.TestScaleIODriver): INVALID """ self.driver.configuration.sio_protection_domain_name = ( - "test_domain_name") + self.PROT_DOMAIN_NAME) self.driver.configuration.sio_protection_domain_id = ( - "test_domain_id") + self.PROT_DOMAIN_ID) self.assertRaises(exception.InvalidInput, self.driver.check_for_setup_error) @@ -186,17 +186,29 @@ class TestMisc(scaleio.TestScaleIODriver): """ self.HTTPS_MOCK_RESPONSES = { self.RESPONSE_MODE.ValidVariant: { - 'types/Domain/instances/getByName::' + - self.domain_name_enc: '"{}"'.format(self.DOMAIN_NAME).encode( - 'ascii', - 'ignore' - ), + 'types/Domain/instances/getByName::{}'.format( + self.PROT_DOMAIN_NAME + ): '"{}"'.format(self.PROT_DOMAIN_ID), 'types/Pool/instances/getByName::{},{}'.format( - self.DOMAIN_NAME, - self.POOL_NAME - ): '"{}"'.format(self.POOL_NAME).encode('ascii', 'ignore'), + self.PROT_DOMAIN_ID, + self.STORAGE_POOL_NAME + ): '"{}"'.format(self.STORAGE_POOL_ID), + 'instances/ProtectionDomain::{}'.format( + self.PROT_DOMAIN_ID + ): { + 'name': self.PROT_DOMAIN_NAME, + 'id': self.PROT_DOMAIN_ID + }, + 'instances/StoragePool::{}'.format( + self.STORAGE_POOL_ID + ): { + 'name': self.STORAGE_POOL_NAME, + 'id': self.STORAGE_POOL_ID, + 'protectionDomainId': self.PROT_DOMAIN_ID, + 'zeroPaddingEnabled': 'true', + }, 'types/StoragePool/instances/action/querySelectedStatistics': { - '"{}"'.format(self.POOL_NAME): { + '"{}"'.format(self.STORAGE_POOL_NAME): { 'capacityAvailableForVolumeAllocationInKb': 5000000, 'capacityLimitInKb': 16000000, 'spareCapacityInKb': 6000000, @@ -212,9 +224,6 @@ class TestMisc(scaleio.TestScaleIODriver): self.new_volume['provider_id']): self.volume['provider_id'], 'version': '"{}"'.format('2.0.1'), - 'instances/StoragePool::{}'.format( - self.STORAGE_POOL_NAME - ): '"{}"'.format(self.STORAGE_POOL_ID), } } diff --git a/cinder/volume/drivers/dell_emc/scaleio/driver.py b/cinder/volume/drivers/dell_emc/scaleio/driver.py index 1c995fe6885..78308562351 100644 --- a/cinder/volume/drivers/dell_emc/scaleio/driver.py +++ b/cinder/volume/drivers/dell_emc/scaleio/driver.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013 - 2015 EMC Corporation. +# Copyright (c) 2017 Dell Inc. or its subsidiaries. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -21,12 +21,13 @@ import binascii from distutils import version import json import math +import re + from os_brick.initiator import connector from oslo_config import cfg from oslo_log import log as logging from oslo_log import versionutils from oslo_utils import units -import re import requests import six from six.moves import http_client @@ -38,16 +39,17 @@ from cinder.i18n import _ from cinder.image import image_utils from cinder import interface from cinder import objects -from cinder import utils - from cinder.objects import fields +from cinder import utils from cinder.volume import configuration from cinder.volume import driver +from cinder.volume.drivers.dell_emc.scaleio import simplecache from cinder.volume.drivers.san import san from cinder.volume import qos_specs from cinder.volume import utils as volume_utils from cinder.volume import volume_types + CONF = cfg.CONF LOG = logging.getLogger(__name__) @@ -136,13 +138,15 @@ SIO_MAX_OVERSUBSCRIPTION_RATIO = 10.0 @interface.volumedriver class ScaleIODriver(driver.VolumeDriver): - """Dell EMC ScaleIO Driver.""" + """Cinder ScaleIO Driver - VERSION = "2.0.2" - # Major changes - # 2.0.1: Added support for SIO 1.3x in addition to 2.0.x - # 2.0.2: Added consistency group support to generic volume groups + ScaleIO Driver version history: + 2.0.1: Added support for SIO 1.3x in addition to 2.0.x + 2.0.2: Added consistency group support to generic volume groups + 2.0.3: Added cache for storage pool and protection domains info + """ + VERSION = "2.0.3" # ThirdPartySystems wiki CI_WIKI_NAME = "EMC_ScaleIO_CI" @@ -152,6 +156,12 @@ class ScaleIODriver(driver.VolumeDriver): def __init__(self, *args, **kwargs): super(ScaleIODriver, self).__init__(*args, **kwargs) + # simple caches for PD and SP properties + self.spCache = simplecache.SimpleCache("Storage Pool", + age_minutes=5) + self.pdCache = simplecache.SimpleCache("Protection Domain", + age_minutes=5) + self.configuration.append_config_values(san.san_opts) self.configuration.append_config_values(scaleio_opts) self.server_ip = self.configuration.san_ip @@ -216,10 +226,6 @@ class ScaleIODriver(driver.VolumeDriver): 'bandwidthLimit': None, } - # simple cache for domain and sp ids - self.cache_pd = {} - self.cache_sp = {} - def check_for_setup_error(self): # make sure both domain name and id are not specified if (self.configuration.sio_protection_domain_name @@ -286,6 +292,34 @@ class ScaleIODriver(driver.VolumeDriver): "sio_storage_pools.")) raise exception.InvalidInput(reason=msg) + # validate the storage pools and check if zero padding is enabled + for pool in self.storage_pools: + try: + pd, sp = pool.split(':') + except (ValueError, IndexError): + msg = (_("Invalid storage pool name. The correct format is: " + "protection_domain:storage_pool. " + "Value supplied was: %(pool)s") % + {'pool': pool}) + raise exception.InvalidInput(reason=msg) + + try: + properties = self._get_storage_pool_properties(pd, sp) + padded = properties['zeroPaddingEnabled'] + except Exception: + msg = (_("Unable to retrieve properties for pool, %(pool)s") % + {'pool': pool}) + raise exception.InvalidInput(reason=msg) + + if not padded: + LOG.warning("Zero padding is disabled for pool, %s. " + "This could lead to existing data being " + "accessible on new thick provisioned volumes. " + "Consult the ScaleIO product documentation " + "for information on how to enable zero padding " + "and prevent this from occurring.", + pool) + def _build_storage_pool_list(self): """Build storage pool list @@ -1230,40 +1264,11 @@ class ScaleIODriver(driver.VolumeDriver): def _get_protection_domain_id(self, domain_name): """"Get the id of the protection domain""" - if not domain_name: - msg = (_("Error getting domain id from None name.")) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) + response = self._get_protection_domain_properties(domain_name) + if response is None: + return None - # do we already have the id? - if domain_name in self.cache_pd: - return self.cache_pd[domain_name] - - encoded_domain_name = urllib.parse.quote(domain_name, '') - req_vars = {'server_ip': self.server_ip, - 'server_port': self.server_port, - 'encoded_domain_name': encoded_domain_name} - request = ("https://%(server_ip)s:%(server_port)s" - "/api/types/Domain/instances/getByName::" - "%(encoded_domain_name)s") % req_vars - - r, domain_id = self._execute_scaleio_get_request(request) - - if not domain_id: - msg = (_("Domain with name %s wasn't found.") - % domain_name) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - if r.status_code != http_client.OK and "errorCode" in domain_id: - msg = (_("Error getting domain id from name %(name)s: %(id)s.") - % {'name': domain_name, - 'id': domain_id['message']}) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - # add it to our cache - self.cache_pd[domain_name] = domain_id - return domain_id + return response['id'] def _get_storage_pool_name(self, pool_id): """Get the protection domain:storage pool name @@ -1317,8 +1322,61 @@ class ScaleIODriver(driver.VolumeDriver): return domain_name - def _get_storage_pool_id(self, domain_name, pool_name): - """Get the id of the configured storage pool""" + def _get_protection_domain_properties(self, domain_name): + """Get the props of the configured protection domain""" + if not domain_name: + msg = _("Error getting domain id from None name.") + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + cached_val = self.pdCache.get_value(domain_name) + if cached_val is not None: + return cached_val + + encoded_domain_name = urllib.parse.quote(domain_name, '') + req_vars = {'server_ip': self.server_ip, + 'server_port': self.server_port, + 'encoded_domain_name': encoded_domain_name} + request = ("https://%(server_ip)s:%(server_port)s" + "/api/types/Domain/instances/getByName::" + "%(encoded_domain_name)s") % req_vars + + r, domain_id = self._execute_scaleio_get_request(request) + + if not domain_id: + msg = (_("Domain with name %s wasn't found.") + % domain_name) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + if r.status_code != http_client.OK and "errorCode" in domain_id: + msg = (_("Error getting domain id from name %(name)s: %(id)s.") + % {'name': domain_name, + 'id': domain_id['message']}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + LOG.info("Domain id is %s.", domain_id) + + req_vars = {'server_ip': self.server_ip, + 'server_port': self.server_port, + 'domain_id': domain_id} + request = ("https://%(server_ip)s:%(server_port)s" + "/api/instances/ProtectionDomain::%(domain_id)s") % req_vars + r, response = self._execute_scaleio_get_request(request) + + if r.status_code != http_client.OK: + msg = (_("Error getting domain properties from id %(domain_id)s: " + "%(err_msg)s.") + % {'domain_id': domain_id, + 'err_msg': response}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + self.pdCache.update(domain_name, response) + return response + + def _get_storage_pool_properties(self, domain_name, pool_name): + """Get the props of the configured storage pool""" if not domain_name or not pool_name: msg = (_("Unable to query the storage pool id for " "Pool %(pool_name)s and Domain %(domain_name)s.") @@ -1328,9 +1386,10 @@ class ScaleIODriver(driver.VolumeDriver): raise exception.VolumeBackendAPIException(data=msg) fullname = "{}:{}".format(domain_name, pool_name) - if fullname in self.cache_sp: - return self.cache_sp[fullname] + cached_val = self.spCache.get_value(fullname) + if cached_val is not None: + return cached_val domain_id = self._get_protection_domain_id(domain_name) encoded_pool_name = urllib.parse.quote(pool_name, '') @@ -1361,9 +1420,32 @@ class ScaleIODriver(driver.VolumeDriver): LOG.info("Pool id is %s.", pool_id) - # add it to ou cache - self.cache_sp[fullname] = pool_id - return pool_id + req_vars = {'server_ip': self.server_ip, + 'server_port': self.server_port, + 'pool_id': pool_id} + request = ("https://%(server_ip)s:%(server_port)s" + "/api/instances/StoragePool::%(pool_id)s") % req_vars + r, response = self._execute_scaleio_get_request(request) + + if r.status_code != http_client.OK: + msg = (_("Error getting pool properties from id %(pool_id)s: " + "%(err_msg)s.") + % {'pool_id': pool_id, + 'err_msg': response}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + self.spCache.update(fullname, response) + return response + + def _get_storage_pool_id(self, domain_name, pool_name): + """Get the id of the configured storage pool""" + + response = self._get_storage_pool_properties(domain_name, pool_name) + if response is None: + return None + + return response['id'] def _get_all_scaleio_volumes(self): """Gets list of all SIO volumes in PD and SP""" diff --git a/cinder/volume/drivers/dell_emc/scaleio/simplecache.py b/cinder/volume/drivers/dell_emc/scaleio/simplecache.py new file mode 100644 index 00000000000..317235bcafe --- /dev/null +++ b/cinder/volume/drivers/dell_emc/scaleio/simplecache.py @@ -0,0 +1,122 @@ +# Copyright (c) 2017 Dell Inc. or its subsidiaries. +# All Rights Reserved. +# +# 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. +""" +SimpleCache utility class for Dell EMC ScaleIO Driver. +""" + +import datetime + +from oslo_log import log as logging +from oslo_utils import timeutils + + +LOG = logging.getLogger(__name__) + + +class SimpleCache(object): + + def __init__(self, name, age_minutes=30): + self.cache = {} + self.name = name + self.age_minutes = age_minutes + + def __contains__(self, key): + """Checks if a key exists in cache + + :param key: Key for the item being checked. + :return: True if item exists, otherwise False + """ + return key in self.cache + + def _remove(self, key): + """Removes item from the cache + + :param key: Key for the item being removed. + :return: + """ + if self.__class__(key): + del self.cache[key] + + def _validate(self, key): + """Validate if an item exists and has not expired. + + :param key: Key for the item being requested. + :return: The value of the related key, or None. + """ + if key not in self: + return None + # make sure the cache has not expired + entry = self.cache[key]['value'] + now = timeutils.utcnow() + age = now - self.cache[key]['date'] + if age > datetime.timedelta(minutes=self.age_minutes): + # if has expired, remove from cache + LOG.debug("Removing item '%(item)s' from cache '%(name)s' " + "due to age", + {'item': key, + 'name': self.name}) + self._remove(key) + return None + + return entry + + def purge(self, key): + """Purge an item from the cache, regardless of age + + :param key: Key for the item being removed. + :return: + """ + self._remove(key) + + def purge_all(self): + """Purge all items from the cache, regardless of age + + :return: + """ + self.cache = {} + + def set_cache_period(self, age_minutes): + """Define the period of time to cache values for + + :param age_minutes: Number of minutes to cache items for. + :return: + """ + self.age_minutes = age_minutes + + def update(self, key, value): + """Update/Store an item in the cache + + :param key: Key for the item being added. + :param value: Value to store + :return: + """ + LOG.debug("Updating item '%(item)s' in cache '%(name)s'", + {'item': key, + 'name': self.name}) + self.cache[key] = {'date': timeutils.utcnow(), + 'value': value} + + def get_value(self, key): + """Returns an item from the cache + + :param key: Key for the item being requested. + :return: Value of item or None if doesn't exist or expired + """ + value = self._validate(key) + if value is None: + LOG.debug("Item '%(item)s' is not in cache '%(name)s' ", + {'item': key, + 'name': self.name}) + return value