diff --git a/actions.yaml b/actions.yaml index 780756f..fade4b2 100644 --- a/actions.yaml +++ b/actions.yaml @@ -9,3 +9,7 @@ resume: Resume swift-proxy services. If the swift-proxy deployment is clustered using the hacluster charm, the corresponding hacluster unit on the node must be resumed as well. +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 0000000..6179301 --- /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 0000000..38ce7ca --- /dev/null +++ b/actions/openstack_upgrade.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +import sys + +sys.path.append('hooks/') + +from charmhelpers.contrib.openstack.utils import ( + do_action_openstack_upgrade, +) + +from swift_hooks import ( + config_changed, + CONFIGS, +) + +from lib.swift_utils import ( + do_openstack_upgrade, +) + + +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 (do_action_openstack_upgrade('python-swift', + do_openstack_upgrade, + CONFIGS)): + config_changed() + +if __name__ == '__main__': + openstack_upgrade() diff --git a/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py index 17a9379..2f5280e 100644 --- a/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -25,6 +25,7 @@ import sys import re import six +import traceback import yaml from charmhelpers.contrib.network import ip @@ -34,6 +35,8 @@ from charmhelpers.core import ( ) from charmhelpers.core.hookenv import ( + action_fail, + action_set, config, log as juju_log, charm_dir, @@ -749,3 +752,47 @@ def git_yaml_value(projects_yaml, key): return projects[key] return None + + +def do_action_openstack_upgrade(package, upgrade_callback, configs): + """Perform action-managed OpenStack upgrade. + + Upgrades packages to the configured openstack-origin version and sets + the corresponding action status as a result. + + If the charm was installed from source we cannot upgrade it. + For backwards compatibility a config flag (action-managed-upgrade) must + be set for this code to run, otherwise a full service level upgrade will + fire on config-changed. + + @param package: package name for determining if upgrade available + @param upgrade_callback: function callback to charm's upgrade function + @param configs: templating object derived from OSConfigRenderer class + + @return: True if upgrade successful; False if upgrade failed or skipped + """ + ret = False + + if git_install_requested(): + action_set({'outcome': 'installed from source, skipped upgrade.'}) + else: + if openstack_upgrade_available(package): + if config('action-managed-upgrade'): + juju_log('Upgrading OpenStack release') + + try: + upgrade_callback(configs=configs) + action_set({'outcome': 'success, upgrade completed.'}) + ret = True + except: + action_set({'outcome': 'upgrade failed, see traceback.'}) + action_set({'traceback': traceback.format_exc()}) + action_fail('do_openstack_upgrade resulted in an ' + 'unexpected error') + else: + action_set({'outcome': 'action-managed-upgrade config is ' + 'False, skipped upgrade.'}) + else: + action_set({'outcome': 'no upgrade available.'}) + + return ret diff --git a/config.yaml b/config.yaml index 6896eeb..36cbd31 100644 --- a/config.yaml +++ b/config.yaml @@ -255,3 +255,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/swift_hooks.py b/hooks/swift_hooks.py index 517dabe..ac67e11 100755 --- a/hooks/swift_hooks.py +++ b/hooks/swift_hooks.py @@ -132,7 +132,8 @@ def config_changed(): update_nrpe_config() # Determine whether or not we should do an upgrade. - if openstack.openstack_upgrade_available('python-swift'): + if not config('action-managed-upgrade') and \ + openstack.openstack_upgrade_available('python-swift'): do_openstack_upgrade(CONFIGS) update_rings(min_part_hours=config('min-hours')) diff --git a/unit_tests/test_actions_openstack_upgrade.py b/unit_tests/test_actions_openstack_upgrade.py new file mode 100644 index 0000000..ca170fc --- /dev/null +++ b/unit_tests/test_actions_openstack_upgrade.py @@ -0,0 +1,75 @@ +from mock import patch +import os +import unittest + +os.environ['JUJU_UNIT_NAME'] = 'swift-proxy' + +with patch('actions.charmhelpers.core.hookenv.config') as config: + config.return_value = 'swift' + import actions.openstack_upgrade as openstack_upgrade + + +TO_PATCH = [ + 'config_changed', + 'do_openstack_upgrade', +] + + +class CharmTestCase(unittest.TestCase): + + def setUp(self, obj, patches): + super(CharmTestCase, self).setUp() + self.patches = patches + self.obj = obj + self.patch_all() + + def patch(self, method): + _m = patch.object(self.obj, method) + mocked = _m.start() + self.addCleanup(_m.stop) + return mocked + + def patch_all(self): + for method in self.patches: + setattr(self, method, self.patch(method)) + + +class TestSwiftUpgradeActions(CharmTestCase): + + def setUp(self): + super(TestSwiftUpgradeActions, self).setUp(openstack_upgrade, + TO_PATCH) + + @patch('actions.charmhelpers.contrib.openstack.utils.config') + @patch('actions.charmhelpers.contrib.openstack.utils.action_set') + @patch('actions.charmhelpers.contrib.openstack.utils.' + 'git_install_requested') + @patch('actions.charmhelpers.contrib.openstack.utils.' + 'openstack_upgrade_available') + def test_openstack_upgrade_true(self, upgrade_avail, git_requested, + action_set, config): + git_requested.return_value = False + upgrade_avail.return_value = True + config.return_value = True + + openstack_upgrade.openstack_upgrade() + + self.assertTrue(self.do_openstack_upgrade.called) + self.assertTrue(self.config_changed.called) + + @patch('actions.charmhelpers.contrib.openstack.utils.config') + @patch('actions.charmhelpers.contrib.openstack.utils.action_set') + @patch('actions.charmhelpers.contrib.openstack.utils.' + 'git_install_requested') + @patch('actions.charmhelpers.contrib.openstack.utils.' + 'openstack_upgrade_available') + def test_openstack_upgrade_false(self, upgrade_avail, git_requested, + action_set, config): + git_requested.return_value = False + upgrade_avail.return_value = True + config.return_value = False + + openstack_upgrade.openstack_upgrade() + + self.assertFalse(self.do_openstack_upgrade.called) + self.assertFalse(self.config_changed.called)