deb-heat/heat/tests/test_nova_client.py
Pavlo Shchelokovskyy 13b3a9af6e Move _check_active to nova client plugin
The method was practically identical in Instance and Server resources,
with only difference in reported resource type as part of error message.

Some mocks that mocked this method directly on resource object were
modified accordingly in unit tests.

The new `_check_active` is made to accept both Nova server objects and server
UUIDs/strings. This will allow changing methods of server resources
(and tests!) one-by-one.

Added a new method `fetch_server` that tolerates intermittent failures
when fetching a fresh server object (similar to existing `refresh_server`).

Unit tests for `_check_active` and `fetch_server` added.

Related-Bug: #1393268
Change-Id: I1b63dbd9182cc8519f43ff859a408d49789263dd
2015-06-03 14:54:26 +03:00

538 lines
21 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.
"""Tests for :module:'heat.engine.clients.os.nova'."""
import collections
import uuid
import mock
from novaclient import exceptions as nova_exceptions
from oslo_config import cfg
import six
from heat.common import exception
from heat.engine.clients.os import nova
from heat.engine import resource
from heat.tests import common
from heat.tests.nova import fakes as fakes_nova
from heat.tests import utils
class NovaClientPluginTestCase(common.HeatTestCase):
def setUp(self):
super(NovaClientPluginTestCase, self).setUp()
self.nova_client = mock.MagicMock()
con = utils.dummy_context()
c = con.clients
self.nova_plugin = c.client_plugin('nova')
self.nova_plugin._client = self.nova_client
class NovaClientPluginTests(NovaClientPluginTestCase):
"""
Basic tests for the helper methods in
:module:'heat.engine.clients.os.nova'.
"""
def test_get_ip(self):
my_image = mock.MagicMock()
my_image.addresses = {
'public': [{'version': 4,
'addr': '4.5.6.7'},
{'version': 6,
'addr': '2401:1801:7800:0101:c058:dd33:ff18:04e6'}],
'private': [{'version': 4,
'addr': '10.13.12.13'}]}
expected = '4.5.6.7'
observed = self.nova_plugin.get_ip(my_image, 'public', 4)
self.assertEqual(expected, observed)
expected = '10.13.12.13'
observed = self.nova_plugin.get_ip(my_image, 'private', 4)
self.assertEqual(expected, observed)
expected = '2401:1801:7800:0101:c058:dd33:ff18:04e6'
observed = self.nova_plugin.get_ip(my_image, 'public', 6)
self.assertEqual(expected, observed)
def test_get_flavor_id(self):
"""Tests the get_flavor_id function."""
flav_id = str(uuid.uuid4())
flav_name = 'X-Large'
my_flavor = mock.MagicMock()
my_flavor.name = flav_name
my_flavor.id = flav_id
self.nova_client.flavors.list.return_value = [my_flavor]
self.assertEqual(flav_id, self.nova_plugin.get_flavor_id(flav_name))
self.assertEqual(flav_id, self.nova_plugin.get_flavor_id(flav_id))
self.assertRaises(exception.FlavorMissing,
self.nova_plugin.get_flavor_id, 'noflavor')
self.assertEqual(3, self.nova_client.flavors.list.call_count)
self.assertEqual([(), (), ()],
self.nova_client.flavors.list.call_args_list)
def test_get_keypair(self):
"""Tests the get_keypair function."""
my_pub_key = 'a cool public key string'
my_key_name = 'mykey'
my_key = mock.MagicMock()
my_key.public_key = my_pub_key
my_key.name = my_key_name
self.nova_client.keypairs.get.side_effect = [
my_key, nova_exceptions.NotFound(404)]
self.assertEqual(my_key, self.nova_plugin.get_keypair(my_key_name))
self.assertRaises(exception.UserKeyPairMissing,
self.nova_plugin.get_keypair, 'notakey')
calls = [mock.call(my_key_name),
mock.call('notakey')]
self.nova_client.keypairs.get.assert_has_calls(calls)
def test_get_server(self):
"""Tests the get_server function."""
my_server = mock.MagicMock()
self.nova_client.servers.get.side_effect = [
my_server, nova_exceptions.NotFound(404)]
self.assertEqual(my_server, self.nova_plugin.get_server('my_server'))
self.assertRaises(exception.EntityNotFound,
self.nova_plugin.get_server, 'idontexist')
calls = [mock.call('my_server'),
mock.call('idontexist')]
self.nova_client.servers.get.assert_has_calls(calls)
def test_get_network_id_by_label(self):
"""Tests the get_net_id_by_label function."""
net = mock.MagicMock()
net.id = str(uuid.uuid4())
self.nova_client.networks.find.side_effect = [
net, nova_exceptions.NotFound(404),
nova_exceptions.NoUniqueMatch()]
self.assertEqual(net.id,
self.nova_plugin.get_net_id_by_label('net_label'))
exc = self.assertRaises(
exception.NovaNetworkNotFound,
self.nova_plugin.get_net_id_by_label, 'idontexist')
expected = 'The Nova network (idontexist) could not be found'
self.assertIn(expected, six.text_type(exc))
exc = self.assertRaises(
exception.PhysicalResourceNameAmbiguity,
self.nova_plugin.get_net_id_by_label, 'notUnique')
expected = ('Multiple physical resources were found '
'with name (notUnique)')
self.assertIn(expected, six.text_type(exc))
calls = [mock.call(label='net_label'),
mock.call(label='idontexist'),
mock.call(label='notUnique')]
self.nova_client.networks.find.assert_has_calls(calls)
def test_get_nova_network_id(self):
"""Tests the get_nova_network_id function."""
net = mock.MagicMock()
net.id = str(uuid.uuid4())
not_existent_net_id = str(uuid.uuid4())
self.nova_client.networks.get.side_effect = [
net, nova_exceptions.NotFound(404)]
self.nova_client.networks.find.side_effect = [
nova_exceptions.NotFound(404)]
self.assertEqual(net.id,
self.nova_plugin.get_nova_network_id(net.id))
exc = self.assertRaises(
exception.NovaNetworkNotFound,
self.nova_plugin.get_nova_network_id, not_existent_net_id)
expected = ('The Nova network (%s) could not be found' %
not_existent_net_id)
self.assertIn(expected, six.text_type(exc))
calls = [mock.call(net.id),
mock.call(not_existent_net_id)]
self.nova_client.networks.get.assert_has_calls(calls)
self.nova_client.networks.find.assert_called_once_with(
label=not_existent_net_id)
def test_get_status(self):
server = self.m.CreateMockAnything()
server.status = 'ACTIVE'
observed = self.nova_plugin.get_status(server)
self.assertEqual('ACTIVE', observed)
server.status = 'ACTIVE(STATUS)'
observed = self.nova_plugin.get_status(server)
self.assertEqual('ACTIVE', observed)
class NovaClientPluginRefreshServerTests(NovaClientPluginTestCase):
msg = ("ClientException: The server has either erred or is "
"incapable of performing the requested operation.")
scenarios = [
('successful_refresh', dict(
value=None,
e_raise=False)),
('overlimit_error', dict(
value=nova_exceptions.OverLimit(413, "limit reached"),
e_raise=False)),
('500_error', dict(
value=nova_exceptions.ClientException(500, msg),
e_raise=False)),
('503_error', dict(
value=nova_exceptions.ClientException(503, msg),
e_raise=False)),
('unhandled_exception', dict(
value=nova_exceptions.ClientException(501, msg),
e_raise=True)),
]
def test_refresh(self):
server = mock.MagicMock()
server.get.side_effect = [self.value]
if self.e_raise:
self.assertRaises(nova_exceptions.ClientException,
self.nova_plugin.refresh_server, server)
else:
self.assertIsNone(self.nova_plugin.refresh_server(server))
server.get.assert_called_once_with()
class NovaClientPluginFetchServerTests(NovaClientPluginTestCase):
server = mock.Mock()
# set explicitly as id and name has internal meaning in mock.Mock
server.id = '1234'
server.name = 'test_fetch_server'
msg = ("ClientException: The server has either erred or is "
"incapable of performing the requested operation.")
scenarios = [
('successful_refresh', dict(
value=server,
e_raise=False)),
('overlimit_error', dict(
value=nova_exceptions.OverLimit(413, "limit reached"),
e_raise=False)),
('500_error', dict(
value=nova_exceptions.ClientException(500, msg),
e_raise=False)),
('503_error', dict(
value=nova_exceptions.ClientException(503, msg),
e_raise=False)),
('unhandled_exception', dict(
value=nova_exceptions.ClientException(501, msg),
e_raise=True)),
]
def test_fetch_server(self):
self.nova_client.servers.get.side_effect = [self.value]
if self.e_raise:
self.assertRaises(nova_exceptions.ClientException,
self.nova_plugin.fetch_server, self.server.id)
elif isinstance(self.value, mock.Mock):
self.assertEqual(self.value,
self.nova_plugin.fetch_server(self.server.id))
else:
self.assertIsNone(self.nova_plugin.fetch_server(self.server.id))
self.nova_client.servers.get.assert_called_once_with(self.server.id)
class NovaClientPluginCheckActiveTests(NovaClientPluginTestCase):
scenarios = [
('active', dict(
status='ACTIVE',
e_raise=False)),
('deferred', dict(
status='BUILD',
e_raise=False)),
('error', dict(
status='ERROR',
e_raise=resource.ResourceInError)),
('unknown', dict(
status='VIKINGS!',
e_raise=resource.ResourceUnknownStatus))
]
def setUp(self):
super(NovaClientPluginCheckActiveTests, self).setUp()
self.server = mock.Mock()
self.server.id = '1234'
self.server.status = self.status
self.r_mock = self.patchobject(self.nova_plugin, 'refresh_server',
return_value=None)
self.f_mock = self.patchobject(self.nova_plugin, 'fetch_server',
return_value=self.server)
def test_check_active_with_object(self):
if self.e_raise:
self.assertRaises(self.e_raise,
self.nova_plugin._check_active, self.server)
self.r_mock.assert_called_once_with(self.server)
elif self.status in self.nova_plugin.deferred_server_statuses:
self.assertFalse(self.nova_plugin._check_active(self.server))
self.r_mock.assert_called_once_with(self.server)
else:
self.assertTrue(self.nova_plugin._check_active(self.server))
self.assertEqual(0, self.r_mock.call_count)
self.assertEqual(0, self.f_mock.call_count)
def test_check_active_with_string(self):
if self.e_raise:
self.assertRaises(self.e_raise,
self.nova_plugin._check_active, self.server.id)
elif self.status in self.nova_plugin.deferred_server_statuses:
self.assertFalse(self.nova_plugin._check_active(self.server.id))
else:
self.assertTrue(self.nova_plugin._check_active(self.server.id))
self.f_mock.assert_called_once_with(self.server.id)
self.assertEqual(0, self.r_mock.call_count)
def test_check_active_with_string_unavailable(self):
self.f_mock.return_value = None
self.assertFalse(self.nova_plugin._check_active(self.server.id))
self.f_mock.assert_called_once_with(self.server.id)
self.assertEqual(0, self.r_mock.call_count)
class NovaClientPluginUserdataTests(NovaClientPluginTestCase):
def test_build_userdata(self):
"""Tests the build_userdata function."""
cfg.CONF.set_override('heat_metadata_server_url',
'http://server.test:123')
cfg.CONF.set_override('heat_watch_server_url',
'http://server.test:345')
cfg.CONF.set_override('instance_connection_is_secure',
False)
cfg.CONF.set_override(
'instance_connection_https_validate_certificates', False)
data = self.nova_plugin.build_userdata({})
self.assertIn("Content-Type: text/cloud-config;", data)
self.assertIn("Content-Type: text/cloud-boothook;", data)
self.assertIn("Content-Type: text/part-handler;", data)
self.assertIn("Content-Type: text/x-cfninitdata;", data)
self.assertIn("Content-Type: text/x-shellscript;", data)
self.assertIn("http://server.test:345", data)
self.assertIn("http://server.test:123", data)
self.assertIn("[Boto]", data)
def test_build_userdata_without_instance_user(self):
"""Don't add a custom instance user when not requested."""
cfg.CONF.set_override('instance_user',
'config_instance_user')
cfg.CONF.set_override('heat_metadata_server_url',
'http://server.test:123')
cfg.CONF.set_override('heat_watch_server_url',
'http://server.test:345')
data = self.nova_plugin.build_userdata({}, instance_user=None)
self.assertNotIn('user: ', data)
self.assertNotIn('useradd', data)
self.assertNotIn('config_instance_user', data)
def test_build_userdata_with_instance_user(self):
"""Add the custom instance user when requested."""
self.m.StubOutWithMock(nova.cfg, 'CONF')
cnf = nova.cfg.CONF
cnf.instance_user = 'config_instance_user'
cnf.heat_metadata_server_url = 'http://server.test:123'
cnf.heat_watch_server_url = 'http://server.test:345'
data = self.nova_plugin.build_userdata(
None, instance_user="custominstanceuser")
self.assertNotIn('config_instance_user', data)
self.assertIn("custominstanceuser", data)
class NovaClientPluginMetadataTests(NovaClientPluginTestCase):
def test_serialize_string(self):
original = {'test_key': 'simple string value'}
self.assertEqual(original, self.nova_plugin.meta_serialize(original))
def test_serialize_int(self):
original = {'test_key': 123}
expected = {'test_key': '123'}
self.assertEqual(expected, self.nova_plugin.meta_serialize(original))
def test_serialize_list(self):
original = {'test_key': [1, 2, 3]}
expected = {'test_key': '[1, 2, 3]'}
self.assertEqual(expected, self.nova_plugin.meta_serialize(original))
def test_serialize_dict(self):
original = {'test_key': {'a': 'b', 'c': 'd'}}
expected = {'test_key': '{"a": "b", "c": "d"}'}
self.assertEqual(expected, self.nova_plugin.meta_serialize(original))
def test_serialize_none(self):
original = {'test_key': None}
expected = {'test_key': 'null'}
self.assertEqual(expected, self.nova_plugin.meta_serialize(original))
def test_serialize_no_value(self):
"""This test is to prove that the user can only pass in a dict to nova
metadata.
"""
excp = self.assertRaises(exception.StackValidationFailed,
self.nova_plugin.meta_serialize, "foo")
self.assertIn('metadata needs to be a Map', six.text_type(excp))
def test_serialize_combined(self):
original = {
'test_key_1': 123,
'test_key_2': 'a string',
'test_key_3': {'a': 'b'},
'test_key_4': None,
}
expected = {
'test_key_1': '123',
'test_key_2': 'a string',
'test_key_3': '{"a": "b"}',
'test_key_4': 'null',
}
self.assertEqual(expected, self.nova_plugin.meta_serialize(original))
class ServerConstraintTest(common.HeatTestCase):
def setUp(self):
super(ServerConstraintTest, self).setUp()
self.ctx = utils.dummy_context()
self.mock_get_server = mock.Mock()
self.ctx.clients.client_plugin(
'nova').get_server = self.mock_get_server
self.constraint = nova.ServerConstraint()
def test_validation(self):
self.mock_get_server.return_value = mock.MagicMock()
self.assertTrue(self.constraint.validate("foo", self.ctx))
def test_validation_error(self):
self.mock_get_server.side_effect = exception.EntityNotFound(
entity='Server', name='bar')
self.assertFalse(self.constraint.validate("bar", self.ctx))
class FlavorConstraintTest(common.HeatTestCase):
def test_validate(self):
client = fakes_nova.FakeClient()
self.stub_keystoneclient()
self.patchobject(nova.NovaClientPlugin, '_create', return_value=client)
client.flavors = mock.MagicMock()
flavor = collections.namedtuple("Flavor", ["id", "name"])
flavor.id = "1234"
flavor.name = "foo"
client.flavors.list.return_value = [flavor]
constraint = nova.FlavorConstraint()
ctx = utils.dummy_context()
self.assertFalse(constraint.validate("bar", ctx))
self.assertTrue(constraint.validate("foo", ctx))
self.assertTrue(constraint.validate("1234", ctx))
nova.NovaClientPlugin._create.assert_called_once_with()
self.assertEqual(3, client.flavors.list.call_count)
self.assertEqual([(), (), ()],
client.flavors.list.call_args_list)
class KeypairConstraintTest(common.HeatTestCase):
def test_validation(self):
client = fakes_nova.FakeClient()
self.patchobject(nova.NovaClientPlugin, '_create', return_value=client)
client.keypairs = mock.MagicMock()
key = collections.namedtuple("Key", ["name"])
key.name = "foo"
client.keypairs.get.side_effect = [
fakes_nova.fake_exception(), key]
constraint = nova.KeypairConstraint()
ctx = utils.dummy_context()
self.assertFalse(constraint.validate("bar", ctx))
self.assertTrue(constraint.validate("foo", ctx))
self.assertTrue(constraint.validate("", ctx))
nova.NovaClientPlugin._create.assert_called_once_with()
calls = [mock.call('bar'),
mock.call(key.name)]
client.keypairs.get.assert_has_calls(calls)
class ConsoleUrlsTest(common.HeatTestCase):
scenarios = [
('novnc', dict(console_type='novnc', srv_method='vnc')),
('xvpvnc', dict(console_type='xvpvnc', srv_method='vnc')),
('spice', dict(console_type='spice-html5', srv_method='spice')),
('rdp', dict(console_type='rdp-html5', srv_method='rdp')),
('serial', dict(console_type='serial', srv_method='serial')),
]
def setUp(self):
super(ConsoleUrlsTest, self).setUp()
self.nova_client = mock.Mock()
con = utils.dummy_context()
c = con.clients
self.nova_plugin = c.client_plugin('nova')
self.nova_plugin._client = self.nova_client
self.server = mock.Mock()
self.console_method = getattr(self.server,
'get_%s_console' % self.srv_method)
def test_get_console_url(self):
console = {
'console': {
'type': self.console_type,
'url': '%s_console_url' % self.console_type
}
}
self.console_method.return_value = console
console_url = self.nova_plugin.get_console_urls(self.server)[
self.console_type]
self.assertEqual(console['console']['url'], console_url)
self.console_method.assert_called_once_with(self.console_type)
def test_get_console_url_tolerate_unavailable(self):
msg = 'Unavailable console type %s.' % self.console_type
self.console_method.side_effect = nova_exceptions.BadRequest(
400, message=msg)
console_url = self.nova_plugin.get_console_urls(self.server)[
self.console_type]
self.console_method.assert_called_once_with(self.console_type)
self.assertEqual(msg, console_url)
def test_get_console_urls_reraises_other_400(self):
exc = nova_exceptions.BadRequest
self.console_method.side_effect = exc(400, message="spam")
urls = self.nova_plugin.get_console_urls(self.server)
e = self.assertRaises(exc, urls.__getitem__, self.console_type)
self.assertIn('spam', e.message)
self.console_method.assert_called_once_with(self.console_type)
def test_get_console_urls_reraises_other(self):
exc = Exception
self.console_method.side_effect = exc("spam")
urls = self.nova_plugin.get_console_urls(self.server)
e = self.assertRaises(exc, urls.__getitem__, self.console_type)
self.assertIn('spam', e.args)
self.console_method.assert_called_once_with(self.console_type)