rebase
This commit is contained in:
@@ -4,3 +4,4 @@ exclude_lines =
|
||||
if __name__ == .__main__.:
|
||||
include=
|
||||
hooks/keystone_*
|
||||
actions/actions.py
|
||||
|
11
actions.yaml
11
actions.yaml
@@ -1,2 +1,13 @@
|
||||
git-reinstall:
|
||||
description: Reinstall keystone from the openstack-origin-git repositories.
|
||||
pause:
|
||||
description: |
|
||||
Pause keystone services.
|
||||
If the keystone deployment is clustered using the hacluster charm, the
|
||||
corresponding hacluster unit on the node must first be paused as well.
|
||||
Not doing so may lead to an interruption of service.
|
||||
resume:
|
||||
description: |
|
||||
Resume keystone services.
|
||||
If the keystone deployment is clustered using the hacluster charm, the
|
||||
corresponding hacluster unit on the node must be resumed as well.
|
||||
|
0
actions/__init__.py
Normal file
0
actions/__init__.py
Normal file
56
actions/actions.py
Executable file
56
actions/actions.py
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from charmhelpers.core.host import service_pause, service_resume
|
||||
from charmhelpers.core.hookenv import action_fail, status_set
|
||||
|
||||
from hooks.keystone_utils import services
|
||||
|
||||
|
||||
def pause(args):
|
||||
"""Pause all the Keystone services.
|
||||
|
||||
@raises Exception if any services fail to stop
|
||||
"""
|
||||
for service in services():
|
||||
stopped = service_pause(service)
|
||||
if not stopped:
|
||||
raise Exception("{} didn't stop cleanly.".format(service))
|
||||
status_set(
|
||||
"maintenance", "Paused. Use 'resume' action to resume normal service.")
|
||||
|
||||
|
||||
def resume(args):
|
||||
"""Resume all the Keystone services.
|
||||
|
||||
@raises Exception if any services fail to start
|
||||
"""
|
||||
for service in services():
|
||||
started = service_resume(service)
|
||||
if not started:
|
||||
raise Exception("{} didn't start cleanly.".format(service))
|
||||
status_set("active", "")
|
||||
|
||||
|
||||
# A dictionary of all the defined actions to callables (which take
|
||||
# parsed arguments).
|
||||
ACTIONS = {"pause": pause, "resume": resume}
|
||||
|
||||
|
||||
def main(args):
|
||||
action_name = os.path.basename(args[0])
|
||||
try:
|
||||
action = ACTIONS[action_name]
|
||||
except KeyError:
|
||||
return "Action %s undefined" % action_name
|
||||
else:
|
||||
try:
|
||||
action(args)
|
||||
except Exception as e:
|
||||
action_fail(str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
1
actions/charmhelpers
Symbolic link
1
actions/charmhelpers
Symbolic link
@@ -0,0 +1 @@
|
||||
../charmhelpers
|
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
sys.path.append('hooks/')
|
||||
import traceback
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
git_install_requested,
|
||||
@@ -14,11 +12,11 @@ from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
)
|
||||
|
||||
from keystone_utils import (
|
||||
from hooks.keystone_utils import (
|
||||
git_install,
|
||||
)
|
||||
|
||||
from keystone_hooks import (
|
||||
from hooks.keystone_hooks import (
|
||||
config_changed,
|
||||
)
|
||||
|
||||
|
1
actions/hooks
Symbolic link
1
actions/hooks
Symbolic link
@@ -0,0 +1 @@
|
||||
../hooks
|
1
actions/pause
Symbolic link
1
actions/pause
Symbolic link
@@ -0,0 +1 @@
|
||||
actions.py
|
1
actions/resume
Symbolic link
1
actions/resume
Symbolic link
@@ -0,0 +1 @@
|
||||
actions.py
|
@@ -1,5 +1,5 @@
|
||||
branch: lp:charm-helpers
|
||||
destination: hooks/charmhelpers
|
||||
destination: charmhelpers
|
||||
include:
|
||||
- core
|
||||
- cli
|
||||
|
0
hooks/__init__.py
Normal file
0
hooks/__init__.py
Normal file
1
hooks/charmhelpers
Symbolic link
1
hooks/charmhelpers
Symbolic link
@@ -0,0 +1 @@
|
||||
../charmhelpers
|
@@ -14,6 +14,7 @@ import time
|
||||
import urlparse
|
||||
import uuid
|
||||
|
||||
from itertools import chain
|
||||
from base64 import b64encode
|
||||
from collections import OrderedDict
|
||||
from copy import deepcopy
|
||||
@@ -150,11 +151,6 @@ GIT_PACKAGE_BLACKLIST = [
|
||||
'keystone',
|
||||
]
|
||||
|
||||
API_PORTS = {
|
||||
'keystone-admin': config('admin-port'),
|
||||
'keystone-public': config('service-port')
|
||||
}
|
||||
|
||||
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
|
||||
KEYSTONE_LOGGER_CONF = "/etc/keystone/logging.conf"
|
||||
KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)
|
||||
@@ -178,7 +174,6 @@ SSH_USER = 'juju_keystone'
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
SSL_SYNC_SEMAPHORE = threading.Semaphore()
|
||||
SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
|
||||
|
||||
BASE_RESOURCE_MAP = OrderedDict([
|
||||
(KEYSTONE_CONF, {
|
||||
'services': BASE_SERVICES,
|
||||
@@ -321,11 +316,8 @@ def restart_map():
|
||||
|
||||
|
||||
def services():
|
||||
"""Returns a list of services associate with this charm"""
|
||||
_services = []
|
||||
for v in restart_map().values():
|
||||
_services = _services + v
|
||||
return list(set(_services))
|
||||
"""Returns a list of (unique) services associated with this charm"""
|
||||
return list(set(chain(*restart_map().values())))
|
||||
|
||||
|
||||
def determine_ports():
|
||||
@@ -335,23 +327,20 @@ def determine_ports():
|
||||
|
||||
|
||||
def api_port(service):
|
||||
return API_PORTS[service]
|
||||
return {
|
||||
'keystone-admin': config('admin-port'),
|
||||
'keystone-public': config('service-port')
|
||||
}[service]
|
||||
|
||||
|
||||
def determine_packages():
|
||||
# currently all packages match service names
|
||||
packages = [] + BASE_PACKAGES
|
||||
for k, v in resource_map().iteritems():
|
||||
packages.extend(v['services'])
|
||||
|
||||
packages = set(services()).union(BASE_PACKAGES)
|
||||
if git_install_requested():
|
||||
packages.extend(BASE_GIT_PACKAGES)
|
||||
# don't include packages that will be installed from git
|
||||
packages = list(set(packages))
|
||||
for p in GIT_PACKAGE_BLACKLIST:
|
||||
packages.remove(p)
|
||||
packages |= set(BASE_GIT_PACKAGES)
|
||||
packages -= set(GIT_PACKAGE_BLACKLIST)
|
||||
|
||||
return list(set(packages))
|
||||
return sorted(packages)
|
||||
|
||||
|
||||
def save_script_rc():
|
||||
|
@@ -38,6 +38,21 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
|
||||
self._deploy()
|
||||
self._initialize_tests()
|
||||
|
||||
def _assert_services(self, should_run):
|
||||
u.get_unit_process_ids(
|
||||
{self.keystone_sentry: ("keystone-all", "apache2", "haproxy")},
|
||||
expect_success=should_run)
|
||||
|
||||
def get_service_overrides(self, unit):
|
||||
"""
|
||||
Return a dict mapping service names to a boolean indicating whether
|
||||
an override file exists for that service.
|
||||
"""
|
||||
init_contents = unit.directory_contents("/etc/init/")
|
||||
return {
|
||||
service: "{}.override".format(service) in init_contents["files"]
|
||||
for service in ("keystone", "apache2", "haproxy")}
|
||||
|
||||
def _add_services(self):
|
||||
"""Add services
|
||||
|
||||
@@ -462,4 +477,23 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
self.d.configure(juju_service, set_default)
|
||||
|
||||
u.log.debug('OK')
|
||||
|
||||
def test_901_pause_resume(self):
|
||||
"""Test pause and resume actions."""
|
||||
unit_name = "keystone/0"
|
||||
unit = self.d.sentry.unit[unit_name]
|
||||
self._assert_services(should_run=True)
|
||||
action_id = u.run_action(unit, "pause")
|
||||
assert u.wait_on_action(action_id), "Pause action failed."
|
||||
|
||||
self._assert_services(should_run=False)
|
||||
assert all(self.get_service_overrides(unit).itervalues()), \
|
||||
"Not all override files were created."
|
||||
|
||||
action_id = u.run_action(unit, "resume")
|
||||
assert u.wait_on_action(action_id), "Resume action failed"
|
||||
assert not any(self.get_service_overrides(unit).itervalues()), \
|
||||
"Not all override files were removed."
|
||||
self._assert_services(should_run=True)
|
||||
|
150
unit_tests/test_actions.py
Normal file
150
unit_tests/test_actions.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import mock
|
||||
|
||||
from test_utils import CharmTestCase
|
||||
|
||||
import actions.actions
|
||||
|
||||
|
||||
class PauseTestCase(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PauseTestCase, self).setUp(
|
||||
actions.actions, ["service_pause", "status_set"])
|
||||
|
||||
def test_pauses_services(self):
|
||||
"""Pause action pauses all Keystone services."""
|
||||
pause_calls = []
|
||||
|
||||
def fake_service_pause(svc):
|
||||
pause_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_pause.side_effect = fake_service_pause
|
||||
|
||||
actions.actions.pause([])
|
||||
self.assertEqual(pause_calls, ['haproxy', 'keystone', 'apache2'])
|
||||
|
||||
def test_bails_out_early_on_error(self):
|
||||
"""Pause action fails early if there are errors stopping a service."""
|
||||
pause_calls = []
|
||||
|
||||
def maybe_kill(svc):
|
||||
if svc == "keystone":
|
||||
return False
|
||||
else:
|
||||
pause_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_pause.side_effect = maybe_kill
|
||||
self.assertRaisesRegexp(
|
||||
Exception, "keystone didn't stop cleanly.",
|
||||
actions.actions.pause, [])
|
||||
self.assertEqual(pause_calls, ['haproxy'])
|
||||
|
||||
def test_status_mode(self):
|
||||
"""Pause action sets the status to maintenance."""
|
||||
status_calls = []
|
||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
||||
state)
|
||||
|
||||
actions.actions.pause([])
|
||||
self.assertEqual(status_calls, ["maintenance"])
|
||||
|
||||
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.assertEqual(
|
||||
status_calls, ["Paused. "
|
||||
"Use 'resume' action to resume normal service."])
|
||||
|
||||
|
||||
class ResumeTestCase(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ResumeTestCase, self).setUp(
|
||||
actions.actions, ["service_resume", "status_set"])
|
||||
|
||||
def test_resumes_services(self):
|
||||
"""Resume action resumes all Keystone services."""
|
||||
resume_calls = []
|
||||
|
||||
def fake_service_resume(svc):
|
||||
resume_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_resume.side_effect = fake_service_resume
|
||||
actions.actions.resume([])
|
||||
self.assertEqual(resume_calls, ['haproxy', 'keystone', 'apache2'])
|
||||
|
||||
def test_bails_out_early_on_error(self):
|
||||
"""Resume action fails early if there are errors starting a service."""
|
||||
resume_calls = []
|
||||
|
||||
def maybe_kill(svc):
|
||||
if svc == "keystone":
|
||||
return False
|
||||
else:
|
||||
resume_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_resume.side_effect = maybe_kill
|
||||
self.assertRaisesRegexp(
|
||||
Exception, "keystone didn't start cleanly.",
|
||||
actions.actions.resume, [])
|
||||
self.assertEqual(resume_calls, ['haproxy'])
|
||||
|
||||
def test_status_mode(self):
|
||||
"""Resume action sets the status to maintenance."""
|
||||
status_calls = []
|
||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
||||
state)
|
||||
|
||||
actions.actions.resume([])
|
||||
self.assertEqual(status_calls, ["active"])
|
||||
|
||||
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.assertEqual(status_calls, [""])
|
||||
|
||||
|
||||
class MainTestCase(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MainTestCase, self).setUp(actions.actions, ["action_fail"])
|
||||
|
||||
def test_invokes_action(self):
|
||||
dummy_calls = []
|
||||
|
||||
def dummy_action(args):
|
||||
dummy_calls.append(True)
|
||||
|
||||
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
|
||||
actions.actions.main(["foo"])
|
||||
self.assertEqual(dummy_calls, [True])
|
||||
|
||||
def test_unknown_action(self):
|
||||
"""Unknown actions aren't a traceback."""
|
||||
exit_string = actions.actions.main(["foo"])
|
||||
self.assertEqual("Action foo undefined", exit_string)
|
||||
|
||||
def test_failing_action(self):
|
||||
"""Actions which traceback trigger action_fail() calls."""
|
||||
dummy_calls = []
|
||||
|
||||
self.action_fail.side_effect = dummy_calls.append
|
||||
|
||||
def dummy_action(args):
|
||||
raise ValueError("uh oh")
|
||||
|
||||
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
|
||||
actions.actions.main(["foo"])
|
||||
self.assertEqual(dummy_calls, ["uh oh"])
|
@@ -4,8 +4,8 @@ with patch('charmhelpers.core.hookenv.config') as config:
|
||||
config.return_value = 'keystone'
|
||||
import keystone_utils as utils # noqa
|
||||
|
||||
with patch('keystone_utils.register_configs') as register_configs:
|
||||
import git_reinstall
|
||||
with patch('keystone_utils.register_configs') as register_configs:
|
||||
import git_reinstall
|
||||
|
||||
from test_utils import (
|
||||
CharmTestCase
|
||||
@@ -36,8 +36,10 @@ class TestKeystoneActions(CharmTestCase):
|
||||
@patch.object(git_reinstall, 'action_fail')
|
||||
@patch.object(git_reinstall, 'git_install')
|
||||
@patch.object(git_reinstall, 'config_changed')
|
||||
def test_git_reinstall(self, config_changed, git_install, action_fail,
|
||||
action_set):
|
||||
@patch('charmhelpers.contrib.openstack.utils.config')
|
||||
def test_git_reinstall(self, config, config_changed, git_install,
|
||||
action_fail, action_set):
|
||||
config.return_value = openstack_origin_git
|
||||
self.test_config.set('openstack-origin-git', openstack_origin_git)
|
||||
|
||||
git_reinstall.git_reinstall()
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
import keystone_context as context
|
||||
from mock import patch, MagicMock
|
||||
|
||||
@@ -45,6 +47,7 @@ class TestKeystoneContexts(CharmTestCase):
|
||||
self.assertTrue(mock_ensure_permissions.called)
|
||||
self.assertFalse(mock_get_ca.called)
|
||||
|
||||
@patch('keystone_utils.determine_ports')
|
||||
@patch('keystone_utils.is_ssl_cert_master')
|
||||
@patch('keystone_utils.is_ssl_enabled')
|
||||
@patch('charmhelpers.contrib.openstack.context.config')
|
||||
@@ -60,7 +63,8 @@ class TestKeystoneContexts(CharmTestCase):
|
||||
mock_is_clustered,
|
||||
mock_config,
|
||||
mock_is_ssl_enabled,
|
||||
mock_is_ssl_cert_master):
|
||||
mock_is_ssl_cert_master,
|
||||
mock_determine_ports):
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
mock_https.return_value = True
|
||||
@@ -69,6 +73,7 @@ class TestKeystoneContexts(CharmTestCase):
|
||||
mock_determine_apache_port.return_value = '34'
|
||||
mock_is_clustered.return_value = False
|
||||
mock_config.return_value = None
|
||||
mock_determine_ports.return_value = ['12']
|
||||
|
||||
ctxt = context.ApacheSSLContext()
|
||||
ctxt.enable_modules = MagicMock()
|
||||
@@ -83,6 +88,7 @@ class TestKeystoneContexts(CharmTestCase):
|
||||
self.assertTrue(mock_https.called)
|
||||
mock_unit_get.assert_called_with('private-address')
|
||||
|
||||
@patch('keystone_utils.api_port')
|
||||
@patch('charmhelpers.contrib.openstack.context.get_netmask_for_address')
|
||||
@patch('charmhelpers.contrib.openstack.context.get_address_in_network')
|
||||
@patch('charmhelpers.contrib.openstack.context.config')
|
||||
@@ -95,7 +101,10 @@ class TestKeystoneContexts(CharmTestCase):
|
||||
def test_haproxy_context_service_enabled(
|
||||
self, mock_open, mock_log, mock_relation_get, mock_related_units,
|
||||
mock_unit_get, mock_relation_ids, mock_config,
|
||||
mock_get_address_in_network, mock_get_netmask_for_address):
|
||||
mock_get_address_in_network, mock_get_netmask_for_address,
|
||||
mock_api_port):
|
||||
os.environ['JUJU_UNIT_NAME'] = 'keystone'
|
||||
|
||||
mock_relation_ids.return_value = ['identity-service:0', ]
|
||||
mock_unit_get.return_value = '1.2.3.4'
|
||||
mock_relation_get.return_value = '10.0.0.0'
|
||||
@@ -104,19 +113,20 @@ class TestKeystoneContexts(CharmTestCase):
|
||||
mock_get_address_in_network.return_value = None
|
||||
mock_get_netmask_for_address.return_value = '255.255.255.0'
|
||||
self.determine_apache_port.return_value = '34'
|
||||
mock_api_port.return_value = '12'
|
||||
|
||||
ctxt = context.HAProxyContext()
|
||||
|
||||
self.maxDiff = None
|
||||
self.assertEquals(
|
||||
ctxt(),
|
||||
{'listen_ports': {'admin_port': 'keystone',
|
||||
'public_port': 'keystone'},
|
||||
{'listen_ports': {'admin_port': '12',
|
||||
'public_port': '12'},
|
||||
'local_host': '127.0.0.1',
|
||||
'haproxy_host': '0.0.0.0',
|
||||
'stat_port': ':8888',
|
||||
'service_ports': {'admin-port': ['keystone', '34'],
|
||||
'public-port': ['keystone', '34']},
|
||||
'service_ports': {'admin-port': ['12', '34'],
|
||||
'public-port': ['12', '34']},
|
||||
'default_backend': '1.2.3.4',
|
||||
'frontends': {'1.2.3.4': {
|
||||
'network': '1.2.3.4/255.255.255.0',
|
||||
|
@@ -92,9 +92,9 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
self.configure_installation_source.assert_called_with(repo)
|
||||
self.assertTrue(self.apt_update.called)
|
||||
self.apt_install.assert_called_with(
|
||||
['haproxy', 'unison', 'python-keystoneclient',
|
||||
'uuid', 'python-mysqldb', 'openssl', 'apache2',
|
||||
'pwgen', 'python-six', 'keystone', 'python-psycopg2'], fatal=True)
|
||||
['apache2', 'haproxy', 'keystone', 'openssl', 'pwgen',
|
||||
'python-keystoneclient', 'python-mysqldb', 'python-psycopg2',
|
||||
'python-six', 'unison', 'uuid'], fatal=True)
|
||||
self.git_install.assert_called_with(None)
|
||||
|
||||
@patch.object(utils, 'git_install_requested')
|
||||
@@ -120,12 +120,12 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
self.configure_installation_source.assert_called_with(repo)
|
||||
self.assertTrue(self.apt_update.called)
|
||||
self.apt_install.assert_called_with(
|
||||
['haproxy', 'unison', 'python-setuptools', 'python-keystoneclient',
|
||||
'uuid', 'python-mysqldb', 'libmysqlclient-dev', 'libssl-dev',
|
||||
'openssl', 'libffi-dev', 'apache2', 'python-pip', 'pwgen',
|
||||
'python-six', 'libxslt1-dev', 'python-psycopg2', 'libyaml-dev',
|
||||
'zlib1g-dev', 'python-dev', 'libxml2-dev'],
|
||||
fatal=True)
|
||||
['apache2', 'haproxy', 'libffi-dev', 'libmysqlclient-dev',
|
||||
'libssl-dev', 'libxml2-dev', 'libxslt1-dev', 'libyaml-dev',
|
||||
'openssl', 'pwgen', 'python-dev', 'python-keystoneclient',
|
||||
'python-mysqldb', 'python-pip', 'python-psycopg2',
|
||||
'python-setuptools', 'python-six', 'unison', 'uuid',
|
||||
'zlib1g-dev'], fatal=True)
|
||||
self.git_install.assert_called_with(projects_yaml)
|
||||
|
||||
mod_ch_openstack_utils = 'charmhelpers.contrib.openstack.utils'
|
||||
|
@@ -281,11 +281,13 @@ class TestKeystoneUtils(CharmTestCase):
|
||||
self.relation_set.assert_called_with(relation_id=relation_id,
|
||||
**filtered)
|
||||
|
||||
@patch('charmhelpers.contrib.openstack.ip.config')
|
||||
@patch.object(utils, 'ensure_valid_service')
|
||||
@patch.object(utils, 'add_endpoint')
|
||||
@patch.object(manager, 'KeystoneManager')
|
||||
def test_add_service_to_keystone_nosubset(
|
||||
self, KeystoneManager, add_endpoint, ensure_valid_service):
|
||||
self, KeystoneManager, add_endpoint, ensure_valid_service,
|
||||
ip_config):
|
||||
relation_id = 'identity-service:0'
|
||||
remote_unit = 'unit/0'
|
||||
|
||||
|
Reference in New Issue
Block a user