2
from __future__ import print_function
4
from argparse import ArgumentParser
5
from datetime import datetime
8
from deploy_stack import (
19
add_basic_testing_arguments,
30
class LogRotateError(Exception):
32
''' LogRotate test Exception base class. '''
34
def __init__(self, message):
35
super(LogRotateError, self).__init__(message)
38
def assess_debug_log(client, timeout=180, lines=100):
39
"""After doing log rotation, we should be able to see debug-log output."""
40
out = client.get_juju_output("debug-log", "--lines={}".format(lines),
41
"--limit={}".format(lines), timeout=timeout)
42
content = out.splitlines()
43
if len(content) != lines:
44
raise LogRotateError("We expected {} lines of output, got {}".format(
48
def test_unit_rotation(client):
49
"""Tests unit log rotation."""
50
# TODO: as part of testing that when a unit sending lots of logs triggers
51
# unit log rotation, we should also test that all-machines.log and future
52
# logsink.log get rotated.
53
# It would also be possible to test that the logs database doesn't grow too
56
"/var/log/juju/unit-fill-logs-0.log",
61
# TODO: either call assess_debug_log here or add a new assess entry for it.
64
def assess_machine_rotation(client):
65
"""Tests machine log rotation."""
66
status = client.wait_for_started()
67
machine_id = status.get_unit('fill-logs/0')['machine']
69
"/var/log/juju/machine-{}.log".format(machine_id),
70
"machine-{}".format(machine_id),
72
"machine-size", "megs=300", "machine={}".format(machine_id))
75
def test_rotation(client, logfile, prefix, fill_action, size_action, *args):
76
"""A reusable help for testing log rotation.log
78
Deploys the fill-logs charm and uses it to fill the machine or unit log and
79
test that the logs roll over correctly.
82
# the rotation point should be 300 megs, so let's make sure we hit that.hit
83
# we'll obviously already have some data in the logs, so adding exactly
84
# 300megs should do the trick.
86
# we run do_fetch here so that we wait for fill-logs to finish.
87
client.action_do_fetch("fill-logs/0", fill_action, FILL_TIMEOUT, *args)
88
out = client.action_do_fetch("fill-logs/0", size_action)
89
action_output = yaml_loads(out)
91
# Now we should have one primary log file, and one backup log file.
92
# The backup should be approximately 300 megs.
93
# The primary should be below 300.
95
check_log0(logfile, action_output)
96
check_expected_backup("log1", prefix, action_output)
98
# we should only have one backup, not two.
99
check_for_extra_backup("log2", action_output)
101
# do it all again, this should generate a second backup.
103
client.action_do_fetch("fill-logs/0", fill_action, FILL_TIMEOUT, *args)
104
out = client.action_do_fetch("fill-logs/0", size_action)
105
action_output = yaml_loads(out)
107
# we should have two backups.
108
check_log0(logfile, action_output)
109
check_expected_backup("log1", prefix, action_output)
110
check_expected_backup("log2", prefix, action_output)
112
check_for_extra_backup("log3", action_output)
114
# one more time... we should still only have 2 backups and primary
116
client.action_do_fetch("fill-logs/0", fill_action, FILL_TIMEOUT, *args)
117
out = client.action_do_fetch("fill-logs/0", size_action)
118
action_output = yaml_loads(out)
120
check_log0(logfile, action_output)
121
check_expected_backup("log1", prefix, action_output)
122
check_expected_backup("log2", prefix, action_output)
124
# we should have two backups.
125
check_for_extra_backup("log3", action_output)
128
def check_for_extra_backup(logname, action_output):
129
"""Check that there are no extra backup files left behind."""
130
log = action_output["results"]["result-map"].get(logname)
135
name = log.get("name")
138
raise LogRotateError("Extra backup log after rotation: " + name)
141
def check_expected_backup(key, logprefix, action_output):
142
"""Check that there the expected backup files exists and is close to 300MB.
144
log = action_output["results"]["result-map"].get(key)
146
raise LogRotateError(
147
"Missing backup log '{}' after rotation.".format(key))
149
backup_pattern = "/var/log/juju/%s-(.+?)\.log" % logprefix
151
log_name = log["name"]
152
matches = re.match(backup_pattern, log_name)
154
raise LogRotateError(
155
"Rotated log '%s' does not match pattern '%s'." %
156
(log_name, backup_pattern))
158
size = int(log["size"])
159
if size < 299 or size > 301:
160
raise LogRotateError(
161
"Backup log '%s' should be ~300MB, but is %sMB." %
164
dt = matches.groups()[0]
165
dt_pattern = "%Y-%m-%dT%H-%M-%S.%f"
168
# note - we have to use datetime's strptime because time's doesn't
169
# support partial seconds.
170
dt = datetime.strptime(dt, dt_pattern)
172
raise LogRotateError(
173
"Log for %s has invalid datetime appended: %s" % (log_name, dt))
176
def check_log0(expected, action_output):
177
"""Check that log0 exists and is not over 299MB"""
178
log = action_output["results"]["result-map"].get("log0")
180
raise LogRotateError("No log returned from size action.")
184
raise LogRotateError(
185
"Wrong unit name: Expected: %s, actual: %s" % (expected, name))
187
size = int(log["size"])
189
raise LogRotateError(
190
"Log0 too big. Expected < 300MB, got: %sMB" % size)
193
def parse_args(argv=None):
194
"""Parse all arguments."""
195
parser = add_basic_testing_arguments(
196
ArgumentParser(description='Test log rotation.'))
199
help='Which agent log rotation to test.',
200
choices=['machine', 'unit'])
201
return parser.parse_args(argv)
204
def make_client_from_args(args):
205
client = make_client(
206
args.juju_bin, args.debug, args.env, args.temp_env_name)
208
client.env, args.temp_env_name, series=args.series,
209
bootstrap_host=args.bootstrap_host, agent_url=args.agent_url,
210
agent_stream=args.agent_stream, region=args.region)
211
jes_enabled = client.is_jes_enabled()
213
client.env.juju_home = jes_home_path(client.env.juju_home,
215
tear_down(client, jes_enabled)
221
client = make_client_from_args(args)
222
with boot_context(args.temp_env_name, client,
223
bootstrap_host=args.bootstrap_host,
224
machines=args.machine, series=args.series,
225
agent_url=args.agent_url, agent_stream=args.agent_stream,
226
log_dir=args.logs, keep_env=args.keep_env,
227
upload_tools=args.upload_tools,
229
charm_path = local_charm_path(
230
charm='fill-logs', juju_ver=client.version, series='trusty')
231
client.deploy(charm_path)
232
if args.agent == "unit":
233
test_unit_rotation(client)
234
if args.agent == "machine":
235
assess_machine_rotation(client)
238
if __name__ == '__main__':