Merge lp:~frankban/charms/trusty/redis/memory-management into lp:~juju-gui/charms/trusty/redis/trunk

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 9
Proposed branch: lp:~frankban/charms/trusty/redis/memory-management
Merge into: lp:~juju-gui/charms/trusty/redis/trunk
Diff against target: 555 lines (+302/-27)
8 files modified
config.yaml (+45/-3)
hooks/services.py (+3/-1)
hooks/serviceutils.py (+27/-0)
hooks/settings.py (+3/-0)
hooks/system.py (+33/-0)
tests/test_10_deploy.py (+26/-5)
unit_tests/test_serviceutils.py (+106/-18)
unit_tests/test_system.py (+59/-0)
To merge this branch: bzr merge lp:~frankban/charms/trusty/redis/memory-management
Reviewer Review Type Date Requested Status
Richard Harding Approve
Jay R. Wren (community) Approve
Review via email: mp+265136@code.launchpad.net

Description of the change

Implement memory management.

Add new maxmemory and maxmemory-policy charm
options. By setting them we should prevent
the whole system to become unresponsive when
running out of memory.

Also enable memory overcommit by default
(but it can be disabled) as suggested
at http://redis.io/topics/admin

Tests (including functional tests): `make check`.

QA:
- `juju bootstrap -e ec2`;
- `make deploy JUJU_ENV=ec2`;
- wait for the unit to be ready;
- play with redis, e.g.
  `juju ssh -e ec2 redis/0 telnet `juju run -e ec2 --unit redis/0 "unit-get private-address"` 6379`,
  then `set foo 42` in the telnet terminal, then `get foo` and then `quit` to
  exit;
- check that overcommit is enabled:
  `juju ssh -e ec2 redis/0 sysctl vm.overcommit_memory`;
- `juju set -e ec2 redis maxmemory=50m`;
- wait for the new configuration to apply;
- play with redis/telnet as usual to check it works:
  the telnet "config get maxmemory*" command should return
  sane values, including 50000000 and volatile-lru.
Done, thank you!

To post a comment you must log in.
Revision history for this message
Jay R. Wren (evarlast) wrote :

LGTM. QA worked exactly as stated. I also tested in LXC local env. I was concerned with sysctl working there, but it works (or fails) perfectly.

review: Approve
Revision history for this message
Richard Harding (rharding) wrote :

LGTM with a few questions/comments.

review: Approve
18. By Francesco Banconi

Improve memory overcommit option description.

Revision history for this message
Francesco Banconi (frankban) wrote :

