1#    (c) Copyright 2016 Brocade Communications Systems Inc.
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#
16
17
18"""Unit tests for brcd fc zone client cli."""
19
20import mock
21from oslo_concurrency import processutils
22
23from cinder import exception
24from cinder import test
25from cinder.zonemanager.drivers.brocade import (brcd_fc_zone_client_cli
26                                                as client_cli)
27import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
28
29
30nsshow = '20:1a:00:05:1e:e8:e3:29'
31switch_data = [' N 011a00;2,3;20:1a:00:05:1e:e8:e3:29;\
32                 20:1a:00:05:1e:e8:e3:29;na',
33               '    Fabric Port Name: 20:1a:00:05:1e:e8:e3:29']
34cfgactvshow = ['Effective configuration:\n',
35               ' cfg:\tOpenStack_Cfg\t\n',
36               ' zone:\topenstack50060b0000c26604201900051ee8e329\t\n',
37               '\t\t50:06:0b:00:00:c2:66:04\n',
38               '\t\t20:19:00:05:1e:e8:e3:29\n']
39active_zoneset = {
40    'zones': {
41        'openstack50060b0000c26604201900051ee8e329':
42        ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29']},
43    'active_zone_config': 'OpenStack_Cfg'}
44active_zoneset_multiple_zones = {
45    'zones': {
46        'openstack50060b0000c26604201900051ee8e329':
47        ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29'],
48        'openstack50060b0000c26602201900051ee8e327':
49        ['50:06:0b:00:00:c2:66:02', '20:19:00:05:1e:e8:e3:27']},
50    'active_zone_config': 'OpenStack_Cfg'}
51new_zone_memb_same = {
52    'openstack50060b0000c26604201900051ee8e329':
53    ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29']}
54new_zone_memb_not_same = {
55    'openstack50060b0000c26604201900051ee8e330':
56    ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:30']}
57new_zone = {'openstack10000012345678902001009876543210':
58            ['10:00:00:12:34:56:78:90', '20:01:00:98:76:54:32:10']}
59new_zones = {'openstack10000012345678902001009876543210':
60             ['10:00:00:12:34:56:78:90', '20:01:00:98:76:54:32:10'],
61             'openstack10000011111111112001001111111111':
62             ['10:00:00:11:11:11:11:11', '20:01:00:11:11:11:11:11']}
63zone_names_to_delete = 'openstack50060b0000c26604201900051ee8e329'
64supported_firmware = ['Kernel: 2.6', 'Fabric OS:  v7.0.1']
65unsupported_firmware = ['Fabric OS:  v6.2.1']
66
67
68class TestBrcdFCZoneClientCLI(client_cli.BrcdFCZoneClientCLI, test.TestCase):
69
70    # override some of the functions
71    def __init__(self, *args, **kwargs):
72        test.TestCase.__init__(self, *args, **kwargs)
73
74    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_get_switch_info')
75    def test_get_active_zone_set(self, get_switch_info_mock):
76        cmd_list = [zone_constant.GET_ACTIVE_ZONE_CFG]
77        get_switch_info_mock.return_value = cfgactvshow
78        active_zoneset_returned = self.get_active_zone_set()
79        get_switch_info_mock.assert_called_once_with(cmd_list)
80        self.assertDictEqual(active_zoneset, active_zoneset_returned)
81
82    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
83    def test_get_active_zone_set_ssh_error(self, run_ssh_mock):
84        run_ssh_mock.side_effect = processutils.ProcessExecutionError
85        self.assertRaises(exception.BrocadeZoningCliException,
86                          self.get_active_zone_set)
87
88    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'get_active_zone_set')
89    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
90    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_cfg_save')
91    def test_add_zones_new_zone_no_activate(self, cfg_save_mock,
92                                            apply_zone_change_mock,
93                                            get_active_zs_mock):
94        get_active_zs_mock.return_value = active_zoneset
95        self.add_zones(new_zones, False, None)
96        self.assertEqual(1, get_active_zs_mock.call_count)
97        self.assertEqual(3, apply_zone_change_mock.call_count)
98        cfg_save_mock.assert_called_once_with()
99
100    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'get_active_zone_set')
101    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
102    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'activate_zoneset')
103    def test_add_zones_new_zone_activate(self, activate_zoneset_mock,
104                                         apply_zone_change_mock,
105                                         get_active_zs_mock):
106        get_active_zs_mock.return_value = active_zoneset
107        self.add_zones(new_zone, True, active_zoneset)
108        self.assertEqual(2, apply_zone_change_mock.call_count)
109        activate_zoneset_mock.assert_called_once_with(
110            active_zoneset['active_zone_config'])
111
112    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'get_active_zone_set')
113    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'activate_zoneset')
114    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
115    def test_update_zone_exists_memb_same(self, apply_zone_change_mock,
116                                          activate_zoneset_mock,
117                                          get_active_zs_mock):
118        get_active_zs_mock.return_value = active_zoneset
119        self.update_zones(new_zone_memb_same, True, zone_constant.ZONE_ADD,
120                          active_zoneset)
121        self.assertEqual(1, apply_zone_change_mock.call_count)
122
123    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'get_active_zone_set')
124    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'activate_zoneset')
125    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
126    def test_update_zone_exists_memb_not_same(self, apply_zone_change_mock,
127                                              activate_zoneset_mock,
128                                              get_active_zs_mock):
129        get_active_zs_mock.return_value = active_zoneset
130        self.update_zones(new_zone_memb_not_same, True,
131                          zone_constant.ZONE_ADD, active_zoneset)
132        self.assertEqual(1, apply_zone_change_mock.call_count)
133
134    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'get_active_zone_set')
135    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'activate_zoneset')
136    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
137    def test_add_zone_all_exists_memb_not_same(self, apply_zone_change_mock,
138                                               activate_zoneset_mock,
139                                               get_active_zs_mock):
140
141        self.add_zones(new_zone_memb_not_same, True, active_zoneset)
142        call_args = apply_zone_change_mock.call_args[0][0]
143        self.assertEqual(0, get_active_zs_mock.call_count)
144        self.assertEqual(2, apply_zone_change_mock.call_count)
145        self.assertIn(zone_constant.CFG_ADD.strip(), call_args)
146
147    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_ssh_execute')
148    def test_activate_zoneset(self, ssh_execute_mock):
149        ssh_execute_mock.return_value = True
150        return_value = self.activate_zoneset('zoneset1')
151        self.assertTrue(return_value)
152
153    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_ssh_execute')
154    def test_deactivate_zoneset(self, ssh_execute_mock):
155        ssh_execute_mock.return_value = True
156        return_value = self.deactivate_zoneset()
157        self.assertTrue(return_value)
158
159    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
160    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_cfg_save')
161    def test_delete_zones_activate_false(self, cfg_save_mock,
162                                         apply_zone_change_mock):
163        with mock.patch.object(self, '_zone_delete') as zone_delete_mock:
164            self.delete_zones(zone_names_to_delete, False,
165                              active_zoneset_multiple_zones)
166            self.assertEqual(1, apply_zone_change_mock.call_count)
167            zone_delete_mock.assert_called_once_with(zone_names_to_delete)
168            cfg_save_mock.assert_called_once_with()
169
170    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
171    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'activate_zoneset')
172    def test_delete_zones_activate_true(self, activate_zs_mock,
173                                        apply_zone_change_mock):
174        with mock.patch.object(self, '_zone_delete') \
175                as zone_delete_mock:
176            self.delete_zones(zone_names_to_delete, True,
177                              active_zoneset_multiple_zones)
178            self.assertEqual(1, apply_zone_change_mock.call_count)
179            zone_delete_mock.assert_called_once_with(zone_names_to_delete)
180            activate_zs_mock.assert_called_once_with(
181                active_zoneset['active_zone_config'])
182
183    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_get_switch_info')
184    def test_get_nameserver_info(self, get_switch_info_mock):
185        ns_info_list_expected = ['20:1a:00:05:1e:e8:e3:29']
186        get_switch_info_mock.return_value = (switch_data)
187        ns_info_list = self.get_nameserver_info()
188        self.assertEqual(ns_info_list_expected, ns_info_list)
189
190    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
191    def test_get_nameserver_info_ssh_error(self, run_ssh_mock):
192        run_ssh_mock.side_effect = processutils.ProcessExecutionError
193        self.assertRaises(exception.BrocadeZoningCliException,
194                          self.get_nameserver_info)
195
196    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_ssh_execute')
197    def test__cfg_save(self, ssh_execute_mock):
198        cmd_list = [zone_constant.CFG_SAVE]
199        self._cfg_save()
200        ssh_execute_mock.assert_called_once_with(cmd_list, True, 1)
201
202    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
203    def test__zone_delete(self, apply_zone_change_mock):
204        zone_name = 'testzone'
205        cmd_list = ['zonedelete', '"testzone"']
206        self._zone_delete(zone_name)
207        apply_zone_change_mock.assert_called_once_with(cmd_list)
208
209    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, 'apply_zone_change')
210    def test__cfg_trans_abort(self, apply_zone_change_mock):
211        cmd_list = [zone_constant.CFG_ZONE_TRANS_ABORT]
212        with mock.patch.object(self, '_is_trans_abortable') \
213                as is_trans_abortable_mock:
214            is_trans_abortable_mock.return_value = True
215            self._cfg_trans_abort()
216            is_trans_abortable_mock.assert_called_once_with()
217            apply_zone_change_mock.assert_called_once_with(cmd_list)
218
219    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
220    def test__is_trans_abortable_true(self, run_ssh_mock):
221        cmd_list = [zone_constant.CFG_SHOW_TRANS]
222        run_ssh_mock.return_value = (Stream(zone_constant.TRANS_ABORTABLE),
223                                     None)
224        data = self._is_trans_abortable()
225        self.assertTrue(data)
226        run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
227
228    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
229    def test__is_trans_abortable_ssh_error(self, run_ssh_mock):
230        run_ssh_mock.return_value = (Stream(), Stream())
231        self.assertRaises(exception.BrocadeZoningCliException,
232                          self._is_trans_abortable)
233
234    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
235    def test__is_trans_abortable_false(self, run_ssh_mock):
236        cmd_list = [zone_constant.CFG_SHOW_TRANS]
237        cfgtransshow = 'There is no outstanding zoning transaction'
238        run_ssh_mock.return_value = (Stream(cfgtransshow), None)
239        data = self._is_trans_abortable()
240        self.assertFalse(data)
241        run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
242
243    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
244    def test_apply_zone_change(self, run_ssh_mock):
245        cmd_list = [zone_constant.CFG_SAVE]
246        run_ssh_mock.return_value = (None, None)
247        self.apply_zone_change(cmd_list)
248        run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
249
250    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_run_ssh')
251    def test__get_switch_info(self, run_ssh_mock):
252        cmd_list = [zone_constant.NS_SHOW]
253        nsshow_list = [nsshow]
254        run_ssh_mock.return_value = (Stream(nsshow), Stream())
255        switch_data = self._get_switch_info(cmd_list)
256        self.assertEqual(nsshow_list, switch_data)
257        run_ssh_mock.assert_called_once_with(cmd_list, True, 1)
258
259    def test__parse_ns_output(self):
260        invalid_switch_data = [' N 011a00;20:1a:00:05:1e:e8:e3:29']
261        expected_wwn_list = ['20:1a:00:05:1e:e8:e3:29']
262        return_wwn_list = self._parse_ns_output(switch_data)
263        self.assertEqual(expected_wwn_list, return_wwn_list)
264        self.assertRaises(exception.InvalidParameterValue,
265                          self._parse_ns_output, invalid_switch_data)
266
267    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_execute_shell_cmd')
268    def test_is_supported_firmware(self, exec_shell_cmd_mock):
269        exec_shell_cmd_mock.return_value = (supported_firmware, None)
270        self.assertTrue(self.is_supported_firmware())
271
272    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_execute_shell_cmd')
273    def test_is_supported_firmware_invalid(self, exec_shell_cmd_mock):
274        exec_shell_cmd_mock.return_value = (unsupported_firmware, None)
275        self.assertFalse(self.is_supported_firmware())
276
277    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_execute_shell_cmd')
278    def test_is_supported_firmware_no_ssh_response(self, exec_shell_cmd_mock):
279        exec_shell_cmd_mock.return_value = (None, Stream())
280        self.assertFalse(self.is_supported_firmware())
281
282    @mock.patch.object(client_cli.BrcdFCZoneClientCLI, '_execute_shell_cmd')
283    def test_is_supported_firmware_ssh_error(self, exec_shell_cmd_mock):
284        exec_shell_cmd_mock.side_effect = processutils.ProcessExecutionError
285        self.assertRaises(exception.BrocadeZoningCliException,
286                          self.is_supported_firmware)
287
288
289class Channel(object):
290    def recv_exit_status(self):
291        return 0
292
293
294class Stream(object):
295    def __init__(self, buffer=''):
296        self.buffer = buffer
297        self.channel = Channel()
298
299    def readlines(self):
300        return self.buffer
301
302    def splitlines(self):
303        return self.buffer.splitlines()
304
305    def close(self):
306        pass
307
308    def flush(self):
309        self.buffer = ''
310