tripleo-common/tripleo_common/tests/actions/test_baremetal_deploy.py

1327 lines
49 KiB
Python

# Copyright 2018 Red Hat, Inc.
#
# 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.
import metalsmith
from metalsmith import sources
import mock
from openstack import exceptions as sdk_exc
from tripleo_common.actions import baremetal_deploy
from tripleo_common.tests import base
@mock.patch.object(baremetal_deploy, '_provisioner', autospec=True)
class TestReserveNodes(base.TestCase):
def test_success(self, mock_pr):
instances = [
{'hostname': 'host1', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'host2', 'resource_class': 'compute',
'capabilities': {'answer': '42'},
'image': {'href': 'overcloud-full'}},
{'name': 'control-0', 'traits': ['CUSTOM_GPU'],
'image': {'href': 'overcloud-full'}},
]
action = baremetal_deploy.ReserveNodesAction(instances)
result = action.run(mock.Mock())
self.assertEqual(
[{'node': mock_pr.return_value.reserve_node.return_value.id,
'instance': req} for req in instances],
result['reservations'])
mock_pr.return_value.reserve_node.assert_has_calls([
mock.call(resource_class='baremetal', traits=None,
capabilities={'profile': 'compute'}, candidates=None),
mock.call(resource_class='compute', traits=None,
capabilities={'answer': '42'}, candidates=None),
mock.call(resource_class='baremetal', traits=['CUSTOM_GPU'],
capabilities=None, candidates=['control-0']),
])
self.assertFalse(mock_pr.return_value.unprovision_node.called)
def test_missing_hostname(self, mock_pr):
instances = [
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
{'resource_class': 'compute', 'capabilities': {'answer': '42'},
'image': {'href': 'overcloud-full'}}
]
action = baremetal_deploy.ReserveNodesAction(instances)
result = action.run(mock.Mock())
self.assertEqual(
[{'node': mock_pr.return_value.reserve_node.return_value.id,
'instance': req} for req in instances],
result['reservations'])
mock_pr.return_value.reserve_node.assert_has_calls([
mock.call(resource_class='baremetal', traits=None,
capabilities=None, candidates=None),
mock.call(resource_class='compute', traits=None,
capabilities={'answer': '42'}, candidates=None),
])
self.assertFalse(mock_pr.return_value.unprovision_node.called)
@mock.patch.object(baremetal_deploy.LOG, 'exception', autospec=True)
def test_failure(self, mock_log, mock_pr):
instances = [
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
{'hostname': 'host2', 'resource_class': 'compute',
'capabilities': {'answer': '42'},
'image': {'href': 'overcloud-full'}},
{'hostname': 'host3',
'image': {'href': 'overcloud-full'}},
]
success_node = mock.Mock(uuid='uuid1')
mock_pr.return_value.reserve_node.side_effect = [
success_node,
RuntimeError("boom"),
]
action = baremetal_deploy.ReserveNodesAction(instances)
result = action.run(mock.Mock())
self.assertIn('RuntimeError: boom', result.error)
mock_pr.return_value.reserve_node.assert_has_calls([
mock.call(resource_class='baremetal', capabilities=None,
candidates=None, traits=None),
mock.call(resource_class='compute', capabilities={'answer': '42'},
candidates=None, traits=None)
])
mock_pr.return_value.unprovision_node.assert_called_once_with(
success_node)
mock_log.assert_called_once_with('Provisioning failed, cleaning up')
@mock.patch.object(baremetal_deploy.LOG, 'exception', autospec=True)
def test_failure_to_clean_up(self, mock_log, mock_pr):
instances = [
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
{'hostname': 'host2', 'resource_class': 'compute',
'capabilities': {'answer': '42'},
'image': {'href': 'overcloud-full'}},
{'hostname': 'host3',
'image': {'href': 'overcloud-full'}},
]
success_node = mock.Mock(uuid='uuid1')
mock_pr.return_value.reserve_node.side_effect = [
success_node,
RuntimeError("boom"),
]
mock_pr.return_value.unprovision_node.side_effect = AssertionError
action = baremetal_deploy.ReserveNodesAction(instances)
result = action.run(mock.Mock())
self.assertIn('RuntimeError: boom', result.error)
mock_pr.return_value.reserve_node.assert_has_calls([
mock.call(resource_class='baremetal', capabilities=None,
candidates=None, traits=None),
mock.call(resource_class='compute', capabilities={'answer': '42'},
candidates=None, traits=None)
])
mock_pr.return_value.unprovision_node.assert_called_once_with(
success_node)
mock_log.assert_has_calls([
mock.call('Provisioning failed, cleaning up'),
mock.call('Unable to release node %s, moving on', success_node)
])
def test_duplicate_hostname(self, mock_pr):
instances = [
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
]
action = baremetal_deploy.ReserveNodesAction(instances)
result = action.run(mock.Mock())
self.assertIn('Hostname host1 is used more than once', result.error)
self.assertFalse(mock_pr.return_value.reserve_node.called)
def test_duplicate_name(self, mock_pr):
instances = [
{'hostname': 'host1', 'name': 'node-1',
'image': {'href': 'overcloud-full'}},
# name is used as the default for hostname
{'name': 'node-1',
'image': {'href': 'overcloud-full'}},
]
action = baremetal_deploy.ReserveNodesAction(instances)
result = action.run(mock.Mock())
self.assertIn('Node node-1 is requested more than once', result.error)
self.assertFalse(mock_pr.return_value.reserve_node.called)
def test_hostname_format(self, mock_pr):
self.assertEqual(
'%stackname%-controller-%index%',
baremetal_deploy._hostname_format(None, 'Controller')
)
self.assertEqual(
'%stackname%-novacompute-%index%',
baremetal_deploy._hostname_format(None, 'Compute')
)
self.assertEqual(
'server-%index%',
baremetal_deploy._hostname_format('server-%index%', 'Compute')
)
def test_build_hostname(self, mock_pr):
self.assertEqual(
'overcloud-controller-2',
baremetal_deploy._build_hostname(
'%stackname%-controller-%index%', 2, 'overcloud'
)
)
self.assertEqual(
'server-2',
baremetal_deploy._build_hostname(
'server-%index%', 2, 'overcloud'
)
)
@mock.patch.object(baremetal_deploy, '_provisioner', autospec=True)
class TestDeployNode(base.TestCase):
def test_success_defaults(self, mock_pr):
action = baremetal_deploy.DeployNodeAction(
instance={'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
node='1234'
)
result = action.run(mock.Mock())
pr = mock_pr.return_value
self.assertEqual(
pr.provision_node.return_value.to_dict.return_value,
result)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'network': 'ctlplane'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
config = pr.provision_node.call_args[1]['config']
self.assertEqual([], config.ssh_keys)
self.assertEqual('heat-admin', config.users[0]['name'])
source = pr.provision_node.call_args[1]['image']
self.assertIsInstance(source, sources.GlanceImage)
# TODO(dtantsur): check the image when it's a public field
def test_success_with_name(self, mock_pr):
action = baremetal_deploy.DeployNodeAction(
instance={
'name': 'host1',
'hostname': 'host1',
'image': {'href': 'overcloud-full'}
},
node='1234'
)
result = action.run(mock.Mock())
pr = mock_pr.return_value
self.assertEqual(
pr.provision_node.return_value.to_dict.return_value,
result)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'network': 'ctlplane'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
config = pr.provision_node.call_args[1]['config']
self.assertEqual([], config.ssh_keys)
self.assertEqual('heat-admin', config.users[0]['name'])
def test_success_advanced_nic(self, mock_pr):
action = baremetal_deploy.DeployNodeAction(
instance={'hostname': 'host1',
'image': {'href': 'overcloud-full'},
'nics': [{'subnet': 'ctlplane-subnet'},
{'network': 'ctlplane',
'fixed_ip': '10.0.0.2'}]},
node='1234'
)
result = action.run(mock.Mock())
pr = mock_pr.return_value
self.assertEqual(
pr.provision_node.return_value.to_dict.return_value,
result)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'subnet': 'ctlplane-subnet'},
{'network': 'ctlplane', 'fixed_ip': '10.0.0.2'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
config = pr.provision_node.call_args[1]['config']
self.assertEqual([], config.ssh_keys)
self.assertEqual('heat-admin', config.users[0]['name'])
def test_success(self, mock_pr):
pr = mock_pr.return_value
action = baremetal_deploy.DeployNodeAction(
instance={'hostname': 'host1',
'image': {'href': 'overcloud-alt'},
'nics': [{'port': 'abcd'}],
'root_size_gb': 100,
'swap_size_mb': 4096},
node='1234',
ssh_keys=['ssh key contents'],
ssh_user_name='admin',
)
result = action.run(mock.Mock())
self.assertEqual(
pr.provision_node.return_value.to_dict.return_value,
result)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'port': 'abcd'}],
hostname='host1',
root_size_gb=100,
swap_size_mb=4096,
config=mock.ANY,
)
config = pr.provision_node.call_args[1]['config']
self.assertEqual(['ssh key contents'], config.ssh_keys)
self.assertEqual('admin', config.users[0]['name'])
source = pr.provision_node.call_args[1]['image']
self.assertIsInstance(source, sources.GlanceImage)
# TODO(dtantsur): check the image when it's a public field
def test_success_http_partition_image(self, mock_pr):
action = baremetal_deploy.DeployNodeAction(
instance={
'hostname': 'host1',
'image': {
'href': 'https://example/image',
'kernel': 'https://example/kernel',
'ramdisk': 'https://example/ramdisk',
'checksum': 'https://example/checksum'
}
},
node='1234'
)
result = action.run(mock.Mock())
pr = mock_pr.return_value
self.assertEqual(
pr.provision_node.return_value.to_dict.return_value,
result)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'network': 'ctlplane'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
config = pr.provision_node.call_args[1]['config']
self.assertEqual([], config.ssh_keys)
self.assertEqual('heat-admin', config.users[0]['name'])
source = pr.provision_node.call_args[1]['image']
self.assertIsInstance(source, sources.HttpPartitionImage)
self.assertEqual('https://example/image', source.url)
self.assertEqual('https://example/kernel', source.kernel_url)
self.assertEqual('https://example/ramdisk', source.ramdisk_url)
self.assertEqual('https://example/checksum', source.checksum_url)
def test_success_file_partition_image(self, mock_pr):
action = baremetal_deploy.DeployNodeAction(
instance={
'hostname': 'host1',
'image': {
'href': 'file:///var/lib/ironic/image',
'kernel': 'file:///var/lib/ironic/kernel',
'ramdisk': 'file:///var/lib/ironic/ramdisk',
'checksum': 'abcd'
}
},
node='1234'
)
result = action.run(mock.Mock())
pr = mock_pr.return_value
self.assertEqual(
pr.provision_node.return_value.to_dict.return_value,
result)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'network': 'ctlplane'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
config = pr.provision_node.call_args[1]['config']
self.assertEqual([], config.ssh_keys)
self.assertEqual('heat-admin', config.users[0]['name'])
source = pr.provision_node.call_args[1]['image']
self.assertIsInstance(source, sources.FilePartitionImage)
self.assertEqual('file:///var/lib/ironic/image', source.location)
self.assertEqual('file:///var/lib/ironic/kernel',
source.kernel_location)
self.assertEqual('file:///var/lib/ironic/ramdisk',
source.ramdisk_location)
self.assertEqual('abcd', source.checksum)
@mock.patch.object(baremetal_deploy.LOG, 'exception', autospec=True)
def test_failure(self, mock_log, mock_pr):
pr = mock_pr.return_value
instance = {'hostname': 'host1',
'image': {'href': 'overcloud-full'}}
action = baremetal_deploy.DeployNodeAction(
instance=instance,
node='1234'
)
pr.provision_node.side_effect = RuntimeError('boom')
result = action.run(mock.Mock())
self.assertIn('RuntimeError: boom', result.error)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'network': 'ctlplane'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
pr.unprovision_node.assert_called_once_with('1234')
mock_log.assert_called_once_with(
'Provisioning of %s on node %s failed',
instance, '1234')
@mock.patch.object(baremetal_deploy.LOG, 'exception', autospec=True)
def test_failure_to_clean_up(self, mock_log, mock_pr):
pr = mock_pr.return_value
instance = {'hostname': 'host1',
'image': {'href': 'overcloud-full'}}
action = baremetal_deploy.DeployNodeAction(
instance=instance,
node='1234'
)
pr.provision_node.side_effect = RuntimeError('boom')
pr.unprovision_node.side_effect = AssertionError
result = action.run(mock.Mock())
self.assertIn('RuntimeError: boom', result.error)
pr.provision_node.assert_called_once_with(
'1234',
image=mock.ANY,
nics=[{'network': 'ctlplane'}],
hostname='host1',
root_size_gb=49,
swap_size_mb=None,
config=mock.ANY,
)
pr.unprovision_node.assert_called_once_with('1234')
mock_log.assert_has_calls([
mock.call('Provisioning of %s on node %s failed',
instance, '1234'),
mock.call('Unable to release node %s, moving on', '1234')
])
@mock.patch.object(baremetal_deploy, '_provisioner', autospec=True)
class TestCheckExistingInstances(base.TestCase):
def test_success(self, mock_pr):
pr = mock_pr.return_value
instances = [
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
{'hostname': 'host3',
'image': {'href': 'overcloud-full'}},
{'hostname': 'host2', 'resource_class': 'compute',
'capabilities': {'answer': '42'},
'image': {'href': 'overcloud-full'}}
]
existing = mock.MagicMock(hostname='host2', allocation=None)
existing.to_dict.return_value = {
'hostname': 'host2',
'allocation': None
}
pr.show_instance.side_effect = [
sdk_exc.ResourceNotFound(""),
metalsmith.exceptions.Error(""),
existing,
]
action = baremetal_deploy.CheckExistingInstancesAction(instances)
result = action.run(mock.Mock())
self.assertEqual({
'instances': [{
'hostname': 'host2',
'allocation': None,
'port_map': {}
}],
'not_found': [{
'hostname': 'host1',
'image': {'href': 'overcloud-full'},
}, {
'hostname': 'host3',
'image': {'href': 'overcloud-full'},
}]
}, result)
pr.show_instance.assert_has_calls([
mock.call(host) for host in ['host1', 'host3', 'host2']
])
@mock.patch('tripleo_common.actions.baremetal_deploy.'
'CheckExistingInstancesAction.get_baremetal_client')
def test_existing_no_allocation(self, mock_gbc, mock_pr):
pr = mock_pr.return_value
instances = [
{'name': 'server2', 'resource_class': 'compute',
'hostname': 'host2',
'capabilities': {'answer': '42'},
'image': {'href': 'overcloud-full'}}
]
existing = mock.MagicMock(
hostname='host2', allocation=None,
state=metalsmith.InstanceState.ACTIVE)
existing.to_dict.return_value = {
'hostname': 'host2',
}
pr.show_instance.return_value = existing
mock_create = mock_gbc.return_value.allocation.create
action = baremetal_deploy.CheckExistingInstancesAction(instances)
ctx = mock.Mock()
result = action.run(ctx)
mock_gbc.assert_called_once_with(ctx)
mock_create.assert_called_once_with(
name='host2', node='server2', resource_class='compute')
self.assertEqual({
'instances': [{
'hostname': 'host2',
'port_map': {}
}],
'not_found': []
}, result)
pr.show_instance.assert_called_once_with('server2')
def test_hostname_mismatch(self, mock_pr):
instances = [
{'hostname': 'host1',
'image': {'href': 'overcloud-full'}},
]
mock_pr.return_value.show_instance.return_value.hostname = 'host2'
action = baremetal_deploy.CheckExistingInstancesAction(instances)
result = action.run(mock.Mock())
self.assertIn("hostname host1 was not found", result.error)
mock_pr.return_value.show_instance.assert_called_once_with('host1')
def test_unexpected_error(self, mock_pr):
instances = [
{'image': {'href': 'overcloud-full'},
'hostname': 'host%d' % i} for i in range(3)
]
mock_pr.return_value.show_instance.side_effect = RuntimeError('boom')
action = baremetal_deploy.CheckExistingInstancesAction(instances)
result = action.run(mock.Mock())
self.assertIn("for host0", result.error)
self.assertIn("RuntimeError: boom", result.error)
mock_pr.return_value.show_instance.assert_called_once_with('host0')
@mock.patch.object(baremetal_deploy, '_provisioner', autospec=True)
class TestWaitForDeployment(base.TestCase):
def test_success(self, mock_pr):
pr = mock_pr.return_value
inst = pr.wait_for_provisioning.return_value[0]
inst.to_dict.return_value = {'hostname': 'compute.cloud'}
action = baremetal_deploy.WaitForDeploymentAction(
{'hostname': 'compute.cloud', 'uuid': 'uuid1',
'image': {'href': 'overcloud-full'}})
result = action.run(mock.Mock())
pr.wait_for_provisioning.assert_called_once_with(['uuid1'],
timeout=3600)
self.assertIs(result, inst.to_dict.return_value)
self.assertEqual({}, result['port_map'])
def test_with_nics(self, mock_pr):
pr = mock_pr.return_value
net_mock = mock.Mock()
net_mock.name = 'ctlplane'
net_mock.to_dict.return_value = {'tags': ['foo']}
inst = pr.wait_for_provisioning.return_value[0]
inst.nics.return_value = [
mock.Mock(fixed_ips=[{'ip_address': '1.2.3.5',
'subnet_id': 'abcd'}],
network=net_mock)
]
pr.connection.network.get_subnet.return_value.to_dict.return_value = {
'cidr': '1.2.3.0/24'
}
inst.to_dict.return_value = {'hostname': 'compute.cloud'}
action = baremetal_deploy.WaitForDeploymentAction(
{'hostname': 'compute.cloud', 'uuid': 'uuid1'})
result = action.run(mock.Mock())
pr.wait_for_provisioning.assert_called_once_with(['uuid1'],
timeout=3600)
self.assertIs(result, inst.to_dict.return_value)
self.assertEqual(
{
'ctlplane': {
'network': {'tags': ['foo']},
'fixed_ips': [{'ip_address': '1.2.3.5'}],
'subnets': [{'cidr': '1.2.3.0/24'}],
}
},
result['port_map'])
def test_failure(self, mock_pr):
pr = mock_pr.return_value
pr.wait_for_provisioning.side_effect = RuntimeError('boom')
action = baremetal_deploy.WaitForDeploymentAction(
{'hostname': 'compute.cloud', 'uuid': 'uuid1'})
result = action.run(mock.Mock())
self.assertIn("RuntimeError: boom", result.error)
pr.wait_for_provisioning.assert_called_once_with(['uuid1'],
timeout=3600)
self.assertFalse(pr.unprovision_node.called)
@mock.patch.object(baremetal_deploy, '_provisioner', autospec=True)
class TestUndeployInstance(base.TestCase):
def test_success(self, mock_pr):
pr = mock_pr.return_value
action = baremetal_deploy.UndeployInstanceAction('inst1')
result = action.run(mock.Mock())
self.assertIsNone(result)
pr.show_instance.assert_called_once_with('inst1')
pr.unprovision_node.assert_called_once_with(
pr.show_instance.return_value.node, wait=1800)
def test_not_found(self, mock_pr):
pr = mock_pr.return_value
pr.show_instance.side_effect = RuntimeError('not found')
action = baremetal_deploy.UndeployInstanceAction('inst1')
result = action.run(mock.Mock())
self.assertIsNone(result)
pr.show_instance.assert_called_once_with('inst1')
self.assertFalse(pr.unprovision_node.called)
class TestExpandRoles(base.TestCase):
def test_simple(self):
roles = [
{'name': 'Compute'},
{'name': 'Controller'},
]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual(
[
{'hostname': 'overcloud-novacompute-0',
'image': {'href': 'overcloud-full'}},
{'hostname': 'overcloud-controller-0',
'image': {'href': 'overcloud-full'}},
],
result['instances'])
self.assertEqual(
{
'ComputeDeployedServerHostnameFormat':
'%stackname%-novacompute-%index%',
'ComputeDeployedServerCount': 1,
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'ControllerDeployedServerCount': 1,
'HostnameMap': {
'overcloud-novacompute-0': 'overcloud-novacompute-0',
'overcloud-controller-0': 'overcloud-controller-0'
}
},
result['environment']['parameter_defaults'])
def test_image_in_defaults(self):
roles = [{
'name': 'Controller',
'defaults': {
'image': {
'href': 'file:///tmp/foo.qcow2',
'checksum': '12345678'
}
},
'count': 3,
'instances': [{
'hostname': 'overcloud-controller-0',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-1',
}]
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual(
[
{'hostname': 'overcloud-controller-0',
'image': {'href': 'overcloud-full'}},
{'hostname': 'overcloud-controller-1',
'image': {'href': 'file:///tmp/foo.qcow2',
'checksum': '12345678'}},
{'hostname': 'overcloud-controller-2',
'image': {'href': 'file:///tmp/foo.qcow2',
'checksum': '12345678'}},
],
result['instances'])
def test_with_parameters(self):
roles = [{
'name': 'Compute',
'count': 2,
'defaults': {
'profile': 'compute'
},
'hostname_format': 'compute-%index%.example.com'
}, {
'name': 'Controller',
'count': 3,
'defaults': {
'profile': 'control'
},
'hostname_format': 'controller-%index%.example.com'
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual(
[
{'hostname': 'compute-0.example.com', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'compute-1.example.com', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'controller-0.example.com', 'profile': 'control',
'image': {'href': 'overcloud-full'}},
{'hostname': 'controller-1.example.com', 'profile': 'control',
'image': {'href': 'overcloud-full'}},
{'hostname': 'controller-2.example.com', 'profile': 'control',
'image': {'href': 'overcloud-full'}},
],
result['instances'])
self.assertEqual(
{
'ComputeDeployedServerHostnameFormat':
'compute-%index%.example.com',
'ComputeDeployedServerCount': 2,
'ControllerDeployedServerHostnameFormat':
'controller-%index%.example.com',
'ControllerDeployedServerCount': 3,
'HostnameMap': {
'compute-0.example.com': 'compute-0.example.com',
'compute-1.example.com': 'compute-1.example.com',
'controller-0.example.com': 'controller-0.example.com',
'controller-1.example.com': 'controller-1.example.com',
'controller-2.example.com': 'controller-2.example.com',
}
},
result['environment']['parameter_defaults'])
def test_explicit_instances(self):
roles = [{
'name': 'Compute',
'count': 2,
'defaults': {
'profile': 'compute'
},
'hostname_format': 'compute-%index%.example.com'
}, {
'name': 'Controller',
'count': 2,
'defaults': {
'profile': 'control'
},
'instances': [{
'hostname': 'controller-X.example.com',
'profile': 'control-X'
}, {
'name': 'node-0',
'traits': ['CUSTOM_FOO'],
'nics': [{'subnet': 'leaf-2'}]},
]},
]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual(
[
{'hostname': 'compute-0.example.com', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'compute-1.example.com', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'controller-X.example.com',
'image': {'href': 'overcloud-full'},
'profile': 'control-X'},
# Name provides the default for hostname later on.
{'name': 'node-0', 'profile': 'control',
'hostname': 'node-0',
'image': {'href': 'overcloud-full'},
'traits': ['CUSTOM_FOO'], 'nics': [{'subnet': 'leaf-2'}]},
],
result['instances'])
self.assertEqual(
{
'ComputeDeployedServerHostnameFormat':
'compute-%index%.example.com',
'ComputeDeployedServerCount': 2,
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'ControllerDeployedServerCount': 2,
'HostnameMap': {
'compute-0.example.com': 'compute-0.example.com',
'compute-1.example.com': 'compute-1.example.com',
'overcloud-controller-0': 'controller-X.example.com',
'overcloud-controller-1': 'node-0',
}
},
result['environment']['parameter_defaults'])
def test_count_with_instances(self):
roles = [{
'name': 'Compute',
'count': 2,
'defaults': {
'profile': 'compute',
},
'hostname_format': 'compute-%index%.example.com'
}, {
'name': 'Controller',
'defaults': {
'profile': 'control',
},
'count': 3,
'instances': [{
'hostname': 'controller-X.example.com',
'profile': 'control-X'
}, {
'name': 'node-0',
'traits': ['CUSTOM_FOO'],
'nics': [{'subnet': 'leaf-2'}]},
]},
]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'compute-0.example.com',
'profile': 'compute',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'compute-1.example.com',
'profile': 'compute',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'controller-X.example.com',
'profile': 'control-X',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'node-0',
'name': 'node-0',
'nics': [{'subnet': 'leaf-2'}],
'profile': 'control',
'traits': ['CUSTOM_FOO'],
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-2',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({
'ComputeDeployedServerCount': 2,
'ComputeDeployedServerHostnameFormat':
'compute-%index%.example.com',
'ControllerDeployedServerCount': 3,
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'HostnameMap': {
'compute-0.example.com': 'compute-0.example.com',
'compute-1.example.com': 'compute-1.example.com',
'overcloud-controller-0': 'controller-X.example.com',
'overcloud-controller-1': 'node-0',
'overcloud-controller-2': 'overcloud-controller-2'}
},
result['environment']['parameter_defaults'])
def test_unprovisioned(self):
roles = [{
'name': 'Controller',
'defaults': {
'profile': 'control',
},
'count': 2,
'instances': [{
'hostname': 'overcloud-controller-1',
'provisioned': False
}, {
'hostname': 'overcloud-controller-2',
'provisioned': False
}]
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'overcloud-controller-0',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-3',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({
'ControllerDeployedServerCount': 2,
'ControllerRemovalPolicies': [
{'resource_list': [1, 2]}
],
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'HostnameMap': {
'overcloud-controller-0': 'overcloud-controller-0',
'overcloud-controller-1': 'overcloud-controller-1',
'overcloud-controller-2': 'overcloud-controller-2',
'overcloud-controller-3': 'overcloud-controller-3'}
},
result['environment']['parameter_defaults'])
# action for an unprovision workflow
action = baremetal_deploy.ExpandRolesAction(roles, provisioned=False)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'overcloud-controller-1',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-2',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({}, result['environment'])
def test_reprovisioned(self):
roles = [{
'name': 'Controller',
'defaults': {
'profile': 'control',
},
'count': 4,
'instances': [{
'hostname': 'overcloud-controller-1',
'provisioned': False
}, {
'hostname': 'overcloud-controller-2',
'provisioned': False
}]
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'overcloud-controller-0',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-3',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-4',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-5',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({
'ControllerDeployedServerCount': 4,
'ControllerRemovalPolicies': [
{'resource_list': [1, 2]}
],
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'HostnameMap': {
'overcloud-controller-0': 'overcloud-controller-0',
'overcloud-controller-1': 'overcloud-controller-1',
'overcloud-controller-2': 'overcloud-controller-2',
'overcloud-controller-3': 'overcloud-controller-3',
'overcloud-controller-4': 'overcloud-controller-4',
'overcloud-controller-5': 'overcloud-controller-5'}
},
result['environment']['parameter_defaults'])
# action for an unprovision workflow
action = baremetal_deploy.ExpandRolesAction(roles, provisioned=False)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'overcloud-controller-1',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'overcloud-controller-2',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({}, result['environment'])
def test_unprovisioned_instances(self):
roles = [{
'name': 'Controller',
'defaults': {
'profile': 'control',
},
'count': 2,
'instances': [{
'name': 'node-0',
'hostname': 'controller-0'
}, {
'name': 'node-1',
'hostname': 'controller-1',
'provisioned': False
}, {
'name': 'node-2',
'hostname': 'controller-2',
'provisioned': False
}, {
'name': 'node-3',
'hostname': 'controller-3',
'provisioned': True
}]
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'controller-0',
'name': 'node-0',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'controller-3',
'name': 'node-3',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({
'ControllerDeployedServerCount': 2,
'ControllerRemovalPolicies': [
{'resource_list': [1, 2]}
],
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'HostnameMap': {
'overcloud-controller-0': 'controller-0',
'overcloud-controller-1': 'controller-1',
'overcloud-controller-2': 'controller-2',
'overcloud-controller-3': 'controller-3'}
},
result['environment']['parameter_defaults'])
# action for an unprovision workflow
action = baremetal_deploy.ExpandRolesAction(roles, provisioned=False)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'controller-1',
'name': 'node-1',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'controller-2',
'name': 'node-2',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({}, result['environment'])
def test_unprovisioned_no_hostname(self):
roles = [{
'name': 'Controller',
'defaults': {
'profile': 'control',
},
'count': 2,
'instances': [{
'name': 'node-0',
}, {
'name': 'node-1',
'provisioned': False
}, {
'name': 'node-2',
'provisioned': False
}, {
'name': 'node-3',
'provisioned': True
}]
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'node-0',
'name': 'node-0',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'node-3',
'name': 'node-3',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({
'ControllerDeployedServerCount': 2,
'ControllerRemovalPolicies': [
{'resource_list': [1, 2]}
],
'ControllerDeployedServerHostnameFormat':
'%stackname%-controller-%index%',
'HostnameMap': {
'overcloud-controller-0': 'node-0',
'overcloud-controller-1': 'node-1',
'overcloud-controller-2': 'node-2',
'overcloud-controller-3': 'node-3'}
},
result['environment']['parameter_defaults'])
# action for an unprovision workflow
action = baremetal_deploy.ExpandRolesAction(roles, provisioned=False)
result = action.run(mock.Mock())
self.assertEqual([
{
'hostname': 'node-1',
'name': 'node-1',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}, {
'hostname': 'node-2',
'name': 'node-2',
'profile': 'control',
'image': {'href': 'overcloud-full'}
}],
result['instances'])
self.assertEqual({}, result['environment'])
def test_name_in_defaults(self):
roles = [{
'name': 'Compute',
'count': 2,
'defaults': {
'profile': 'compute',
'name': 'compute-0'
}
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertIn('Compute: cannot specify name in defaults',
result.error)
def test_hostname_in_defaults(self):
roles = [{
'name': 'Compute',
'count': 2,
'defaults': {
'profile': 'compute',
'hostname': 'compute-0'
}
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertIn('Compute: cannot specify hostname in defaults',
result.error)
def test_instances_without_hostname(self):
roles = [{
'name': 'Compute',
'count': 2,
'defaults': {
'profile': 'compute'
},
'hostname_format': 'compute-%index%.example.com'
}, {
'name': 'Controller',
'count': 2,
'defaults': {
'profile': 'control'
},
'instances': [{
'profile': 'control-X'
# missing hostname here
}, {
'name': 'node-0',
'traits': ['CUSTOM_FOO'],
'nics': [{'subnet': 'leaf-2'}]},
]},
]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertEqual(
[
{'hostname': 'compute-0.example.com', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'compute-1.example.com', 'profile': 'compute',
'image': {'href': 'overcloud-full'}},
{'hostname': 'overcloud-controller-0', 'profile': 'control-X',
'image': {'href': 'overcloud-full'}},
# Name provides the default for hostname
{'name': 'node-0', 'profile': 'control',
'hostname': 'node-0',
'image': {'href': 'overcloud-full'},
'traits': ['CUSTOM_FOO'], 'nics': [{'subnet': 'leaf-2'}]},
],
result['instances'])
def test_more_instances_than_count(self):
roles = [{
'name': 'Compute',
'count': 3,
'defaults': {
'profile': 'compute',
'name': 'compute-0'
},
'instances': [{
'name': 'node-0'
}, {
'name': 'node-1'
}, {
'name': 'node-2'
}, {
'name': 'node-3'
}]
}]
action = baremetal_deploy.ExpandRolesAction(roles)
result = action.run(mock.Mock())
self.assertIn('Compute: number of instance entries 4 '
'cannot be greater than count 3',
result.error)
class TestPopulateEnvironment(base.TestCase):
def test_success(self):
port_map = {
'compute-0': {
'ctlplane': {
'network': {
'tags': ['foo'],
'mtu': 1350,
'id': 'aaaa' # will be filtered out
},
'fixed_ips': [{'ip_address': '1.2.3.5'}],
'subnets': [{
'cidr': '1.2.3.0/24',
'gateway_ip': '192.168.24.1',
'host_routes': [{
'destination': '169.254.169.254/32',
'nexthop': '192.168.24.3'
}],
'dns_nameservers': ['8.8.8.8'],
'id': 'bbbb' # will be filtered out
}],
},
'foobar': {
'network': {},
'fixed_ips': [{'ip_address': '1.2.4.5'}],
'subnets': [{'cidr': '1.2.4.0/24'}],
},
},
'controller-0': {
'ctlplane': {
'network': {'tags': ['foo']},
'fixed_ips': [{'ip_address': '1.2.3.4'}],
'subnets': [{'cidr': '1.2.3.0/24'}],
},
'foobar': {
'network': {},
'fixed_ips': [{'ip_address': '1.2.4.4'}],
'subnets': [{'cidr': '1.2.4.0/24'}],
},
},
}
action = baremetal_deploy.PopulateEnvironmentAction({}, port_map)
result = action.run(mock.Mock())
self.assertEqual(
{
'resource_registry': {
'OS::TripleO::DeployedServer::ControlPlanePort':
'/usr/share/openstack-tripleo-heat-templates/'
'deployed-server/deployed-neutron-port.yaml'
},
'parameter_defaults': {
'DeployedServerPortMap': {
'compute-0-ctlplane': {
'fixed_ips': [{'ip_address': '1.2.3.5'}],
'subnets': [{
'cidr': '1.2.3.0/24',
'gateway_ip': '192.168.24.1',
'host_routes': [{
'destination': '169.254.169.254/32',
'nexthop': '192.168.24.3'
}],
'dns_nameservers': ['8.8.8.8'],
}],
'network': {
'tags': ['foo'],
'mtu': 1350,
},
},
'controller-0-ctlplane': {
'fixed_ips': [{'ip_address': '1.2.3.4'}],
'subnets': [{'cidr': '1.2.3.0/24'}],
'network': {'tags': ['foo']},
},
}
}
},
result)