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