Merge lp:~veebers/juju-ci-tools/initial-log-forwarding-tests into lp:juju-ci-tools

Proposed by Christopher Lee
Status: Approved
Approved by: Martin Packman
Approved revision: 1510
Proposed branch: lp:~veebers/juju-ci-tools/initial-log-forwarding-tests
Merge into: lp:juju-ci-tools
Diff against target: 707 lines (+661/-0)
7 files modified
assess_log_forward.py (+282/-0)
certificates.py (+139/-0)
jujupy.py (+7/-0)
log_check.py (+55/-0)
tests/test_assess_log_forward.py (+96/-0)
tests/test_jujupy.py (+29/-0)
tests/test_log_check.py (+53/-0)
To merge this branch: bzr merge lp:~veebers/juju-ci-tools/initial-log-forwarding-tests
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
Review via email: mp+300039@code.launchpad.net

Commit message

Add initial test for log forwarding feature

Description of the change

Adds initial test for the log-forwarding feature.

Handles creation of certificates tied to the rsylog sync as required by the feature

Bootstraps 2 environments, one being the rsyslog sink for logs the other being the emitter of logs.

Has a basic check that exercises the feature, there is further tests to be added but this MP is getting large as it is.

I added a new file 'certificates.py'. I was tempted to create a python package but this isn't a small step and wanted to converse with the team if it was the direction we wanted to go.

I.e. I would propose something like this initially:
  juju_ci_tools/
    __init__.py
    certificates.py

To post a comment you must log in.
1496. By Christopher Lee

Fix assertion messages.

1497. By Christopher Lee

Remove yet to be used methods.

Revision history for this message
Martin Packman (gz) wrote :

Have a bunch of random comments inline. Overall looks like a good approach, my main points of feedback:

* Assess scripts this complex can benefit from a bit more structure. A bunch of cooperating functions gets hard to follow around the 200 line mark. An encapsulating class for each environment seems reasonable.

* A bunch of the setup work is done by manual ssh steps on charms. Ideally, most of this complexity is encapsulated in the charm instead.

* I'd generally have more unit tests, but can see how a bunch of the current code is not amenable to it.

Revision history for this message
Martin Packman (gz) wrote :

This time with the comments...

Revision history for this message
Christopher Lee (veebers) wrote :

Responded to comments ans made some changes. Still have a couple of changes to make.

1498. By Christopher Lee

Grammar fix and import fix.

1499. By Christopher Lee

Better naming for boostrap managers.

1500. By Christopher Lee

Change certificate details to not be a getter function.

Revision history for this message
Martin Packman (gz) wrote :

Replied to some comments.

1501. By Christopher Lee

Merge trunk

1502. By Christopher Lee

Rename and reduce assess function

1503. By Christopher Lee

Using python script run on remote machine for logs check.

1504. By Christopher Lee

Add failling test for get_controller_uuid

1505. By Christopher Lee

Make test pass for added get_controller_uuid

1506. By Christopher Lee

Modify test to use get_controller_uuid

1507. By Christopher Lee

Cater for ipv6 addresses + port. Incl. tests.

1508. By Christopher Lee

Fix remote python script.

Revision history for this message
Martin Packman (gz) wrote :

Couple of notes from eyeballing this again, see inline comments.

Revision history for this message
Christopher Lee (veebers) wrote :

Made the suggested changes.

1509. By Christopher Lee

Put check logic into separate file with tests.

Revision history for this message
Martin Packman (gz) wrote :

Some comments on how to write the new code a little better.

1510. By Christopher Lee

Improved file existence checks and handling.

Revision history for this message
Martin Packman (gz) wrote :

Thanks! Lets land it.

review: Approve

Unmerged revisions

1510. By Christopher Lee

