tenks/tests/test_tenks_update_state.py
gugug 9ed29ce4e0 Use importlib to take place of imp module
The imp module is deprecated[1] since version 3.4, use importlib to
instead

[1]: https://docs.python.org/3/library/imp.html

Change-Id: Id301c0c6ffacea81e4771219beb6cbf8462c1dc4
2020-09-09 09:10:52 +08:00

307 lines
12 KiB
Python

# Copyright (c) 2018 StackHPC Ltd.
#
# 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 copy
from importlib import util as imp_util
import os
import unittest
from ansible.errors import AnsibleActionFail
def load_module(name, path):
module_spec = imp_util.spec_from_file_location(
name, path
)
module = imp_util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
return module
# Import method lifted from kolla_ansible's test_merge_config.py
PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
PLUGIN_FILE = os.path.join(PROJECT_DIR,
'ansible/action_plugins/tenks_update_state.py')
tus = load_module('tenks_update_state', PLUGIN_FILE)
class TestTenksUpdateState(unittest.TestCase):
def setUp(self):
# Pass dummy arguments to allow instantiation of action plugin.
self.mod = tus.ActionModule(None, None, None, None, None, None)
self.mod.localhost_vars = {
'cmd': 'deploy',
'default_ironic_driver': 'def_ir_driv',
}
# Minimal inputs required.
self.node_types = {
'type0': {
'memory_mb': 1024,
'vcpus': 2,
'volumes': [
{
'capacity': '10GB',
},
{
'capacity': '20GB',
},
],
'physical_networks': [
'physnet0',
],
},
}
self.specs = [
{
'type': 'type0',
'count': 2,
'ironic_config': {
'resource_class': 'testrc',
},
},
]
self.mod.hypervisor_vars = {
'foo': {
'physnet_mappings': {
'physnet0': 'dev0',
},
'ipmi_port_range_start': 100,
'ipmi_port_range_end': 102,
},
}
self.mod.args = {
'node_types': self.node_types,
'node_name_prefix': 'test_node_pfx',
'specs': self.specs,
'state': {},
'vol_name_prefix': 'test_vol_pfx',
}
# Alias for brevity.
self.args = self.mod.args
def test__set_physnet_idxs_no_state(self):
self.mod._set_physnet_idxs()
expected_indices = {
'physnet0': 0,
}
self.assertEqual(self.args['state']['foo']['physnet_indices'],
expected_indices)
def test__set_physnet_idxs_no_state_two_hosts(self):
self.mod.hypervisor_vars['bar'] = self.mod.hypervisor_vars['foo']
self.mod._set_physnet_idxs()
expected_indices = {
'physnet0': 0,
}
for hyp in {'foo', 'bar'}:
self.assertEqual(self.args['state'][hyp]['physnet_indices'],
expected_indices)
def test_set_physnet_idxs__no_state_two_hosts_different_nets(self):
self.mod.hypervisor_vars['bar'] = self.mod.hypervisor_vars['foo']
self.mod.hypervisor_vars['foo']['physnet_mappings'].update({
'physnet1': 'dev1',
'physnet2': 'dev2',
})
self.mod.hypervisor_vars['bar']['physnet_mappings'].update({
'physnet2': 'dev2',
})
self.mod._set_physnet_idxs()
for host in {'foo', 'bar'}:
idxs = list(self.args['state'][host]['physnet_indices'].values())
# Check all physnets have different IDs on the same host.
self.assertCountEqual(idxs, set(idxs))
def test_set_physnet_idxs__idx_maintained_after_removal(self):
self.mod.hypervisor_vars['foo']['physnet_mappings'].update({
'physnet1': 'dev1',
})
self.mod._set_physnet_idxs()
physnet1_idx = self.args['state']['foo']['physnet_indices']['physnet1']
del self.mod.hypervisor_vars['foo']['physnet_mappings']['physnet0']
self.mod._set_physnet_idxs()
self.assertEqual(
physnet1_idx,
self.args['state']['foo']['physnet_indices']['physnet1']
)
def _test__process_specs_no_state_create_nodes(self):
self.mod._process_specs()
self.assertEqual(len(self.args['state']['foo']['nodes']), 2)
return self.args['state']['foo']['nodes']
def test__process_specs_no_state_attrs(self):
nodes = self._test__process_specs_no_state_create_nodes()
for node in nodes:
self.assertTrue(node['name'].startswith('test_node_pfx'))
self.assertEqual(node['memory_mb'], 1024)
self.assertEqual(node['vcpus'], 2)
self.assertEqual(node['physical_networks'], ['physnet0'])
def test__process_specs_no_state_ipmi_ports(self):
nodes = self._test__process_specs_no_state_create_nodes()
used_ipmi_ports = set()
for node in nodes:
self.assertGreaterEqual(
node['ipmi_port'],
self.mod.hypervisor_vars['foo']['ipmi_port_range_start']
)
self.assertLessEqual(
node['ipmi_port'],
self.mod.hypervisor_vars['foo']['ipmi_port_range_end']
)
self.assertNotIn(node['ipmi_port'], used_ipmi_ports)
used_ipmi_ports.add(node['ipmi_port'])
def test__process_specs_no_state_volumes(self):
nodes = self._test__process_specs_no_state_create_nodes()
for node in nodes:
self.assertEqual(len(node['volumes']), 2)
for n in {'0', '1'}:
self.assertIn(node['name'] + 'test_vol_pfx' + n,
[vol['name'] for vol in node['volumes']])
for c in {'10GB', '20GB'}:
self.assertIn(c, [vol['capacity'] for vol in node['volumes']])
def test__process_specs_apply_twice(self):
self.mod._process_specs()
created_state = copy.deepcopy(self.args['state'])
self.mod._process_specs()
self.assertEqual(created_state, self.args['state'])
def test__process_specs_multiple_hosts(self):
self.mod.hypervisor_vars['bar'] = self.mod.hypervisor_vars['foo']
self.mod._process_specs()
foo_nodes = self.args['state']['foo']['nodes']
bar_nodes = self.args['state']['bar']['nodes']
names = {foo_nodes[0]['name'], bar_nodes[0]['name']}
self.assertEqual(names, {'test_node_pfx0', 'test_node_pfx1'})
def test__process_specs_unnecessary_node(self):
# Create some nodes definitions.
self.mod._process_specs()
# Add another node to the state that isn't required.
self.args['state']['foo']['nodes'].append(copy.deepcopy(
self.args['state']['foo']['nodes'][0]))
self.args['state']['foo']['nodes'][-1]['vcpus'] = 42
new_node = copy.deepcopy(self.args['state']['foo']['nodes'][-1])
self.mod._process_specs()
# Check that node has been marked for deletion.
self.assertNotIn(new_node, self.args['state']['foo']['nodes'])
new_node['state'] = 'absent'
self.assertIn(new_node, self.args['state']['foo']['nodes'])
def test__process_specs_teardown(self):
# Create some node definitions.
self.mod._process_specs()
# After teardown, we expected all created definitions to now have an
# 'absent' state.
expected_state = copy.deepcopy(self.args['state'])
for node in expected_state['foo']['nodes']:
node['state'] = 'absent'
self.mod.localhost_vars['cmd'] = 'teardown'
# After one or more runs, the 'absent' state nodes should still exist,
# since they're only removed after completion of deployment in a
# playbook.
for _ in range(3):
self.mod._process_specs()
self.assertEqual(expected_state, self.args['state'])
def test__process_specs_no_hypervisors(self):
self.mod.hypervisor_vars = {}
self.assertRaises(AnsibleActionFail, self.mod._process_specs)
def test__process_specs_no_hypervisors_on_physnet(self):
self.node_types['type0']['physical_networks'].append('another_pn')
self.assertRaises(AnsibleActionFail, self.mod._process_specs)
def test__process_specs_one_hypervisor_on_physnet(self):
self.node_types['type0']['physical_networks'].append('another_pn')
self.mod.hypervisor_vars['bar'] = copy.deepcopy(
self.mod.hypervisor_vars['foo'])
self.mod.hypervisor_vars['bar']['physnet_mappings']['another_pn'] = (
'dev1')
self.mod._process_specs()
# Check all nodes were scheduled to the hypervisor connected to the
# new physnet.
self.assertEqual(len(self.args['state']['foo']['nodes']), 0)
self.assertEqual(len(self.args['state']['bar']['nodes']), 2)
def test__process_specs_not_enough_ports(self):
# Give 'foo' only a single IPMI port to allocate.
self.mod.hypervisor_vars['foo']['ipmi_port_range_start'] = 123
self.mod.hypervisor_vars['foo']['ipmi_port_range_end'] = 123
self.assertRaises(AnsibleActionFail, self.mod._process_specs)
def test__process_specs_node_name_prefix(self):
self.specs[0]['node_name_prefix'] = 'foo-prefix'
self.mod._process_specs()
foo_nodes = self.args['state']['foo']['nodes']
self.assertEqual(foo_nodes[0]['name'], 'foo-prefix0')
self.assertEqual(foo_nodes[1]['name'], 'foo-prefix1')
def test__process_specs_node_name_prefix_multiple_specs(self):
self.specs[0]['node_name_prefix'] = 'foo-prefix'
self.specs.append({
'type': 'type0',
'count': 1,
'ironic_config': {
'resource_class': 'testrc',
},
})
self.mod._process_specs()
foo_nodes = self.args['state']['foo']['nodes']
self.assertEqual(foo_nodes[0]['name'], 'foo-prefix0')
self.assertEqual(foo_nodes[1]['name'], 'foo-prefix1')
self.assertEqual(foo_nodes[2]['name'], 'test_node_pfx0')
def test__process_specs_node_name_prefix_multiple_hosts(self):
self.specs[0]['node_name_prefix'] = 'foo-prefix'
self.mod.hypervisor_vars['bar'] = self.mod.hypervisor_vars['foo']
self.mod._process_specs()
foo_nodes = self.args['state']['foo']['nodes']
bar_nodes = self.args['state']['bar']['nodes']
names = {foo_nodes[0]['name'], bar_nodes[0]['name']}
self.assertEqual(names, {'foo-prefix0', 'foo-prefix1'})
def test__process_specs_vol_name_prefix(self):
self.specs[0]['vol_name_prefix'] = 'foo-prefix'
self.mod._process_specs()
foo_nodes = self.args['state']['foo']['nodes']
self.assertEqual(foo_nodes[0]['volumes'][0]['name'],
'test_node_pfx0foo-prefix0')
self.assertEqual(foo_nodes[0]['volumes'][1]['name'],
'test_node_pfx0foo-prefix1')
self.assertEqual(foo_nodes[1]['volumes'][0]['name'],
'test_node_pfx1foo-prefix0')
self.assertEqual(foo_nodes[1]['volumes'][1]['name'],
'test_node_pfx1foo-prefix1')
def test__prune_absent_nodes(self):
# Create some node definitions.
self.mod._process_specs()
# Set them to be 'absent'.
for node in self.args['state']['foo']['nodes']:
node['state'] = 'absent'
self.mod._prune_absent_nodes()
# Ensure they were removed.
self.assertEqual(self.args['state']['foo']['nodes'], [])