210 lines
8.9 KiB
Python
210 lines
8.9 KiB
Python
# 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.
|
|
|
|
from unittest import mock
|
|
|
|
import ddt
|
|
from keystoneauth1 import exceptions as kaexception
|
|
import requests
|
|
|
|
from ironic.common import context
|
|
from ironic.common import keystone
|
|
from ironic.common import nova
|
|
from ironic.tests import base
|
|
|
|
|
|
@mock.patch.object(keystone, 'get_session', autospec=True)
|
|
@mock.patch.object(keystone, 'get_adapter', autospec=True)
|
|
class TestNovaAdapter(base.TestCase):
|
|
|
|
def test_get_nova_adapter(self, mock_adapter, mock_nova_session):
|
|
nova._NOVA_ADAPTER = None
|
|
mock_session_obj = mock.Mock()
|
|
expected = {'session': mock_session_obj,
|
|
'auth': None,
|
|
'version': "2.1"}
|
|
mock_nova_session.return_value = mock_session_obj
|
|
nova._get_nova_adapter()
|
|
mock_nova_session.assert_called_once_with('nova')
|
|
mock_adapter.assert_called_once_with(group='nova', **expected)
|
|
|
|
"""Check if existing adapter is used."""
|
|
mock_nova_session.reset_mock()
|
|
nova._get_nova_adapter()
|
|
mock_nova_session.assert_not_called()
|
|
|
|
|
|
@ddt.ddt
|
|
@mock.patch.object(nova, 'LOG', autospec=True)
|
|
class NovaApiTestCase(base.TestCase):
|
|
def setUp(self):
|
|
super(NovaApiTestCase, self).setUp()
|
|
|
|
self.api = nova
|
|
self.ctx = context.get_admin_context()
|
|
|
|
@ddt.unpack
|
|
# one @ddt.data element comprises:
|
|
# - nova_result: POST response JSON dict
|
|
# - resp_status: POST response status_code
|
|
# - exp_ret: Expected bool return value from power_update()
|
|
@ddt.data([{'events': [{'status': 'completed',
|
|
'tag': 'POWER_OFF',
|
|
'name': 'power-update',
|
|
'server_uuid': '1234',
|
|
'code': 200}]},
|
|
200, True],
|
|
[{'events': [{'code': 422}]}, 207, False],
|
|
[{'events': [{'code': 404}]}, 207, False],
|
|
[{'events': [{'code': 400}]}, 207, False],
|
|
# This (response 207, event code 200) will never happen IRL
|
|
[{'events': [{'code': 200}]}, 207, True])
|
|
@mock.patch.object(nova, '_get_nova_adapter')
|
|
def test_power_update(self, nova_result, resp_status, exp_ret,
|
|
mock_adapter, mock_log):
|
|
server_ids = ['server-id-1', 'server-id-2']
|
|
nova_adapter = mock.Mock()
|
|
with mock.patch.object(nova_adapter, 'post') as mock_post_event:
|
|
post_resp_mock = requests.Response()
|
|
|
|
def json_func():
|
|
return nova_result
|
|
post_resp_mock.json = json_func
|
|
post_resp_mock.status_code = resp_status
|
|
mock_adapter.return_value = nova_adapter
|
|
mock_post_event.return_value = post_resp_mock
|
|
for server in server_ids:
|
|
result = self.api.power_update(self.ctx, server, 'power on')
|
|
self.assertEqual(exp_ret, result)
|
|
|
|
mock_adapter.assert_has_calls([mock.call(), mock.call()])
|
|
req_url = '/os-server-external-events'
|
|
mock_post_event.assert_has_calls([
|
|
mock.call(req_url,
|
|
json={'events': [{'name': 'power-update',
|
|
'server_uuid': 'server-id-1',
|
|
'tag': 'POWER_ON'}]},
|
|
microversion='2.76',
|
|
global_request_id=self.ctx.global_id,
|
|
raise_exc=False),
|
|
mock.call(req_url,
|
|
json={'events': [{'name': 'power-update',
|
|
'server_uuid': 'server-id-2',
|
|
'tag': 'POWER_ON'}]},
|
|
microversion='2.76',
|
|
global_request_id=self.ctx.global_id,
|
|
raise_exc=False)
|
|
])
|
|
if not exp_ret:
|
|
expected = ('Nova event: %s returned with failed status.',
|
|
nova_result['events'][0])
|
|
mock_log.warning.assert_called_with(*expected)
|
|
else:
|
|
expected = ("Nova event response: %s.", nova_result['events'][0])
|
|
mock_log.debug.assert_called_with(*expected)
|
|
|
|
@mock.patch.object(nova, '_get_nova_adapter')
|
|
def test_invalid_power_update(self, mock_adapter, mock_log):
|
|
nova_adapter = mock.Mock()
|
|
with mock.patch.object(nova_adapter, 'post') as mock_post_event:
|
|
result = self.api.power_update(self.ctx, 'server', None)
|
|
self.assertFalse(result)
|
|
expected = ('Invalid Power State %s.', None)
|
|
mock_log.error.assert_called_once_with(*expected)
|
|
|
|
mock_adapter.assert_not_called()
|
|
mock_post_event.assert_not_called()
|
|
|
|
def test_power_update_failed(self, mock_log):
|
|
nova_adapter = nova._get_nova_adapter()
|
|
event = [{'name': 'power-update',
|
|
'server_uuid': 'server-id-1',
|
|
'tag': 'POWER_OFF'}]
|
|
nova_result = requests.Response()
|
|
with mock.patch.object(nova_adapter, 'post') as mock_post_event:
|
|
for stat_code in (500, 404, 400):
|
|
mock_log.reset_mock()
|
|
nova_result.status_code = stat_code
|
|
type(nova_result).text = mock.PropertyMock(return_value="blah")
|
|
mock_post_event.return_value = nova_result
|
|
result = self.api.power_update(
|
|
self.ctx, 'server-id-1', 'power off')
|
|
self.assertFalse(result)
|
|
expected = ("Failed to notify nova on event: %s. %s.",
|
|
event[0], "blah")
|
|
mock_log.warning.assert_called_once_with(*expected)
|
|
|
|
mock_post_event.assert_has_calls([
|
|
mock.call('/os-server-external-events',
|
|
json={'events': event},
|
|
microversion='2.76',
|
|
global_request_id=self.ctx.global_id,
|
|
raise_exc=False)
|
|
])
|
|
|
|
@ddt.data({'events': [{}]},
|
|
{'events': []},
|
|
{'events': None},
|
|
{})
|
|
@mock.patch.object(nova, '_get_nova_adapter')
|
|
def test_power_update_invalid_reponse_format(self, nova_result,
|
|
mock_adapter, mock_log):
|
|
nova_adapter = mock.Mock()
|
|
with mock.patch.object(nova_adapter, 'post') as mock_post_event:
|
|
post_resp_mock = requests.Response()
|
|
|
|
def json_func():
|
|
return nova_result
|
|
|
|
post_resp_mock.json = json_func
|
|
post_resp_mock.status_code = 207
|
|
mock_adapter.return_value = nova_adapter
|
|
mock_post_event.return_value = post_resp_mock
|
|
result = self.api.power_update(self.ctx, 'server-id-1', 'power on')
|
|
self.assertFalse(result)
|
|
|
|
mock_adapter.assert_has_calls([mock.call()])
|
|
req_url = '/os-server-external-events'
|
|
mock_post_event.assert_has_calls([
|
|
mock.call(req_url,
|
|
json={'events': [{'name': 'power-update',
|
|
'server_uuid': 'server-id-1',
|
|
'tag': 'POWER_ON'}]},
|
|
microversion='2.76',
|
|
global_request_id=self.ctx.global_id,
|
|
raise_exc=False),
|
|
])
|
|
self.assertIn('Invalid response', mock_log.error.call_args[0][0])
|
|
|
|
@mock.patch.object(keystone, 'get_adapter', autospec=True)
|
|
def test_power_update_failed_no_nova(self, mock_adapter, mock_log):
|
|
self.config(send_power_notifications=False, group="nova")
|
|
result = self.api.power_update(self.ctx, 'server-id-1', 'power off')
|
|
self.assertFalse(result)
|
|
mock_adapter.assert_not_called()
|
|
|
|
@mock.patch.object(nova, '_get_nova_adapter')
|
|
def test_power_update_failed_no_nova_auth_url(self, mock_adapter,
|
|
mock_log):
|
|
server = 'server-id-1'
|
|
emsg = 'An auth plugin is required to determine endpoint URL'
|
|
side_effect = kaexception.MissingAuthPlugin(emsg)
|
|
mock_nova = mock.Mock()
|
|
mock_adapter.return_value = mock_nova
|
|
mock_nova.post.side_effect = side_effect
|
|
result = self.api.power_update(self.ctx, server, 'power off')
|
|
msg = ('Could not connect to Nova to send a power notification, '
|
|
'please check configuration. %s', side_effect)
|
|
self.assertFalse(result)
|
|
mock_log.warning.assert_called_once_with(*msg)
|
|
mock_adapter.assert_called_once_with()
|