virtual-deployment/virtualbox/pybox/helper/tests/test_install_lab.py

364 lines
12 KiB
Python

"""
Unit tests related to install_lab
"""
import unittest
import install_lab
from consts.timeout import HostTimeout
from helper.install_lab import exec_cmd
from unittest.mock import MagicMock, patch, call
from utils import kpi
class ExecCmdTestCase(unittest.TestCase):
"""
Class to test help function exec_cmd
"""
SUCCESS = 0
FAILED1 = 1
def setUp(self):
"""
Method to set up the parameters used on the tests in this class
"""
self.cmd = 'print ("Hello!")'
self.reattempt_delay = HostTimeout.REATTEMPT_DELAY
self.max_attempts = len(self.reattempt_delay)
self.counter = 0
self.trigger = 0
self.result = None
def dynamicMock(self, *args, **kwargs):
"""
Method to simulate a function with temporary failures
"""
process = MagicMock()
process.__enter__.return_value = process
self.counter += 1
if self.counter == self.trigger:
self.counter = 0
attrs = {
'wait.return_value': None,
'stdout.readline.side_effect': [bytes('Hello!\n', 'utf-8'), b'']
}
process.configure_mock(**attrs)
process.returncode = self.SUCCESS
else:
attrs = {
'wait.return_value': None,
'stdout.readline.side_effect': [b'']
}
process.configure_mock(**attrs)
process.returncode = self.FAILED1
return process
@patch('time.sleep')
@patch('utils.install_log.LOG.error')
@patch('utils.install_log.LOG.warning')
@patch('utils.install_log.LOG.info')
@patch('subprocess.Popen')
def test_exec_cmd_1st_attempt_ok(
self, m_s_Popen, m_LOG_info, m_LOG_warning, m_LOG_error, m_time_sleep
):
"""
Test successful call in the first attempt (normal case)
This test focus on info messages. No warnings or error messages are expected.
The expect result code is None (refer to the description of fault_tolerant decorator)
"""
# Setup
m_time_sleep.return_value = 0
process = MagicMock()
attrs = {
'wait.return_value': None,
'returncode': self.SUCCESS,
'stdout.readline.side_effect': [bytes('Hello!\n', 'utf-8'), b'']
}
process.configure_mock(**attrs)
m_s_Popen.return_value.__enter__.return_value = process
# Run
self.result = exec_cmd(self.cmd)
# Assert
calls = [
call("#### Executing command on the host machine:\n$ %s\n", self.cmd),
call("%s", "Hello!"),
]
m_LOG_info.assert_has_calls(calls, any_order=False)
self.assertEqual(m_LOG_warning.call_count, 0)
self.assertEqual(m_LOG_error.call_count, 0)
self.assertIsNone(self.result)
@patch('time.sleep')
@patch('utils.install_log.LOG.error')
@patch('utils.install_log.LOG.warning')
@patch('utils.install_log.LOG.info')
@patch('subprocess.Popen')
def test_exec_cmd_3rd_attempt_ok(
self, m_s_Popen, m_LOG_info, m_LOG_warning, m_LOG_error, m_time_sleep
):
"""
Test successful call after a few retries (this may occor in system instability scenarios)
This test focus on log info messages, for re-attempt scenarios.
Warning messages are covered in next test.
No error messages are expected.
The expect result code is None (refer to the description of fault_tolerant decorator)
"""
# Setup
m_s_Popen.side_effect = self.dynamicMock
m_time_sleep.return_value = 0
self.trigger = 3
# Run
self.result = exec_cmd(self.cmd)
# Assert
calls = [
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[1])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[2])),
call("#### Executing command on the host machine:\n$ %s\n", self.cmd),
call("%s", "Hello!"),
]
m_LOG_info.assert_has_calls(calls, any_order=False)
self.assertEqual(m_LOG_error.call_count, 0)
self.assertEqual(m_LOG_warning.call_count, 2)
self.assertIsNone(self.result)
@patch('time.sleep')
@patch('utils.install_log.LOG.error')
@patch('utils.install_log.LOG.warning')
@patch('subprocess.Popen')
def test_exec_cmd_5th_attempt_ok(
self, m_s_Popen, m_LOG_warning, m_LOG_error, m_time_sleep
):
"""
Test successful call after a few retries (thsi may occor in system instability scenarios)
This test focus on warning messages, for re-attempt scenarios.
No error messages are expected.
The expect result code is None (refer to the description of fault_tolerant decorator)
"""
# Setup
m_s_Popen.side_effect = self.dynamicMock
m_time_sleep.return_value = 0
self.trigger = 5
# Run
self.result = exec_cmd(self.cmd)
# Assert
calls = [
call('#### Failed command:\n$ %s [attempt: %s/%s]\n', self.cmd, 1, 10),
call('#### Failed command:\n$ %s [attempt: %s/%s]\n', self.cmd, 2, 10),
call('#### Failed command:\n$ %s [attempt: %s/%s]\n', self.cmd, 3, 10),
call('#### Failed command:\n$ %s [attempt: %s/%s]\n', self.cmd, 4, 10),
]
m_LOG_warning.assert_has_calls(calls, any_order=False)
self.assertEqual(m_LOG_error.call_count, 0)
self.assertIsNone(self.result)
@patch("sys.exit")
@patch('time.sleep')
@patch('utils.install_log.LOG.error')
@patch('utils.install_log.LOG.warning')
@patch('utils.install_log.LOG.info')
@patch('subprocess.Popen')
def test_exec_cmd_failed(
self, m_s_Popen, m_LOG_info, m_LOG_warning, m_LOG_error, m_time_sleep, mock_exit
):
"""
Test unsuccessful call
This may occur after a maximum number of retries, in strong system instability scenarios)
This test focus on info and error messages, for the failure scenarios.
The expected warning messages are the same as for successful calls, covered previously.
The expect result code is None (refer to the description of fault_tolerant decorator)
"""
# Setup
m_s_Popen.side_effect = self.dynamicMock
m_LOG_warning.return_value = 0
m_time_sleep.return_value = 0
mock_exit.side_effect = SystemExit(1)
self.trigger = self.max_attempts+1
with self.assertRaises(TimeoutError):
# Run
self.result = exec_cmd(self.cmd)
# Assert
calls = [
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[1])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[2])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[3])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[4])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[5])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[6])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[7])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[8])),
call('#### Executing command on the host machine:\n$ %s\n', self.cmd),
call("Trying again after %s ... ", kpi.get_formated_time(self.reattempt_delay[9])),
]
m_LOG_info.assert_has_calls(calls, any_order=False)
m_LOG_error.assert_called_once_with(
"#### Failed command:\n$ %s [attempt: %s/%s]\n",
self.cmd, self.max_attempts, self.max_attempts
)
self.assertIsNone(self.result)
class UpdatePlatformCpusTestCase(unittest.TestCase):
"""
Class to test update_platform_cpus method
"""
@patch("install_lab.serial")
def test_update_platform_cpus(self, mock_serial):
"""
Test update_platform_cpus method
"""
# Setup
mock_stream = MagicMock()
mock_hostname = "hostname"
mock_cpu_num = 5
# Run
install_lab.update_platform_cpus(mock_stream, mock_hostname, cpu_num=mock_cpu_num)
# Assert
command_string = (
"\nsource /etc/platform/openrc; system host-cpu-modify "
f"{mock_hostname} -f platform -p0 {mock_cpu_num}"
)
mock_serial.send_bytes.assert_called_once_with(
mock_stream, command_string, prompt="keystone", timeout=300
)
class SetDnsTestCase(unittest.TestCase):
"""
Class to test set_dns method
"""
@patch("install_lab.serial")
def test_set_dns(self, mock_serial):
"""
Test set_dns method
"""
# Setup
mock_stream = MagicMock()
mock_dns_ip = "8.8.8.8"
# Run
install_lab.set_dns(mock_stream, mock_dns_ip)
# Assert
command_string = (
"source /etc/platform/openrc; system dns-modify "
f"nameservers={mock_dns_ip}"
)
mock_serial.send_bytes.assert_called_once_with(
mock_stream, command_string, prompt="keystone"
)
class ConfigControllerTestCase(unittest.TestCase):
"""
Class to test config_controller method
"""
command_string = (
"ansible-playbook /usr/share/ansible/stx-ansible/playbooks/bootstrap.yml"
)
mock_stream = MagicMock()
mock_password = "Li69nux*"
@patch("install_lab.serial")
@patch("install_lab.host_helper.check_password")
def test_config_controller_successful(self, mock_check_password, mock_serial):
"""
Test config_controller method with success
"""
# Setup
mock_serial.expect_bytes.return_value = 0
# Run
install_lab.config_controller(self.mock_stream, password=self.mock_password)
# Assert
calls = [
call(self.mock_stream, self.command_string, expect_prompt=False),
call(self.mock_stream, 'echo [$?]', expect_prompt=False, log=False),
]
mock_serial.send_bytes.assert_has_calls(calls, any_order=False)
calls = [
call(self.mock_stream, "~$", timeout=HostTimeout.LAB_CONFIG),
call(self.mock_stream, '[0]', timeout=HostTimeout.NORMAL_OP, log=False),
]
mock_serial.expect_bytes.assert_has_calls(calls, any_order=False)
mock_check_password.assert_called_once_with(self.mock_stream, password=self.mock_password)
@patch("install_lab.serial")
@patch("install_lab.host_helper.check_password")
def test_config_controller_unsuccessful(self, mock_check_password, mock_serial):
"""
Test config_controller method without success raising an exception
"""
# Setup
mock_serial.expect_bytes.return_value = 1
# Run
with self.assertRaises(SystemExit):
install_lab.config_controller(self.mock_stream, password=self.mock_password)
# Assert
calls = [
call(self.mock_stream, self.command_string, expect_prompt=False),
call(self.mock_stream, 'echo [$?]', expect_prompt=False, log=False),
]
mock_serial.send_bytes.assert_has_calls(calls, any_order=False)
calls = [
call(self.mock_stream, "~$", timeout=HostTimeout.LAB_CONFIG),
call(self.mock_stream, '[0]', timeout=HostTimeout.NORMAL_OP, log=False),
]
mock_serial.expect_bytes.assert_has_calls(calls, any_order=False)
mock_check_password.assert_called_once_with(self.mock_stream, password=self.mock_password)
if __name__ == '__main__':
unittest.main()