1# Copyright (c) 2016 EMC Corporation. 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# 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, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16import mock 17import six 18 19from cinder import context 20from cinder.tests.unit.consistencygroup import fake_cgsnapshot 21from cinder.tests.unit.consistencygroup import fake_consistencygroup 22from cinder.tests.unit import fake_constants 23from cinder.tests.unit import fake_group 24from cinder.tests.unit import fake_snapshot 25from cinder.tests.unit import fake_volume 26from cinder.tests.unit.volume.drivers.dell_emc.vnx import fake_exception as \ 27 lib_ex 28from cinder.tests.unit.volume.drivers.dell_emc.vnx import fake_storops as \ 29 storops 30from cinder.tests.unit.volume.drivers.dell_emc.vnx import utils 31from cinder.volume.drivers.dell_emc.vnx import adapter 32from cinder.volume.drivers.dell_emc.vnx import client 33from cinder.volume.drivers.dell_emc.vnx import common 34from cinder.volume.drivers.dell_emc.vnx import driver 35from cinder.volume.drivers.dell_emc.vnx import utils as vnx_utils 36 37SYMBOL_TYPE = '_type' 38SYMBOL_PROPERTIES = '_properties' 39SYMBOL_METHODS = '_methods' 40SYMBOL_SIDE_EFFECT = '_side_effect' 41SYMBOL_RAISE = '_raise' 42SYMBOL_CONTEXT = '_context' 43UUID = '_uuid' 44SYMBOL_ENUM = '_enum' 45 46 47def _is_driver_object(obj_body): 48 return isinstance(obj_body, dict) and SYMBOL_PROPERTIES in obj_body 49 50 51class DriverResourceMock(dict): 52 fake_func_mapping = {} 53 54 def __init__(self, yaml_file): 55 yaml_dict = utils.load_yaml(yaml_file) 56 if not isinstance(yaml_dict, dict): 57 return 58 for case_name, case_res in yaml_dict.items(): 59 if not isinstance(case_res, dict): 60 continue 61 self[case_name] = {} 62 for obj_name, obj_body in case_res.items(): 63 self[case_name][obj_name] = self._parse_driver_object(obj_body) 64 65 def _parse_driver_object(self, obj_body): 66 if isinstance(obj_body, dict): 67 obj_body = {k: self._parse_driver_object(v) 68 for k, v in obj_body.items()} 69 if _is_driver_object(obj_body): 70 return self._create_object(obj_body) 71 else: 72 return obj_body 73 elif isinstance(obj_body, list): 74 return map(self._parse_driver_object, obj_body) 75 else: 76 return obj_body 77 78 def _create_object(self, obj_body): 79 props = obj_body[SYMBOL_PROPERTIES] 80 for prop_name, prop_value in props.items(): 81 if isinstance(prop_value, dict) and prop_value: 82 # get the first key as the convert function 83 func_name = list(prop_value.keys())[0] 84 if func_name.startswith('_'): 85 func = getattr(self, func_name) 86 props[prop_name] = func(prop_value[func_name]) 87 88 if (SYMBOL_TYPE in obj_body and 89 obj_body[SYMBOL_TYPE] in self.fake_func_mapping): 90 return self.fake_func_mapping[obj_body[SYMBOL_TYPE]](**props) 91 else: 92 return props 93 94 @staticmethod 95 def _uuid(uuid_key): 96 uuid_key = uuid_key.upper() 97 return getattr(fake_constants, uuid_key) 98 99 100def _fake_volume_wrapper(*args, **kwargs): 101 expected_attrs_key = {'volume_attachment': 'volume_attachment', 102 'volume_metadata': 'metadata'} 103 if 'group' in kwargs: 104 expected_attrs_key['group'] = kwargs['group'] 105 106 return fake_volume.fake_volume_obj( 107 context.get_admin_context(), 108 expected_attrs=[ 109 v for (k, v) in expected_attrs_key.items() if k in kwargs], 110 **kwargs) 111 112 113def _fake_cg_wrapper(*args, **kwargs): 114 return fake_consistencygroup.fake_consistencyobject_obj( 115 'fake_context', **kwargs) 116 117 118def _fake_snapshot_wrapper(*args, **kwargs): 119 return fake_snapshot.fake_snapshot_obj('fake_context', 120 expected_attrs=( 121 ['volume'] if 'volume' in kwargs 122 else None), 123 **kwargs) 124 125 126def _fake_cg_snapshot_wrapper(*args, **kwargs): 127 return fake_cgsnapshot.fake_cgsnapshot_obj(None, **kwargs) 128 129 130def _fake_group_wrapper(*args, **kwargs): 131 return fake_group.fake_group_obj(None, **kwargs) 132 133 134class EnumBuilder(object): 135 def __init__(self, enum_dict): 136 enum_dict = enum_dict[SYMBOL_ENUM] 137 for k, v in enum_dict.items(): 138 self.klazz = k 139 self.value = v 140 141 def __call__(self, *args, **kwargs): 142 return getattr(storops, self.klazz).parse(self.value) 143 144 145class CinderResourceMock(DriverResourceMock): 146 # fake_func in the mapping should be like func(*args, **kwargs) 147 fake_func_mapping = {'volume': _fake_volume_wrapper, 148 'cg': _fake_cg_wrapper, 149 'snapshot': _fake_snapshot_wrapper, 150 'cg_snapshot': _fake_cg_snapshot_wrapper, 151 'group': _fake_group_wrapper} 152 153 def __init__(self, yaml_file): 154 super(CinderResourceMock, self).__init__(yaml_file) 155 156 @staticmethod 157 def _build_provider_location(props): 158 return vnx_utils.build_provider_location( 159 props.get('system'), props.get('type'), 160 six.text_type(props.get('id')), 161 six.text_type(props.get('base_lun_name')), 162 props.get('version')) 163 164 165class ContextMock(object): 166 """Mocks the return value of a context function.""" 167 168 def __enter__(self): 169 pass 170 171 def __exit__(self, exc_type, exc_valu, exc_tb): 172 pass 173 174 175class MockBase(object): 176 """Base object of all the Mocks. 177 178 This mock convert the dict to object when the '_type' is 179 included in the dict 180 """ 181 182 def _is_mock_object(self, yaml_info): 183 return (isinstance(yaml_info, dict) and 184 (SYMBOL_PROPERTIES in yaml_info or 185 SYMBOL_METHODS in yaml_info)) 186 187 def _is_object_with_type(self, yaml_dict): 188 return isinstance(yaml_dict, dict) and SYMBOL_TYPE in yaml_dict 189 190 def _is_object_with_enum(self, yaml_dict): 191 return isinstance(yaml_dict, dict) and SYMBOL_ENUM in yaml_dict 192 193 def _build_mock_object(self, yaml_dict): 194 if self._is_object_with_type(yaml_dict): 195 return FakePort(yaml_dict) 196 elif self._is_object_with_enum(yaml_dict): 197 return EnumBuilder(yaml_dict)() 198 elif self._is_mock_object(yaml_dict): 199 return StorageObjectMock(yaml_dict) 200 elif isinstance(yaml_dict, dict): 201 return {k: self._build_mock_object(v) 202 for k, v in yaml_dict.items()} 203 elif isinstance(yaml_dict, list): 204 return [self._build_mock_object(each) for each in yaml_dict] 205 else: 206 return yaml_dict 207 208 209class StorageObjectMock(object): 210 PROPS = 'props' 211 212 def __init__(self, yaml_dict): 213 self.__dict__[StorageObjectMock.PROPS] = {} 214 props = yaml_dict.get(SYMBOL_PROPERTIES, None) 215 if props: 216 for k, v in props.items(): 217 setattr(self, k, StoragePropertyMock(k, v)()) 218 219 methods = yaml_dict.get(SYMBOL_METHODS, None) 220 if methods: 221 for k, v in methods.items(): 222 setattr(self, k, StorageMethodMock(k, v)) 223 224 def __setattr__(self, key, value): 225 self.__dict__[StorageObjectMock.PROPS][key] = value 226 227 def __getattr__(self, item): 228 try: 229 super(StorageObjectMock, self).__getattr__(item) 230 except AttributeError: 231 return self.__dict__[StorageObjectMock.PROPS][item] 232 except KeyError: 233 raise KeyError('%(item)s not exist in mock object.' 234 ) % {'item': item} 235 236 237class FakePort(StorageObjectMock): 238 239 def __eq__(self, other): 240 o_sp = other.sp 241 o_port_id = other.port_id 242 o_vport_id = other.vport_id 243 244 ret = True 245 ret &= self.sp == o_sp 246 ret &= self.port_id == o_port_id 247 ret &= self.vport_id == o_vport_id 248 249 return ret 250 251 def __hash__(self): 252 return hash((self.sp, self.port_id, self.vport_id)) 253 254 255class StoragePropertyMock(mock.PropertyMock, MockBase): 256 def __init__(self, name, property_body): 257 return_value = property_body 258 side_effect = None 259 260 # only support return_value and side_effect for property 261 if (isinstance(property_body, dict) and 262 SYMBOL_SIDE_EFFECT in property_body): 263 side_effect = self._build_mock_object( 264 property_body[SYMBOL_SIDE_EFFECT]) 265 return_value = None 266 267 if side_effect is not None: 268 super(StoragePropertyMock, self).__init__( 269 name=name, 270 side_effect=side_effect) 271 else: 272 return_value = self._build_mock_object(return_value) 273 274 super(StoragePropertyMock, self).__init__( 275 name=name, 276 return_value=return_value) 277 278 279class StorageMethodMock(mock.Mock, MockBase): 280 def __init__(self, name, method_body): 281 return_value = method_body 282 exception = None 283 side_effect = None 284 285 # support return_value, side_effect and exception for method 286 if isinstance(method_body, dict): 287 if (SYMBOL_SIDE_EFFECT in method_body or 288 SYMBOL_RAISE in method_body): 289 exception = method_body.get(SYMBOL_RAISE, None) 290 side_effect = method_body.get(SYMBOL_SIDE_EFFECT, None) 291 return_value = None 292 293 if exception is not None: 294 ex = None 295 if isinstance(exception, dict) and exception: 296 ex_name = list(exception.keys())[0] 297 ex_tmp = [getattr(ex_module, ex_name, None) 298 for ex_module in [lib_ex, common]] 299 try: 300 ex = [each for each in ex_tmp if each is not None][0] 301 super(StorageMethodMock, self).__init__( 302 name=name, 303 side_effect=ex(exception[ex_name])) 304 except IndexError: 305 raise KeyError('Exception %(ex_name)s not found.' 306 % {'ex_name': ex_name}) 307 else: 308 raise KeyError('Invalid Exception body, should be a dict.') 309 elif side_effect is not None: 310 super(StorageMethodMock, self).__init__( 311 name=name, 312 side_effect=self._build_mock_object(side_effect)) 313 elif return_value is not None: 314 super(StorageMethodMock, self).__init__( 315 name=name, 316 return_value=(ContextMock() if return_value == SYMBOL_CONTEXT 317 else self._build_mock_object(return_value))) 318 else: 319 super(StorageMethodMock, self).__init__( 320 name=name, return_value=None) 321 322 323class StorageResourceMock(dict, MockBase): 324 def __init__(self, yaml_file): 325 yaml_dict = utils.load_yaml(yaml_file) 326 if not isinstance(yaml_dict, dict): 327 return 328 for section, sec_body in yaml_dict.items(): 329 if isinstance(sec_body, dict): 330 self[section] = {obj_name: self._build_mock_object(obj_body) 331 for obj_name, obj_body 332 in sec_body.items()} 333 else: 334 self[section] = {} 335 336 337cinder_res = CinderResourceMock('mocked_cinder.yaml') 338DRIVER_RES_MAPPING = { 339 'TestResMock': cinder_res, 340 'TestCommonAdapter': cinder_res, 341 'TestReplicationAdapter': cinder_res, 342 'TestISCSIAdapter': cinder_res, 343 'TestFCAdapter': cinder_res, 344 'TestUtils': cinder_res, 345 'TestClient': cinder_res 346} 347 348 349def mock_driver_input(func): 350 @six.wraps(func) 351 def decorated(cls, *args, **kwargs): 352 return func(cls, 353 DRIVER_RES_MAPPING[cls.__class__.__name__][func.__name__], 354 *args, **kwargs) 355 return decorated 356 357 358vnx_res = StorageResourceMock('mocked_vnx.yaml') 359STORAGE_RES_MAPPING = { 360 'TestResMock': StorageResourceMock('test_res_mock.yaml'), 361 'TestCondition': vnx_res, 362 'TestClient': vnx_res, 363 'TestCommonAdapter': vnx_res, 364 'TestReplicationAdapter': vnx_res, 365 'TestISCSIAdapter': vnx_res, 366 'TestFCAdapter': vnx_res, 367 'TestTaskflow': vnx_res, 368 'TestExtraSpecs': vnx_res, 369} 370DEFAULT_STORAGE_RES = 'vnx' 371 372 373def _build_client(): 374 return client.Client(ip='192.168.1.2', 375 username='sysadmin', 376 password='sysadmin', 377 scope='global', 378 naviseccli=None, 379 sec_file=None, 380 queue_path='vnx-cinder') 381 382 383def patch_client(func): 384 @six.wraps(func) 385 def decorated(cls, *args, **kwargs): 386 storage_res = ( 387 STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) 388 with utils.patch_vnxsystem as patched_vnx: 389 if DEFAULT_STORAGE_RES in storage_res: 390 patched_vnx.return_value = storage_res[DEFAULT_STORAGE_RES] 391 client = _build_client() 392 return func(cls, client, storage_res, *args, **kwargs) 393 return decorated 394 395 396PROTOCOL_COMMON = 'Common' 397PROTOCOL_MAPPING = { 398 PROTOCOL_COMMON: adapter.CommonAdapter, 399 common.PROTOCOL_ISCSI: adapter.ISCSIAdapter, 400 common.PROTOCOL_FC: adapter.FCAdapter 401} 402 403 404def patch_adapter_init(protocol): 405 def inner_patch_adapter(func): 406 @six.wraps(func) 407 def decorated(cls, *args, **kwargs): 408 storage_res = ( 409 STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) 410 with utils.patch_vnxsystem as patched_vnx: 411 if DEFAULT_STORAGE_RES in storage_res: 412 patched_vnx.return_value = storage_res[DEFAULT_STORAGE_RES] 413 adapter = PROTOCOL_MAPPING[protocol](cls.configuration) 414 return func(cls, adapter, storage_res, *args, **kwargs) 415 return decorated 416 return inner_patch_adapter 417 418 419def _patch_adapter_prop(adapter, client): 420 try: 421 adapter.serial_number = client.get_serial() 422 except KeyError: 423 adapter.serial_number = 'faked_serial_number' 424 adapter.VERSION = driver.VNXDriver.VERSION 425 426 427def patch_adapter(protocol): 428 def inner_patch_adapter(func): 429 @six.wraps(func) 430 def decorated(cls, *args, **kwargs): 431 storage_res = ( 432 STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) 433 with utils.patch_vnxsystem: 434 client = _build_client() 435 adapter = PROTOCOL_MAPPING[protocol](cls.configuration, None) 436 if DEFAULT_STORAGE_RES in storage_res: 437 client.vnx = storage_res[DEFAULT_STORAGE_RES] 438 adapter.client = client 439 _patch_adapter_prop(adapter, client) 440 return func(cls, adapter, storage_res, *args, **kwargs) 441 return decorated 442 return inner_patch_adapter 443 444 445patch_common_adapter = patch_adapter(PROTOCOL_COMMON) 446patch_iscsi_adapter = patch_adapter(common.PROTOCOL_ISCSI) 447patch_fc_adapter = patch_adapter(common.PROTOCOL_FC) 448 449 450def mock_storage_resources(func): 451 @six.wraps(func) 452 def decorated(cls, *args, **kwargs): 453 storage_res = ( 454 STORAGE_RES_MAPPING[cls.__class__.__name__][func.__name__]) 455 return func(cls, storage_res, *args, **kwargs) 456 return decorated 457