[corey.bryant, r=thedac] Workload Status
This commit is contained in:
commit
55b3938836
@ -5,10 +5,13 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from charmhelpers.core.host import service_pause, service_resume
|
sys.path.append('hooks/')
|
||||||
from charmhelpers.core.hookenv import action_fail, status_set
|
|
||||||
|
|
||||||
from lib.swift_utils import services
|
from charmhelpers.core.host import service_pause, service_resume
|
||||||
|
from charmhelpers.core.hookenv import action_fail
|
||||||
|
from charmhelpers.core.unitdata import HookData, kv
|
||||||
|
from lib.swift_utils import assess_status, services
|
||||||
|
from swift_hooks import CONFIGS
|
||||||
|
|
||||||
|
|
||||||
def get_action_parser(actions_yaml_path, action_name,
|
def get_action_parser(actions_yaml_path, action_name,
|
||||||
@ -31,8 +34,9 @@ def pause(args):
|
|||||||
stopped = service_pause(service)
|
stopped = service_pause(service)
|
||||||
if not stopped:
|
if not stopped:
|
||||||
raise Exception("{} didn't stop cleanly.".format(service))
|
raise Exception("{} didn't stop cleanly.".format(service))
|
||||||
status_set(
|
with HookData()():
|
||||||
"maintenance", "Paused. Use 'resume' action to resume normal service.")
|
kv().set('unit-paused', True)
|
||||||
|
assess_status(CONFIGS)
|
||||||
|
|
||||||
|
|
||||||
def resume(args):
|
def resume(args):
|
||||||
@ -44,7 +48,9 @@ def resume(args):
|
|||||||
started = service_resume(service)
|
started = service_resume(service)
|
||||||
if not started:
|
if not started:
|
||||||
raise Exception("{} didn't start cleanly.".format(service))
|
raise Exception("{} didn't start cleanly.".format(service))
|
||||||
status_set("active", "")
|
with HookData()():
|
||||||
|
kv().set('unit-paused', False)
|
||||||
|
assess_status(CONFIGS)
|
||||||
|
|
||||||
|
|
||||||
# A dictionary of all the defined actions to callables (which take
|
# A dictionary of all the defined actions to callables (which take
|
||||||
|
1
actions/hooks
Symbolic link
1
actions/hooks
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../hooks/
|
@ -33,7 +33,8 @@ from lib.swift_utils import (
|
|||||||
all_responses_equal,
|
all_responses_equal,
|
||||||
ensure_www_dir_permissions,
|
ensure_www_dir_permissions,
|
||||||
is_paused,
|
is_paused,
|
||||||
pause_aware_restart_on_change
|
pause_aware_restart_on_change,
|
||||||
|
assess_status,
|
||||||
)
|
)
|
||||||
|
|
||||||
import charmhelpers.contrib.openstack.utils as openstack
|
import charmhelpers.contrib.openstack.utils as openstack
|
||||||
@ -56,6 +57,7 @@ from charmhelpers.core.hookenv import (
|
|||||||
ERROR,
|
ERROR,
|
||||||
Hooks, UnregisteredHookError,
|
Hooks, UnregisteredHookError,
|
||||||
open_port,
|
open_port,
|
||||||
|
status_set,
|
||||||
)
|
)
|
||||||
from charmhelpers.core.host import (
|
from charmhelpers.core.host import (
|
||||||
service_reload,
|
service_reload,
|
||||||
@ -96,11 +98,13 @@ CONFIGS = register_configs()
|
|||||||
|
|
||||||
@hooks.hook('install.real')
|
@hooks.hook('install.real')
|
||||||
def install():
|
def install():
|
||||||
|
status_set('maintenance', 'Executing pre-install')
|
||||||
execd_preinstall()
|
execd_preinstall()
|
||||||
src = config('openstack-origin')
|
src = config('openstack-origin')
|
||||||
if src != 'distro':
|
if src != 'distro':
|
||||||
openstack.configure_installation_source(src)
|
openstack.configure_installation_source(src)
|
||||||
|
|
||||||
|
status_set('maintenance', 'Installing apt packages')
|
||||||
apt_update(fatal=True)
|
apt_update(fatal=True)
|
||||||
rel = openstack.get_os_codename_install_source(src)
|
rel = openstack.get_os_codename_install_source(src)
|
||||||
pkgs = determine_packages(rel)
|
pkgs = determine_packages(rel)
|
||||||
@ -125,6 +129,7 @@ def config_changed():
|
|||||||
config('min-hours'))
|
config('min-hours'))
|
||||||
|
|
||||||
if config('prefer-ipv6'):
|
if config('prefer-ipv6'):
|
||||||
|
status_set('maintenance', 'Configuring ipv6')
|
||||||
setup_ipv6()
|
setup_ipv6()
|
||||||
|
|
||||||
configure_https()
|
configure_https()
|
||||||
@ -135,7 +140,9 @@ def config_changed():
|
|||||||
if not config('action-managed-upgrade') and \
|
if not config('action-managed-upgrade') and \
|
||||||
openstack.openstack_upgrade_available('python-swift'):
|
openstack.openstack_upgrade_available('python-swift'):
|
||||||
do_openstack_upgrade(CONFIGS)
|
do_openstack_upgrade(CONFIGS)
|
||||||
|
status_set('maintenance', 'Running openstack upgrade')
|
||||||
|
|
||||||
|
status_set('maintenance', 'Updating and balancing rings')
|
||||||
update_rings(min_part_hours=config('min-hours'))
|
update_rings(min_part_hours=config('min-hours'))
|
||||||
|
|
||||||
if not config('disable-ring-balance') and is_elected_leader(SWIFT_HA_RES):
|
if not config('disable-ring-balance') and is_elected_leader(SWIFT_HA_RES):
|
||||||
@ -519,6 +526,7 @@ def main():
|
|||||||
hooks.execute(sys.argv)
|
hooks.execute(sys.argv)
|
||||||
except UnregisteredHookError as e:
|
except UnregisteredHookError as e:
|
||||||
log('Unknown hook {} - skipping.'.format(e), level=DEBUG)
|
log('Unknown hook {} - skipping.'.format(e), level=DEBUG)
|
||||||
|
assess_status(CONFIGS)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -26,7 +26,8 @@ from charmhelpers.contrib.openstack.utils import (
|
|||||||
os_release,
|
os_release,
|
||||||
get_os_codename_package,
|
get_os_codename_package,
|
||||||
get_os_codename_install_source,
|
get_os_codename_install_source,
|
||||||
configure_installation_source
|
configure_installation_source,
|
||||||
|
set_os_workload_status,
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.hahelpers.cluster import (
|
from charmhelpers.contrib.hahelpers.cluster import (
|
||||||
is_elected_leader,
|
is_elected_leader,
|
||||||
@ -43,7 +44,8 @@ from charmhelpers.core.hookenv import (
|
|||||||
relation_set,
|
relation_set,
|
||||||
relation_ids,
|
relation_ids,
|
||||||
related_units,
|
related_units,
|
||||||
status_get
|
status_get,
|
||||||
|
status_set,
|
||||||
)
|
)
|
||||||
from charmhelpers.fetch import (
|
from charmhelpers.fetch import (
|
||||||
apt_update,
|
apt_update,
|
||||||
@ -62,6 +64,11 @@ from charmhelpers.contrib.network.ip import (
|
|||||||
from charmhelpers.core.decorators import (
|
from charmhelpers.core.decorators import (
|
||||||
retry_on_exception,
|
retry_on_exception,
|
||||||
)
|
)
|
||||||
|
from charmhelpers.core.unitdata import (
|
||||||
|
HookData,
|
||||||
|
kv,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Various config files that are managed via templating.
|
# Various config files that are managed via templating.
|
||||||
SWIFT_CONF_DIR = '/etc/swift'
|
SWIFT_CONF_DIR = '/etc/swift'
|
||||||
@ -547,17 +554,7 @@ def should_balance(rings):
|
|||||||
if config('disable-ring-balance'):
|
if config('disable-ring-balance'):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for ring in rings:
|
return has_minimum_zones(rings)
|
||||||
builder = _load_builder(ring).to_dict()
|
|
||||||
replicas = builder['replicas']
|
|
||||||
zones = [dev['zone'] for dev in builder['devs']]
|
|
||||||
num_zones = len(set(zones))
|
|
||||||
if num_zones < replicas:
|
|
||||||
log("Not enough zones (%d) defined to allow ring balance "
|
|
||||||
"(need >= %d)" % (num_zones, replicas), level=INFO)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def do_openstack_upgrade(configs):
|
def do_openstack_upgrade(configs):
|
||||||
@ -1001,8 +998,11 @@ def get_hostaddr():
|
|||||||
|
|
||||||
def is_paused(status_get=status_get):
|
def is_paused(status_get=status_get):
|
||||||
"""Is the unit paused?"""
|
"""Is the unit paused?"""
|
||||||
status, message = status_get()
|
with HookData()():
|
||||||
return status == "maintenance" and message.startswith("Paused")
|
if kv().get('unit-paused'):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def pause_aware_restart_on_change(restart_map):
|
def pause_aware_restart_on_change(restart_map):
|
||||||
@ -1013,3 +1013,53 @@ def pause_aware_restart_on_change(restart_map):
|
|||||||
else:
|
else:
|
||||||
return restart_on_change(restart_map)(f)
|
return restart_on_change(restart_map)(f)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def has_minimum_zones(rings):
|
||||||
|
"""Determine if enough zones exist to satisfy minimum replicas"""
|
||||||
|
for ring in rings:
|
||||||
|
builder = _load_builder(ring).to_dict()
|
||||||
|
replicas = builder['replicas']
|
||||||
|
zones = [dev['zone'] for dev in builder['devs']]
|
||||||
|
num_zones = len(set(zones))
|
||||||
|
if num_zones < replicas:
|
||||||
|
log("Not enough zones (%d) defined to satisfy minimum replicas "
|
||||||
|
"(need >= %d)" % (num_zones, replicas), level=INFO)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def assess_status(configs):
|
||||||
|
"""Assess status of current unit"""
|
||||||
|
required_interfaces = {}
|
||||||
|
|
||||||
|
if is_paused():
|
||||||
|
status_set("maintenance",
|
||||||
|
"Paused. Use 'resume' action to resume normal service.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for required swift-storage relation
|
||||||
|
if len(relation_ids('swift-storage')) < 1:
|
||||||
|
status_set('blocked', 'Missing relation: storage')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verify allowed_hosts is populated with enough unit IP addresses
|
||||||
|
ctxt = SwiftRingContext()()
|
||||||
|
if len(ctxt['allowed_hosts']) < config('replicas'):
|
||||||
|
status_set('blocked', 'Not enough related storage nodes')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verify there are enough storage zones to satisfy minimum replicas
|
||||||
|
rings = [r for r in SWIFT_RINGS.itervalues()]
|
||||||
|
if not has_minimum_zones(rings):
|
||||||
|
status_set('blocked', 'Not enough storage zones for minimum replicas')
|
||||||
|
return
|
||||||
|
|
||||||
|
if relation_ids('identity-service'):
|
||||||
|
required_interfaces['identity'] = ['identity-service']
|
||||||
|
|
||||||
|
if required_interfaces:
|
||||||
|
set_os_workload_status(configs, required_interfaces)
|
||||||
|
else:
|
||||||
|
status_set('active', 'Unit is ready')
|
||||||
|
@ -635,7 +635,7 @@ class SwiftProxyBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
msg = ("Resume action failed to move unit to active "
|
msg = ("Resume action failed to move unit to active "
|
||||||
"status (got {} instead)".format(status))
|
"status (got {} instead)".format(status))
|
||||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||||
if message != "":
|
if message != "Unit is ready":
|
||||||
msg = ("Resume action failed to clear message"
|
msg = ("Resume action failed to clear message"
|
||||||
" (got {} instead)".format(message))
|
" (got {} instead)".format(message))
|
||||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||||
|
@ -5,7 +5,9 @@ import unittest
|
|||||||
import mock
|
import mock
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
import actions.actions
|
from mock import patch
|
||||||
|
with patch('lib.swift_utils.is_paused') as is_paused:
|
||||||
|
import actions.actions
|
||||||
|
|
||||||
|
|
||||||
class CharmTestCase(unittest.TestCase):
|
class CharmTestCase(unittest.TestCase):
|
||||||
@ -31,7 +33,8 @@ class PauseTestCase(CharmTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(PauseTestCase, self).setUp(
|
super(PauseTestCase, self).setUp(
|
||||||
actions.actions, ["service_pause", "status_set"])
|
actions.actions, ["service_pause", "HookData", "kv",
|
||||||
|
"assess_status"])
|
||||||
|
|
||||||
class FakeArgs(object):
|
class FakeArgs(object):
|
||||||
services = ['swift-proxy', 'haproxy', 'memcached', 'apache2']
|
services = ['swift-proxy', 'haproxy', 'memcached', 'apache2']
|
||||||
@ -68,32 +71,20 @@ class PauseTestCase(CharmTestCase):
|
|||||||
actions.actions.pause, self.args)
|
actions.actions.pause, self.args)
|
||||||
self.assertEqual(pause_calls, ["swift-proxy"])
|
self.assertEqual(pause_calls, ["swift-proxy"])
|
||||||
|
|
||||||
def test_status_mode(self):
|
def test_pause_sets_value(self):
|
||||||
"""Pause action sets the status to maintenance."""
|
"""Pause action sets the unit-paused value to True."""
|
||||||
status_calls = []
|
self.HookData()().return_value = True
|
||||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
|
||||||
state)
|
|
||||||
|
|
||||||
actions.actions.pause(self.args)
|
actions.actions.pause(self.args)
|
||||||
self.assertEqual(status_calls, ["maintenance"])
|
self.kv().set.assert_called_with('unit-paused', True)
|
||||||
|
|
||||||
def test_status_message(self):
|
|
||||||
"""Pause action sets a status message reflecting that it's paused."""
|
|
||||||
status_calls = []
|
|
||||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
|
||||||
msg)
|
|
||||||
|
|
||||||
actions.actions.pause(self.args)
|
|
||||||
self.assertEqual(
|
|
||||||
status_calls, ["Paused. "
|
|
||||||
"Use 'resume' action to resume normal service."])
|
|
||||||
|
|
||||||
|
|
||||||
class ResumeTestCase(CharmTestCase):
|
class ResumeTestCase(CharmTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ResumeTestCase, self).setUp(
|
super(ResumeTestCase, self).setUp(
|
||||||
actions.actions, ["service_resume", "status_set"])
|
actions.actions, ["service_resume", "HookData", "kv",
|
||||||
|
"assess_status"])
|
||||||
|
|
||||||
class FakeArgs(object):
|
class FakeArgs(object):
|
||||||
services = ['swift-proxy', 'haproxy', 'memcached', 'apache2']
|
services = ['swift-proxy', 'haproxy', 'memcached', 'apache2']
|
||||||
@ -129,23 +120,12 @@ class ResumeTestCase(CharmTestCase):
|
|||||||
actions.actions.resume, self.args)
|
actions.actions.resume, self.args)
|
||||||
self.assertEqual(resume_calls, ['swift-proxy'])
|
self.assertEqual(resume_calls, ['swift-proxy'])
|
||||||
|
|
||||||
def test_status_mode(self):
|
def test_resume_sets_value(self):
|
||||||
"""Resume action sets the status to maintenance."""
|
"""Resume action sets the unit-paused value to False."""
|
||||||
status_calls = []
|
self.HookData()().return_value = True
|
||||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
|
||||||
state)
|
|
||||||
|
|
||||||
actions.actions.resume(self.args)
|
actions.actions.resume(self.args)
|
||||||
self.assertEqual(status_calls, ["active"])
|
self.kv().set.assert_called_with('unit-paused', False)
|
||||||
|
|
||||||
def test_status_message(self):
|
|
||||||
"""Resume action sets an empty status message."""
|
|
||||||
status_calls = []
|
|
||||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
|
||||||
msg)
|
|
||||||
|
|
||||||
actions.actions.resume(self.args)
|
|
||||||
self.assertEqual(status_calls, [""])
|
|
||||||
|
|
||||||
|
|
||||||
class GetActionParserTestCase(unittest.TestCase):
|
class GetActionParserTestCase(unittest.TestCase):
|
||||||
|
@ -5,8 +5,9 @@ import unittest
|
|||||||
os.environ['JUJU_UNIT_NAME'] = 'swift-proxy'
|
os.environ['JUJU_UNIT_NAME'] = 'swift-proxy'
|
||||||
|
|
||||||
with patch('actions.charmhelpers.core.hookenv.config') as config:
|
with patch('actions.charmhelpers.core.hookenv.config') as config:
|
||||||
config.return_value = 'swift'
|
with patch('lib.swift_utils.is_paused') as is_paused:
|
||||||
import actions.openstack_upgrade as openstack_upgrade
|
config.return_value = 'swift'
|
||||||
|
import actions.openstack_upgrade as openstack_upgrade
|
||||||
|
|
||||||
|
|
||||||
TO_PATCH = [
|
TO_PATCH = [
|
||||||
|
@ -5,7 +5,8 @@ import uuid
|
|||||||
|
|
||||||
sys.path.append("hooks")
|
sys.path.append("hooks")
|
||||||
with patch('charmhelpers.core.hookenv.log'):
|
with patch('charmhelpers.core.hookenv.log'):
|
||||||
import swift_hooks
|
with patch('lib.swift_utils.is_paused') as is_paused:
|
||||||
|
import swift_hooks
|
||||||
|
|
||||||
|
|
||||||
class SwiftHooksTestCase(unittest.TestCase):
|
class SwiftHooksTestCase(unittest.TestCase):
|
||||||
|
@ -5,8 +5,10 @@ import tempfile
|
|||||||
import uuid
|
import uuid
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from mock import patch
|
||||||
with mock.patch('charmhelpers.core.hookenv.config'):
|
with mock.patch('charmhelpers.core.hookenv.config'):
|
||||||
import lib.swift_utils as swift_utils
|
with patch('lib.swift_utils.is_paused') as is_paused:
|
||||||
|
import lib.swift_utils as swift_utils
|
||||||
|
|
||||||
|
|
||||||
def init_ring_paths(tmpdir):
|
def init_ring_paths(tmpdir):
|
||||||
@ -223,14 +225,39 @@ class SwiftUtilsTestCase(unittest.TestCase):
|
|||||||
rsps = []
|
rsps = []
|
||||||
self.assertIsNone(swift_utils.get_first_available_value(rsps, 'key3'))
|
self.assertIsNone(swift_utils.get_first_available_value(rsps, 'key3'))
|
||||||
|
|
||||||
def test_is_paused_unknown(self):
|
@mock.patch('lib.swift_utils.is_paused')
|
||||||
fake_status_get = lambda: ("unknown", "")
|
@mock.patch('lib.swift_utils.config')
|
||||||
self.assertFalse(swift_utils.is_paused(status_get=fake_status_get))
|
@mock.patch('lib.swift_utils.set_os_workload_status')
|
||||||
|
@mock.patch('lib.swift_utils.SwiftRingContext.__call__')
|
||||||
|
@mock.patch('lib.swift_utils.status_set')
|
||||||
|
@mock.patch('lib.swift_utils.has_minimum_zones')
|
||||||
|
@mock.patch('lib.swift_utils.relation_ids')
|
||||||
|
def test_assess_status(self, relation_ids, has_min_zones, status_set,
|
||||||
|
ctxt, workload_status, config, is_paused):
|
||||||
|
config.return_value = 3
|
||||||
|
|
||||||
def test_is_paused_paused(self):
|
is_paused.return_value = True
|
||||||
fake_status_get = lambda: ("maintenance", "Paused")
|
swift_utils.assess_status(None)
|
||||||
self.assertTrue(swift_utils.is_paused(status_get=fake_status_get))
|
status_set.assert_called_with('maintenance',
|
||||||
|
"Paused. Use 'resume' action to resume "
|
||||||
|
"normal service.")
|
||||||
|
|
||||||
def test_is_paused_other_maintenance(self):
|
is_paused.return_value = False
|
||||||
fake_status_get = lambda: ("maintenance", "Hook")
|
relation_ids.return_value = []
|
||||||
self.assertFalse(swift_utils.is_paused(status_get=fake_status_get))
|
swift_utils.assess_status(None)
|
||||||
|
status_set.assert_called_with('blocked', 'Missing relation: storage')
|
||||||
|
|
||||||
|
relation_ids.return_value = ['swift-storage:1']
|
||||||
|
|
||||||
|
ctxt.return_value = {'allowed_hosts': ['1.2.3.4']}
|
||||||
|
swift_utils.assess_status(None)
|
||||||
|
status_set.assert_called_with('blocked',
|
||||||
|
'Not enough related storage nodes')
|
||||||
|
|
||||||
|
ctxt.return_value = {'allowed_hosts': ['1.2.3.4', '2.3.4.5',
|
||||||
|
'3.4.5.6']}
|
||||||
|
has_min_zones.return_value = False
|
||||||
|
swift_utils.assess_status(None)
|
||||||
|
status_set.assert_called_with('blocked',
|
||||||
|
'Not enough storage zones for minimum '
|
||||||
|
'replicas')
|
||||||
|
Loading…
Reference in New Issue
Block a user