diff options
author | 2017-03-23 06:19:54 -0700 | |
---|---|---|
committer | 2017-03-23 06:19:54 -0700 | |
commit | 88df88a19674ccc0017836941b8ee32eaadf19fb (patch) | |
tree | f930c90f75846ec8d8e33cf27325ff8fafc85d5c /charms/trusty/cassandra/tests/test_actions.py | |
parent | 9f50a40437477432a21b326b15c343ca6b8fe516 (diff) |
Deleted charms with wrong license. Will source them differently in future.
Change-Id: I0fc99ea03c6b6ca4701e63793cb2be60e56c7588
Signed-off-by: Stuart Mackie <wsmackie@juniper.net>
Diffstat (limited to 'charms/trusty/cassandra/tests/test_actions.py')
-rwxr-xr-x | charms/trusty/cassandra/tests/test_actions.py | 1156 |
1 files changed, 0 insertions, 1156 deletions
diff --git a/charms/trusty/cassandra/tests/test_actions.py b/charms/trusty/cassandra/tests/test_actions.py deleted file mode 100755 index f97df0c..0000000 --- a/charms/trusty/cassandra/tests/test_actions.py +++ /dev/null @@ -1,1156 +0,0 @@ -#!.venv3/bin/python3 - -# Copyright 2015 Canonical Ltd. -# -# This file is part of the Cassandra Charm for Juju. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 3, as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranties of -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import errno -from itertools import repeat -import os.path -import re -import shutil -import subprocess -import tempfile -from textwrap import dedent -import unittest -from unittest.mock import ANY, call, patch, sentinel -import yaml - -import cassandra -from charmhelpers.core import hookenv - -from tests.base import TestCaseBase -import actions -from coordinator import coordinator -import helpers - - -class TestActions(TestCaseBase): - def test_action_wrapper(self): - @actions.action - def somefunc(*args, **kw): - return 42, args, kw - - hookenv.hook_name.return_value = 'catch-fire' - - # The wrapper stripts the servicename argument, which we have no - # use for, logs a message and invokes the wrapped function. - hookenv.remote_unit.return_value = None - self.assertEqual(somefunc('sn', 1, foo=4), (42, (1,), dict(foo=4))) - hookenv.log.assert_called_once_with('** Action catch-fire/somefunc') - - # Different log message if there is a remote unit. - hookenv.log.reset_mock() - os.environ['JUJU_REMOTE_UNIT'] = 'foo' - self.assertEqual(somefunc('sn', 1, foo=4), (42, (1,), dict(foo=4))) - hookenv.log.assert_called_once_with( - '** Action catch-fire/somefunc (foo)') - - def test_revert_unchangeable_config(self): - config = hookenv.config() - - self.assertIn('datacenter', actions.UNCHANGEABLE_KEYS) - - # In the first hook, revert does nothing as there is nothing to - # revert too. - config['datacenter'] = 'mission_control' - self.assertTrue(config.changed('datacenter')) - actions.revert_unchangeable_config('') - self.assertEqual(config['datacenter'], 'mission_control') - - config.save() - config.load_previous() - config['datacenter'] = 'orbital_1' - - actions.revert_unchangeable_config('') - self.assertEqual(config['datacenter'], 'mission_control') # Reverted - - hookenv.log.assert_any_call(ANY, hookenv.ERROR) # Logged the problem. - - @patch('charmhelpers.core.hookenv.is_leader') - def test_leader_only(self, is_leader): - - @actions.leader_only - def f(*args, **kw): - return args, kw - - is_leader.return_value = False - self.assertIsNone(f(1, foo='bar')) - - is_leader.return_value = True - self.assertEqual(f(1, foo='bar'), ((1,), dict(foo='bar'))) - - def test_set_proxy(self): - # NB. Environment is already mocked. - os.environ['http_proxy'] = '' - os.environ['https_proxy'] = '' - actions.set_proxy('') - self.assertEqual(os.environ['http_proxy'], '') - self.assertEqual(os.environ['https_proxy'], '') - hookenv.config()['http_proxy'] = 'foo' - actions.set_proxy('') - self.assertEqual(os.environ['http_proxy'], 'foo') - self.assertEqual(os.environ['https_proxy'], 'foo') - - @patch('subprocess.check_call') - def test_preinstall(self, check_call): - # Noop if there are no preinstall hooks found running the - # install hook. - hookenv.hook_name.return_value = 'install' - actions.preinstall('') - self.assertFalse(check_call.called) - hookenv.log.assert_any_call('No preinstall hooks found') - - # If preinstall hooks are found running the install hook, - # the preinstall hooks are run. - hook_dirs = [] - hook_files = [] - for i in range(1, 3): - hook_dirs.append(os.path.join(hookenv.charm_dir(), - 'exec.d', str(i))) - hook_files.append(os.path.join(hook_dirs[-1], 'charm-pre-install')) - - os.makedirs(hook_dirs[-1]) - with open(hook_files[-1], 'w') as f1: - print('mocked', file=f1) - os.chmod(hook_files[-1], 0o755) - - check_call.reset_mock() - actions.preinstall('') - - calls = [call(['sh', '-c', f2]) for f2 in hook_files] - check_call.assert_has_calls(calls) - - # If a preinstall hook is not executable, a warning is raised. - hook_dir = os.path.join(hookenv.charm_dir(), 'exec.d', '55') - hook_file = os.path.join(hook_dir, 'charm-pre-install') - os.makedirs(hook_dir) - with open(hook_file, 'w') as f1: - print('whoops', file=f1) - os.chmod(hook_file, 0o644) - check_call.reset_mock() - hookenv.log.reset_mock() - actions.preinstall('') - check_call.assert_has_calls(calls) # Only previous hooks run. - hookenv.log.assert_has_calls([ - call(ANY), - call(ANY), - call(ANY, hookenv.WARNING)]) - - # Nothing happens if the install hook is not being run. - hookenv.hook_name.return_value = 'config-changed' - check_call.reset_mock() - actions.preinstall('') - self.assertFalse(check_call.called) - - @patch('subprocess.check_call') - def test_swapoff(self, check_call): - fstab = ( - b'UUID=abc / ext4 errors=remount-ro 0 1\n' - b'/dev/mapper/cryptswap1 none swap sw 0 0') - with tempfile.NamedTemporaryFile() as f: - f.write(fstab) - f.flush() - actions.swapoff('', f.name) - f.seek(0) - self.assertTrue(b'swap' not in f.read()) - - check_call.assert_called_once_with(['swapoff', '-a']) - - @patch('subprocess.check_call') - def test_swapoff_fails(self, check_call): - check_call.side_effect = RuntimeError() - actions.swapoff('', '') - # A warning is generated if swapoff fails. - hookenv.log.assert_any_call(ANY, hookenv.WARNING) - - @patch('subprocess.check_call') - def test_swapoff_lxc(self, check_call): - # Under LXC, the swapoff action does nothing except log. - helpers.is_lxc.return_value = True - actions.swapoff('') - self.assertFalse(check_call.called) - - @patch('charmhelpers.fetch.configure_sources') - def test_configure_sources(self, configure_sources): - config = hookenv.config() - - # fetch.configure_sources called the first time - actions.configure_sources('') - configure_sources.assert_called_once_with(True) - - # fetch.configure_sources not called if relevant config is unchanged. - config.save() - config.load_previous() - configure_sources.reset_mock() - actions.configure_sources('') - self.assertFalse(configure_sources.called) - - # Changing install_sources causes fetch.configure_sources to be - # called. - config.save() - config.load_previous() - configure_sources.reset_mock() - config['install_sources'] = 'foo' - actions.configure_sources('') - configure_sources.assert_called_once_with(True) - - # Changing install_keys causes fetch.configure_sources to be - # called. - config.save() - config.load_previous() - configure_sources.reset_mock() - config['install_keys'] = 'foo' - actions.configure_sources('') - configure_sources.assert_called_once_with(True) - - @patch('charmhelpers.core.hookenv.charm_dir') - @patch('subprocess.check_call') - def test_add_implicit_package_signing_keys(self, check_call, charm_dir): - charm_dir.return_value = os.path.join(os.path.dirname(__file__), - os.pardir) - actions.add_implicit_package_signing_keys('') - - keys = ['apache', 'datastax'] - - self.assertEqual(check_call.call_count, len(keys)) - - for k in keys: - with self.subTest(key=k): - path = os.path.join(hookenv.charm_dir(), - 'lib', '{}.key'.format(k)) - self.assertTrue(os.path.exists(path)) - check_call.assert_any_call(['apt-key', 'add', path], - stdin=subprocess.DEVNULL) - - @patch('charmhelpers.core.host.write_file') - @patch('subprocess.check_call') - def test_reset_sysctl(self, check_call, write_file): - actions.reset_sysctl('') - - ctl_file = '/etc/sysctl.d/99-cassandra.conf' - # Magic value per Cassandra best practice. - write_file.assert_called_once_with(ctl_file, - b"vm.max_map_count = 131072\n") - check_call.assert_called_once_with(['sysctl', '-p', - '/etc/sysctl.d/99-cassandra.conf']) - - @patch('subprocess.check_call') - @patch('charmhelpers.core.host.write_file') - def test_reset_sysctl_expected_fails(self, write_file, check_call): - check_call.side_effect = repeat(OSError(errno.EACCES, - 'Permission Denied')) - actions.reset_sysctl('') - # A warning is generated if permission denied was raised. - hookenv.log.assert_any_call(ANY, hookenv.WARNING) - - @patch('subprocess.check_call') - @patch('charmhelpers.core.host.write_file') - def test_reset_sysctl_fails_badly(self, write_file, check_call): - # Other OSErrors are reraised since we don't know how to handle - # them. - check_call.side_effect = repeat(OSError(errno.EFAULT, 'Whoops')) - self.assertRaises(OSError, actions.reset_sysctl, '') - - @patch('subprocess.check_call') - def test_reset_sysctl_lxc(self, check_call): - helpers.is_lxc.return_value = True - actions.reset_sysctl('') - self.assertFalse(check_call.called) - hookenv.log.assert_any_call('In an LXC. ' - 'Leaving sysctl unchanged.') - - @patch('helpers.get_cassandra_packages') - @patch('helpers.ensure_package_status') - def test_ensure_cassandra_package_status(self, ensure_package_status, - get_cassandra_packages): - get_cassandra_packages.return_value = sentinel.cassandra_packages - actions.ensure_cassandra_package_status('') - ensure_package_status.assert_called_once_with( - sentinel.cassandra_packages) - - @patch('subprocess.check_call') - @patch('helpers.get_jre') - @patch('helpers.get_cassandra_packages') - @patch('helpers.install_packages') - def test_install_cassandra_packages(self, install_packages, - get_cassandra_packages, - get_jre, check_call): - get_cassandra_packages.return_value = sentinel.cassandra_packages - get_jre.return_value = 'openjdk' - actions.install_cassandra_packages('') - install_packages.assert_called_once_with(sentinel.cassandra_packages) - check_call.assert_called_once_with(['update-java-alternatives', - '--jre-headless', '--set', - 'java-1.8.0-openjdk-amd64']) - - @patch('subprocess.check_call') - @patch('helpers.get_jre') - @patch('helpers.get_cassandra_packages') - @patch('helpers.install_packages') - def test_install_cassandra_packages_oracle(self, install_packages, - get_cassandra_packages, - get_jre, check_call): - get_cassandra_packages.return_value = sentinel.cassandra_packages - get_jre.return_value = 'oracle' - actions.install_cassandra_packages('') - install_packages.assert_called_once_with(sentinel.cassandra_packages) - # No alternatives selected, as the Oracle JRE installer method - # handles this. - self.assertFalse(check_call.called) - - @patch('actions._install_oracle_jre_tarball') - @patch('actions._fetch_oracle_jre') - def test_install_oracle_jre(self, fetch, install_tarball): - fetch.return_value = sentinel.tarball - - actions.install_oracle_jre('') - self.assertFalse(fetch.called) - self.assertFalse(install_tarball.called) - - hookenv.config()['jre'] = 'oracle' - actions.install_oracle_jre('') - fetch.assert_called_once_with() - install_tarball.assert_called_once_with(sentinel.tarball) - - @patch('helpers.status_set') - @patch('urllib.request') - def test_fetch_oracle_jre(self, req, status_set): - config = hookenv.config() - url = 'https://foo.example.com/server-jre-7u42-linux-x64.tar.gz' - expected_tarball = os.path.join(hookenv.charm_dir(), 'lib', - 'server-jre-7u42-linux-x64.tar.gz') - config['private_jre_url'] = url - - # Create a dummy tarball, since the mock urlretrieve won't. - os.makedirs(os.path.dirname(expected_tarball)) - with open(expected_tarball, 'w'): - pass # Empty file - - self.assertEqual(actions._fetch_oracle_jre(), expected_tarball) - req.urlretrieve.assert_called_once_with(url, expected_tarball) - - def test_fetch_oracle_jre_local(self): - # Create an existing tarball. If it is found, it will be used - # without needing to specify a remote url or actually download - # anything. - expected_tarball = os.path.join(hookenv.charm_dir(), 'lib', - 'server-jre-7u42-linux-x64.tar.gz') - os.makedirs(os.path.dirname(expected_tarball)) - with open(expected_tarball, 'w'): - pass # Empty file - - self.assertEqual(actions._fetch_oracle_jre(), expected_tarball) - - @patch('helpers.status_set') - def test_fetch_oracle_jre_notfound(self, status_set): - with self.assertRaises(SystemExit) as x: - actions._fetch_oracle_jre() - self.assertEqual(x.code, 0) - status_set.assert_called_once_with('blocked', ANY) - - @patch('subprocess.check_call') - @patch('charmhelpers.core.host.mkdir') - @patch('os.path.isdir') - def test_install_oracle_jre_tarball(self, isdir, mkdir, check_call): - isdir.return_value = False - - dest = '/usr/lib/jvm/java-8-oracle' - - actions._install_oracle_jre_tarball(sentinel.tarball) - mkdir.assert_called_once_with(dest) - check_call.assert_has_calls([ - call(['tar', '-xz', '-C', dest, - '--strip-components=1', '-f', sentinel.tarball]), - call(['update-alternatives', '--install', - '/usr/bin/java', 'java', - os.path.join(dest, 'bin', 'java'), '1']), - call(['update-alternatives', '--set', 'java', - os.path.join(dest, 'bin', 'java')]), - call(['update-alternatives', '--install', - '/usr/bin/javac', 'javac', - os.path.join(dest, 'bin', 'javac'), '1']), - call(['update-alternatives', '--set', 'javac', - os.path.join(dest, 'bin', 'javac')])]) - - @patch('os.path.exists') - @patch('subprocess.check_call') - @patch('charmhelpers.core.host.mkdir') - @patch('os.path.isdir') - def test_install_oracle_jre_tarball_already(self, isdir, - mkdir, check_call, exists): - isdir.return_value = True - exists.return_value = True # jre already installed - - # Store the version previously installed. - hookenv.config()['oracle_jre_tarball'] = sentinel.tarball - - dest = '/usr/lib/jvm/java-8-oracle' - - actions._install_oracle_jre_tarball(sentinel.tarball) - - self.assertFalse(mkdir.called) # The jvm dir already existed. - - exists.assert_called_once_with('/usr/lib/jvm/java-8-oracle/bin/java') - - # update-alternatives done, but tarball not extracted. - check_call.assert_has_calls([ - call(['update-alternatives', '--install', - '/usr/bin/java', 'java', - os.path.join(dest, 'bin', 'java'), '1']), - call(['update-alternatives', '--set', 'java', - os.path.join(dest, 'bin', 'java')]), - call(['update-alternatives', '--install', - '/usr/bin/javac', 'javac', - os.path.join(dest, 'bin', 'javac'), '1']), - call(['update-alternatives', '--set', 'javac', - os.path.join(dest, 'bin', 'javac')])]) - - @patch('subprocess.check_output') - def test_emit_java_version(self, check_output): - check_output.return_value = 'Line 1\nLine 2' - actions.emit_java_version('') - check_output.assert_called_once_with(['java', '-version'], - universal_newlines=True) - hookenv.log.assert_has_calls([call(ANY), - call('JRE: Line 1'), - call('JRE: Line 2')]) - - @patch('helpers.configure_cassandra_yaml') - def test_configure_cassandra_yaml(self, configure_cassandra_yaml): - # actions.configure_cassandra_yaml is just a wrapper around the - # helper. - actions.configure_cassandra_yaml('') - configure_cassandra_yaml.assert_called_once_with() - - @patch('helpers.get_cassandra_env_file') - @patch('charmhelpers.core.host.write_file') - def test_configure_cassandra_env(self, write_file, env_file): - def _wf(path, contents, perms=None): - with open(path, 'wb') as f: - f.write(contents) - write_file.side_effect = _wf - - # cassandra-env.sh is a shell script that unfortunately - # embeds configuration we need to change. - existing_config = dedent('''\ - Everything is ignored - unless a regexp matches - #MAX_HEAP_SIZE="1G" - #HEAP_NEWSIZE="800M" - #JMX_PORT="1234" - And done - ''') - - with tempfile.TemporaryDirectory() as tempdir: - cassandra_env = os.path.join(tempdir, 'c.sh') - env_file.return_value = cassandra_env - - with open(cassandra_env, 'w', encoding='UTF-8') as f: - f.write(existing_config) - - overrides = dict( - max_heap_size=re.compile('^MAX_HEAP_SIZE=(.*)$', re.M), - heap_newsize=re.compile('^HEAP_NEWSIZE=(.*)$', re.M)) - - for key in overrides: - hookenv.config()[key] = '' - - # By default, the settings will be commented out. - actions.configure_cassandra_env('') - with open(cassandra_env, 'r', encoding='UTF-8') as f: - generated_env = f.read() - for config_key, regexp in overrides.items(): - with self.subTest(override=config_key): - self.assertIsNone(regexp.search(generated_env)) - - # Settings can be overridden. - for config_key, regexp in overrides.items(): - hookenv.config()[config_key] = '{} val'.format(config_key) - actions.configure_cassandra_env('') - with open(cassandra_env, 'r') as f: - generated_env = f.read() - for config_key, regexp in overrides.items(): - with self.subTest(override=config_key): - match = regexp.search(generated_env) - self.assertIsNotNone(match) - # Note the value has been shell quoted. - self.assertTrue( - match.group(1).startswith( - "'{} val'".format(config_key))) - - # Settings can be returned to the defaults. - for config_key, regexp in overrides.items(): - hookenv.config()[config_key] = '' - actions.configure_cassandra_env('') - with open(cassandra_env, 'r', encoding='UTF-8') as f: - generated_env = f.read() - for config_key, regexp in overrides.items(): - with self.subTest(override=config_key): - self.assertIsNone(regexp.search(generated_env)) - - @patch('helpers.get_cassandra_rackdc_file') - def test_configure_cassandra_rackdc(self, rackdc_file): - hookenv.config()['datacenter'] = 'test_dc' - hookenv.config()['rack'] = 'test_rack' - with tempfile.NamedTemporaryFile() as rackdc: - rackdc_file.return_value = rackdc.name - actions.configure_cassandra_rackdc('') - with open(rackdc.name, 'r') as f: - self.assertEqual(f.read().strip(), - 'dc=test_dc\nrack=test_rack') - - @patch('helpers.connect') - @patch('helpers.get_auth_keyspace_replication') - @patch('helpers.num_nodes') - def test_needs_reset_auth_keyspace_replication(self, num_nodes, - get_auth_ks_rep, - connect): - num_nodes.return_value = 4 - connect().__enter__.return_value = sentinel.session - connect().__exit__.return_value = False - get_auth_ks_rep.return_value = {'another': '8'} - self.assertTrue(actions.needs_reset_auth_keyspace_replication()) - - @patch('helpers.connect') - @patch('helpers.get_auth_keyspace_replication') - @patch('helpers.num_nodes') - def test_needs_reset_auth_keyspace_replication_false(self, num_nodes, - get_auth_ks_rep, - connect): - config = hookenv.config() - config['datacenter'] = 'mydc' - connect().__enter__.return_value = sentinel.session - connect().__exit__.return_value = False - - num_nodes.return_value = 3 - get_auth_ks_rep.return_value = {'another': '8', - 'mydc': '3'} - self.assertFalse(actions.needs_reset_auth_keyspace_replication()) - - @patch('helpers.set_active') - @patch('helpers.repair_auth_keyspace') - @patch('helpers.connect') - @patch('helpers.set_auth_keyspace_replication') - @patch('helpers.get_auth_keyspace_replication') - @patch('helpers.num_nodes') - @patch('charmhelpers.core.hookenv.is_leader') - def test_reset_auth_keyspace_replication(self, is_leader, num_nodes, - get_auth_ks_rep, - set_auth_ks_rep, - connect, repair, set_active): - is_leader.return_value = True - num_nodes.return_value = 4 - coordinator.grants = {} - coordinator.requests = {hookenv.local_unit(): {}} - coordinator.grant('repair', hookenv.local_unit()) - config = hookenv.config() - config['datacenter'] = 'mydc' - connect().__enter__.return_value = sentinel.session - connect().__exit__.return_value = False - get_auth_ks_rep.return_value = {'another': '8'} - self.assertTrue(actions.needs_reset_auth_keyspace_replication()) - actions.reset_auth_keyspace_replication('') - set_auth_ks_rep.assert_called_once_with( - sentinel.session, - {'class': 'NetworkTopologyStrategy', 'another': '8', 'mydc': 4}) - repair.assert_called_once_with() - set_active.assert_called_once_with() - - def test_store_unit_private_ip(self): - hookenv.unit_private_ip.side_effect = None - hookenv.unit_private_ip.return_value = sentinel.ip - actions.store_unit_private_ip('') - self.assertEqual(hookenv.config()['unit_private_ip'], sentinel.ip) - - @patch('charmhelpers.core.host.service_start') - @patch('helpers.status_set') - @patch('helpers.actual_seed_ips') - @patch('helpers.get_seed_ips') - @patch('relations.StorageRelation.needs_remount') - @patch('helpers.is_bootstrapped') - @patch('helpers.is_cassandra_running') - @patch('helpers.is_decommissioned') - def test_needs_restart(self, is_decom, is_running, is_bootstrapped, - needs_remount, seed_ips, actual_seeds, - status_set, service_start): - is_decom.return_value = False - is_running.return_value = True - needs_remount.return_value = False - seed_ips.return_value = set(['1.2.3.4']) - actual_seeds.return_value = set(['1.2.3.4']) - - config = hookenv.config() - config['configured_seeds'] = list(sorted(seed_ips())) - config.save() - config.load_previous() # Ensure everything flagged as unchanged. - - self.assertFalse(actions.needs_restart()) - - # Decommissioned nodes are not restarted. - is_decom.return_value = True - self.assertFalse(actions.needs_restart()) - is_decom.return_value = False - self.assertFalse(actions.needs_restart()) - - # Nodes not running need to be restarted. - is_running.return_value = False - self.assertTrue(actions.needs_restart()) - is_running.return_value = True - self.assertFalse(actions.needs_restart()) - - # If we have a new mountpoint, we need to restart in order to - # migrate data. - needs_remount.return_value = True - self.assertTrue(actions.needs_restart()) - needs_remount.return_value = False - self.assertFalse(actions.needs_restart()) - - # Certain changed config items trigger a restart. - config['max_heap_size'] = '512M' - self.assertTrue(actions.needs_restart()) - config.save() - config.load_previous() - self.assertFalse(actions.needs_restart()) - - # A new IP address requires a restart. - config['unit_private_ip'] = 'new' - self.assertTrue(actions.needs_restart()) - config.save() - config.load_previous() - self.assertFalse(actions.needs_restart()) - - # If the seeds have changed, we need to restart. - seed_ips.return_value = set(['9.8.7.6']) - actual_seeds.return_value = set(['9.8.7.6']) - self.assertTrue(actions.needs_restart()) - is_running.side_effect = iter([False, True]) - helpers.start_cassandra() - is_running.side_effect = None - is_running.return_value = True - self.assertFalse(actions.needs_restart()) - - @patch('charmhelpers.core.hookenv.is_leader') - @patch('helpers.is_bootstrapped') - @patch('helpers.ensure_database_directories') - @patch('helpers.remount_cassandra') - @patch('helpers.start_cassandra') - @patch('helpers.stop_cassandra') - @patch('helpers.status_set') - def test_maybe_restart(self, status_set, stop_cassandra, start_cassandra, - remount, ensure_directories, is_bootstrapped, - is_leader): - coordinator.grants = {} - coordinator.requests = {hookenv.local_unit(): {}} - coordinator.relid = 'cluster:1' - coordinator.grant('restart', hookenv.local_unit()) - actions.maybe_restart('') - stop_cassandra.assert_called_once_with() - remount.assert_called_once_with() - ensure_directories.assert_called_once_with() - start_cassandra.assert_called_once_with() - - @patch('helpers.stop_cassandra') - def test_stop_cassandra(self, helpers_stop_cassandra): - actions.stop_cassandra('ignored') - helpers_stop_cassandra.assert_called_once_with() - - @patch('helpers.start_cassandra') - def test_start_cassandra(self, helpers_start_cassandra): - actions.start_cassandra('ignored') - helpers_start_cassandra.assert_called_once_with() - - @patch('os.path.isdir') - @patch('helpers.get_all_database_directories') - @patch('helpers.set_io_scheduler') - def test_reset_all_io_schedulers(self, set_io_scheduler, dbdirs, isdir): - hookenv.config()['io_scheduler'] = sentinel.io_scheduler - dbdirs.return_value = dict( - data_file_directories=[sentinel.d1, sentinel.d2], - commitlog_directory=sentinel.cl, - saved_caches_directory=sentinel.sc) - isdir.return_value = True - actions.reset_all_io_schedulers('') - set_io_scheduler.assert_has_calls([ - call(sentinel.io_scheduler, sentinel.d1), - call(sentinel.io_scheduler, sentinel.d2), - call(sentinel.io_scheduler, sentinel.cl), - call(sentinel.io_scheduler, sentinel.sc)], - any_order=True) - - # If directories don't exist yet, nothing happens. - set_io_scheduler.reset_mock() - isdir.return_value = False - actions.reset_all_io_schedulers('') - self.assertFalse(set_io_scheduler.called) - - def test_config_key_lists_complete(self): - # Ensure that we have listed all keys in either - # RESTART_REQUIRED_KEYS, RESTART_NOT_REQUIRED_KEYS or - # UNCHANGEABLE_KEYS. This is to ensure that RESTART_REQUIRED_KEYS - # is maintained as new config items are added over time. - config_path = os.path.join(os.path.dirname(__file__), os.pardir, - 'config.yaml') - with open(config_path, 'r') as f: - config = yaml.safe_load(f) - - combined = actions.RESTART_REQUIRED_KEYS.union( - actions.RESTART_NOT_REQUIRED_KEYS).union( - actions.UNCHANGEABLE_KEYS) - - for key in config['options']: - with self.subTest(key=key): - self.assertIn(key, combined) - - @patch('actions._publish_database_relation') - def test_publish_database_relations(self, publish_db_rel): - actions.publish_database_relations('') - publish_db_rel.assert_called_once_with('database:1', superuser=False) - - @patch('actions._publish_database_relation') - def test_publish_database_admin_relations(self, publish_db_rel): - actions.publish_database_admin_relations('') - publish_db_rel.assert_called_once_with('database-admin:1', - superuser=True) - - @patch('helpers.leader_ping') - @patch('helpers.ensure_user') - @patch('helpers.connect') - @patch('helpers.get_service_name') - @patch('helpers.encrypt_password') - @patch('charmhelpers.core.host.pwgen') - @patch('charmhelpers.core.hookenv.is_leader') - @patch('actions._client_credentials') - def test_publish_database_relation_leader(self, client_creds, is_leader, - pwgen, encrypt_password, - get_service_name, - connect, ensure_user, - leader_ping): - is_leader.return_value = True # We are the leader. - client_creds.return_value = (None, None) # No creds published yet. - - get_service_name.return_value = 'cservice' - pwgen.side_effect = iter(['secret1', 'secret2']) - encrypt_password.side_effect = iter(['crypt1', 'crypt2']) - connect().__enter__.return_value = sentinel.session - - config = hookenv.config() - config['native_transport_port'] = 666 - config['rpc_port'] = 777 - config['cluster_name'] = 'fred' - config['datacenter'] = 'mission_control' - config['rack'] = '01' - - actions._publish_database_relation('database:1', superuser=False) - - ensure_user.assert_called_once_with(sentinel.session, - 'juju_cservice', 'crypt1', - False) - leader_ping.assert_called_once_with() # Peers woken. - - hookenv.relation_set.assert_has_calls([ - call('database:1', - username='juju_cservice', password='secret1', - host='10.30.0.1', native_transport_port=666, rpc_port=777, - cluster_name='fred', datacenter='mission_control', - rack='01')]) - - @patch('helpers.leader_ping') - @patch('helpers.ensure_user') - @patch('helpers.connect') - @patch('helpers.get_service_name') - @patch('helpers.encrypt_password') - @patch('charmhelpers.core.host.pwgen') - @patch('charmhelpers.core.hookenv.is_leader') - @patch('actions._client_credentials') - def test_publish_database_relation_super(self, client_creds, is_leader, - pwgen, encrypt_password, - get_service_name, - connect, ensure_user, - leader_ping): - is_leader.return_value = True # We are the leader. - client_creds.return_value = (None, None) # No creds published yet. - - get_service_name.return_value = 'cservice' - pwgen.side_effect = iter(['secret1', 'secret2']) - encrypt_password.side_effect = iter(['crypt1', 'crypt2']) - connect().__enter__.return_value = sentinel.session - - config = hookenv.config() - config['native_transport_port'] = 666 - config['rpc_port'] = 777 - config['cluster_name'] = 'fred' - config['datacenter'] = 'mission_control' - config['rack'] = '01' - - actions._publish_database_relation('database:1', superuser=True) - - ensure_user.assert_called_once_with(sentinel.session, - 'juju_cservice_admin', 'crypt1', - True) - - @patch('charmhelpers.core.host.write_file') - def test_install_maintenance_crontab(self, write_file): - # First 7 units get distributed, one job per day. - hookenv.local_unit.return_value = 'foo/0' - actions.install_maintenance_crontab('') - write_file.assert_called_once_with('/etc/cron.d/cassandra-maintenance', - ANY) - contents = write_file.call_args[0][1] - # Not the complete command, but includes all the expanded - # variables. - expected = (b'\n0 0 * * 0 cassandra run-one-until-success ' - b'nodetool repair -pr') - self.assertIn(expected, contents) - - # Next 7 units distributed 12 hours out of sync with the first - # batch. - hookenv.local_unit.return_value = 'foo/8' - actions.install_maintenance_crontab('') - contents = write_file.call_args[0][1] - expected = (b'\n0 12 * * 1 cassandra run-one-until-success ' - b'nodetool repair -pr') - self.assertIn(expected, contents) - - # Later units per helpers.week_spread() - hookenv.local_unit.return_value = 'foo/411' - actions.install_maintenance_crontab('') - contents = write_file.call_args[0][1] - expected = (b'\n37 8 * * 5 cassandra run-one-until-success ' - b'nodetool repair -pr') - self.assertIn(expected, contents) - - @patch('helpers.emit_netstats') - @patch('helpers.emit_status') - @patch('helpers.emit_describe_cluster') - def test_emit_cluster_info(self, emit_desc, emit_status, emit_netstats): - actions.emit_cluster_info('') - emit_desc.assert_called_once_with() - emit_status.assert_called_once_with() - emit_netstats.assert_called_once_with() - - @patch('charmhelpers.core.hookenv.relations_of_type') - @patch('actions.ufw') - def test_configure_firewall(self, ufw, rel_of_type): - rel_of_type.side_effect = iter([[{'private-address': '1.1.0.1'}, - {'private-address': '1.1.0.2'}], - []]) - actions.configure_firewall('') - - # Confirm our mock provided the expected data. - rel_of_type.assert_has_calls([call('cluster'), call('database-admin')]) - - ufw.enable.assert_called_once_with(soft_fail=True) # Always enabled. - - # SSH and the client protocol ports are always fully open. - ufw.service.assert_has_calls([call('ssh', 'open'), - call('nrpe', 'open'), - call('rsync', 'open'), - call(9042, 'open'), - call(9160, 'open')]) - - # This test is running for the first time, so there are no - # previously applied rules to remove. It opens necessary access - # to peers and other related units. The 1.1.* addresses are - # peers, and they get storage (7000), ssl_storage (7001), - # JMX (7199), Thrift (9160) and native (9042). The remaining - # addresses are clients, getting just Thrift and native. - ufw.grant_access.assert_has_calls([call('1.1.0.1', 'any', 7000), - call('1.1.0.1', 'any', 7001), - - call('1.1.0.2', 'any', 7000), - call('1.1.0.2', 'any', 7001)], - any_order=True) - - # If things change in a later hook, unwanted rules are removed - # and new ones added. - config = hookenv.config() - config.save() - config.load_previous() - config['native_transport_port'] = 7777 # 9042 -> 7777 - config['storage_port'] = 7002 # 7000 -> 7002 - config['open_client_ports'] = True - ufw.reset_mock() - - rel_of_type.side_effect = iter([[], - [{'private-address': '1.1.0.1'}, - {'private-address': '1.1.0.2'}]]) - actions.configure_firewall('') - - # Three ports now globally open. Yes, having the globally open - # native and Thrift ports does make the later more specific - # rules meaningless, but we add the specific rules anyway. - ufw.service.assert_has_calls([call('ssh', 'open'), - call('nrpe', 'open'), - call(9042, 'close'), - call(7777, 'open'), - call(9160, 'open')], any_order=True) - ufw.revoke_access.assert_has_calls([call('1.1.0.1', 'any', 7000), - call('1.1.0.2', 'any', 7000)], - any_order=True) - ufw.grant_access.assert_has_calls([call('1.1.0.1', 'any', 7001), - call('1.1.0.1', 'any', 7002), - call('1.1.0.2', 'any', 7001), - call('1.1.0.2', 'any', 7002)], - any_order=True) - - @patch('helpers.mountpoint') - @patch('helpers.get_cassandra_version') - @patch('charmhelpers.core.host.write_file') - @patch('charmhelpers.contrib.charmsupport.nrpe.NRPE') - @patch('helpers.local_plugins_dir') - def test_nrpe_external_master_relation(self, local_plugins_dir, nrpe, - write_file, cassandra_version, - mountpoint): - mountpoint.side_effect = os.path.dirname - cassandra_version.return_value = '2.2' - # The fake charm_dir() needs populating. - plugin_src_dir = os.path.join(os.path.dirname(__file__), - os.pardir, 'files') - shutil.copytree(plugin_src_dir, - os.path.join(hookenv.charm_dir(), 'files')) - - with tempfile.TemporaryDirectory() as d: - local_plugins_dir.return_value = d - actions.nrpe_external_master_relation('') - - # The expected file was written to the expected filename - # with required perms. - with open(os.path.join(plugin_src_dir, 'check_cassandra_heap.sh'), - 'rb') as f: - write_file.assert_called_once_with( - os.path.join(d, 'check_cassandra_heap.sh'), f.read(), - perms=0o555) - - nrpe().add_check.assert_has_calls([ - call(shortname='cassandra_heap', - description='Check Cassandra Heap', - check_cmd='check_cassandra_heap.sh localhost 80 90'), - call(description=('Check Cassandra Disk ' - '/var/lib/cassandra'), - shortname='cassandra_disk_var_lib_cassandra', - check_cmd=('check_disk -u GB -w 50% -c 25% -K 5% ' - '-p /var/lib/cassandra'))], - any_order=True) - - nrpe().write.assert_called_once_with() - - @patch('helpers.get_cassandra_version') - @patch('charmhelpers.core.host.write_file') - @patch('os.path.exists') - @patch('charmhelpers.contrib.charmsupport.nrpe.NRPE') - def test_nrpe_external_master_relation_no_local(self, nrpe, exists, - write_file, ver): - ver.return_value = '2.2' - # If the local plugins directory doesn't exist, we don't attempt - # to write files to it. Wait until the subordinate has set it - # up. - exists.return_value = False - actions.nrpe_external_master_relation('') - self.assertFalse(write_file.called) - - @patch('helpers.mountpoint') - @patch('helpers.get_cassandra_version') - @patch('os.path.exists') - @patch('charmhelpers.contrib.charmsupport.nrpe.NRPE') - def test_nrpe_external_master_relation_disable_heapchk(self, nrpe, exists, - ver, mountpoint): - ver.return_value = '2.2' - exists.return_value = False - mountpoint.side_effect = os.path.dirname - - # Disable our checks - config = hookenv.config() - config['nagios_heapchk_warn_pct'] = 0 # Only one needs to be disabled. - config['nagios_heapchk_crit_pct'] = 90 - - actions.nrpe_external_master_relation('') - exists.assert_called_once_with(helpers.local_plugins_dir()) - - nrpe().add_check.assert_has_calls([ - call(shortname='cassandra_disk_var_lib_cassandra', - description=ANY, check_cmd=ANY)], any_order=True) - - @patch('helpers.get_cassandra_version') - @patch('os.path.exists') - @patch('charmhelpers.contrib.charmsupport.nrpe.NRPE') - def test_nrpe_external_master_relation_disable_diskchk(self, nrpe, - exists, ver): - ver.return_value = '2.2' - exists.return_value = False - - # Disable our checks - config = hookenv.config() - config['nagios_disk_warn_pct'] = 0 # Only one needs to be disabled. - config['magios_disk_crit_pct'] = 50 - - actions.nrpe_external_master_relation('') - exists.assert_called_once_with(helpers.local_plugins_dir()) - - nrpe().add_check.assert_called_once_with(shortname='cassandra_heap', - description=ANY, - check_cmd=ANY) - - @patch('helpers.get_bootstrapped_ips') - @patch('helpers.get_seed_ips') - @patch('charmhelpers.core.hookenv.leader_set') - @patch('charmhelpers.core.hookenv.is_leader') - def test_maintain_seeds(self, is_leader, leader_set, - seed_ips, bootstrapped_ips): - is_leader.return_value = True - - seed_ips.return_value = set(['1.2.3.4']) - bootstrapped_ips.return_value = set(['2.2.3.4', '3.2.3.4', - '4.2.3.4', '5.2.3.4']) - - actions.maintain_seeds('') - leader_set.assert_called_once_with(seeds='2.2.3.4,3.2.3.4,4.2.3.4') - - @patch('helpers.get_bootstrapped_ips') - @patch('helpers.get_seed_ips') - @patch('charmhelpers.core.hookenv.leader_set') - @patch('charmhelpers.core.hookenv.is_leader') - def test_maintain_seeds_start(self, is_leader, leader_set, - seed_ips, bootstrapped_ips): - seed_ips.return_value = set() - bootstrapped_ips.return_value = set() - actions.maintain_seeds('') - # First seed is the first leader, which lets is get everything - # started. - leader_set.assert_called_once_with(seeds=hookenv.unit_private_ip()) - - @patch('charmhelpers.core.host.pwgen') - @patch('helpers.query') - @patch('helpers.set_unit_superusers') - @patch('helpers.ensure_user') - @patch('helpers.encrypt_password') - @patch('helpers.superuser_credentials') - @patch('helpers.connect') - @patch('charmhelpers.core.hookenv.is_leader') - @patch('charmhelpers.core.hookenv.leader_set') - @patch('charmhelpers.core.hookenv.leader_get') - def test_reset_default_password(self, leader_get, leader_set, is_leader, - connect, sup_creds, encrypt_password, - ensure_user, set_sups, query, pwgen): - is_leader.return_value = True - leader_get.return_value = None - connect().__enter__.return_value = sentinel.session - connect().__exit__.return_value = False - connect.reset_mock() - - sup_creds.return_value = (sentinel.username, sentinel.password) - encrypt_password.return_value = sentinel.pwhash - pwgen.return_value = sentinel.random_password - - actions.reset_default_password('') - - # First, a superuser account for the unit was created. - connect.assert_called_once_with('cassandra', 'cassandra', - timeout=120, auth_timeout=120) - encrypt_password.assert_called_once_with(sentinel.password) - ensure_user.assert_called_once_with(sentinel.session, - sentinel.username, - sentinel.pwhash, - superuser=True) - set_sups.assert_called_once_with([hookenv.local_unit()]) - - # After that, the default password is reset. - query.assert_called_once_with(sentinel.session, - 'ALTER USER cassandra WITH PASSWORD %s', - cassandra.ConsistencyLevel.ALL, - (sentinel.random_password,)) - - # Flag stored to avoid attempting this again. - leader_set.assert_called_once_with(default_admin_password_changed=True) - - @patch('helpers.connect') - @patch('charmhelpers.core.hookenv.is_leader') - @patch('charmhelpers.core.hookenv.leader_get') - def test_reset_default_password_noop(self, leader_get, is_leader, connect): - leader_get.return_value = True - is_leader.return_value = True - actions.reset_default_password('') # noop - self.assertFalse(connect.called) - - @patch('helpers.get_seed_ips') - @patch('helpers.status_set') - @patch('charmhelpers.core.hookenv.status_get') - @patch('charmhelpers.core.hookenv.is_leader') - def test_set_active(self, is_leader, status_get, status_set, seed_ips): - is_leader.return_value = False - status_get.return_value = ('waiting', '') - seed_ips.return_value = set() - actions.set_active('') - status_set.assert_called_once_with('active', 'Live node') - - @patch('helpers.get_seed_ips') - @patch('helpers.status_set') - @patch('charmhelpers.core.hookenv.status_get') - @patch('charmhelpers.core.hookenv.is_leader') - def test_set_active_seed(self, is_leader, - status_get, status_set, seed_ips): - is_leader.return_value = False - status_get.return_value = ('waiting', '') - seed_ips.return_value = set([hookenv.unit_private_ip()]) - actions.set_active('') - status_set.assert_called_once_with('active', 'Live seed') - - @patch('helpers.num_nodes') - @patch('helpers.get_seed_ips') - @patch('helpers.service_status_set') - @patch('helpers.status_set') - @patch('charmhelpers.core.hookenv.status_get') - @patch('charmhelpers.core.hookenv.is_leader') - def test_set_active_service(self, is_leader, - status_get, status_set, service_status_set, - seed_ips, num_nodes): - status_get.return_value = ('waiting', '') - is_leader.return_value = True - seed_ips.return_value = set([hookenv.unit_private_ip()]) - num_nodes.return_value = 1 - actions.set_active('') - service_status_set.assert_called_once_with('active', - 'Single node cluster') - - service_status_set.reset_mock() - num_nodes.return_value = 6 - actions.set_active('') - service_status_set.assert_called_once_with('active', - '6 node cluster') - - @patch('helpers.encrypt_password') - @patch('helpers.superuser_credentials') - @patch('helpers.peer_relid') - def test_request_unit_superuser(self, peer_relid, sup_creds, crypt): - peer_relid.return_value = sentinel.peer_relid - sup_creds.return_value = (sentinel.username, sentinel.password) - crypt.return_value = sentinel.pwhash - hookenv.relation_get.return_value = dict() - actions.request_unit_superuser('') - hookenv.relation_set.assert_called_once_with( - sentinel.peer_relid, - username=sentinel.username, pwhash=sentinel.pwhash) - - @patch('helpers.update_hosts_file') - @patch('socket.gethostname') - def test_update_etc_hosts(self, gethostname, update_hosts_file): - gethostname.return_value = sentinel.hostname - actions.update_etc_hosts('') - update_hosts_file.assert_called_once_with( - '/etc/hosts', {'10.20.0.1': sentinel.hostname}) - - -if __name__ == '__main__': - unittest.main(verbosity=2) |