diff --git a/actions.yaml b/actions.yaml index 7872146c..a47df032 100644 --- a/actions.yaml +++ b/actions.yaml @@ -1,2 +1,4 @@ git-reinstall: description: Reinstall glance from the openstack-origin-git repositories. +openstack-upgrade: + description: Perform openstack upgrades. Config option action-managed-upgrade must be set to True. diff --git a/actions/openstack-upgrade b/actions/openstack-upgrade new file mode 120000 index 00000000..61793013 --- /dev/null +++ b/actions/openstack-upgrade @@ -0,0 +1 @@ +openstack_upgrade.py \ No newline at end of file diff --git a/actions/openstack_upgrade.py b/actions/openstack_upgrade.py new file mode 100755 index 00000000..b4331753 --- /dev/null +++ b/actions/openstack_upgrade.py @@ -0,0 +1,62 @@ +#!/usr/bin/python +import sys +import traceback + +sys.path.append('hooks/') + +from charmhelpers.core.hookenv import ( + action_set, + action_fail, + config +) + +from glance_relations import config_changed + +from charmhelpers.contrib.openstack.utils import ( + juju_log, + git_install_requested, + openstack_upgrade_available +) + +from glance_utils import ( + do_openstack_upgrade, + register_configs +) + + +CONFIGS = register_configs() + + +def openstack_upgrade(): + """Upgrade packages to config-set Openstack version. + + If the charm was installed from source we cannot upgrade it. + For backwards compatibility a config flag must be set for this + code to run, otherwise a full service level upgrade will fire + on config-changed.""" + + if git_install_requested(): + action_set({'outcome': 'installed from source, skipped upgrade.'}) + else: + if openstack_upgrade_available('glance-common'): + if config('action-managed-upgrade'): + juju_log('Upgrading OpenStack release') + + try: + do_openstack_upgrade(CONFIGS) + action_set({'outcome': 'success, upgrade completed.'}) + except: + action_set({'outcome': 'upgrade failed, see traceback.'}) + action_set({'traceback': traceback.format_exc()}) + action_fail('do_openstack_upgrade resulted in an ' + 'unexpected error') + + config_changed() + else: + action_set({'outcome': 'action-managed-upgrade config is ' + 'False, skipped upgrade.'}) + else: + action_set({'outcome': 'no upgrade available.'}) + +if __name__ == '__main__': + openstack_upgrade() diff --git a/config.yaml b/config.yaml index ea4c6688..cf012c6c 100644 --- a/config.yaml +++ b/config.yaml @@ -233,3 +233,13 @@ options: description: | A comma-separated list of nagios servicegroups. If left empty, the nagios_context will be used as the servicegroup + action-managed-upgrade: + type: boolean + default: False + description: | + If True enables openstack upgrades for this charm via juju actions. + You will still need to set openstack-origin to the new repository but + instead of an upgrade running automatically across all units, it will + wait for you to execute the openstack-upgrade action for this charm on + each unit. If False it will revert to existing behavior of upgrading + all units on config change. diff --git a/hooks/glance_relations.py b/hooks/glance_relations.py index 2c86c73b..d5be56a6 100755 --- a/hooks/glance_relations.py +++ b/hooks/glance_relations.py @@ -333,7 +333,7 @@ def config_changed(): if git_install_requested(): if config_value_changed('openstack-origin-git'): git_install(config('openstack-origin-git')) - else: + elif not config('action-managed-upgrade'): if openstack_upgrade_available('glance-common'): juju_log('Upgrading OpenStack release') do_openstack_upgrade(CONFIGS) diff --git a/unit_tests/test_actions_openstack_upgrade.py b/unit_tests/test_actions_openstack_upgrade.py new file mode 100644 index 00000000..fed22ec2 --- /dev/null +++ b/unit_tests/test_actions_openstack_upgrade.py @@ -0,0 +1,117 @@ +from mock import patch +import os + +os.environ['JUJU_UNIT_NAME'] = 'glance' + +with patch('glance_utils.register_configs') as register_configs: + import openstack_upgrade + +from test_utils import ( + CharmTestCase +) + +TO_PATCH = [ + 'config' +] + + +class TestGlanceUpgradeActions(CharmTestCase): + + def setUp(self): + super(TestGlanceUpgradeActions, self).setUp(openstack_upgrade, + TO_PATCH) + self.config.side_effect = self.test_config.get + + @patch.object(openstack_upgrade, 'action_set') + @patch.object(openstack_upgrade, 'action_fail') + @patch.object(openstack_upgrade, 'do_openstack_upgrade') + @patch.object(openstack_upgrade, 'openstack_upgrade_available') + @patch.object(openstack_upgrade, 'config_changed') + @patch('charmhelpers.contrib.openstack.utils.config') + def test_openstack_upgrade(self, _config, config_changed, + openstack_upgrade_available, + do_openstack_upgrade, action_fail, + action_set): + _config.return_value = None + openstack_upgrade_available.return_value = True + + self.test_config.set('action-managed-upgrade', True) + + openstack_upgrade.openstack_upgrade() + + self.assertTrue(do_openstack_upgrade.called) + self.assertTrue(config_changed.called) + self.assertFalse(action_fail.called) + + @patch.object(openstack_upgrade, 'action_set') + @patch.object(openstack_upgrade, 'do_openstack_upgrade') + @patch.object(openstack_upgrade, 'openstack_upgrade_available') + @patch.object(openstack_upgrade, 'config_changed') + @patch('charmhelpers.contrib.openstack.utils.config') + def test_openstack_upgrade_not_configured(self, _config, config_changed, + openstack_upgrade_available, + do_openstack_upgrade, + action_set): + _config.return_value = None + openstack_upgrade_available.return_value = True + + openstack_upgrade.openstack_upgrade() + + msg = ('action-managed-upgrade config is False, skipped upgrade.') + + action_set.assert_called_with({'outcome': msg}) + self.assertFalse(do_openstack_upgrade.called) + + @patch.object(openstack_upgrade, 'action_set') + @patch.object(openstack_upgrade, 'do_openstack_upgrade') + @patch.object(openstack_upgrade, 'openstack_upgrade_available') + @patch.object(openstack_upgrade, 'config_changed') + @patch('charmhelpers.contrib.openstack.utils.config') + def test_openstack_upgrade_git_install(self, _config, config_changed, + openstack_upgrade_available, + do_openstack_upgrade, + action_set): + + self.test_config.set('action-managed-upgrade', True) + self.test_config.set('openstack-origin-git', True) + + openstack_upgrade.openstack_upgrade() + + msg = ('installed from source, skipped upgrade.') + action_set.assert_called_with({'outcome': msg}) + self.assertFalse(do_openstack_upgrade.called) + + @patch.object(openstack_upgrade, 'action_set') + @patch.object(openstack_upgrade, 'action_fail') + @patch.object(openstack_upgrade, 'do_openstack_upgrade') + @patch.object(openstack_upgrade, 'openstack_upgrade_available') + @patch.object(openstack_upgrade, 'config_changed') + @patch('traceback.format_exc') + @patch('charmhelpers.contrib.openstack.utils.config') + def test_openstack_upgrade_exception(self, _config, format_exc, + config_changed, + openstack_upgrade_available, + do_openstack_upgrade, + action_fail, action_set): + _config.return_value = None + self.test_config.set('action-managed-upgrade', True) + openstack_upgrade_available.return_value = True + + e = OSError('something bad happened') + do_openstack_upgrade.side_effect = e + traceback = ( + "Traceback (most recent call last):\n" + " File \"actions/openstack_upgrade.py\", line 37, in openstack_upgrade\n" # noqa + " openstack_upgrade(config(\'openstack-origin-git\'))\n" + " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 964, in __call__\n" # noqa + " return _mock_self._mock_call(*args, **kwargs)\n" + " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 1019, in _mock_call\n" # noqa + " raise effect\n" + "OSError: something bad happened\n") + format_exc.return_value = traceback + + openstack_upgrade.openstack_upgrade() + + msg = 'do_openstack_upgrade resulted in an unexpected error' + action_fail.assert_called_with(msg) + action_set.assert_called_with({'traceback': traceback}) diff --git a/unit_tests/test_glance_relations.py b/unit_tests/test_glance_relations.py index 8a43859d..25123928 100644 --- a/unit_tests/test_glance_relations.py +++ b/unit_tests/test_glance_relations.py @@ -560,6 +560,15 @@ class GlanceRelationTests(CharmTestCase): self.assertTrue(self.do_openstack_upgrade.called) self.assertTrue(configure_https.called) + @patch.object(relations, 'git_install_requested') + def test_config_changed_with_openstack_upgrade_action(self, git_requested): + git_requested.return_value = False + self.openstack_upgrade_available.return_value = True + self.test_config.set('action-managed-upgrade', True) + + relations.config_changed() + self.assertFalse(self.do_openstack_upgrade.called) + @patch.object(relations, 'configure_https') @patch.object(relations, 'git_install_requested') @patch.object(relations, 'config_value_changed')