cinder/cinder/tests/unit/volume/drivers/dell_emc/vnx/res_mock.py

451 lines
15 KiB
Python

# Copyright (c) 2016 EMC Corporation.
# 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.
import mock
import six
from cinder import context
from cinder.tests.unit.consistencygroup import fake_cgsnapshot
from cinder.tests.unit.consistencygroup import fake_consistencygroup
from cinder.tests.unit import fake_constants
from cinder.tests.unit import fake_group
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit.volume.drivers.dell_emc.vnx import fake_exception as \
lib_ex
from cinder.tests.unit.volume.drivers.dell_emc.vnx import fake_storops as \
storops
from cinder.tests.unit.volume.drivers.dell_emc.vnx import utils
from cinder.volume.drivers.dell_emc.vnx import adapter
from cinder.volume.drivers.dell_emc.vnx import client
from cinder.volume.drivers.dell_emc.vnx import common
from cinder.volume.drivers.dell_emc.vnx import driver
from cinder.volume.drivers.dell_emc.vnx import utils as vnx_utils
SYMBOL_TYPE = '_type'
SYMBOL_PROPERTIES = '_properties'
SYMBOL_METHODS = '_methods'
SYMBOL_SIDE_EFFECT = '_side_effect'
SYMBOL_RAISE = '_raise'
SYMBOL_CONTEXT = '_context'
UUID = '_uuid'
SYMBOL_ENUM = '_enum'
def _is_driver_object(obj_body):
return isinstance(obj_body, dict) and SYMBOL_PROPERTIES in obj_body
class DriverResourceMock(dict):
fake_func_mapping = {}
def __init__(self, yaml_file):
yaml_dict = utils.load_yaml(yaml_file)
if not isinstance(yaml_dict, dict):
return
for case_name, case_res in yaml_dict.items():
if not isinstance(case_res, dict):
continue
self[case_name] = {}
for obj_name, obj_body in case_res.items():
self[case_name][obj_name] = self._parse_driver_object(obj_body)
def _parse_driver_object(self, obj_body):
if isinstance(obj_body, dict):
obj_body = {k: self._parse_driver_object(v)
for k, v in obj_body.items()}
if _is_driver_object(obj_body):
return self._create_object(obj_body)
else:
return obj_body
elif isinstance(obj_body, list):
return map(self._parse_driver_object, obj_body)
else:
return obj_body
def _create_object(self, obj_body):
props = obj_body[SYMBOL_PROPERTIES]
for prop_name, prop_value in props.items():
if isinstance(prop_value, dict) and prop_value:
# get the first key as the convert function
func_name = list(prop_value.keys())[0]
if func_name.startswith('_'):
func = getattr(self, func_name)
props[prop_name] = func(prop_value[func_name])
if (SYMBOL_TYPE in obj_body and
obj_body[SYMBOL_TYPE] in self.fake_func_mapping):
return self.fake_func_mapping[obj_body[SYMBOL_TYPE]](**props)
else:
return props
@staticmethod
def _uuid(uuid_key):
uuid_key = uuid_key.upper()
return getattr(fake_constants, uuid_key)
def _fake_volume_wrapper(*args, **kwargs):
expected_attrs_key = {'volume_attachment': 'volume_attachment',
'volume_metadata': 'metadata'}
return fake_volume.fake_volume_obj(
context.get_admin_context(),
expected_attrs=[
v for (k, v) in expected_attrs_key.items() if k in kwargs],
**kwargs)
def _fake_cg_wrapper(*args, **kwargs):
return fake_consistencygroup.fake_consistencyobject_obj(None, **kwargs)
def _fake_snapshot_wrapper(*args, **kwargs):
return fake_snapshot.fake_snapshot_obj('fake_context',
expected_attrs=(
['volume'] if 'volume' in kwargs
else None),
**kwargs)
def _fake_cg_snapshot_wrapper(*args, **kwargs):
return fake_cgsnapshot.fake_cgsnapshot_obj(None, **kwargs)
def _fake_group_wrapper(*args, **kwargs):
return fake_group.fake_group_obj(None, **kwargs)
class EnumBuilder(object):
def __init__(self, enum_dict):
enum_dict = enum_dict[SYMBOL_ENUM]
for k, v in enum_dict.items():
self.klazz = k
self.value = v
def __call__(self, *args, **kwargs):
return getattr(storops, self.klazz).parse(self.value)
class CinderResourceMock(DriverResourceMock):
# fake_func in the mapping should be like func(*args, **kwargs)
fake_func_mapping = {'volume': _fake_volume_wrapper,
'cg': _fake_cg_wrapper,
'snapshot': _fake_snapshot_wrapper,
'cg_snapshot': _fake_cg_snapshot_wrapper,
'group': _fake_group_wrapper}
def __init__(self, yaml_file):
super(CinderResourceMock, self).__init__(yaml_file)
@staticmethod
def _build_provider_location(props):
return vnx_utils.build_provider_location(
props.get('system'), props.get('type'),
six.text_type(props.get('id')),
six.text_type(props.get('base_lun_name')),
props.get('version'))
class ContextMock(object):
"""Mocks the return value of a context function."""
def __enter__(self):
pass
def __exit__(self, exc_type, exc_valu, exc_tb):
pass
class MockBase(object):
"""Base object of all the Mocks.
This mock convert the dict to object when the '_type' is
included in the dict
"""
def _is_mock_object(self, yaml_info):
return (isinstance(yaml_info, dict) and
(SYMBOL_PROPERTIES in yaml_info or
SYMBOL_METHODS in yaml_info))
def _is_object_with_type(self, yaml_dict):
return isinstance(yaml_dict, dict) and SYMBOL_TYPE in yaml_dict
def _is_object_with_enum(self, yaml_dict):
return isinstance(yaml_dict, dict) and SYMBOL_ENUM in yaml_dict
def _build_mock_object(self, yaml_dict):
if self._is_object_with_type(yaml_dict):
return FakePort(yaml_dict)
elif self._is_object_with_enum(yaml_dict):
return EnumBuilder(yaml_dict)()
elif self._is_mock_object(yaml_dict):
return StorageObjectMock(yaml_dict)
elif isinstance(yaml_dict, dict):
return {k: self._build_mock_object(v)
for k, v in yaml_dict.items()}
elif isinstance(yaml_dict, list):
return [self._build_mock_object(each) for each in yaml_dict]
else:
return yaml_dict
class StorageObjectMock(object):
PROPS = 'props'
def __init__(self, yaml_dict):
self.__dict__[StorageObjectMock.PROPS] = {}
props = yaml_dict.get(SYMBOL_PROPERTIES, None)
if props:
for k, v in props.items():
setattr(self, k, StoragePropertyMock(k, v)())
methods = yaml_dict.get(SYMBOL_METHODS, None)
if methods:
for k, v in methods.items():
setattr(self, k, StorageMethodMock(k, v))
def __setattr__(self, key, value):
self.__dict__[StorageObjectMock.PROPS][key] = value
def __getattr__(self, item):
try:
super(StorageObjectMock, self).__getattr__(item)
except AttributeError:
return self.__dict__[StorageObjectMock.PROPS][item]
except KeyError:
raise KeyError('%(item)s not exist in mock object.'
) % {'item': item}
class FakePort(StorageObjectMock):
def __eq__(self, other):
o_sp = other.sp
o_port_id = other.port_id
o_vport_id = other.vport_id
ret = True
ret &= self.sp == o_sp
ret &= self.port_id == o_port_id
ret &= self.vport_id == o_vport_id
return ret
def __hash__(self):
return hash((self.sp, self.port_id, self.vport_id))
class StoragePropertyMock(mock.PropertyMock, MockBase):
def __init__(self, name, property_body):
return_value = property_body
side_effect = None
# only support return_value and side_effect for property
if (isinstance(property_body, dict) and
SYMBOL_SIDE_EFFECT in property_body):
side_effect = self._build_mock_object(
property_body[SYMBOL_SIDE_EFFECT])
return_value = None
if side_effect is not None:
super(StoragePropertyMock, self).__init__(
name=name,
side_effect=side_effect)
else:
return_value = self._build_mock_object(return_value)
super(StoragePropertyMock, self).__init__(
name=name,
return_value=return_value)
class StorageMethodMock(mock.Mock, MockBase):
def __init__(self, name, method_body):
return_value = method_body
exception = None
side_effect = None
# support return_value, side_effect and exception for method
if isinstance(method_body, dict):
if (SYMBOL_SIDE_EFFECT in method_body or
SYMBOL_RAISE in method_body):
exception = method_body.get(SYMBOL_RAISE, None)
side_effect = method_body.get(SYMBOL_SIDE_EFFECT, None)
return_value = None
if exception is not None:
ex = None
if isinstance(exception, dict) and exception:
ex_name = list(exception.keys())[0]
ex_tmp = [getattr(ex_module, ex_name, None)
for ex_module in [lib_ex, common]]
try:
ex = [each for each in ex_tmp if each is not None][0]
super(StorageMethodMock, self).__init__(
name=name,
side_effect=ex(exception[ex_name]))
except IndexError:
raise KeyError('Exception %(ex_name)s not found.'
% {'ex_name': ex_name})
else:
raise KeyError('Invalid Exception body, should be a dict.')
elif side_effect is not None:
super(StorageMethodMock, self).__init__(
name=name,
side_effect=self._build_mock_object(side_effect))
elif return_value is not None:
super(StorageMethodMock, self).__init__(
name=name,
return_value=(ContextMock() if return_value == SYMBOL_CONTEXT
else self._build_mock_object(return_value)))
else:
super(StorageMethodMock, self).__init__(
name=name, return_value=None)
class StorageResourceMock(dict, MockBase):
def __init__(self, yaml_file):
yaml_dict = utils.load_yaml(yaml_file)
if not isinstance(yaml_dict, dict):
return
for section, sec_body in yaml_dict.items():
if isinstance(sec_body, dict):
self[section] = {obj_name: self._build_mock_object(obj_body)
for obj_name, obj_body
in sec_body.items()}
else:
self[section] = {}
cinder_res = CinderResourceMock('mocked_cinder.yaml')
DRIVER_RES_MAPPING = {
'TestResMock': cinder_res,
'TestCommonAdapter': cinder_res,
'TestISCSIAdapter': cinder_res,
'TestFCAdapter': cinder_res,
'TestUtils': cinder_res,
'TestClient': cinder_res
}
def mock_driver_input(func):
@six.wraps(func)
def decorated(cls, *args, **kwargs):
return func(cls,
DRIVER_RES_MAPPING[cls.__class__.__name__][func.__name__],
*args, **kwargs)
return decorated
vnx_res = StorageResourceMock('mocked_vnx.yaml')
STORAGE_RES_MAPPING = {
'TestResMock': StorageResourceMock('test_res_mock.yaml'),
'TestCondition': vnx_res,
'TestClient': vnx_res,
'TestCommonAdapter': vnx_res,
'TestISCSIAdapter': vnx_res,
'TestFCAdapter': vnx_res,
'TestTaskflow': vnx_res,
'TestExtraSpecs': vnx_res,
}
DEFAULT_STORAGE_RES = 'vnx'
def _build_client():
return client.Client(ip='192.168.1.2',
username='sysadmin',
password='sysadmin',
scope='global',
naviseccli=None,
sec_file=None,
queue_path='vnx-cinder')
def patch_client(func):
@six.wraps(func)
def decorated(cls, *args, **kwargs):
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
with utils.patch_vnxsystem as patched_vnx:
if DEFAULT_STORAGE_RES in storage_res:
patched_vnx.return_value = storage_res[DEFAULT_STORAGE_RES]
client = _build_client()
return func(cls, client, storage_res, *args, **kwargs)
return decorated
PROTOCOL_COMMON = 'Common'
PROTOCOL_MAPPING = {
PROTOCOL_COMMON: adapter.CommonAdapter,
common.PROTOCOL_ISCSI: adapter.ISCSIAdapter,
common.PROTOCOL_FC: adapter.FCAdapter
}
def patch_adapter_init(protocol):
def inner_patch_adapter(func):
@six.wraps(func)
def decorated(cls, *args, **kwargs):
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
with utils.patch_vnxsystem as patched_vnx:
if DEFAULT_STORAGE_RES in storage_res:
patched_vnx.return_value = storage_res[DEFAULT_STORAGE_RES]
adapter = PROTOCOL_MAPPING[protocol](cls.configuration)
return func(cls, adapter, storage_res, *args, **kwargs)
return decorated
return inner_patch_adapter
def _patch_adapter_prop(adapter, client):
try:
adapter.serial_number = client.get_serial()
except KeyError:
adapter.serial_number = 'faked_serial_number'
adapter.VERSION = driver.VNXDriver.VERSION
def patch_adapter(protocol):
def inner_patch_adapter(func):
@six.wraps(func)
def decorated(cls, *args, **kwargs):
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
with utils.patch_vnxsystem:
client = _build_client()
adapter = PROTOCOL_MAPPING[protocol](cls.configuration, None)
if DEFAULT_STORAGE_RES in storage_res:
client.vnx = storage_res[DEFAULT_STORAGE_RES]
adapter.client = client
_patch_adapter_prop(adapter, client)
return func(cls, adapter, storage_res, *args, **kwargs)
return decorated
return inner_patch_adapter
patch_common_adapter = patch_adapter(PROTOCOL_COMMON)
patch_iscsi_adapter = patch_adapter(common.PROTOCOL_ISCSI)
patch_fc_adapter = patch_adapter(common.PROTOCOL_FC)
def mock_storage_resources(func):
@six.wraps(func)
def decorated(cls, *args, **kwargs):
storage_res = (
STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__])
return func(cls, storage_res, *args, **kwargs)
return decorated