Thanks for the reviews!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'config.yaml'
--- config.yaml 2015-06-19 09:47:04 +0000
+++ config.yaml 2015-07-20 11:52:30 +0000
@@ -44,6 +44,48 @@
44 Set the number of databases. The default database is DB 0. You can select44 Set the number of databases. The default database is DB 0. You can select
45 a different one on a per-connection basis using SELECT <dbid> where dbid45 a different one on a per-connection basis using SELECT <dbid> where dbid
46 is a number between 0 and 'databases'-1.46 is a number between 0 and 'databases'-1.
4747 maxmemory:
4848 type: string
4949 default: ""
50 description: |
51 Don't use more than the specified amount of memory.
52 When the memory limit is reached Redis will try to remove keys according
53 to the eviction policy selected (see "maxmemory-policy" below).
54 If Redis can't remove keys according to the policy, or if the policy is
55 set to "noeviction", Redis will start to reply with errors to commands
56 that would use more memory, like SET, LPUSH, and so on, and will continue
57 to reply to read-only commands like GET.
58 This option is usually useful when using Redis as an LRU cache, or to set
59 a hard memory limit for an instance (using the "noeviction" policy).
60 It is possible to specify the memory size in the usual form of 1k, 5GB,
61 4M and so forth:
62 - 1k: 1000 bytes;
63 - 1kb: 1024 bytes;
64 - 1m: 1000000 bytes;
65 - 1mb: 1024*1024 bytes;
66 - 1g: 1000000000 bytes;
67 - 1gb: 1024*1024*1024 bytes.
68 maxmemory-policy:
69 type: string
70 default: volatile-lru
71 description: |
72 how Redis will select what to remove when maxmemory is reached.
73 You can select among five behaviors:
74 - volatile-lru: remove the key with an expire set using an LRU algorithm;
75 - allkeys-lru: remove any key according to the LRU algorithm;
76 - volatile-random: remove a random key with an expire set;
77 - allkeys-random: remove a random key, any key;
78 - volatile-ttl: remove the key with the nearest expire time (minor TTL);
79 - noeviction: don't expire at all, just return an error on write
80 operations.
81 Note: with any of the above policies, Redis will return an error on write
82 operations, when there are no suitable keys for eviction.
83 memory-overcommit:
84 type: boolean
85 default: true
86 description: |
87 Set the Linux kernel overcommit memory setting to 1 so that no memory
88 overcommit handling is performed. When this is enabled, the performance
89 for memory-intensive tasks is increased, but there is more risk of memory
90 overload. This option is ignored when the charm is deployed to a local
91 environment.
5092
=== modified file 'hooks/services.py'
--- hooks/services.py 2015-05-25 13:34:16 +0000
+++ hooks/services.py 2015-07-20 11:52:30 +0000
@@ -26,8 +26,8 @@
26from charmhelpers.core.services import base26from charmhelpers.core.services import base
2727
28import hookutils28import hookutils
29import relations
29import serviceutils30import serviceutils
30import relations
3131
3232
33@hookutils.hook_name_logged33@hookutils.hook_name_logged
@@ -60,6 +60,7 @@
6060
61 # Callables called when required data is ready.61 # Callables called when required data is ready.
62 'data_ready': [62 'data_ready': [
63 serviceutils.write_sysctl(config),
63 serviceutils.write_config_file(64 serviceutils.write_config_file(
64 config,65 config,
65 db_relation=db_relation,66 db_relation=db_relation,
@@ -87,6 +88,7 @@
8788
88 # Callables called when required data is ready.89 # Callables called when required data is ready.
89 'data_ready': [90 'data_ready': [
91 serviceutils.write_sysctl(config),
90 serviceutils.write_config_file(92 serviceutils.write_config_file(
91 config,93 config,
92 db_relation=db_relation,94 db_relation=db_relation,
9395
=== modified file 'hooks/serviceutils.py'
--- hooks/serviceutils.py 2015-05-26 09:41:30 +0000
+++ hooks/serviceutils.py 2015-07-20 11:52:30 +0000
@@ -16,6 +16,7 @@
16import configfile16import configfile
17import hookutils17import hookutils
18import settings18import settings
19import system
1920
2021
21def service_start(port, previous_port, service_name):22def service_start(port, previous_port, service_name):
@@ -51,6 +52,9 @@
51 # Remove redis package and clean up files.52 # Remove redis package and clean up files.
52 hookutils.log('Removing system packages.')53 hookutils.log('Removing system packages.')
53 fetch.apt_purge(settings.PACKAGES)54 fetch.apt_purge(settings.PACKAGES)
55 # Restore the default sysctl values and remove the sysctl file.
56 hookutils.log('Cleaning up sysctl state.')
57 system.remove_sysctl()
5458
5559
56def write_config_file(60def write_config_file(
@@ -87,6 +91,20 @@
87 return callback91 return callback
8892
8993
94def write_sysctl(config):
95 """Handle sysctl configuration file creation.
96
97 Return a function that can be used as a callback in the services framework,
98 and that generates the redis syctl configuration file.
99 """
100 def callback(service_name):
101 hookutils.log('Setting up sysctl state.')
102 overcommit_memory = config.get('memory-overcommit', True)
103 system.write_sysctl(overcommit_memory=overcommit_memory)
104
105 return callback
106
107
90def _get_service_options(config, slave_relation=None):108def _get_service_options(config, slave_relation=None):
91 """Return a dict containing the redis service configuration options.109 """Return a dict containing the redis service configuration options.
92110
@@ -108,9 +126,18 @@
108 'tcp-keepalive': config['tcp-keepalive'],126 'tcp-keepalive': config['tcp-keepalive'],
109 'timeout': config['timeout'],127 'timeout': config['timeout'],
110 }128 }
129 # Add memory management options.
130 maxmemory = config.get('maxmemory', '').strip()
131 if maxmemory:
132 options.update({
133 'maxmemory': maxmemory,
134 'maxmemory-policy': config['maxmemory-policy'],
135 })
136 # Add authentication options.
111 password = config['password'].strip()137 password = config['password'].strip()
112 if password:138 if password:
113 options['requirepass'] = password139 options['requirepass'] = password
140 # Add master/slave relationship options.
114 if slave_relation is not None:141 if slave_relation is not None:
115 hookutils.log('Setting up slave relation.')142 hookutils.log('Setting up slave relation.')
116 # If slave_relation is defined, it is assumed that the relation is143 # If slave_relation is defined, it is assumed that the relation is
117144
=== modified file 'hooks/settings.py'
--- hooks/settings.py 2015-05-25 14:09:49 +0000
+++ hooks/settings.py 2015-07-20 11:52:30 +0000
@@ -8,6 +8,9 @@
8DEFAULT_REDIS_CONF = '/etc/redis/redis.conf'8DEFAULT_REDIS_CONF = '/etc/redis/redis.conf'
9REDIS_CONF = '/etc/redis/redis-charm.conf'9REDIS_CONF = '/etc/redis/redis-charm.conf'
1010
11# Define the path to the redis sysctl configuration file.
12SYSCTL_CONF = '/etc/sysctl.d/60-redis.conf'
13
11# Define Debian packages to be installed.14# Define Debian packages to be installed.
12PACKAGES = ['redis-server']15PACKAGES = ['redis-server']
1316
1417
=== added file 'hooks/system.py'
--- hooks/system.py 1970-01-01 00:00:00 +0000
+++ hooks/system.py 2015-07-20 11:52:30 +0000
@@ -0,0 +1,33 @@
1# Copyright 2015 Canonical Ltd.
2# Licensed under the GPLv3, see copyright file for details.
3
4"""Helpers for interacting with the operating system."""
5
6import os
7
8from charmhelpers.core import host
9
10import settings
11
12
13def write_sysctl(overcommit_memory=False):
14 """Set the sysctl "vm.overcommit_memory" based on the given boolean value.
15
16 The default value indicates the system default if values were not changed.
17 Also restart procps when done, so that new values take effect.
18
19 Note that this will not work when using the local provider, since /sys
20 is a read-only file system and sysctl options really belong to the host.
21 Anyway, this function still executes without errors.
22 """
23 value = 1 if overcommit_memory else 0
24 with open(settings.SYSCTL_CONF, 'w') as sysctl_file:
25 # See http://redis.io/topics/admin.
26 sysctl_file.write('vm.overcommit_memory = {}\n'.format(value))
27 host.service_restart('procps')
28
29
30def remove_sysctl():
31 """Remove the sysctl configuration file for redis."""
32 write_sysctl()
33 os.remove(settings.SYSCTL_CONF)
034
=== modified file 'tests/test_10_deploy.py'
--- tests/test_10_deploy.py 2015-05-25 15:59:59 +0000
+++ tests/test_10_deploy.py 2015-07-20 11:52:30 +0000
@@ -25,11 +25,13 @@
25import settings25import settings
2626
2727
28# Define a test decorator for running the test only if the current environment28# Define a test decorator for running/skipping tests based on whether the suite
29# type is local.29# is using a local Juju environment.
30_env_type = helpers.get_environment_type()
30only_on_local_environments = unittest.skipIf(31only_on_local_environments = unittest.skipIf(
31 helpers.get_environment_type() != 'local',32 _env_type != 'local', 'only available when using a local environment')
32 'only available whe using a local environment')33not_on_local_environments = unittest.skipIf(
34 _env_type == 'local', 'not available when using a local environment')
3335
3436
35class TestDeployment(unittest.TestCase):37class TestDeployment(unittest.TestCase):
@@ -68,6 +70,12 @@
68 client.set('my-key', 'my-value')70 client.set('my-key', 'my-value')
69 self.assertEqual('my-value', client.get('my-key'))71 self.assertEqual('my-value', client.get('my-key'))
7072
73 @not_on_local_environments
74 def test_sysctl(self):
75 output, retcode = self.unit.run('sysctl vm.overcommit_memory')
76 self.assertEqual(0, retcode, output)
77 self.assertEqual('vm.overcommit_memory = 1', output)
78
7179
72class TestDeploymentOptions(unittest.TestCase):80class TestDeploymentOptions(unittest.TestCase):
7381
@@ -78,6 +86,9 @@
78 'loglevel': 'verbose',86 'loglevel': 'verbose',
79 'logfile': '/tmp/redis.log',87 'logfile': '/tmp/redis.log',
80 'timeout': 60,88 'timeout': 60,
89 'maxmemory': '1g',
90 'maxmemory-policy': 'noeviction',
91 'memory-overcommit': False,
81 }92 }
8293
83 @classmethod94 @classmethod
@@ -97,6 +108,8 @@
97 'databases 3\n'108 'databases 3\n'
98 'logfile /tmp/redis.log\n'109 'logfile /tmp/redis.log\n'
99 'loglevel verbose\n'110 'loglevel verbose\n'
111 'maxmemory 1g\n'
112 'maxmemory-policy noeviction\n'
100 'port 4242\n'113 'port 4242\n'
101 'requirepass secret\n'114 'requirepass secret\n'
102 'tcp-keepalive 0\n'115 'tcp-keepalive 0\n'
@@ -116,6 +129,12 @@
116 client.set('my-key', 'my-value')129 client.set('my-key', 'my-value')
117 self.assertEqual('my-value', client.get('my-key'))130 self.assertEqual('my-value', client.get('my-key'))
118131
132 @not_on_local_environments
133 def test_sysctl(self):
134 output, retcode = self.unit.run('sysctl vm.overcommit_memory')
135 self.assertEqual(0, retcode, output)
136 self.assertEqual('vm.overcommit_memory = 0', output)
137
119138
120class TestMasterSlaveRelation(unittest.TestCase):139class TestMasterSlaveRelation(unittest.TestCase):
121140
@@ -178,7 +197,7 @@
178197
179class TestMasterSlaveRelationOptions(unittest.TestCase):198class TestMasterSlaveRelationOptions(unittest.TestCase):
180199
181 master_options = {'databases': 5, 'password': 'secret'}200 master_options = {'databases': 5, 'password': 'secret', 'maxmemory': '2g'}
182 slave_options = {'port': 4747, 'loglevel': 'warning', 'timeout': 42}201 slave_options = {'port': 4747, 'loglevel': 'warning', 'timeout': 42}
183202
184 @classmethod203 @classmethod
@@ -202,6 +221,8 @@
202 'databases 5\n'221 'databases 5\n'
203 'logfile /var/log/redis/redis-server.log\n'222 'logfile /var/log/redis/redis-server.log\n'
204 'loglevel notice\n'223 'loglevel notice\n'
224 'maxmemory 2g\n'
225 'maxmemory-policy volatile-lru\n'
205 'port 6379\n'226 'port 6379\n'
206 'requirepass secret\n'227 'requirepass secret\n'
207 'tcp-keepalive 0\n'228 'tcp-keepalive 0\n'
208229
=== modified file 'unit_tests/test_serviceutils.py'
--- unit_tests/test_serviceutils.py 2015-05-25 15:34:53 +0000
+++ unit_tests/test_serviceutils.py 2015-07-20 11:52:30 +0000
@@ -79,6 +79,7 @@
79@mock.patch('charmhelpers.fetch.apt_purge')79@mock.patch('charmhelpers.fetch.apt_purge')
80@mock.patch('charmhelpers.core.host.service_stop')80@mock.patch('charmhelpers.core.host.service_stop')
81@mock.patch('charmhelpers.core.hookenv.close_port')81@mock.patch('charmhelpers.core.hookenv.close_port')
82@mock.patch('system.remove_sysctl')
82@mock.patch('hookutils.log')83@mock.patch('hookutils.log')
83class TestServiceStop(unittest.TestCase):84class TestServiceStop(unittest.TestCase):
8485
@@ -86,22 +87,23 @@
86 port = 637987 port = 6379
8788
88 def test_service_running_stop_hook(89 def test_service_running_stop_hook(
89 self, mock_log, mock_close_port, mock_service_stop,90 self, mock_log, mock_remove_sysctl, mock_close_port,
90 mock_apt_purge):91 mock_service_stop, mock_apt_purge):
91 with patch_service_running(True):92 with patch_service_running(True):
92 with patch_hook_name('stop'):93 with patch_hook_name('stop'):
93 serviceutils.service_stop(self.port, self.service_name)94 serviceutils.service_stop(self.port, self.service_name)
94 mock_service_stop.assert_called_once_with(settings.SERVICE_NAME)95 mock_service_stop.assert_called_once_with(settings.SERVICE_NAME)
95 self.assertEqual(2, mock_log.call_count)96 self.assertEqual(3, mock_log.call_count)
96 mock_log.assert_has_calls([97 mock_log.assert_has_calls([
97 mock.call('Stopping service {}.'.format(self.service_name)),98 mock.call('Stopping service {}.'.format(self.service_name)),
98 mock.call('Removing system packages.')99 mock.call('Removing system packages.'),
100 mock.call('Cleaning up sysctl state.'),
99 ])101 ])
100 mock_close_port.assert_called_once_with(self.port)102 mock_close_port.assert_called_once_with(self.port)
101103
102 def test_service_not_running_stop_hook(104 def test_service_not_running_stop_hook(
103 self, mock_log, mock_close_port, mock_service_stop,105 self, mock_log, mock_remove_sysctl, mock_close_port,
104 mock_apt_purge):106 mock_service_stop, mock_apt_purge):
105 with patch_service_running(False):107 with patch_service_running(False):
106 with patch_hook_name('stop'):108 with patch_hook_name('stop'):
107 serviceutils.service_stop(self.port, self.service_name)109 serviceutils.service_stop(self.port, self.service_name)
@@ -109,8 +111,8 @@
109 mock_close_port.assert_called_once_with(self.port)111 mock_close_port.assert_called_once_with(self.port)
110112
111 def test_customized_port(113 def test_customized_port(
112 self, mock_log, mock_close_port, mock_service_stop,114 self, mock_log, mock_remove_sysctl, mock_close_port,
113 mock_apt_purge):115 mock_service_stop, mock_apt_purge):
114 port = 4747116 port = 4747
115 with patch_service_running(False):117 with patch_service_running(False):
116 with patch_hook_name('stop'):118 with patch_hook_name('stop'):
@@ -118,18 +120,23 @@
118 mock_close_port.assert_called_once_with(port)120 mock_close_port.assert_called_once_with(port)
119121
120 def test_cleaning_up(122 def test_cleaning_up(
121 self, mock_log, mock_close_port, mock_service_stop,123 self, mock_log, mock_remove_sysctl, mock_close_port,
122 mock_apt_purge):124 mock_service_stop, mock_apt_purge):
123 port = 4747125 port = 4747
124 with patch_service_running(False):126 with patch_service_running(False):
125 with patch_hook_name('stop'):127 with patch_hook_name('stop'):
126 serviceutils.service_stop(port, self.service_name)128 serviceutils.service_stop(port, self.service_name)
127 mock_log.assert_called_once_with('Removing system packages.')129 self.assertEqual(2, mock_log.call_count)
130 mock_log.assert_has_calls([
131 mock.call('Removing system packages.'),
132 mock.call('Cleaning up sysctl state.'),
133 ])
128 mock_apt_purge.assert_called_once_with(settings.PACKAGES)134 mock_apt_purge.assert_called_once_with(settings.PACKAGES)
135 mock_remove_sysctl.assert_called_once_with()
129136
130 def test_service_running_other_hook(137 def test_service_running_other_hook(
131 self, mock_log, mock_close_port, mock_service_stop,138 self, mock_log, mock_remove_sysctl, mock_close_port,
132 mock_apt_purge):139 mock_service_stop, mock_apt_purge):
133 with patch_service_running(True):140 with patch_service_running(True):
134 with patch_hook_name('config-changed'):141 with patch_hook_name('config-changed'):
135 serviceutils.service_stop(self.port, self.service_name)142 serviceutils.service_stop(self.port, self.service_name)
@@ -139,8 +146,8 @@
139 self.assertFalse(mock_apt_purge.called)146 self.assertFalse(mock_apt_purge.called)
140147
141 def test_service_not_running_other_hook(148 def test_service_not_running_other_hook(
142 self, mock_log, mock_close_port, mock_service_stop,149 self, mock_log, mock_remove_sysctl, mock_close_port,
143 mock_apt_purge):150 mock_service_stop, mock_apt_purge):
144 with patch_service_running(False):151 with patch_service_running(False):
145 with patch_hook_name('config-changed'):152 with patch_hook_name('config-changed'):
146 serviceutils.service_stop(self.port, self.service_name)153 serviceutils.service_stop(self.port, self.service_name)
@@ -216,9 +223,38 @@
216 mocks.log.assert_has_calls([223 mocks.log.assert_has_calls([
217 mock.call('Retrieving service options.'),224 mock.call('Retrieving service options.'),
218 mock.call('Writing configuration file for foo.'),225 mock.call('Writing configuration file for foo.'),
219 mock.call('Restarting service due to configuration change.')226 mock.call('Restarting service due to configuration change.'),
220 ])227 ])
221228
229 def test_configuration_changed_maxmemory(self):
230 config = {
231 'databases': 3,
232 'logfile': '/path/to/logfile',
233 'loglevel': 'debug',
234 'maxmemory': '5gb',
235 'maxmemory-policy': 'volatile-lru',
236 'password': '',
237 'port': 4242,
238 'tcp-keepalive': 10,
239 'timeout': 42,
240 }
241 callback = serviceutils.write_config_file(config)
242 with self.patch_all(configuration_changed=True) as mocks:
243 callback('foo')
244 mocks.write.assert_called_once_with({
245 'bind': '1.2.3.4',
246 'databases': 3,
247 'maxmemory': '5gb',
248 'maxmemory-policy': 'volatile-lru',
249 'logfile': '/path/to/logfile',
250 'loglevel': 'debug',
251 'port': 4242,
252 'tcp-keepalive': 10,
253 'timeout': 42,
254 }, settings.REDIS_CONF)
255 mocks.unit_get.assert_called_once_with('private-address')
256 mocks.service_restart.assert_called_once_with(settings.SERVICE_NAME)
257
222 def test_configuration_changed_password(self):258 def test_configuration_changed_password(self):
223 config = {259 config = {
224 'databases': 3,260 'databases': 3,
@@ -307,7 +343,7 @@
307 mocks.log.assert_has_calls([343 mocks.log.assert_has_calls([
308 mock.call('Retrieving service options.'),344 mock.call('Retrieving service options.'),
309 mock.call('Writing configuration file for foo.'),345 mock.call('Writing configuration file for foo.'),
310 mock.call('No changes detected in the configuration file.')346 mock.call('No changes detected in the configuration file.'),
311 ])347 ])
312348
313 def test_configuration_unchanged_slave(self):349 def test_configuration_unchanged_slave(self):
@@ -345,7 +381,42 @@
345 mock.call('Retrieving service options.'),381 mock.call('Retrieving service options.'),
346 mock.call('Setting up slave relation.'),382 mock.call('Setting up slave relation.'),
347 mock.call('Writing configuration file for foo.'),383 mock.call('Writing configuration file for foo.'),
348 mock.call('No changes detected in the configuration file.')384 mock.call('No changes detected in the configuration file.'),
385 ])
386
387 def test_configuration_unchanged_master_maxmemory(self):
388 config = {
389 'databases': 3,
390 'logfile': '/path/to/logfile',
391 'loglevel': 'debug',
392 'maxmemory': '42m',
393 'maxmemory-policy': 'noeviction',
394 'password': '',
395 'port': 4242,
396 'tcp-keepalive': 60,
397 'timeout': 10,
398 }
399 callback = serviceutils.write_config_file(config)
400 with self.patch_all() as mocks:
401 callback('foo')
402 mocks.write.assert_called_once_with({
403 'bind': '1.2.3.4',
404 'databases': 3,
405 'logfile': '/path/to/logfile',
406 'loglevel': 'debug',
407 'maxmemory': '42m',
408 'maxmemory-policy': 'noeviction',
409 'port': 4242,
410 'tcp-keepalive': 60,
411 'timeout': 10,
412 }, settings.REDIS_CONF)
413 mocks.unit_get.assert_called_once_with('private-address')
414 self.assertFalse(mocks.service_restart.called)
415 self.assertEqual(3, mocks.log.call_count)
416 mocks.log.assert_has_calls([
417 mock.call('Retrieving service options.'),
418 mock.call('Writing configuration file for foo.'),
419 mock.call('No changes detected in the configuration file.'),
349 ])420 ])
350421
351 def test_configuration_unchanged_master_password(self):422 def test_configuration_unchanged_master_password(self):
@@ -406,3 +477,20 @@
406 }, settings.REDIS_CONF)477 }, settings.REDIS_CONF)
407 mocks.unit_get.assert_called_once_with('private-address')478 mocks.unit_get.assert_called_once_with('private-address')
408 self.assertFalse(mocks.service_restart.called)479 self.assertFalse(mocks.service_restart.called)
480
481
482@mock.patch('system.write_sysctl')
483@mock.patch('hookutils.log')
484class TestWriteSysctl(unittest.TestCase):
485
486 def test_memory_overcommit_enabled(self, mock_log, mock_write_sysctl):
487 callback = serviceutils.write_sysctl({'memory-overcommit': True})
488 callback('service')
489 mock_log.assert_called_once_with('Setting up sysctl state.')
490 mock_write_sysctl.assert_called_once_with(overcommit_memory=True)
491
492 def test_memory_overcommit_disabled(self, mock_log, mock_write_sysctl):
493 callback = serviceutils.write_sysctl({'memory-overcommit': False})
494 callback('service')
495 mock_log.assert_called_once_with('Setting up sysctl state.')
496 mock_write_sysctl.assert_called_once_with(overcommit_memory=False)
409497
=== added file 'unit_tests/test_system.py'
--- unit_tests/test_system.py 1970-01-01 00:00:00 +0000
+++ unit_tests/test_system.py 2015-07-20 11:52:30 +0000
@@ -0,0 +1,59 @@
1# Copyright 2015 Canonical Ltd.
2# Licensed under the GPLv3, see copyright file for details.
3
4import contextlib
5from pkg_resources import resource_filename
6import sys
7import tempfile
8import unittest
9
10import mock
11
12# Allow importing modules and packages from the hooks directory.
13sys.path.append(resource_filename(__name__, '../hooks'))
14
15import system
16
17
18@contextlib.contextmanager
19def patch_sysctl_conf():
20 """Replace the sysctl configuration path.
21
22 Return a temporary file that can be used for tests when entering the
23 context block. The file is automatically closed and deleted when exiting
24 the context block.
25 """
26 conf = tempfile.NamedTemporaryFile()
27 with mock.patch('charmhelpers.core.host.service_restart') as mock_restart:
28 with mock.patch('settings.SYSCTL_CONF', conf.name):
29 try:
30 yield conf
31 finally:
32 conf.close()
33 mock_restart.assert_called_once_with('procps')
34
35
36class TestWriteSysctl(unittest.TestCase):
37
38 def test_memory_overcommit_enabled(self):
39 with patch_sysctl_conf() as conf:
40 system.write_sysctl(overcommit_memory=True)
41 content = conf.read()
42 self.assertEqual('vm.overcommit_memory = 1\n', content)
43
44 def test_memory_overcommit_disabled(self):
45 with patch_sysctl_conf() as conf:
46 system.write_sysctl(overcommit_memory=False)
47 content = conf.read()
48 self.assertEqual('vm.overcommit_memory = 0\n', content)
49
50
51@mock.patch('os.remove')
52class TestRemoveSysctl(unittest.TestCase):
53
54 def test_file_removed(self, mock_remove):
55 with patch_sysctl_conf() as conf:
56 system.remove_sysctl()
57 content = conf.read()
58 self.assertEqual('vm.overcommit_memory = 0\n', content)
59 mock_remove.assert_called_once_with(conf.name)

Subscribers

People subscribed via source and target branches