1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5#      http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from unittest import mock
14
15from openstack.baremetal.v1 import _proxy
16from openstack.baremetal.v1 import allocation
17from openstack.baremetal.v1 import chassis
18from openstack.baremetal.v1 import driver
19from openstack.baremetal.v1 import node
20from openstack.baremetal.v1 import port
21from openstack.baremetal.v1 import port_group
22from openstack.baremetal.v1 import volume_connector
23from openstack.baremetal.v1 import volume_target
24from openstack import exceptions
25from openstack.tests.unit import base
26from openstack.tests.unit import test_proxy_base
27
28
29_MOCK_METHOD = 'openstack.baremetal.v1._proxy.Proxy._get_with_fields'
30
31
32class TestBaremetalProxy(test_proxy_base.TestProxyBase):
33
34    def setUp(self):
35        super(TestBaremetalProxy, self).setUp()
36        self.proxy = _proxy.Proxy(self.session)
37
38    def test_drivers(self):
39        self.verify_list(self.proxy.drivers, driver.Driver)
40
41    def test_get_driver(self):
42        self.verify_get(self.proxy.get_driver, driver.Driver)
43
44    @mock.patch.object(chassis.Chassis, 'list')
45    def test_chassis_detailed(self, mock_list):
46        result = self.proxy.chassis(details=True, query=1)
47        self.assertIs(result, mock_list.return_value)
48        mock_list.assert_called_once_with(self.proxy, details=True, query=1)
49
50    @mock.patch.object(chassis.Chassis, 'list')
51    def test_chassis_not_detailed(self, mock_list):
52        result = self.proxy.chassis(query=1)
53        self.assertIs(result, mock_list.return_value)
54        mock_list.assert_called_once_with(self.proxy, details=False, query=1)
55
56    def test_create_chassis(self):
57        self.verify_create(self.proxy.create_chassis, chassis.Chassis)
58
59    def test_find_chassis(self):
60        self.verify_find(self.proxy.find_chassis, chassis.Chassis)
61
62    def test_get_chassis(self):
63        self.verify_get(self.proxy.get_chassis, chassis.Chassis,
64                        mock_method=_MOCK_METHOD,
65                        expected_kwargs={'fields': None})
66
67    def test_update_chassis(self):
68        self.verify_update(self.proxy.update_chassis, chassis.Chassis)
69
70    def test_delete_chassis(self):
71        self.verify_delete(self.proxy.delete_chassis, chassis.Chassis, False)
72
73    def test_delete_chassis_ignore(self):
74        self.verify_delete(self.proxy.delete_chassis, chassis.Chassis, True)
75
76    @mock.patch.object(node.Node, 'list')
77    def test_nodes_detailed(self, mock_list):
78        result = self.proxy.nodes(details=True, query=1)
79        self.assertIs(result, mock_list.return_value)
80        mock_list.assert_called_once_with(self.proxy, details=True, query=1)
81
82    @mock.patch.object(node.Node, 'list')
83    def test_nodes_not_detailed(self, mock_list):
84        result = self.proxy.nodes(query=1)
85        self.assertIs(result, mock_list.return_value)
86        mock_list.assert_called_once_with(self.proxy, details=False, query=1)
87
88    def test_create_node(self):
89        self.verify_create(self.proxy.create_node, node.Node)
90
91    def test_find_node(self):
92        self.verify_find(self.proxy.find_node, node.Node)
93
94    def test_get_node(self):
95        self.verify_get(self.proxy.get_node, node.Node,
96                        mock_method=_MOCK_METHOD,
97                        expected_kwargs={'fields': None})
98
99    @mock.patch.object(node.Node, 'commit', autospec=True)
100    def test_update_node(self, mock_commit):
101        self.proxy.update_node('uuid', instance_id='new value')
102        mock_commit.assert_called_once_with(mock.ANY, self.proxy,
103                                            retry_on_conflict=True)
104        self.assertEqual('new value', mock_commit.call_args[0][0].instance_id)
105
106    @mock.patch.object(node.Node, 'commit', autospec=True)
107    def test_update_node_no_retries(self, mock_commit):
108        self.proxy.update_node('uuid', instance_id='new value',
109                               retry_on_conflict=False)
110        mock_commit.assert_called_once_with(mock.ANY, self.proxy,
111                                            retry_on_conflict=False)
112        self.assertEqual('new value', mock_commit.call_args[0][0].instance_id)
113
114    def test_delete_node(self):
115        self.verify_delete(self.proxy.delete_node, node.Node, False)
116
117    def test_delete_node_ignore(self):
118        self.verify_delete(self.proxy.delete_node, node.Node, True)
119
120    @mock.patch.object(port.Port, 'list')
121    def test_ports_detailed(self, mock_list):
122        result = self.proxy.ports(details=True, query=1)
123        self.assertIs(result, mock_list.return_value)
124        mock_list.assert_called_once_with(self.proxy, details=True, query=1)
125
126    @mock.patch.object(port.Port, 'list')
127    def test_ports_not_detailed(self, mock_list):
128        result = self.proxy.ports(query=1)
129        self.assertIs(result, mock_list.return_value)
130        mock_list.assert_called_once_with(self.proxy, details=False, query=1)
131
132    def test_create_port(self):
133        self.verify_create(self.proxy.create_port, port.Port)
134
135    def test_find_port(self):
136        self.verify_find(self.proxy.find_port, port.Port)
137
138    def test_get_port(self):
139        self.verify_get(self.proxy.get_port, port.Port,
140                        mock_method=_MOCK_METHOD,
141                        expected_kwargs={'fields': None})
142
143    def test_update_port(self):
144        self.verify_update(self.proxy.update_port, port.Port)
145
146    def test_delete_port(self):
147        self.verify_delete(self.proxy.delete_port, port.Port, False)
148
149    def test_delete_port_ignore(self):
150        self.verify_delete(self.proxy.delete_port, port.Port, True)
151
152    @mock.patch.object(port_group.PortGroup, 'list')
153    def test_port_groups_detailed(self, mock_list):
154        result = self.proxy.port_groups(details=True, query=1)
155        self.assertIs(result, mock_list.return_value)
156        mock_list.assert_called_once_with(self.proxy, details=True, query=1)
157
158    @mock.patch.object(port_group.PortGroup, 'list')
159    def test_port_groups_not_detailed(self, mock_list):
160        result = self.proxy.port_groups(query=1)
161        self.assertIs(result, mock_list.return_value)
162        mock_list.assert_called_once_with(self.proxy, details=False, query=1)
163
164    def test_get_port_group(self):
165        self.verify_get(self.proxy.get_port_group, port_group.PortGroup,
166                        mock_method=_MOCK_METHOD,
167                        expected_kwargs={'fields': None})
168
169    def test_create_allocation(self):
170        self.verify_create(self.proxy.create_allocation, allocation.Allocation)
171
172    def test_get_allocation(self):
173        self.verify_get(self.proxy.get_allocation, allocation.Allocation,
174                        mock_method=_MOCK_METHOD,
175                        expected_kwargs={'fields': None})
176
177    def test_delete_allocation(self):
178        self.verify_delete(self.proxy.delete_allocation, allocation.Allocation,
179                           False)
180
181    def test_delete_allocation_ignore(self):
182        self.verify_delete(self.proxy.delete_allocation, allocation.Allocation,
183                           True)
184
185    def test_create_volume_connector(self):
186        self.verify_create(self.proxy.create_volume_connector,
187                           volume_connector.VolumeConnector)
188
189    def test_find_volume_connector(self):
190        self.verify_find(self.proxy.find_volume_connector,
191                         volume_connector.VolumeConnector)
192
193    def test_get_volume_connector(self):
194        self.verify_get(self.proxy.get_volume_connector,
195                        volume_connector.VolumeConnector,
196                        mock_method=_MOCK_METHOD,
197                        expected_kwargs={'fields': None})
198
199    def test_delete_volume_connector(self):
200        self.verify_delete(self.proxy.delete_volume_connector,
201                           volume_connector.VolumeConnector,
202                           False)
203
204    def test_delete_volume_connector_ignore(self):
205        self.verify_delete(self.proxy.delete_volume_connector,
206                           volume_connector.VolumeConnector,
207                           True)
208
209    @mock.patch.object(volume_target.VolumeTarget, 'list')
210    def test_volume_target_detailed(self, mock_list):
211        result = self.proxy.volume_targets(details=True, query=1)
212        self.assertIs(result, mock_list.return_value)
213        mock_list.assert_called_once_with(self.proxy, detail=True, query=1)
214
215    @mock.patch.object(volume_target.VolumeTarget, 'list')
216    def test_volume_target_not_detailed(self, mock_list):
217        result = self.proxy.volume_targets(query=1)
218        self.assertIs(result, mock_list.return_value)
219        mock_list.assert_called_once_with(self.proxy, query=1)
220
221    def test_create_volume_target(self):
222        self.verify_create(self.proxy.create_volume_target,
223                           volume_target.VolumeTarget)
224
225    def test_find_volume_target(self):
226        self.verify_find(self.proxy.find_volume_target,
227                         volume_target.VolumeTarget)
228
229    def test_get_volume_target(self):
230        self.verify_get(self.proxy.get_volume_target,
231                        volume_target.VolumeTarget,
232                        mock_method=_MOCK_METHOD,
233                        expected_kwargs={'fields': None})
234
235    def test_delete_volume_target(self):
236        self.verify_delete(self.proxy.delete_volume_target,
237                           volume_target.VolumeTarget,
238                           False)
239
240    def test_delete_volume_target_ignore(self):
241        self.verify_delete(self.proxy.delete_volume_target,
242                           volume_target.VolumeTarget,
243                           True)
244
245    @mock.patch.object(node.Node, 'fetch', autospec=True)
246    def test__get_with_fields_none(self, mock_fetch):
247        result = self.proxy._get_with_fields(node.Node, 'value')
248        self.assertIs(result, mock_fetch.return_value)
249        mock_fetch.assert_called_once_with(mock.ANY, self.proxy,
250                                           error_message=mock.ANY)
251
252    @mock.patch.object(node.Node, 'fetch', autospec=True)
253    def test__get_with_fields_node(self, mock_fetch):
254        result = self.proxy._get_with_fields(
255            # Mix of server-side and client-side fields
256            node.Node, 'value', fields=['maintenance', 'id', 'instance_id'])
257        self.assertIs(result, mock_fetch.return_value)
258        mock_fetch.assert_called_once_with(
259            mock.ANY, self.proxy, error_message=mock.ANY,
260            # instance_id converted to server-side instance_uuid
261            fields='maintenance,uuid,instance_uuid')
262
263    @mock.patch.object(port.Port, 'fetch', autospec=True)
264    def test__get_with_fields_port(self, mock_fetch):
265        result = self.proxy._get_with_fields(
266            port.Port, 'value', fields=['address', 'id', 'node_id'])
267        self.assertIs(result, mock_fetch.return_value)
268        mock_fetch.assert_called_once_with(
269            mock.ANY, self.proxy, error_message=mock.ANY,
270            # node_id converted to server-side node_uuid
271            fields='address,uuid,node_uuid')
272
273
274@mock.patch('time.sleep', lambda _sec: None)
275@mock.patch.object(_proxy.Proxy, 'get_node', autospec=True)
276class TestWaitForNodesProvisionState(base.TestCase):
277
278    def setUp(self):
279        super(TestWaitForNodesProvisionState, self).setUp()
280        self.session = mock.Mock()
281        self.proxy = _proxy.Proxy(self.session)
282
283    def test_success(self, mock_get):
284        # two attempts, one node succeeds after the 1st
285        nodes = [mock.Mock(spec=node.Node, id=str(i))
286                 for i in range(3)]
287        for i, n in enumerate(nodes):
288            # 1st attempt on 1st node, 2nd attempt on 2nd node
289            n._check_state_reached.return_value = not (i % 2)
290        mock_get.side_effect = nodes
291
292        result = self.proxy.wait_for_nodes_provision_state(
293            ['abcd', node.Node(id='1234')], 'fake state')
294        self.assertEqual([nodes[0], nodes[2]], result)
295
296        for n in nodes:
297            n._check_state_reached.assert_called_once_with(
298                self.proxy, 'fake state', True)
299
300    def test_success_no_fail(self, mock_get):
301        # two attempts, one node succeeds after the 1st
302        nodes = [mock.Mock(spec=node.Node, id=str(i))
303                 for i in range(3)]
304        for i, n in enumerate(nodes):
305            # 1st attempt on 1st node, 2nd attempt on 2nd node
306            n._check_state_reached.return_value = not (i % 2)
307        mock_get.side_effect = nodes
308
309        result = self.proxy.wait_for_nodes_provision_state(
310            ['abcd', node.Node(id='1234')], 'fake state', fail=False)
311        self.assertEqual([nodes[0], nodes[2]], result.success)
312        self.assertEqual([], result.failure)
313        self.assertEqual([], result.timeout)
314
315        for n in nodes:
316            n._check_state_reached.assert_called_once_with(
317                self.proxy, 'fake state', True)
318
319    def test_timeout(self, mock_get):
320        mock_get.return_value._check_state_reached.return_value = False
321        mock_get.return_value.id = '1234'
322
323        self.assertRaises(exceptions.ResourceTimeout,
324                          self.proxy.wait_for_nodes_provision_state,
325                          ['abcd', node.Node(id='1234')], 'fake state',
326                          timeout=0.001)
327        mock_get.return_value._check_state_reached.assert_called_with(
328            self.proxy, 'fake state', True)
329
330    def test_timeout_no_fail(self, mock_get):
331        mock_get.return_value._check_state_reached.return_value = False
332        mock_get.return_value.id = '1234'
333
334        result = self.proxy.wait_for_nodes_provision_state(
335            ['abcd'], 'fake state', timeout=0.001, fail=False)
336        mock_get.return_value._check_state_reached.assert_called_with(
337            self.proxy, 'fake state', True)
338
339        self.assertEqual([], result.success)
340        self.assertEqual([mock_get.return_value], result.timeout)
341        self.assertEqual([], result.failure)
342
343    def test_timeout_and_failures_not_fail(self, mock_get):
344        def _fake_get(_self, node):
345            result = mock.Mock()
346            result.id = getattr(node, 'id', node)
347            if result.id == '1':
348                result._check_state_reached.return_value = True
349            elif result.id == '2':
350                result._check_state_reached.side_effect = \
351                    exceptions.ResourceFailure("boom")
352            else:
353                result._check_state_reached.return_value = False
354            return result
355
356        mock_get.side_effect = _fake_get
357
358        result = self.proxy.wait_for_nodes_provision_state(
359            ['1', '2', '3'], 'fake state', timeout=0.001, fail=False)
360
361        self.assertEqual(['1'], [x.id for x in result.success])
362        self.assertEqual(['3'], [x.id for x in result.timeout])
363        self.assertEqual(['2'], [x.id for x in result.failure])
364