Improved file existence checks and handling.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'assess_log_forward.py'
--- assess_log_forward.py 1970-01-01 00:00:00 +0000
+++ assess_log_forward.py 2016-08-04 22:17:14 +0000
@@ -0,0 +1,282 @@
1#!/usr/bin/env python
2"""Test Juju's log forwarding feature.
3
4Log forwarding allows a controller to forward syslog from all models of a
5controller to a syslog host via TCP (using SSL).
6
7"""
8
9from __future__ import print_function
10
11import argparse
12import logging
13import os
14import re
15import sys
16import socket
17import subprocess
18from textwrap import dedent
19
20from assess_model_migration import get_bootstrap_managers
21import certificates
22from utility import (
23 add_basic_testing_arguments,
24 configure_logging,
25 JujuAssertionError,
26 temp_dir,
27)
28
29
30__metaclass__ = type
31
32
33log = logging.getLogger("assess_log_forward")
34
35
36def assess_log_forward(bs_dummy, bs_rsyslog, upload_tools):
37 """Ensure logs are forwarded after forwarding enabled after bootstrapping.
38
39 Given 2 controllers set rsyslog and dummy:
40 - setup rsyslog with secure details
41 - Enable log forwarding on dummy
42 - Ensure intial logs are present in the rsyslog sinks logs
43
44 """
45 with bs_rsyslog.booted_context(upload_tools):
46 log.info('Bootstrapped rsyslog environment')
47 rsyslog = bs_rsyslog.client
48 rsyslog_details = deploy_rsyslog(rsyslog)
49
50 update_client_config(bs_dummy.client, rsyslog_details)
51
52 with bs_dummy.existing_booted_context(upload_tools):
53 log.info('Bootstrapped dummy environment')
54 dummy_client = bs_dummy.client
55
56 # ensure turning on log-fwd emits logs to the client.
57 # Should I ensure that nothing turns up beforehand
58 ensure_enabling_log_forwarding_forwards_previous_messages(
59 rsyslog, dummy_client)
60
61
62def ensure_enabling_log_forwarding_forwards_previous_messages(rsyslog, dummy):
63 """Assert when enabled log forwarding forwards messages from log start."""
64 uuid = dummy.get_controller_uuid()
65
66 enable_log_forwarding(dummy)
67 assert_initial_message_forwarded(rsyslog, uuid)
68
69
70def assert_initial_message_forwarded(rsyslog, uuid):
71 """Assert that mention of the sources logs appear in the sinks logging.
72
73 Given a rsyslog sink and an output source assert that logging details from
74 the source appear in the sinks logging.
75 Attempt a check over a period of time (10 seconds).
76
77 :returns: As soon as the expected message appears.
78 :raises JujuAssertionError: If the expected message does not appear in the
79 given timeframe.
80 :raises JujuAssertionError: If the log message check fails in an unexpected
81 way.
82
83 """
84 check_string = get_assert_regex(uuid)
85 unit_machine = 'rsyslog/0'
86
87 remote_script_path = create_check_script_on_unit(rsyslog, unit_machine)
88
89 try:
90 rsyslog.juju(
91 'ssh',
92 (
93 unit_machine,
94 'sudo',
95 'python',
96 remote_script_path,
97 check_string,
98 '/var/log/syslog'))
99 log.info('Check script passed on target machine.')
100 except subprocess.CalledProcessError:
101 # This is where a failure happened
102 raise JujuAssertionError('Forwarded log message never appeared.')
103
104
105def create_check_script_on_unit(client, unit_machine):
106 script_path = os.path.join(os.path.dirname(__file__), 'log_check.py')
107 script_dest_path = os.path.join('/tmp', os.path.basename(script_path))
108 client.juju(
109 'scp',
110 (script_path, '{}:{}'.format(unit_machine, script_dest_path)))
111 return script_dest_path
112
113
114def get_assert_regex(raw_uuid, message=None):
115 """Create a regex string to check syslog file.
116
117 If message is supplied it is expected to be escaped as needed (i.e. spaces)
118 no further massaging will be done to the message string.
119
120 """
121 # Maybe over simplified removing the last 8 characters
122 uuid = re.escape(raw_uuid)
123 short_uuid = re.escape(raw_uuid[:-8])
124 date_check = '[A-Z][a-z]{,2}\ +[0-9]+\ +[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}'
125 machine = 'machine-0.{}'.format(uuid)
126 agent = 'jujud-machine-agent-{}'.format(short_uuid)
127 message = message or '.*'
128
129 return '"^{datecheck}\ {machine}\ {agent}\ {message}$"'.format(
130 datecheck=date_check,
131 machine=machine,
132 agent=agent,
133 message=message)
134
135
136def enable_log_forwarding(client):
137 client.juju(
138 'set-model-config',
139 ('-m', 'controller', 'logforward-enabled=true'), include_e=False)
140
141
142def update_client_config(client, rsyslog_details):
143 client.env.config['logforward-enabled'] = False
144 client.env.config.update(rsyslog_details)
145
146
147def deploy_rsyslog(client):
148 """Deploy and setup the rsyslog charm on client.
149
150 :returns: Configuration details needed: cert, ca, key and ip:port.
151
152 """
153 app_name = 'rsyslog'
154 client.deploy('rsyslog', (app_name))
155 client.wait_for_started()
156 client.juju('set-config', (app_name, 'protocol="tcp"'))
157 client.juju('expose', app_name)
158
159 return setup_tls_rsyslog(client, app_name)
160
161
162def setup_tls_rsyslog(client, app_name):
163 unit_machine = '{}/0'.format(app_name)
164
165 ip_address = get_unit_ipaddress(client, unit_machine)
166
167 client.juju(
168 'ssh',
169 (unit_machine, 'sudo apt-get install rsyslog-gnutls'))
170
171 with temp_dir() as config_dir:
172 install_rsyslog_config(client, config_dir, unit_machine)
173 rsyslog_details = install_certificates(
174 client, config_dir, ip_address, unit_machine)
175
176 # restart rsyslog to take into affect all changes
177 client.juju('ssh', (unit_machine, 'sudo', 'service', 'rsyslog', 'restart'))
178
179 return rsyslog_details
180
181
182def install_certificates(client, config_dir, ip_address, unit_machine):
183 cert, key = certificates.create_certificate(config_dir, ip_address)
184
185 # Write contents to file to scp across
186 ca_file = os.path.join(config_dir, 'ca.pem')
187 with open(ca_file, 'wt') as f:
188 f.write(certificates.ca_pem_contents)
189
190 scp_command = (
191 '--', cert, key, ca_file, '{unit}:/home/ubuntu/'.format(
192 unit=unit_machine))
193 client.juju('scp', scp_command)
194
195 return _get_rsyslog_details(cert, key, ip_address)
196
197
198def _get_rsyslog_details(cert_file, key_file, ip_address):
199 with open(cert_file, 'rt') as f:
200 cert_contents = f.read()
201 with open(key_file, 'rt') as f:
202 key_contents = f.read()
203
204 return {
205 'syslog-host': '{}'.format(add_port_to_ip(ip_address, '10514')),
206 'syslog-ca-cert': certificates.ca_pem_contents,
207 'syslog-client-cert': cert_contents,
208 'syslog-client-key': key_contents
209 }
210
211
212def add_port_to_ip(ip_address, port):
213 """Return an ipv4/ipv6 address with port added to `ip_address`."""
214 try:
215 socket.inet_aton(ip_address)
216 return '{}:{}'.format(ip_address, port)
217 except socket.error:
218 try:
219 socket.inet_pton(socket.AF_INET6, ip_address)
220 return '[{}]:{}'.format(ip_address, port)
221 except socket.error:
222 pass
223 raise ValueError(
224 'IP Address "{}" is neither an ipv4 or ipv6 address.'.format(
225 ip_address))
226
227
228def install_rsyslog_config(client, config_dir, unit_machine):
229 config = write_rsyslog_config_file(config_dir)
230 client.juju('scp', (config, '{unit}:/tmp'.format(unit=unit_machine)))
231 client.juju(
232 'ssh',
233 (unit_machine, 'sudo', 'mv', '/tmp/{}'.format(
234 os.path.basename(config)), '/etc/rsyslog.d/'))
235
236
237def get_unit_ipaddress(client, unit_name):
238 status = client.get_status()
239 return status.get_unit(unit_name)['public-address']
240
241
242def write_rsyslog_config_file(tmp_dir):
243 """Write rsyslog config file to `tmp_dir`/10-securelogging.conf."""
244 config = dedent("""\
245 # make gtls driver the default
246 $DefaultNetstreamDriver gtls
247
248 # certificate files
249 $DefaultNetstreamDriverCAFile /home/ubuntu/ca.pem
250 $DefaultNetstreamDriverCertFile /home/ubuntu/cert.pem
251 $DefaultNetstreamDriverKeyFile /home/ubuntu/key.pem
252
253 $ModLoad imtcp # load TCP listener
254 $InputTCPServerStreamDriverAuthMode x509/name
255 $InputTCPServerStreamDriverPermittedPeer anyServer
256 $InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode
257 $InputTCPServerRun 10514 # port 10514
258 """)
259 config_path = os.path.join(tmp_dir, '10-securelogging.conf')
260 with open(config_path, 'wt') as f:
261 f.write(config)
262 return config_path
263
264
265def parse_args(argv):
266 """Parse all arguments."""
267 parser = argparse.ArgumentParser(
268 description="Test log forwarding of logs.")
269 add_basic_testing_arguments(parser)
270 return parser.parse_args(argv)
271
272
273def main(argv=None):
274 args = parse_args(argv)
275 configure_logging(args.verbose)
276 bs_dummy, bs_rsyslog = get_bootstrap_managers(args)
277 assess_log_forward(bs_dummy, bs_rsyslog, args.upload_tools)
278 return 0
279
280
281if __name__ == '__main__':
282 sys.exit(main())
0283
=== added file 'certificates.py'
--- certificates.py 1970-01-01 00:00:00 +0000
+++ certificates.py 2016-08-04 22:17:14 +0000
@@ -0,0 +1,139 @@
1from OpenSSL import crypto
2import os
3from textwrap import dedent
4
5
6def create_certificate(target_dir, ip_address):
7 """Generate a cert and key file incl. IP SAN for `ip_address`
8
9 Creates a cert.pem and key.pem file signed with a known ca cert.
10 The generated cert will contain a IP SAN (subject alternative name) that
11 includes the ip address of the server. This is required for log-forwarding.
12
13 :return: tuple containing generated cert, key filepath pair
14
15 """
16 ip_address = 'IP:{}'.format(ip_address)
17
18 key = crypto.PKey()
19 key.generate_key(crypto.TYPE_RSA, 2048)
20
21 csr_contents = generate_csr(target_dir, key, ip_address)
22 req = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_contents)
23
24 ca_cert = crypto.load_certificate(
25 crypto.FILETYPE_PEM, ca_pem_contents)
26 ca_key = crypto.load_privatekey(
27 crypto.FILETYPE_PEM, ca_key_pem_contents)
28
29 cert = crypto.X509()
30 cert.set_version(0x2)
31 cert.set_subject(req.get_subject())
32 cert.set_serial_number(1)
33 cert.gmtime_adj_notBefore(0)
34 cert.gmtime_adj_notAfter(24 * 60 * 60)
35 cert.set_issuer(ca_cert.get_subject())
36 cert.set_pubkey(req.get_pubkey())
37 cert.add_extensions([
38 crypto.X509Extension('subjectAltName', False, ip_address),
39 crypto.X509Extension(
40 'extendedKeyUsage', False, 'clientAuth, serverAuth'),
41 crypto.X509Extension(
42 'keyUsage', True, 'keyEncipherment'),
43 ])
44 cert.sign(ca_key, "sha256")
45
46 cert_filepath = os.path.join(target_dir, 'cert.pem')
47 with open(cert_filepath, 'wt') as f:
48 f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
49
50 key_filepath = os.path.join(target_dir, 'key.pem')
51 with open(key_filepath, 'wt') as f:
52 f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
53
54 return (cert_filepath, key_filepath)
55
56
57def generate_csr(target_dir, key, ip_address):
58 req = crypto.X509Req()
59 req.set_version(0x2)
60 req.get_subject().CN = "anyServer"
61 # Add the IP SAN
62 req.add_extensions([
63 crypto.X509Extension("subjectAltName", False, ip_address)
64 ])
65 req.set_pubkey(key)
66 req.sign(key, "sha256")
67
68 return crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
69
70
71ca_pem_contents = dedent("""\
72 -----BEGIN CERTIFICATE-----
73 MIIEFTCCAn2gAwIBAgIBBzANBgkqhkiG9w0BAQsFADAjMRIwEAYDVQQDEwlhbnlT
74 ZXJ2ZXIxDTALBgNVBAoTBGp1anUwHhcNMTYwNzExMDQyOTM1WhcNMjYwNzA5MDQy
75 OTM1WjAjMRIwEAYDVQQDEwlhbnlTZXJ2ZXIxDTALBgNVBAoTBGp1anUwggGiMA0G
76 CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCn6OxY33yAirABoE4UaZJBOnQORIzC
77 125R71E2TG5gSHjHKA70L0C3dgyWhW9wcyhUbXBuz8Oep2J7kHvzuUPw2AWXI+Y2
78 c0afWVqfj5kuyUpGhXsqylyf7NDPFs8hwGA6ZCFS3oUAvX8awsVucklxGeZNXZNK
79 ZFilXKaX1Z3soORmKFZzVfDRqDuofZ2E0tmPh9C5gQ8qswjdBnTrj+0rCnvNekO0
80 aND6AlkBHU+87pvcax0uUF6PYkXxPikKk1ftCQSII5oB5ksAtRpcZsYl5hT3U/t1
81 DOA7c35RuIx7ogkcXP9jZ6J2tkmX+GMtUF29KEEnVCht32VDX+C3yS6lbfQB4oDt
82 Yp3wXRY/LXTW7XTUrhoXB4nkYbw59gis5Cr7zDtUpiWFVYgy/kbxalljSM4N3w2i
83 dtfxJHYjTfK98124qbCBb4A4ZNBJE2jy//lSIcIMXJv1LXQtTqR4rO1j6TBurohF
84 NmUYpy3Zv7gn2CkfX6QfNFIj8elKT6dd+RUCAwEAAaNUMFIwDwYDVR0TAQH/BAUw
85 AwEB/zAPBgNVHREECDAGhwQKwoylMA8GA1UdDwEB/wQFAwMHBAAwHQYDVR0OBBYE
86 FP+v8GAqHiUCIygXbwWzbUhl/22DMA0GCSqGSIb3DQEBCwUAA4IBgQBVYKeT1O2M
87 U3OPOy0IwqcA1/64rS1GlRmiw+papmjsy3aru03r8igahnbFd7wQawHaCScXbI/n
88 OAPT4PDGXn6b71t4uHwWaM8wde159RO3G32N/VfhV6LPRUQunmAZh5QcJK6wWpYu
89 B1f0dPkU+Q1AfX12oTOX/ld2/o7jaVswHoHoW6K2WQmwzlRQ953J+RJ7jXfrYDKl
90 OAp3Hb69wAN4Ayc1s92iYUwV5q8UaHQoskHOLWJu964yFBHL8SLe6TLD+Jjv05Mc
91 Ca7NKq/n25VTDNNaXl5MCNZ048m/GGHfktxxCddaF2grhC5HTUetwkq026PE0Wcq
92 P+cDrIq6uTA25QqyBYistSa/7z2o0NBi56ySRqxlP2J2TPFZyOb+ZiA4EgYY5no5
93 u2E+WuKZLVWl7eaQYOHgfYzFf3CvalSBwIjNynRwD/2Ebk7K29GPrIugb3V2+Vwh
94 rltUXOHUkFGjEHIhr8zixfCxh5OzPJMnJwCZZRYzMO0/0Gw7ll9DmH0=
95 -----END CERTIFICATE-----
96 """)
97
98
99ca_key_pem_contents = dedent("""\
100 -----BEGIN RSA PRIVATE KEY-----
101 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
138 -----END RSA PRIVATE KEY-----
139 """)
0140
=== modified file 'jujupy.py'
--- jujupy.py 2016-08-03 21:23:26 +0000
+++ jujupy.py 2016-08-04 22:17:14 +0000
@@ -1476,6 +1476,13 @@
1476 env = self.env.clone(model_name=name)1476 env = self.env.clone(model_name=name)
1477 return self.clone(env=env)1477 return self.clone(env=env)
14781478
1479 def get_controller_uuid(self):
1480 name = self.env.controller.name
1481 output_yaml = self.get_juju_output(
1482 'show-controller', '--format', 'yaml', include_e=False)
1483 output = yaml.safe_load(output_yaml)
1484 return output[name]['details']['uuid']
1485
1479 def get_controller_client(self):1486 def get_controller_client(self):
1480 """Return a client for the controller model. May return self.1487 """Return a client for the controller model. May return self.
14811488
14821489
=== added file 'log_check.py'
--- log_check.py 1970-01-01 00:00:00 +0000
+++ log_check.py 2016-08-04 22:17:14 +0000
@@ -0,0 +1,55 @@
1#!/usr/bin/env python
2"""Simple looped check over provided file for regex content."""
3from __future__ import print_function
4
5import argparse
6import os
7import subprocess
8import sys
9import time
10
11
12class check_result:
13 success = 0
14 failure = 1
15 exception = 2
16
17
18def check_file(check_string, file_path):
19 print('Checking for:\n{}'.format(check_string))
20 for _ in range(0, 10):
21 try:
22 with open(file_path, 'r') as f:
23 subprocess.check_call(['sudo', 'egrep', check_string], stdin=f)
24 print('Log content found. No need to continue.')
25 return check_result.success
26 except subprocess.CalledProcessError as e:
27 if e.returncode == 1:
28 time.sleep(1)
29 else:
30 return check_result.exception
31 print('Unexpected error with file check.')
32 return check_result.failure
33
34
35def parse_args(argv=None):
36 parser = argparse.ArgumentParser(
37 description='File content check.')
38 parser.add_argument(
39 'regex', help='Regex string to check file with.')
40 parser.add_argument(
41 'file_path', help='Path to file to check.')
42 return parser.parse_args(argv)
43
44
45def main(argv=None):
46 args = parse_args(argv)
47 try:
48 return check_file(args.regex, args.file_path)
49 except EnvironmentError as e:
50 print('Cannot open file "{}": {}'.format(args.file_path, str(e)))
51 return check_result.exception
52
53
54if __name__ == '__main__':
55 sys.exit(main())
056
=== added file 'tests/test_assess_log_forward.py'
--- tests/test_assess_log_forward.py 1970-01-01 00:00:00 +0000
+++ tests/test_assess_log_forward.py 2016-08-04 22:17:14 +0000
@@ -0,0 +1,96 @@
1"""Tests for assess_log_forward module."""
2
3import argparse
4from mock import patch
5import re
6import StringIO
7
8import assess_log_forward as alf
9from tests import (
10 parse_error,
11 TestCase,
12)
13
14
15class TestParseArgs(TestCase):
16
17 def test_common_args(self):
18 args = alf.parse_args(
19 ['an-env', '/bin/juju', '/tmp/logs', 'an-env-mod'])
20 self.assertEqual(
21 args,
22 argparse.Namespace(
23 env='an-env',
24 juju_bin='/bin/juju',
25 temp_env_name='an-env-mod',
26 debug=False,
27 agent_stream=None,
28 agent_url=None,
29 bootstrap_host=None,
30 keep_env=False,
31 logs='/tmp/logs',
32 machine=[],
33 region=None,
34 series=None,
35 upload_tools=False,
36 verbose=20))
37
38 def test_help(self):
39 fake_stdout = StringIO.StringIO()
40 with parse_error(self) as fake_stderr:
41 with patch("sys.stdout", fake_stdout):
42 alf.parse_args(["--help"])
43 self.assertEqual("", fake_stderr.getvalue())
44 self.assertIn(
45 'Test log forwarding of logs.',
46 fake_stdout.getvalue())
47
48
49class TestAssertRegex(TestCase):
50
51 def test_default_message_check(self):
52 self.assertTrue(
53 alf.get_assert_regex('').endswith('.*$"'))
54
55 def test_fails_when_uuid_doesnt_match(self):
56 uuid = 'fail'
57 check = alf.get_assert_regex(uuid)
58 failing_string = 'abc'
59
60 self.assertIsNone(re.search(check, failing_string))
61
62 def test_succeeds_with_matching_uuid(self):
63 uuid = '1234567812345678'
64 short_uuid = uuid[:-8]
65 check = alf.get_assert_regex(
66 uuid, message='abc').rstrip('"').strip('"')
67 success = 'Jul 13 00:00:00 machine-0.{} '\
68 'jujud-machine-agent-{} abc'.format(
69 uuid, short_uuid)
70
71 self.assertIsNotNone(
72 re.search(check, success))
73
74
75class TestAddPortToIP(TestCase):
76 def test_adds_port_to_ipv4(self):
77 ip_address = '192.168.1.1'
78 port = '123'
79 expected = '192.168.1.1:123'
80 self.assertEqual(
81 alf.add_port_to_ip(ip_address, port),
82 expected
83 )
84
85 def test_adds_port_to_ipv6(self):
86 ip_address = '1fff:0:a88:85a3::ac1f'
87 port = '123'
88 expected = '[1fff:0:a88:85a3::ac1f]:123'
89 self.assertEqual(
90 alf.add_port_to_ip(ip_address, port),
91 expected
92 )
93
94 def test_raises_ValueError_on_invalid_address(self):
95 with self.assertRaises(ValueError):
96 alf.add_port_to_ip('abc', 'abc')
097
=== modified file 'tests/test_jujupy.py'
--- tests/test_jujupy.py 2016-08-03 21:23:26 +0000
+++ tests/test_jujupy.py 2016-08-04 22:17:14 +0000
@@ -2343,6 +2343,35 @@
2343 controller_name = client.get_controller_model_name()2343 controller_name = client.get_controller_model_name()
2344 self.assertEqual('controller', controller_name)2344 self.assertEqual('controller', controller_name)
23452345
2346 def test_get_controller_uuid_returns_uuid(self):
2347 controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202'
2348 yaml_string = dedent("""\
2349 foo:
2350 details:
2351 uuid: {uuid}
2352 api-endpoints: ['10.194.140.213:17070']
2353 cloud: lxd
2354 region: localhost
2355 models:
2356 controller:
2357 uuid: {uuid}
2358 default:
2359 uuid: 772cdd39-b454-4bd5-8704-dc9aa9ff1750
2360 current-model: default
2361 account:
2362 user: admin@local
2363 bootstrap-config:
2364 config:
2365 cloud: lxd
2366 cloud-type: lxd
2367 region: localhost""".format(uuid=controller_uuid))
2368 client = EnvJujuClient(JujuData('foo'), None, None)
2369 with patch.object(client, 'get_juju_output', return_value=yaml_string):
2370 self.assertEqual(
2371 client.get_controller_uuid(),
2372 controller_uuid
2373 )
2374
2346 def test_get_controller_client(self):2375 def test_get_controller_client(self):
2347 client = EnvJujuClient(2376 client = EnvJujuClient(
2348 JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None)2377 JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None)
23492378
=== added file 'tests/test_log_check.py'
--- tests/test_log_check.py 1970-01-01 00:00:00 +0000
+++ tests/test_log_check.py 2016-08-04 22:17:14 +0000
@@ -0,0 +1,53 @@
1"""Tests for log_check script."""
2
3from mock import patch
4import subprocess
5
6import log_check as lc
7from tests import TestCase
8
9
10class TestCheckFile(TestCase):
11
12 regex = 'test123'
13 file_path = '/tmp/file.log'
14
15 def test_calls_check_call(self):
16 with patch.object(lc.subprocess, 'check_call') as m_checkcall:
17 lc.check_file(self.regex, self.file_path)
18
19 m_checkcall.assert_called_once_with(
20 ['sudo', 'egrep', self.regex, self.file_path])
21
22 def test_fails_after_attempting_multiple_times(self):
23 with patch.object(lc.subprocess, 'check_call') as m_checkcall:
24 m_checkcall.side_effect = subprocess.CalledProcessError(
25 1, ['sudo', 'egrep', self.regex, self.file_path])
26 with patch.object(lc.time, 'sleep') as m_sleep:
27 self.assertEqual(
28 lc.check_file(self.regex, self.file_path),
29 lc.check_result.failure)
30 self.assertEqual(m_sleep.call_count, 10)
31
32 def test_fails_when_meeting_unexpected_outcome(self):
33 with patch.object(lc.subprocess, 'check_call') as m_checkcall:
34 m_checkcall.side_effect = subprocess.CalledProcessError(
35 -1, ['sudo', 'egrep', self.regex, self.file_path])
36 self.assertEqual(
37 lc.check_file(self.regex, self.file_path),
38 lc.check_result.exception)
39
40 def test_succeeds_when_regex_found(self):
41 with patch.object(lc.subprocess, 'check_call'):
42 self.assertEqual(
43 lc.check_file(self.regex, self.file_path),
44 lc.check_result.success)
45
46
47class TestParseArgs(TestCase):
48
49 def test_basic_args(self):
50 args = ['test .*', '/tmp/log.file']
51 parsed = lc.parse_args(args)
52 self.assertEqual(parsed.regex, 'test .*')
53 self.assertEqual(parsed.file_path, '/tmp/log.file')

Subscribers

People subscribed via source and target branches