tripleo-common/tripleo_common/tests/utils/test_stack_parameters.py

862 lines
30 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 yaml
from swiftclient import exceptions as swiftexceptions
from ironicclient import exceptions as ironicexceptions
from tripleo_common import constants
from tripleo_common.tests import base
from tripleo_common.utils import stack_parameters
from tripleo_common.utils import nodes
class StackParametersTest(base.TestCase):
@mock.patch('tripleo_common.utils.plan.'
'cache_delete')
def test_reset_parameter(self, mock_cache):
swift = mock.MagicMock(url="http://test.com")
mock_env = yaml.safe_dump({
'name': constants.DEFAULT_CONTAINER_NAME,
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}],
'parameter_defaults': {'SomeTestParameter': 42}
}, default_flow_style=False)
swift.get_object.return_value = ({}, mock_env)
# Test
stack_parameters.reset_parameters(swift)
mock_env_reset = yaml.safe_dump({
'name': constants.DEFAULT_CONTAINER_NAME,
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}]
}, default_flow_style=False)
swift.put_object.assert_called_once_with(
constants.DEFAULT_CONTAINER_NAME,
constants.PLAN_ENVIRONMENT,
mock_env_reset
)
mock_cache.assert_called_once_with(
swift,
"overcloud",
"tripleo.parameters.get"
)
@mock.patch('uuid.uuid4')
@mock.patch('heatclient.common.template_utils.'
'process_multiple_environments_and_files')
@mock.patch('heatclient.common.template_utils.'
'get_template_contents')
@mock.patch('tripleo_common.utils.plan.'
'cache_set')
def test_update_parameters(self, mock_cache,
mock_get_template_contents,
mock_env_files,
mock_uuid):
mock_env_files.return_value = ({}, {})
swift = mock.MagicMock(url="http://test.com")
mock_env = yaml.safe_dump({
'name': constants.DEFAULT_CONTAINER_NAME,
'temp_environment': 'temp_environment',
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}],
}, default_flow_style=False)
mock_roles = yaml.safe_dump([{"name": "foo"}])
mock_network = yaml.safe_dump([{'enabled': False}])
mock_exclude = yaml.safe_dump({"name": "foo"})
swift.get_object.side_effect = (
({}, mock_env),
({}, mock_env),
({}, mock_roles),
({}, mock_network),
({}, mock_exclude),
({}, mock_env),
({}, mock_env),
({}, mock_env),
({}, mock_roles),
({}, mock_network),
({}, mock_exclude),
({}, mock_env),
({}, mock_env),
swiftexceptions.ClientException('atest2')
)
def return_container_files(*args):
return ('headers', [{'name': 'foo.role.j2.yaml'}])
swift.get_container = mock.MagicMock(
side_effect=return_container_files)
mock_heat = mock.MagicMock()
mock_heat.stacks.validate.return_value = {
"Type": "Foo",
"Description": "Le foo bar",
"Parameters": {"bar": {"foo": "bar barz"}},
"NestedParameters": {"Type": "foobar"}
}
mock_uuid.return_value = "cheese"
expected_value = {
'environment_parameters': None,
'heat_resource_tree': {
'parameters': {'bar': {'foo': 'bar barz',
'name': 'bar'}},
'resources': {'cheese': {
'id': 'cheese',
'name': 'Root',
'description': 'Le foo bar',
'parameters': ['bar'],
'resources': ['cheese'],
'type': 'Foo'}
}
}
}
mock_get_template_contents.return_value = ({}, {
'heat_template_version': '2016-04-30'
})
# Test
test_parameters = {'SomeTestParameter': 42}
result = stack_parameters.update_parameters(
swift, mock_heat, test_parameters)
mock_env_updated = yaml.safe_dump({
'name': constants.DEFAULT_CONTAINER_NAME,
'temp_environment': 'temp_environment',
'parameter_defaults': {'SomeTestParameter': 42},
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}]
}, default_flow_style=False)
swift.put_object.assert_any_call(
constants.DEFAULT_CONTAINER_NAME,
constants.PLAN_ENVIRONMENT,
mock_env_updated
)
mock_heat.stacks.validate.assert_called_once_with(
environment={},
files={},
show_nested=True,
template={'heat_template_version': '2016-04-30'},
)
mock_cache.assert_called_once_with(
swift,
"overcloud",
"tripleo.parameters.get",
expected_value
)
self.assertEqual(result, expected_value)
@mock.patch('heatclient.common.template_utils.'
'process_multiple_environments_and_files')
@mock.patch('heatclient.common.template_utils.'
'get_template_contents')
@mock.patch('tripleo_common.utils.plan.'
'cache_set')
def test_update_parameter_new_key(self, mock_cache,
mock_get_template_contents,
mock_env_files):
mock_env_files.return_value = ({}, {})
swift = mock.MagicMock(url="http://test.com")
mock_env = yaml.safe_dump({
'name': constants.DEFAULT_CONTAINER_NAME,
'temp_environment': 'temp_environment',
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}],
}, default_flow_style=False)
mock_roles = yaml.safe_dump([{"name": "foo"}])
mock_network = yaml.safe_dump([{'enabled': False}])
mock_exclude = yaml.safe_dump({"name": "foo"})
swift.get_object.side_effect = (
({}, mock_env),
({}, mock_env),
({}, mock_roles),
({}, mock_network),
({}, mock_exclude),
({}, mock_env),
({}, mock_env),
({}, mock_env),
({}, mock_roles),
({}, mock_network),
({}, mock_exclude),
({}, mock_env),
({}, mock_env),
swiftexceptions.ClientException('atest2')
)
def return_container_files(*args):
return ('headers', [{'name': 'foo.role.j2.yaml'}])
swift.get_container = mock.MagicMock(
side_effect=return_container_files)
heat = mock.MagicMock()
heat.stacks.validate.return_value = {}
mock_get_template_contents.return_value = ({}, {
'heat_template_version': '2016-04-30'
})
# Test
test_parameters = {'SomeTestParameter': 42}
stack_parameters.update_parameters(
swift, heat, test_parameters,
parameter_key='test_key')
mock_env_updated = yaml.safe_dump({
'name': constants.DEFAULT_CONTAINER_NAME,
'temp_environment': 'temp_environment',
'test_key': {'SomeTestParameter': 42},
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}]
}, default_flow_style=False)
swift.put_object.assert_any_call(
constants.DEFAULT_CONTAINER_NAME,
constants.PLAN_ENVIRONMENT,
mock_env_updated
)
heat.stacks.validate.assert_called_once_with(
environment={},
files={},
show_nested=True,
template={'heat_template_version': '2016-04-30'},
)
mock_cache.assert_called_once_with(
swift,
"overcloud",
"tripleo.parameters.get",
{'environment_parameters': None, 'heat_resource_tree': {}}
)
@mock.patch('heatclient.common.template_utils.'
'process_multiple_environments_and_files')
@mock.patch('heatclient.common.template_utils.'
'get_template_contents')
@mock.patch('tripleo_common.utils.plan.'
'cache_set')
@mock.patch('tripleo_common.utils.parameters.set_count_and_flavor_params')
def test_update_role_parameter(self, mock_set_count_and_flavor,
mock_cache, mock_get_template_contents,
mock_env_files):
mock_env_files.return_value = ({}, {})
swift = mock.MagicMock(url="http://test.com")
mock_env = yaml.safe_dump({
'name': 'overcast'
}, default_flow_style=False)
mock_roles = yaml.safe_dump([{"name": "foo"}])
mock_network = yaml.safe_dump([{'enabled': False}])
mock_exclude = yaml.safe_dump({"name": "foo"})
swift.get_object.side_effect = (
({}, mock_env),
({}, mock_env),
({}, mock_roles),
({}, mock_network),
({}, mock_exclude),
({}, mock_env),
({}, mock_env),
({}, mock_env),
({}, mock_roles),
({}, mock_network),
({}, mock_exclude),
({}, mock_env),
({}, mock_env),
swiftexceptions.ClientException('atest2')
)
def return_container_files(*args):
return ('headers', [{'name': 'foo.yaml'}])
swift.get_container = mock.MagicMock(
side_effect=return_container_files)
heat = mock.MagicMock()
ironic = mock.MagicMock()
compute = mock.MagicMock()
heat.stacks.validate.return_value = {}
mock_get_template_contents.return_value = ({}, {
'heat_template_version': '2016-04-30'
})
params = {'CephStorageCount': 1,
'OvercloudCephStorageFlavor': 'ceph-storage'}
mock_set_count_and_flavor.return_value = params
stack_parameters.update_role_parameters(
swift, heat, ironic, compute,
'ceph-storage', 'overcast')
mock_env_updated = yaml.safe_dump({
'name': 'overcast',
'parameter_defaults': params
}, default_flow_style=False)
swift.put_object.assert_any_call(
'overcast',
constants.PLAN_ENVIRONMENT,
mock_env_updated
)
heat.stacks.validate.assert_called_once_with(
environment={},
files={},
show_nested=True,
template={'heat_template_version': '2016-04-30'},
)
mock_cache.assert_called_once_with(
swift,
"overcast",
"tripleo.parameters.get",
{'environment_parameters': None, 'heat_resource_tree': {}}
)
@mock.patch('tripleo_common.utils.plan.'
'cache_set')
@mock.patch('tripleo_common.utils.plan.'
'cache_get')
@mock.patch('heatclient.common.template_utils.'
'process_multiple_environments_and_files')
@mock.patch('heatclient.common.template_utils.get_template_contents')
def test_empty_resource_tree(self,
mock_get_template_contents,
mock_process_multiple_environments_and_files,
mock_cache_get,
mock_cache_set):
mock_cache_get.return_value = None
swift = mock.MagicMock(url="http://test.com")
mock_env = yaml.safe_dump({
'temp_environment': 'temp_environment',
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}]
}, default_flow_style=False)
swift.get_object.side_effect = (
({}, mock_env),
swiftexceptions.ClientException('atest2'),
({}, mock_env)
)
mock_get_template_contents.return_value = ({}, {
'heat_template_version': '2016-04-30'
})
mock_process_multiple_environments_and_files.return_value = ({}, {})
mock_heat = mock.MagicMock()
mock_heat.stacks.validate.return_value = {}
expected_value = {
'heat_resource_tree': {},
'environment_parameters': None,
}
# Test
result = stack_parameters.get_flattened_parameters(swift, mock_heat)
mock_heat.stacks.validate.assert_called_once_with(
environment={},
files={},
show_nested=True,
template={'heat_template_version': '2016-04-30'},
)
self.assertEqual(result, expected_value)
@mock.patch('tripleo_common.utils.plan.'
'cache_set')
@mock.patch('tripleo_common.utils.plan.'
'cache_get')
@mock.patch('uuid.uuid4', side_effect=['1', '2'])
@mock.patch('heatclient.common.template_utils.'
'process_multiple_environments_and_files')
@mock.patch('heatclient.common.template_utils.get_template_contents')
def test_valid_resource_tree(self,
mock_get_template_contents,
mock_process_multiple_environments_and_files,
mock_uuid,
mock_cache_get,
mock_cache_set):
mock_cache_get.return_value = None
swift = mock.MagicMock(url="http://test.com")
mock_env = yaml.safe_dump({
'temp_environment': 'temp_environment',
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}]
}, default_flow_style=False)
swift.get_object.side_effect = (
({}, mock_env),
swiftexceptions.ClientException('atest2'),
({}, mock_env)
)
mock_get_template_contents.return_value = ({}, {
'heat_template_version': '2016-04-30'
})
mock_process_multiple_environments_and_files.return_value = ({}, {})
mock_heat = mock.MagicMock()
mock_heat.stacks.validate.return_value = {
'NestedParameters': {
'CephStorageHostsDeployment': {
'Type': 'OS::Heat::StructuredDeployments',
},
},
'description': 'sample',
'Parameters': {
'ControllerCount': {
'Default': 1,
'Type': 'Number',
},
}
}
expected_value = {
'heat_resource_tree': {
'resources': {
'1': {
'id': '1',
'name': 'Root',
'resources': [
'2'
],
'parameters': [
'ControllerCount'
]
},
'2': {
'id': '2',
'name': 'CephStorageHostsDeployment',
'type': 'OS::Heat::StructuredDeployments'
}
},
'parameters': {
'ControllerCount': {
'default': 1,
'type': 'Number',
'name': 'ControllerCount'
}
},
},
'environment_parameters': None,
}
# Test
result = stack_parameters.get_flattened_parameters(swift, mock_heat)
self.assertEqual(result, expected_value)
def test_generate_hostmap(self):
# two instances in 'nova list'.
# vm1 with id=123 and vm2 with id=234
server1 = mock.MagicMock()
server1.id = 123
server1.name = 'vm1'
server2 = mock.MagicMock()
server2.id = 234
server2.name = 'vm2'
servers = mock.MagicMock()
servers = [server1, server2]
compute_client = mock.MagicMock()
compute_client.servers.list.side_effect = (servers, )
# we assume instance id=123 has been provisioned using bm node 'bm1'
# while instance id=234 is in error state, so no bm node has been used
def side_effect(args):
if args == 123:
return bm1
elif args == 234:
raise ironicexceptions.NotFound
baremetal_client = mock.MagicMock()
baremetal_client.node.get_by_instance_uuid = mock.MagicMock(
side_effect=side_effect)
# bm server with name='bm1' and uuid='9876'
bm1 = mock.MagicMock()
bm1.uuid = 9876
bm1.name = 'bm1'
# 'bm1' has a single port with mac='aa:bb:cc:dd:ee:ff'
port1 = mock.MagicMock()
port1.address = 'aa:bb:cc:dd:ee:ff'
def side_effect2(node, *args):
if node == 9876:
return [port1, ]
else:
raise ironicexceptions.NotFound
baremetal_client.port.list = mock.MagicMock(side_effect=side_effect2)
expected_hostmap = {
'aa:bb:cc:dd:ee:ff': {
'compute_name': 'vm1',
'baremetal_name': 'bm1'
}
}
result = nodes.generate_hostmap(baremetal_client, compute_client)
self.assertEqual(result, expected_hostmap)
@mock.patch('tripleo_common.utils.nodes.generate_hostmap')
def test_generate_fencing_parameters(self, mock_generate_hostmap):
test_hostmap = {
"00:11:22:33:44:55": {
"compute_name": "compute_name_0",
"baremetal_name": "baremetal_name_0"
},
"11:22:33:44:55:66": {
"compute_name": "compute_name_1",
"baremetal_name": "baremetal_name_1"
},
"aa:bb:cc:dd:ee:ff": {
"compute_name": "compute_name_4",
"baremetal_name": "baremetal_name_4"
},
"bb:cc:dd:ee:ff:gg": {
"compute_name": "compute_name_5",
"baremetal_name": "baremetal_name_5"
}
}
mock_generate_hostmap.return_value = test_hostmap
test_envjson = [{
"name": "control-0",
"pm_password": "control-0-password",
"pm_type": "ipmi",
"pm_user": "control-0-admin",
"pm_addr": "0.1.2.3",
"pm_port": "0123",
"mac": [
"00:11:22:33:44:55"
]
}, {
"name": "control-1",
"pm_password": "control-1-password",
# Still support deprecated drivers
"pm_type": "pxe_ipmitool",
"pm_user": "control-1-admin",
"pm_addr": "1.2.3.4",
"mac": [
"11:22:33:44:55:66"
]
}, {
# test node using redfish pm
"name": "compute-4",
"pm_password": "calvin",
"pm_type": "redfish",
"pm_user": "root",
"pm_addr": "172.16.0.1:8000",
"pm_port": "8000",
"redfish_verify_ca": "false",
"pm_system_id": "/redfish/v1/Systems/5678",
"mac": [
"aa:bb:cc:dd:ee:ff"
]
}, {
# This is an extra node on oVirt/RHV
"name": "control-3",
"pm_password": "ovirt-password",
"pm_type": "staging-ovirt",
"pm_user": "admin@internal",
"pm_addr": "3.4.5.6",
"pm_vm_name": "control-3",
"mac": [
"bb:cc:dd:ee:ff:gg"
]
}, {
# This is an extra node that is not in the hostmap, to ensure we
# cope with unprovisioned nodes
"name": "control-2",
"pm_password": "control-2-password",
"pm_type": "ipmi",
"pm_user": "control-2-admin",
"pm_addr": "2.3.4.5",
"mac": [
"22:33:44:55:66:77"
]
}
]
ironic = mock.MagicMock()
compute = mock.MagicMock()
result = stack_parameters.generate_fencing_parameters(
ironic, compute, test_envjson,
28, 5, 0, True)['parameter_defaults']
self.assertTrue(result["EnableFencing"])
self.assertEqual(len(result["FencingConfig"]["devices"]), 4)
self.assertEqual(result["FencingConfig"]["devices"][0], {
"agent": "fence_ipmilan",
"host_mac": "00:11:22:33:44:55",
"params": {
"delay": 28,
"ipaddr": "0.1.2.3",
"ipport": "0123",
"lanplus": True,
"privlvl": 5,
"login": "control-0-admin",
"passwd": "control-0-password",
"pcmk_host_list": "compute_name_0"
}
})
self.assertEqual(result["FencingConfig"]["devices"][1], {
"agent": "fence_ipmilan",
"host_mac": "11:22:33:44:55:66",
"params": {
"delay": 28,
"ipaddr": "1.2.3.4",
"lanplus": True,
"privlvl": 5,
"login": "control-1-admin",
"passwd": "control-1-password",
"pcmk_host_list": "compute_name_1"
}
})
self.assertEqual(result["FencingConfig"]["devices"][2], {
"agent": "fence_redfish",
"host_mac": "aa:bb:cc:dd:ee:ff",
"params": {
"delay": 28,
"ipaddr": "172.16.0.1:8000",
"ipport": "8000",
"privlvl": 5,
"login": "root",
"passwd": "calvin",
"systems_uri": "/redfish/v1/Systems/5678",
"ssl_insecure": "true",
"pcmk_host_list": "compute_name_4"
}
})
self.assertEqual(result["FencingConfig"]["devices"][3], {
"agent": "fence_rhevm",
"host_mac": "bb:cc:dd:ee:ff:gg",
"params": {
"delay": 28,
"ipaddr": "3.4.5.6",
"login": "admin@internal",
"passwd": "ovirt-password",
"port": "control-3",
"ssl": 1,
"ssl_insecure": 1,
"pcmk_host_list": "compute_name_5"
}
})
@mock.patch('tripleo_common.utils.template.process_templates')
def test_run_valid_network_config(self, mock_process_templates):
swift = mock.MagicMock(url="http://test.com")
mock_env = {
'template': {},
'files': {},
'environment': [{'path': 'environments/test.yaml'}]
}
mock_process_templates.return_value = mock_env
mock_heat = mock.MagicMock()
mock_heat.stacks.preview.return_value = mock.Mock(resources=[{
"resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"},
"resource_name": "OsNetConfigImpl",
"properties": {"config": "echo \'{\"network_config\": {}}\'"
" > /etc/os-net-config/config.json"}
}])
mock_heat.stacks.get.return_value = mock.Mock(
stack_name="overcloud-TEMP")
expected = {"network_config": {}}
# Test
result = stack_parameters.get_network_configs(
swift, mock_heat, container='overcloud', role_name='Compute')
self.assertEqual(expected, result)
mock_heat.stacks.preview.assert_called_once_with(
environment=[{'path': 'environments/test.yaml'}],
files={},
template={},
stack_name='overcloud-TEMP',
)
@mock.patch('tripleo_common.utils.template.process_templates')
def test_run_invalid_network_config(self, mock_process_templates):
swift = mock.MagicMock(url="http://test.com")
mock_env = {
'template': {},
'files': {},
'environment': [{'path': 'environments/test.yaml'}]
}
mock_process_templates.return_value = mock_env
mock_heat = mock.MagicMock()
mock_heat.stacks.preview.return_value = mock.Mock(resources=[{
"resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"},
"resource_name": "OsNetConfigImpl",
"properties": {"config": ""}
}])
mock_heat.stacks.get.return_value = mock.Mock(
stack_name="overcloud-TEMP")
# Test
self.assertRaises(RuntimeError,
stack_parameters.get_network_configs, swift,
mock_heat, container='overcloud',
role_name='Compute')
mock_heat.stacks.preview.assert_called_once_with(
environment=[{'path': 'environments/test.yaml'}],
files={},
template={},
stack_name='overcloud-TEMP',
)
@mock.patch('tripleo_common.utils.template.process_templates')
def test_run_valid_network_config_with_no_if_routes_inputs(
self, mock_process_templates):
swift = mock.MagicMock(url="http://test.com")
mock_env = {
'template': {
'resources': {
'ComputeGroupVars': {
'properties': {
'value': {
'role_networks': ['InternalApi',
'Storage']}
}
}
}
},
'files': {},
'environment': {'parameter_defaults': {}}
}
mock_process_templates.return_value = mock_env
mock_heat = mock.MagicMock()
mock_heat.stacks.preview.return_value = mock.Mock(resources=[{
"resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"},
"resource_name": "OsNetConfigImpl",
"properties": {"config": "echo \'{\"network_config\": {}}\'"
" > /etc/os-net-config/config.json"}
}])
mock_heat.stacks.get.return_value = mock.Mock(
stack_name="overcloud-TEMP")
expected = {"network_config": {}}
# Test
result = stack_parameters.get_network_configs(
swift, mock_heat, container='overcloud', role_name='Compute')
self.assertEqual(expected, result)
mock_heat.stacks.preview.assert_called_once_with(
environment={
'parameter_defaults': {
'InternalApiInterfaceRoutes': [[]],
'StorageInterfaceRoutes': [[]]
}
},
files={},
template={'resources': {'ComputeGroupVars': {'properties': {
'value': {'role_networks': ['InternalApi', 'Storage']}
}}}},
stack_name='overcloud-TEMP',
)
@mock.patch('tripleo_common.utils.template.process_templates')
def test_run_valid_network_config_with_if_routes_inputs(
self, mock_process_templates):
swift = mock.MagicMock(url="http://test.com")
mock_env = {
'template': {
'resources': {
'ComputeGroupVars': {
'properties': {
'value': {
'role_networks': ['InternalApi',
'Storage']}
}
}
}
},
'files': {},
'environment': {
'parameter_defaults': {
'InternalApiInterfaceRoutes': ['test1'],
'StorageInterfaceRoutes': ['test2']
}}
}
mock_process_templates.return_value = mock_env
mock_heat = mock.MagicMock()
mock_heat.stacks.preview.return_value = mock.Mock(resources=[{
"resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"},
"resource_name": "OsNetConfigImpl",
"properties": {"config": "echo \'{\"network_config\": {}}\'"
" > /etc/os-net-config/config.json"}
}])
mock_heat.stacks.get.return_value = mock.Mock(
stack_name="overcloud-TEMP")
expected = {"network_config": {}}
# Test
result = stack_parameters.get_network_configs(
swift, mock_heat, container='overcloud', role_name='Compute')
self.assertEqual(expected, result)
mock_heat.stacks.preview.assert_called_once_with(
environment={
'parameter_defaults': {
'InternalApiInterfaceRoutes': ['test1'],
'StorageInterfaceRoutes': ['test2']
}
},
files={},
template={'resources': {'ComputeGroupVars': {'properties': {
'value': {'role_networks': ['InternalApi', 'Storage']}
}}}},
stack_name='overcloud-TEMP',
)