~juju-gui/charms/xenial/redis/trunk

« back to all changes in this revision

Viewing changes to tests/test_10_deploy.py

  • Committer: Francesco Banconi
  • Date: 2015-05-27 15:47:01 UTC
  • mfrom: (2.1.16 handle-ports)
  • Revision ID: francesco.banconi@canonical.com-20150527154701-3gc4399ht2wl8sgp
Handle service ports + other fixes.

Open/close the redis ports.
Add the timeout, tcp-keepalive and databases options.
Make the Redis service listen on the unit private address.
Install charmhelpers from a local tarball rather than PyPI.

QA: make ftest

R=martin.hilton, jay.wren
CC=
https://codereview.appspot.com/243730043

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
 
8
8
These tests use the Amulet test helpers:
9
9
see https://jujucharms.com/docs/stable/tools-amulet
 
10
 
 
11
Connection tests are only run on local environments where the internal unit
 
12
addresses are reachable.
10
13
"""
11
14
 
12
 
import itertools
13
15
from pkg_resources import resource_filename
14
16
import sys
15
 
import telnetlib
 
17
import time
16
18
import unittest
17
19
 
18
 
import amulet
 
20
import helpers
19
21
 
20
22
# Allow importing modules and packages from the hooks directory.
21
23
sys.path.append(resource_filename(__name__, '../hooks'))
22
24
 
23
 
import configfile
24
 
 
25
 
 
26
 
# Define the charm name.
27
 
CHARM_NAME = 'redis'
28
 
 
29
 
 
30
 
class RedisClient(object):
31
 
    """A very simple and naive telnet redis client used for tests."""
32
 
 
33
 
    def __init__(self, host, port=6379):
34
 
        """Initialize the client."""
35
 
        self._host = host
36
 
        self._port = port
37
 
        self._client = None
38
 
 
39
 
    def connect(self, password=None):
40
 
        """Connect to the client."""
41
 
        self._client = telnetlib.Telnet(self._host, self._port)
42
 
        if password is not None:
43
 
            self._client.write('AUTH {}\n'.format(password))
44
 
            response = self._readline()
45
 
            if response != '+OK':
46
 
                raise ValueError('authentication error: {}'.format(response))
47
 
 
48
 
    def close(self):
49
 
        """Close the client connection."""
50
 
        if self._client is not None:
51
 
            self._client.close()
52
 
        self._client = None
53
 
 
54
 
    def set(self, key, value):
55
 
        """Set a key in the redis database, with the given value."""
56
 
        self._client.write('SET {} {}\n'.format(key, value))
57
 
        response = self._readline()
58
 
        if response != '+OK':
59
 
            raise ValueError('unexpected response: {}'.format(response))
60
 
 
61
 
    def get(self, key):
62
 
        """Return the value corresponding to key from the redis database.
63
 
 
64
 
        Return None if the key is not found.
65
 
        """
66
 
        self._client.write('GET {}\n'.format(key))
67
 
        response = self._readline()
68
 
        if response == '$-1':
69
 
            return None
70
 
        return self._readline()
71
 
 
72
 
    def _readline(self):
73
 
        """Read next line from the client connection."""
74
 
        return self._client.read_until('\r\n').strip()
75
 
 
76
 
 
77
 
_counter = itertools.count()
78
 
 
79
 
 
80
 
def get_service_name():
81
 
    """Return an incremental redis service name."""
82
 
    return 'redis{}'.format(next(_counter))
83
 
 
84
 
 
85
 
def deploy(options=None):
86
 
    """Deploy one unit of the given service using the redis charm.
87
 
 
88
 
    Return the Amulet deployment and the unit object.
