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
This commit is contained in:
parent
f645cdab36
commit
aa8b87a83c
@ -109,6 +109,8 @@ class TestScaleIODriver(test.TestCase):
|
|||||||
PROT_DOMAIN_ID = six.text_type('1')
|
PROT_DOMAIN_ID = six.text_type('1')
|
||||||
PROT_DOMAIN_NAME = 'PD1'
|
PROT_DOMAIN_NAME = 'PD1'
|
||||||
|
|
||||||
|
STORAGE_POOLS = ['{}:{}'.format(PROT_DOMAIN_NAME, STORAGE_POOL_NAME)]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup a test case environment.
|
"""Setup a test case environment.
|
||||||
|
|
||||||
@ -135,13 +137,14 @@ class TestScaleIODriver(test.TestCase):
|
|||||||
group=conf.SHARED_CONF_GROUP)
|
group=conf.SHARED_CONF_GROUP)
|
||||||
self.override_config('san_password', override='pass',
|
self.override_config('san_password', override='pass',
|
||||||
group=conf.SHARED_CONF_GROUP)
|
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)
|
group=conf.SHARED_CONF_GROUP)
|
||||||
self.override_config('sio_protection_domain_id',
|
self.override_config('sio_protection_domain_id',
|
||||||
override='test_domain',
|
override=self.PROT_DOMAIN_ID,
|
||||||
group=conf.SHARED_CONF_GROUP)
|
group=conf.SHARED_CONF_GROUP)
|
||||||
self.override_config('sio_storage_pools',
|
self.override_config('sio_storage_pools',
|
||||||
override='test_domain:test_pool',
|
override='PD1:SP1',
|
||||||
group=conf.SHARED_CONF_GROUP)
|
group=conf.SHARED_CONF_GROUP)
|
||||||
self.override_config('max_over_subscription_ratio',
|
self.override_config('max_over_subscription_ratio',
|
||||||
override=5.0, group=conf.SHARED_CONF_GROUP)
|
override=5.0, group=conf.SHARED_CONF_GROUP)
|
||||||
|
@ -51,6 +51,12 @@ class TestCreateVolume(scaleio.TestScaleIODriver):
|
|||||||
self.PROT_DOMAIN_ID,
|
self.PROT_DOMAIN_ID,
|
||||||
self.STORAGE_POOL_NAME
|
self.STORAGE_POOL_NAME
|
||||||
): '"{}"'.format(self.STORAGE_POOL_ID),
|
): '"{}"'.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: {
|
self.RESPONSE_MODE.Invalid: {
|
||||||
'types/Domain/instances/getByName::' +
|
'types/Domain/instances/getByName::' +
|
||||||
|
@ -117,17 +117,21 @@ class ScaleIOManageableCase(scaleio.TestScaleIODriver):
|
|||||||
|
|
||||||
self.HTTPS_MOCK_RESPONSES = {
|
self.HTTPS_MOCK_RESPONSES = {
|
||||||
self.RESPONSE_MODE.Valid: {
|
self.RESPONSE_MODE.Valid: {
|
||||||
'instances/StoragePool::test_pool/relationships/Volume':
|
'instances/StoragePool::{}/relationships/Volume'.format(
|
||||||
scaleio_objects,
|
self.STORAGE_POOL_ID
|
||||||
|
): scaleio_objects,
|
||||||
'types/Pool/instances/getByName::{},{}'.format(
|
'types/Pool/instances/getByName::{},{}'.format(
|
||||||
"test_domain",
|
self.PROT_DOMAIN_ID,
|
||||||
"test_pool"
|
self.STORAGE_POOL_NAME
|
||||||
): '"{}"'.format("test_pool").encode('ascii', 'ignore'),
|
): '"{}"'.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::' +
|
'types/Domain/instances/getByName::' +
|
||||||
"test_domain": '"{}"'.format("test_domain").encode(
|
self.PROT_DOMAIN_NAME: '"{}"'.format(self.PROT_DOMAIN_ID),
|
||||||
'ascii',
|
|
||||||
'ignore'
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
from six.moves import urllib
|
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
@ -28,9 +27,9 @@ from cinder.volume import configuration
|
|||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
class TestMisc(scaleio.TestScaleIODriver):
|
class TestMisc(scaleio.TestScaleIODriver):
|
||||||
DOMAIN_NAME = 'PD1'
|
|
||||||
POOL_NAME = 'SP1'
|
DOMAIN_ID = '1'
|
||||||
STORAGE_POOLS = ['{}:{}'.format(DOMAIN_NAME, POOL_NAME)]
|
POOL_ID = '1'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Set up the test case environment.
|
"""Set up the test case environment.
|
||||||
@ -38,8 +37,6 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
Defines the mock HTTPS responses for the REST API calls.
|
Defines the mock HTTPS responses for the REST API calls.
|
||||||
"""
|
"""
|
||||||
super(TestMisc, self).setUp()
|
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.ctx = context.RequestContext('fake', 'fake', auth_token=True)
|
||||||
|
|
||||||
self.volume = fake_volume.fake_volume_obj(
|
self.volume = fake_volume.fake_volume_obj(
|
||||||
@ -51,17 +48,15 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
|
|
||||||
self.HTTPS_MOCK_RESPONSES = {
|
self.HTTPS_MOCK_RESPONSES = {
|
||||||
self.RESPONSE_MODE.Valid: {
|
self.RESPONSE_MODE.Valid: {
|
||||||
'types/Domain/instances/getByName::' +
|
'types/Domain/instances/getByName::{}'.format(
|
||||||
self.domain_name_enc: '"{}"'.format(self.DOMAIN_NAME).encode(
|
self.PROT_DOMAIN_NAME
|
||||||
'ascii',
|
): '"{}"'.format(self.PROT_DOMAIN_ID),
|
||||||
'ignore'
|
|
||||||
),
|
|
||||||
'types/Pool/instances/getByName::{},{}'.format(
|
'types/Pool/instances/getByName::{},{}'.format(
|
||||||
self.DOMAIN_NAME,
|
self.PROT_DOMAIN_ID,
|
||||||
self.POOL_NAME
|
self.STORAGE_POOL_NAME
|
||||||
): '"{}"'.format(self.POOL_NAME).encode('ascii', 'ignore'),
|
): '"{}"'.format(self.STORAGE_POOL_ID),
|
||||||
'types/StoragePool/instances/action/querySelectedStatistics': {
|
'types/StoragePool/instances/action/querySelectedStatistics': {
|
||||||
'"{}"'.format(self.POOL_NAME): {
|
'"{}"'.format(self.STORAGE_POOL_NAME): {
|
||||||
'capacityAvailableForVolumeAllocationInKb': 5000000,
|
'capacityAvailableForVolumeAllocationInKb': 5000000,
|
||||||
'capacityLimitInKb': 16000000,
|
'capacityLimitInKb': 16000000,
|
||||||
'spareCapacityInKb': 6000000,
|
'spareCapacityInKb': 6000000,
|
||||||
@ -77,20 +72,23 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
self.volume['provider_id'],
|
self.volume['provider_id'],
|
||||||
'version': '"{}"'.format('2.0.1'),
|
'version': '"{}"'.format('2.0.1'),
|
||||||
'instances/StoragePool::{}'.format(
|
'instances/StoragePool::{}'.format(
|
||||||
"test_pool"
|
self.STORAGE_POOL_ID
|
||||||
): {
|
): {
|
||||||
'name': 'test_pool',
|
'name': self.STORAGE_POOL_NAME,
|
||||||
'protectionDomainId': 'test_domain',
|
'id': self.STORAGE_POOL_ID,
|
||||||
|
'protectionDomainId': self.PROT_DOMAIN_ID,
|
||||||
|
'zeroPaddingEnabled': 'true',
|
||||||
},
|
},
|
||||||
'instances/ProtectionDomain::{}'.format(
|
'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: {
|
self.RESPONSE_MODE.BadStatus: {
|
||||||
'types/Domain/instances/getByName::' +
|
'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(
|
'instances/Volume::{}/action/setVolumeName'.format(
|
||||||
self.volume['provider_id']): mocks.MockHTTPSResponse(
|
self.volume['provider_id']): mocks.MockHTTPSResponse(
|
||||||
{
|
{
|
||||||
@ -101,7 +99,7 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
},
|
},
|
||||||
self.RESPONSE_MODE.Invalid: {
|
self.RESPONSE_MODE.Invalid: {
|
||||||
'types/Domain/instances/getByName::' +
|
'types/Domain/instances/getByName::' +
|
||||||
self.domain_name_enc: None,
|
self.PROT_DOMAIN_NAME: None,
|
||||||
'instances/Volume::{}/action/setVolumeName'.format(
|
'instances/Volume::{}/action/setVolumeName'.format(
|
||||||
self.volume['provider_id']): mocks.MockHTTPSResponse(
|
self.volume['provider_id']): mocks.MockHTTPSResponse(
|
||||||
{
|
{
|
||||||
@ -120,8 +118,10 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
|
|
||||||
INVALID
|
INVALID
|
||||||
"""
|
"""
|
||||||
self.driver.configuration.sio_storage_pool_id = "test_pool_id"
|
self.driver.configuration.sio_storage_pool_id = self.STORAGE_POOL_ID
|
||||||
self.driver.configuration.sio_storage_pool_name = "test_pool_name"
|
self.driver.configuration.sio_storage_pool_name = (
|
||||||
|
self.STORAGE_POOL_NAME
|
||||||
|
)
|
||||||
self.assertRaises(exception.InvalidInput,
|
self.assertRaises(exception.InvalidInput,
|
||||||
self.driver.check_for_setup_error)
|
self.driver.check_for_setup_error)
|
||||||
|
|
||||||
@ -140,9 +140,9 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
INVALID
|
INVALID
|
||||||
"""
|
"""
|
||||||
self.driver.configuration.sio_protection_domain_name = (
|
self.driver.configuration.sio_protection_domain_name = (
|
||||||
"test_domain_name")
|
self.PROT_DOMAIN_NAME)
|
||||||
self.driver.configuration.sio_protection_domain_id = (
|
self.driver.configuration.sio_protection_domain_id = (
|
||||||
"test_domain_id")
|
self.PROT_DOMAIN_ID)
|
||||||
self.assertRaises(exception.InvalidInput,
|
self.assertRaises(exception.InvalidInput,
|
||||||
self.driver.check_for_setup_error)
|
self.driver.check_for_setup_error)
|
||||||
|
|
||||||
@ -185,17 +185,29 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
"""
|
"""
|
||||||
self.HTTPS_MOCK_RESPONSES = {
|
self.HTTPS_MOCK_RESPONSES = {
|
||||||
self.RESPONSE_MODE.ValidVariant: {
|
self.RESPONSE_MODE.ValidVariant: {
|
||||||
'types/Domain/instances/getByName::' +
|
'types/Domain/instances/getByName::{}'.format(
|
||||||
self.domain_name_enc: '"{}"'.format(self.DOMAIN_NAME).encode(
|
self.PROT_DOMAIN_NAME
|
||||||
'ascii',
|
): '"{}"'.format(self.PROT_DOMAIN_ID),
|
||||||
'ignore'
|
|
||||||
),
|
|
||||||
'types/Pool/instances/getByName::{},{}'.format(
|
'types/Pool/instances/getByName::{},{}'.format(
|
||||||
self.DOMAIN_NAME,
|
self.PROT_DOMAIN_ID,
|
||||||
self.POOL_NAME
|
self.STORAGE_POOL_NAME
|
||||||
): '"{}"'.format(self.POOL_NAME).encode('ascii', 'ignore'),
|
): '"{}"'.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': {
|
'types/StoragePool/instances/action/querySelectedStatistics': {
|
||||||
'"{}"'.format(self.POOL_NAME): {
|
'"{}"'.format(self.STORAGE_POOL_NAME): {
|
||||||
'capacityAvailableForVolumeAllocationInKb': 5000000,
|
'capacityAvailableForVolumeAllocationInKb': 5000000,
|
||||||
'capacityLimitInKb': 16000000,
|
'capacityLimitInKb': 16000000,
|
||||||
'spareCapacityInKb': 6000000,
|
'spareCapacityInKb': 6000000,
|
||||||
@ -210,9 +222,6 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
self.new_volume['provider_id']):
|
self.new_volume['provider_id']):
|
||||||
self.volume['provider_id'],
|
self.volume['provider_id'],
|
||||||
'version': '"{}"'.format('2.0.1'),
|
'version': '"{}"'.format('2.0.1'),
|
||||||
'instances/StoragePool::{}'.format(
|
|
||||||
self.STORAGE_POOL_NAME
|
|
||||||
): '"{}"'.format(self.STORAGE_POOL_ID),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2013 - 2015 EMC Corporation.
|
# Copyright (c) 2017 Dell Inc. or its subsidiaries.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@ -21,12 +21,13 @@ import binascii
|
|||||||
from distutils import version
|
from distutils import version
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
import re
|
||||||
|
|
||||||
from os_brick.initiator import connector
|
from os_brick.initiator import connector
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_log import versionutils
|
from oslo_log import versionutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
import six
|
import six
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
@ -38,16 +39,17 @@ from cinder.i18n import _
|
|||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
from cinder import interface
|
from cinder import interface
|
||||||
from cinder import objects
|
from cinder import objects
|
||||||
from cinder import utils
|
|
||||||
|
|
||||||
from cinder.objects import fields
|
from cinder.objects import fields
|
||||||
|
from cinder import utils
|
||||||
from cinder.volume import configuration
|
from cinder.volume import configuration
|
||||||
from cinder.volume import driver
|
from cinder.volume import driver
|
||||||
|
from cinder.volume.drivers.dell_emc.scaleio import simplecache
|
||||||
from cinder.volume.drivers.san import san
|
from cinder.volume.drivers.san import san
|
||||||
from cinder.volume import qos_specs
|
from cinder.volume import qos_specs
|
||||||
from cinder.volume import utils as volume_utils
|
from cinder.volume import utils as volume_utils
|
||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -130,13 +132,15 @@ SIO_MAX_OVERSUBSCRIPTION_RATIO = 10.0
|
|||||||
|
|
||||||
@interface.volumedriver
|
@interface.volumedriver
|
||||||
class ScaleIODriver(driver.VolumeDriver):
|
class ScaleIODriver(driver.VolumeDriver):
|
||||||
"""Dell EMC ScaleIO Driver."""
|
"""Cinder ScaleIO Driver
|
||||||
|
|
||||||
VERSION = "2.0.2"
|
ScaleIO Driver version history:
|
||||||
# Major changes
|
2.0.1: Added support for SIO 1.3x in addition to 2.0.x
|
||||||
# 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.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
|
# ThirdPartySystems wiki
|
||||||
CI_WIKI_NAME = "EMC_ScaleIO_CI"
|
CI_WIKI_NAME = "EMC_ScaleIO_CI"
|
||||||
|
|
||||||
@ -146,6 +150,12 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ScaleIODriver, self).__init__(*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(san.san_opts)
|
||||||
self.configuration.append_config_values(scaleio_opts)
|
self.configuration.append_config_values(scaleio_opts)
|
||||||
self.server_ip = self.configuration.san_ip
|
self.server_ip = self.configuration.san_ip
|
||||||
@ -210,10 +220,6 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
'bandwidthLimit': None,
|
'bandwidthLimit': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# simple cache for domain and sp ids
|
|
||||||
self.cache_pd = {}
|
|
||||||
self.cache_sp = {}
|
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
def check_for_setup_error(self):
|
||||||
# make sure both domain name and id are not specified
|
# make sure both domain name and id are not specified
|
||||||
if (self.configuration.sio_protection_domain_name
|
if (self.configuration.sio_protection_domain_name
|
||||||
@ -280,6 +286,34 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
"sio_storage_pools."))
|
"sio_storage_pools."))
|
||||||
raise exception.InvalidInput(reason=msg)
|
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):
|
def _build_storage_pool_list(self):
|
||||||
"""Build storage pool list
|
"""Build storage pool list
|
||||||
|
|
||||||
@ -1178,40 +1212,11 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
def _get_protection_domain_id(self, domain_name):
|
def _get_protection_domain_id(self, domain_name):
|
||||||
""""Get the id of the protection domain"""
|
""""Get the id of the protection domain"""
|
||||||
|
|
||||||
if not domain_name:
|
response = self._get_protection_domain_properties(domain_name)
|
||||||
msg = (_("Error getting domain id from None name."))
|
if response is None:
|
||||||
LOG.error(msg)
|
return None
|
||||||
raise exception.VolumeBackendAPIException(data=msg)
|
|
||||||
|
|
||||||
# do we already have the id?
|
return response['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
|
|
||||||
|
|
||||||
def _get_storage_pool_name(self, pool_id):
|
def _get_storage_pool_name(self, pool_id):
|
||||||
"""Get the protection domain:storage pool name
|
"""Get the protection domain:storage pool name
|
||||||
@ -1265,8 +1270,61 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
|
|
||||||
return domain_name
|
return domain_name
|
||||||
|
|
||||||
def _get_storage_pool_id(self, domain_name, pool_name):
|
def _get_protection_domain_properties(self, domain_name):
|
||||||
"""Get the id of the configured storage pool"""
|
"""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:
|
if not domain_name or not pool_name:
|
||||||
msg = (_("Unable to query the storage pool id for "
|
msg = (_("Unable to query the storage pool id for "
|
||||||
"Pool %(pool_name)s and Domain %(domain_name)s.")
|
"Pool %(pool_name)s and Domain %(domain_name)s.")
|
||||||
@ -1276,9 +1334,10 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
raise exception.VolumeBackendAPIException(data=msg)
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
|
||||||
fullname = "{}:{}".format(domain_name, pool_name)
|
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)
|
domain_id = self._get_protection_domain_id(domain_name)
|
||||||
encoded_pool_name = urllib.parse.quote(pool_name, '')
|
encoded_pool_name = urllib.parse.quote(pool_name, '')
|
||||||
@ -1309,9 +1368,32 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
|
|
||||||
LOG.info("Pool id is %s.", pool_id)
|
LOG.info("Pool id is %s.", pool_id)
|
||||||
|
|
||||||
# add it to ou cache
|
req_vars = {'server_ip': self.server_ip,
|
||||||
self.cache_sp[fullname] = pool_id
|
'server_port': self.server_port,
|
||||||
return pool_id
|
'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):
|
def _get_all_scaleio_volumes(self):
|
||||||
"""Gets list of all SIO volumes in PD and SP"""
|
"""Gets list of all SIO volumes in PD and SP"""
|
||||||
|
122
cinder/volume/drivers/dell_emc/scaleio/simplecache.py
Normal file
122
cinder/volume/drivers/dell_emc/scaleio/simplecache.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user