diff --git a/instack_undercloud/tests/test_undercloud.py b/instack_undercloud/tests/test_undercloud.py index 4a43e3a56..bc8f1bfa1 100644 --- a/instack_undercloud/tests/test_undercloud.py +++ b/instack_undercloud/tests/test_undercloud.py @@ -17,6 +17,7 @@ import io import json import os import subprocess +import tempfile import fixtures import mock @@ -41,6 +42,7 @@ class BaseTestCase(base.BaseTestCase): class TestUndercloud(BaseTestCase): + @mock.patch('instack_undercloud.undercloud._handle_upgrade_fact') @mock.patch('instack_undercloud.undercloud._configure_logging') @mock.patch('instack_undercloud.undercloud._validate_configuration') @mock.patch('instack_undercloud.undercloud._run_command') @@ -53,7 +55,8 @@ class TestUndercloud(BaseTestCase): def test_install(self, mock_load_config, mock_generate_environment, mock_run_yum_update, mock_run_instack, mock_run_orc, mock_post_config, mock_run_command, - mock_validate_configuration, mock_configure_logging): + mock_validate_configuration, mock_configure_logging, + mock_upgrade_fact): fake_env = mock.MagicMock() mock_generate_environment.return_value = fake_env undercloud.install('.') @@ -63,6 +66,33 @@ class TestUndercloud(BaseTestCase): mock_run_orc.assert_called_with(fake_env) mock_run_command.assert_called_with( ['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm') + mock_upgrade_fact.assert_called_with(False) + + @mock.patch('instack_undercloud.undercloud._handle_upgrade_fact') + @mock.patch('instack_undercloud.undercloud._configure_logging') + @mock.patch('instack_undercloud.undercloud._validate_configuration') + @mock.patch('instack_undercloud.undercloud._run_command') + @mock.patch('instack_undercloud.undercloud._post_config') + @mock.patch('instack_undercloud.undercloud._run_orc') + @mock.patch('instack_undercloud.undercloud._run_yum_update') + @mock.patch('instack_undercloud.undercloud._run_instack') + @mock.patch('instack_undercloud.undercloud._generate_environment') + @mock.patch('instack_undercloud.undercloud._load_config') + def test_install_upgrade(self, mock_load_config, mock_generate_environment, + mock_run_yum_update, mock_run_instack, + mock_run_orc, mock_post_config, mock_run_command, + mock_validate_configuration, + mock_configure_logging, mock_upgrade_fact): + fake_env = mock.MagicMock() + mock_generate_environment.return_value = fake_env + undercloud.install('.', upgrade=True) + self.assertTrue(mock_validate_configuration.called) + mock_generate_environment.assert_called_with('.') + mock_run_instack.assert_called_with(fake_env) + mock_run_orc.assert_called_with(fake_env) + mock_run_command.assert_called_with( + ['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm') + mock_upgrade_fact.assert_called_with(True) @mock.patch('instack_undercloud.undercloud._configure_logging') def test_install_exception(self, mock_configure_logging): @@ -744,3 +774,64 @@ class TestPostConfig(base.BaseTestCase): mock_instance.flavors.list.return_value = mock_flavors undercloud._delete_default_flavors(mock_instance) mock_instance.flavors.delete.assert_called_once_with('8ar') + + +class TestUpgradeFact(base.BaseTestCase): + @mock.patch('instack_undercloud.undercloud._run_command') + @mock.patch('os.path.dirname') + @mock.patch('os.path.exists') + @mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file')) + def test_upgrade_fact(self, mock_mkstemp, mock_exists, mock_dirname, + mock_run): + fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt' + mock_dirname.return_value = '/etc/facter/facts.d' + mock_exists.side_effect = [False, True] + + with mock.patch('instack_undercloud.undercloud.open') as mock_open: + undercloud._handle_upgrade_fact(True) + mock_open.assert_called_with('/tmp/file', 'w') + + run_calls = [ + mock.call(['sudo', 'mkdir', '-p', '/etc/facter/facts.d']), + mock.call(['sudo', 'mv', '/tmp/file', fact_path]), + mock.call(['sudo', 'chmod', '0644', fact_path]) + ] + mock_run.assert_has_calls(run_calls) + self.assertEqual(mock_run.call_count, 3) + + @mock.patch('instack_undercloud.undercloud._run_command') + @mock.patch('os.path.dirname') + @mock.patch('os.path.exists') + @mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file')) + def test_upgrade_fact_install(self, mock_mkstemp, mock_exists, + mock_dirname, mock_run): + mock_dirname.return_value = '/etc/facter/facts.d' + mock_exists.return_value = False + + with mock.patch('instack_undercloud.undercloud.open') as mock_open: + undercloud._handle_upgrade_fact(False) + mock_open.assert_not_called() + + mock_run.assert_not_called() + + @mock.patch('instack_undercloud.undercloud._run_command') + @mock.patch('os.path.dirname') + @mock.patch('os.path.exists') + @mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file')) + def test_upgrade_fact_upgrade_after_install(self, mock_mkstemp, + mock_exists, mock_dirname, + mock_run): + fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt' + mock_dirname.return_value = '/etc/facter/facts.d' + mock_exists.return_value = True + + with mock.patch('instack_undercloud.undercloud.open') as open_m: + undercloud._handle_upgrade_fact(True) + open_m.assert_called_with('/tmp/file', 'w') + + run_calls = [ + mock.call(['sudo', 'mv', '/tmp/file', fact_path]), + mock.call(['sudo', 'chmod', '0644', fact_path]) + ] + mock_run.assert_has_calls(run_calls) + self.assertEqual(mock_run.call_count, 2) diff --git a/instack_undercloud/undercloud.py b/instack_undercloud/undercloud.py index b9094a196..d1981f22c 100644 --- a/instack_undercloud/undercloud.py +++ b/instack_undercloud/undercloud.py @@ -1236,7 +1236,36 @@ def _post_config(instack_env): _post_config_mistral(instack_env, mistral) -def install(instack_root): +def _handle_upgrade_fact(upgrade=False): + """Create an upgrade fact for use in puppet + + Since we don't run different puppets depending on if it's an upgrade or + not, we need to be able to pass a flag into puppet to let it know if + we're doing an upgrade. This is helpful when trying to handle state + transitions from an already installed undercloud. This function creates + a static fact named undercloud_upgrade only after the install has occured. + When invoked with upgrade=True, the $::undercloud_upgrade fact should + be set to true. + + :param upgrade: Boolean indicating if this is an upgrade action or not + """ + + fact_string = 'undercloud_upgrade={}'.format(upgrade) + fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt' + if not os.path.exists(os.path.dirname(fact_path)) and upgrade: + _run_command(['sudo', 'mkdir', '-p', os.path.dirname(fact_path)]) + + # We only need to ensure the fact is correct when we've already installed + # the undercloud. + if os.path.exists(os.path.dirname(fact_path)): + tmp_fact = tempfile.mkstemp()[1] + with open(tmp_fact, 'w') as f: + f.write(fact_string.lower()) + _run_command(['sudo', 'mv', tmp_fact, fact_path]) + _run_command(['sudo', 'chmod', '0644', fact_path]) + + +def install(instack_root, upgrade=False): """Install the undercloud :param instack_root: The path containing the instack-undercloud elements @@ -1252,6 +1281,7 @@ def install(instack_root): _generate_init_data(instack_env) if CONF.undercloud_update_packages: _run_yum_update(instack_env) + _handle_upgrade_fact(upgrade) _run_instack(instack_env) _run_orc(instack_env) _post_config(instack_env) diff --git a/scripts/instack-upgrade-undercloud b/scripts/instack-upgrade-undercloud new file mode 100755 index 000000000..d7e0c9fe7 --- /dev/null +++ b/scripts/instack-upgrade-undercloud @@ -0,0 +1,3 @@ +#!/bin/bash + +python -c "from instack_undercloud import undercloud; undercloud.install('$(dirname $0)/..', upgrade=True)" diff --git a/setup.cfg b/setup.cfg index d872d6956..b3c3fb46f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ packages = scripts = scripts/instack-install-undercloud + scripts/instack-upgrade-undercloud scripts/instack-virt-setup scripts/instack-haproxy-cert-update