89
 
    """
90
 
    deployment = amulet.Deployment(series='trusty')
91
 
    service_name = get_service_name()
92
 
    deployment.add(service_name, charm=CHARM_NAME)
93
 
    if options is not None:
94
 
        deployment.configure(service_name, options)
95
 
    deployment.expose(service_name)
96
 
    try:
97
 
        deployment.setup(timeout=900)
98
 
        deployment.sentry.wait()
99
 
    except amulet.helpers.TimeoutError:
100
 
        amulet.raise_status(
101
 
            amulet.FAIL, msg='Environment was not stood up in time.')
102
 
    return deployment, deployment.sentry.unit[service_name + '/0']
103
 
 
104
 
 
105
 
def deploy_master_slave(master_options=None, slave_options=None):
106
 
    """Deploy two redis services related in a master-slave relationship.
107
 
 
108
 
    Return the Amulet deployment and the two unit objects.
109
 
    """
110
 
    deployment = amulet.Deployment(series='trusty')
111
 
    master, slave = get_service_name(), get_service_name()
112
 
    deployment.add(master, charm=CHARM_NAME)
113
 
    deployment.add(slave, charm=CHARM_NAME)
114
 
    if master_options is not None:
115
 
        deployment.configure(master, master_options)
116
 
    if slave_options is not None:
117
 
        deployment.configure(slave, slave_options)
118
 
    deployment.relate(master + ':master', slave + ':slave')
119
 
    deployment.expose(master)
120
 
    deployment.expose(slave)
121
 
    try:
122
 
        deployment.setup(timeout=900)
123
 
        deployment.sentry.wait()
124
 
    except amulet.helpers.TimeoutError:
125
 
        amulet.raise_status(
126
 
            amulet.FAIL, msg='Environment was not stood up in time.')
127
 
    units = deployment.sentry.unit
128
 
    return deployment, units[master + '/0'], units[slave + '/0']
 
25
import settings
 
26
 
 
27
 
 
28
# Define a test decorator for running the test only if the current environment
 
29
# type is local.
 
30
only_on_local_environments = unittest.skipIf(
 
31
    helpers.get_environment_type() != 'local',
 
32
    'only available whe using a local environment')
129
33
 
130
34
 
131
35
class TestDeployment(unittest.TestCase):
133
37
    @classmethod
134
38
    def setUpClass(cls):
135
39
        # Set up the environment and deploy the charm.
136
 
        cls.deployment, cls.unit = deploy()
 
40
        cls.deployment, cls.unit = helpers.deploy()
137
41
 
138
42
    @classmethod
139
43
    def tearDownClass(cls):
141
45
        cls.deployment.remove_service(cls.unit.info['service'])
142
46
 
143
47
    def test_config_file(self):
 
48
        address = helpers.get_private_address(self.unit)
144
49
        expected_content = (
145
50
            'bind {}\n'
 
51
            'databases 16\n'
146
52
            'logfile /var/log/redis/redis-server.log\n'
147
53
            'loglevel notice\n'
148
54
            'port 6379\n'
149
 
        ).format(self.unit.info['public-address'])
 
55
            'tcp-keepalive 0\n'
 
56
            'timeout 0\n'
 
57
        ).format(address)
150
58
        self.assertEqual(
151
59
            expected_content,
152
 
            self.unit.file_contents(configfile.REDIS_CONF))
 
60
            self.unit.file_contents(settings.REDIS_CONF))
153
61
 
 
62
    @only_on_local_environments
154
63
    def test_connection(self):
155
 
        client = RedisClient(self.unit.info['public-address'])
 
64
        client = helpers.RedisClient(self.unit.info['public-address'])
156
65
        client.connect()
157
66
        self.addCleanup(client.close)
158
67
        self.assertIsNone(client.get('my-key'))
163
72
class TestDeploymentOptions(unittest.TestCase):
164
73
 
165
74
    options = {
 
75
        'databases': 3,
166
76
        'port': 4242,
167
77
        'password': 'secret',
168
78
        'loglevel': 'verbose',
169
79
        'logfile': '/tmp/redis.log',
 
80
        'timeout': 60,
170
81
    }
171
82
 
172
83
    @classmethod
173
84
    def setUpClass(cls):
174
85
        # Set up the environment and deploy the charm.
175
 
        cls.deployment, cls.unit = deploy(options=cls.options)
 
86
        cls.deployment, cls.unit = helpers.deploy(options=cls.options)
176
87
 
177
88
    @classmethod
178
89
    def tearDownClass(cls):
180
91
        cls.deployment.remove_service(cls.unit.info['service'])
181
92
 
182
93
    def test_config_file(self):
 
94
        address = helpers.get_private_address(self.unit)
183
95
        expected_content = (
184
96
            'bind {}\n'
 
97
            'databases 3\n'
185
98
            'logfile /tmp/redis.log\n'
186
99
            'loglevel verbose\n'
187
100
            'port 4242\n'
188
101
            'requirepass secret\n'
189
 
        ).format(self.unit.info['public-address'])
 
102
            'tcp-keepalive 0\n'
 
103
            'timeout 60\n'
 
104
        ).format(address)
190
105
        self.assertEqual(
191
106
            expected_content,
192
 
            self.unit.file_contents(configfile.REDIS_CONF))
 
107
            self.unit.file_contents(settings.REDIS_CONF))
193
108
 
 
109
    @only_on_local_environments
194
110
    def test_connection(self):
195
 
        client = RedisClient(
 
111
        client = helpers.RedisClient(
196
112
            self.unit.info['public-address'], port=self.options['port'])
197
113
        client.connect(password=self.options['password'])
198
114
        self.addCleanup(client.close)
206
122
    @classmethod
207
123
    def setUpClass(cls):
208
124
        # Set up the environment and deploy the charm.
209
 
        cls.deployment, cls.master, cls.slave = deploy_master_slave()
 
125
        cls.deployment, cls.master, cls.slave = helpers.deploy_master_slave()
 
126
        cls.master_address = helpers.get_private_address(cls.master)
 
127
        cls.slave_address = helpers.get_private_address(cls.slave)
210
128
 
211
129
    @classmethod
212
130
    def tearDownClass(cls):
217
135
    def test_master_config_file(self):
218
136
        expected_content = (
219
137
            'bind {}\n'
 
138
            'databases 16\n'
220
139
            'logfile /var/log/redis/redis-server.log\n'
221
140
            'loglevel notice\n'
222
141
            'port 6379\n'
223
 
        ).format(self.master.info['public-address'])
 
142
            'tcp-keepalive 0\n'
 
143
            'timeout 0\n'
 
144
        ).format(self.master_address)
224
145
        self.assertEqual(
225
146
            expected_content,
226
 
            self.master.file_contents(configfile.REDIS_CONF))
 
147
            self.master.file_contents(settings.REDIS_CONF))
227
148
 
228
149
    def test_slave_config_file(self):
229
150
        expected_content = (
230
151
            'bind {}\n'
 
152
            'databases 16\n'
231
153
            'logfile /var/log/redis/redis-server.log\n'
232
154
            'loglevel notice\n'
233
155
            'port 6379\n'
234
156
            'slaveof {} 6379\n'
235
 
        ).format(
236
 
            self.slave.info['public-address'],
237
 
            self.master.info['public-address'])
 
157
            'tcp-keepalive 0\n'
 
158
            'timeout 0\n'
 
159
        ).format(self.slave_address, self.master_address)
238
160
        self.assertEqual(
239
161
            expected_content,
240
 
            self.slave.file_contents(configfile.REDIS_CONF))
 
162
            self.slave.file_contents(settings.REDIS_CONF))
241
163
 
 
164
    @only_on_local_environments
242
165
    def test_connection(self):
243
 
        master_client = RedisClient(self.master.info['public-address'])
 
166
        master_client = helpers.RedisClient(self.master.info['public-address'])
244
167
        master_client.connect()
245
168
        self.addCleanup(master_client.close)
246
169
        master_client.set('my-key', '42')
 
170
        # Wait for master and slave synchronization.
 
171
        time.sleep(1)
247
172
        # Retrieve the value from the slave.
248
 
        slave_client = RedisClient(self.slave.info['public-address'])
 
173
        slave_client = helpers.RedisClient(self.slave.info['public-address'])
249
174
        slave_client.connect()
250
175
        self.addCleanup(slave_client.close)
251
176
        self.assertEqual('42', slave_client.get('my-key'))
253
178
 
254
179
class TestMasterSlaveRelationOptions(unittest.TestCase):
255
180
 
256
 
    master_options = {'password': 'secret'}
257
 
    slave_options = {'port': 4747, 'loglevel': 'warning'}
 
181
    master_options = {'databases': 5, 'password': 'secret'}
 
182
    slave_options = {'port': 4747, 'loglevel': 'warning', 'timeout': 42}
258
183
 
259
184
    @classmethod
260
185
    def setUpClass(cls):
261
186
        # Set up the environment and deploy the charm.
262
 
        cls.deployment, cls.master, cls.slave = deploy_master_slave(
 
187
        cls.deployment, cls.master, cls.slave = helpers.deploy_master_slave(
263
188
            master_options=cls.master_options,
264
189
            slave_options=cls.slave_options)
 
190
        cls.master_address = helpers.get_private_address(cls.master)
 
191
        cls.slave_address = helpers.get_private_address(cls.slave)
265
192
 
266
193
    @classmethod
267
194
    def tearDownClass(cls):
272
199
    def test_master_config_file(self):
273
200
        expected_content = (
274
201
            'bind {}\n'
 
202
            'databases 5\n'
275
203
            'logfile /var/log/redis/redis-server.log\n'
276
204
            'loglevel notice\n'
277
205
            'port 6379\n'
278
206
            'requirepass secret\n'
279
 
        ).format(self.master.info['public-address'])
 
207
            'tcp-keepalive 0\n'
 
208
            'timeout 0\n'
 
209
        ).format(self.master_address)
280
210
        self.assertEqual(
281
211
            expected_content,
282
 
            self.master.file_contents(configfile.REDIS_CONF))
 
212
            self.master.file_contents(settings.REDIS_CONF))
283
213
 
284
214
    def test_slave_config_file(self):
285
215
        expected_content = (
286
216
            'bind {}\n'
 
217
            'databases 16\n'
287
218
            'logfile /var/log/redis/redis-server.log\n'
288
219
            'loglevel warning\n'
289
220
            'masterauth secret\n'
290
221
            'port 4747\n'
291
222
            'slaveof {} 6379\n'
292
 
        ).format(
293
 
            self.slave.info['public-address'],
294
 
            self.master.info['public-address'])
 
223
            'tcp-keepalive 0\n'
 
224
            'timeout 42\n'
 
225
        ).format(self.slave_address, self.master_address)
295
226
        self.assertEqual(
296
227
            expected_content,
297
 
            self.slave.file_contents(configfile.REDIS_CONF))
 
228
            self.slave.file_contents(settings.REDIS_CONF))
298
229
 
 
230
    @only_on_local_environments
299
231
    def test_connection(self):
300
 
        master_client = RedisClient(self.master.info['public-address'])
 
232
        master_client = helpers.RedisClient(self.master.info['public-address'])
301
233
        master_client.connect(password=self.master_options['password'])
302
234
        self.addCleanup(master_client.close)
303
235
        master_client.set('my-key', '42')
 
236
        # Wait for master and slave synchronization.
 
237
        time.sleep(1)
304
238
        # Retrieve the value from the slave.
305
 
        slave_client = RedisClient(
 
239
        slave_client = helpers.RedisClient(
306
240
            self.slave.info['public-address'], port=self.slave_options['port'])
307
241
        slave_client.connect()
308
242
        self.addCleanup(slave_client.close)