~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to assess_log_rotation.py

  • Committer: Curtis Hovey
  • Date: 2014-08-01 12:44:38 UTC
  • Revision ID: curtis@canonical.com-20140801124438-l48516pldkzh7g5n
Do not show all the files in the tarball because it distracts from the test output.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
from __future__ import print_function
3
 
 
4
 
from argparse import ArgumentParser
5
 
from datetime import datetime
6
 
import re
7
 
 
8
 
from deploy_stack import (
9
 
    boot_context,
10
 
    tear_down,
11
 
    update_env,
12
 
)
13
 
from jujupy import (
14
 
    jes_home_path,
15
 
    make_client,
16
 
    yaml_loads,
17
 
)
18
 
from utility import (
19
 
    add_basic_testing_arguments,
20
 
    local_charm_path,
21
 
)
22
 
 
23
 
 
24
 
__metaclass__ = type
25
 
 
26
 
 
27
 
FILL_TIMEOUT = '8m'
28
 
 
29
 
 
30
 
class LogRotateError(Exception):
31
 
 
32
 
    ''' LogRotate test Exception base class. '''
33
 
 
34
 
    def __init__(self, message):
35
 
        super(LogRotateError, self).__init__(message)
36
 
 
37
 
 
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(
45
 
            lines, len(content)))
46
 
 
47
 
 
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
54
 
    # large.
55
 
    test_rotation(client,
56
 
                  "/var/log/juju/unit-fill-logs-0.log",
57
 
                  "unit-fill-logs-0",
58
 
                  "fill-unit",
59
 
                  "unit-size",
60
 
                  "megs=300")
61
 
    # TODO: either call assess_debug_log here or add a new assess entry for it.
62
 
 
63
 
 
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']
68
 
    test_rotation(client,
69
 
                  "/var/log/juju/machine-{}.log".format(machine_id),
70
 
                  "machine-{}".format(machine_id),
71
 
                  "fill-machine",
72
 
                  "machine-size", "megs=300", "machine={}".format(machine_id))
73
 
 
74
 
 
75
 
def test_rotation(client, logfile, prefix, fill_action, size_action, *args):
76
 
    """A reusable help for testing log rotation.log
77
 
 
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.
80
 
    """
81
 
 
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.
85
 
 
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)
90
 
 
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.
94
 
 
95
 
    check_log0(logfile, action_output)
96
 
    check_expected_backup("log1", prefix, action_output)
97
 
 
98
 
    # we should only have one backup, not two.
99
 
    check_for_extra_backup("log2", action_output)
100
 
 
101
 
    # do it all again, this should generate a second backup.
102
 
 
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)
106
 
 
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)
111
 
 
112
 
    check_for_extra_backup("log3", action_output)
113
 
 
114
 
    # one more time... we should still only have 2 backups and primary
115
 
 
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)
119
 
 
120
 
    check_log0(logfile, action_output)
121
 
    check_expected_backup("log1", prefix, action_output)
122
 
    check_expected_backup("log2", prefix, action_output)
123
 
 
124
 
    # we should have two backups.
125
 
    check_for_extra_backup("log3", action_output)
126
 
 
127
 
 
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)
131
 
    if log is None:
132
 
        # this is correct
133
 
        return
134
 
    # log exists.
135
 
    name = log.get("name")
136
 
    if name is None:
137
 
        name = "(no name)"
138
 
    raise LogRotateError("Extra backup log after rotation: " + name)
139
 
 
140
 
 
141
 
def check_expected_backup(key, logprefix, action_output):
142
 
    """Check that there the expected backup files exists and is close to 300MB.
143
 
    """
144
 
    log = action_output["results"]["result-map"].get(key)
145
 
    if log is None:
146
 
        raise LogRotateError(
147
 
            "Missing backup log '{}' after rotation.".format(key))
148
 
 
149
 
    backup_pattern = "/var/log/juju/%s-(.+?)\.log" % logprefix
150
 
 
151
 
    log_name = log["name"]
152
 
    matches = re.match(backup_pattern, log_name)
153
 
    if matches is None:
154
 
        raise LogRotateError(
155
 
            "Rotated log '%s' does not match pattern '%s'." %
156
 
            (log_name, backup_pattern))
157
 
 
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." %
162
 
            (log_name, size))
163
 
 
164
 
    dt = matches.groups()[0]
165
 
    dt_pattern = "%Y-%m-%dT%H-%M-%S.%f"
166
 
 
167
 
    try:
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)
171
 
    except Exception:
172
 
        raise LogRotateError(
173
 
            "Log for %s has invalid datetime appended: %s" % (log_name, dt))
174
 
 
175
 
 
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")
179
 
    if log is None:
180
 
        raise LogRotateError("No log returned from size action.")
181
 
 
182
 
    name = log["name"]
183
 
    if name != expected:
184
 
        raise LogRotateError(
185
 
            "Wrong unit name: Expected: %s, actual: %s" % (expected, name))
186
 
 
187
 
    size = int(log["size"])
188
 
    if size > 299:
189
 
        raise LogRotateError(
190
 
            "Log0 too big. Expected < 300MB, got: %sMB" % size)
191
 
 
192
 
 
193
 
def parse_args(argv=None):
194
 
    """Parse all arguments."""
195
 
    parser = add_basic_testing_arguments(
196
 
        ArgumentParser(description='Test log rotation.'))
197
 
    parser.add_argument(
198
 
        'agent',
199
 
        help='Which agent log rotation to test.',
200
 
        choices=['machine', 'unit'])
201
 
    return parser.parse_args(argv)
202
 
 
203
 
 
204
 
def make_client_from_args(args):
205
 
    client = make_client(
206
 
        args.juju_bin, args.debug, args.env, args.temp_env_name)
207
 
    update_env(
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()
212
 
    if jes_enabled:
213
 
        client.env.juju_home = jes_home_path(client.env.juju_home,
214
 
                                             args.temp_env_name)
215
 
    tear_down(client, jes_enabled)
216
 
    return client
217
 
 
218
 
 
219
 
def main():
220
 
    args = parse_args()
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,
228
 
                      region=args.region):
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)
236
 
 
237
 
 
238
 
if __name__ == '__main__':
239
 
    main()