From a661b6d5e17fbe3652f77726a0b4c7a87f7fcbbc Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Wed, 11 Jan 2017 12:43:01 -0700 Subject: [PATCH] Improve upgrade process to include upgrade flag Previously, the upgrade process for the undercloud has been to just rerun the install process. The problem is that this does not include anwyay to indicate that it's already been run if we need to trigger some different actions on subsequent runs. As part of the Newton to Ocata upgrade, we need to be able to run the cell v2 setup in a different order than on a traditional install. We need a way for the puppet scripts that ultimately get run to know if it's an upgrade or install action. This change adds the creation of an undercloud_upgrade fact that will be used when the upgrade process is run. Additionally a new script called instack-upgrade-undercloud has been created to be used when upgrade. The use of instack-install-undercloud will cause the install flow to be execuated, while the instack-upgrade-undercloud will set the fact to true to allow for the puppet scripts to use the upgrade flow. Change-Id: Ie3cb21e30334fe8ffc0a9d6e707b42269b64c9ec Related-Bug: #1649341 Related-Blueprint: undercloud-upgrade --- instack_undercloud/tests/test_undercloud.py | 93 ++++++++++++++++++++- instack_undercloud/undercloud.py | 32 ++++++- scripts/instack-upgrade-undercloud | 3 + setup.cfg | 1 + 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100755 scripts/instack-upgrade-undercloud diff --git a/instack_undercloud/tests/test_undercloud.py b/instack_undercloud/tests/test_undercloud.py index 5aae2dbb0..f1aef0ef4 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 67cf438be..e2b83e54c 100644 --- a/instack_undercloud/undercloud.py +++ b/instack_undercloud/undercloud.py @@ -1232,7 +1232,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 @@ -1248,6 +1277,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