Merge ~logrotate-charmers/charm-logrotated:bugs_1833095_1833093 into ~logrotate-charmers/charm-logrotated:master
- Git
- lp:~logrotate-charmers/charm-logrotated
- bugs_1833095_1833093
- Merge into master
Proposed by
Diko Parvanov
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Diko Parvanov | ||||||||
Approved revision: | 6d391ef597fad5552bfdee132025c4aa603a21d9 | ||||||||
Merge reported by: | Diko Parvanov | ||||||||
Merged at revision: | 6d391ef597fad5552bfdee132025c4aa603a21d9 | ||||||||
Proposed branch: | ~logrotate-charmers/charm-logrotated:bugs_1833095_1833093 | ||||||||
Merge into: | ~logrotate-charmers/charm-logrotated:master | ||||||||
Diff against target: |
672 lines (+148/-131) 10 files modified
actions/actions.py (+12/-21) dev/null (+0/-13) lib/lib_cron.py (+38/-32) lib/lib_logrotate.py (+17/-24) reactive/logrotate.py (+20/-8) tests/functional/conftest.py (+14/-9) tests/functional/juju_tools.py (+14/-13) tests/functional/test_logrotate.py (+5/-2) tests/unit/conftest.py (+11/-0) tests/unit/test_logrotate.py (+17/-9) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Diko Parvanov | Approve | ||
Review via email: mp+369020@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Diko Parvanov (dparv) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/actions/__init__.py b/actions/__init__.py | |||
2 | 0 | deleted file mode 100755 | 0 | deleted file mode 100755 |
3 | index 9b088de..0000000 | |||
4 | --- a/actions/__init__.py | |||
5 | +++ /dev/null | |||
6 | @@ -1,13 +0,0 @@ | |||
7 | 1 | # Copyright 2016 Canonical Ltd | ||
8 | 2 | # | ||
9 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
10 | 4 | # you may not use this file except in compliance with the License. | ||
11 | 5 | # You may obtain a copy of the License at | ||
12 | 6 | # | ||
13 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
14 | 8 | # | ||
15 | 9 | # Unless required by applicable law or agreed to in writing, software | ||
16 | 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
17 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
18 | 12 | # See the License for the specific language governing permissions and | ||
19 | 13 | # limitations under the License. | ||
20 | diff --git a/actions/actions.py b/actions/actions.py | |||
21 | index aee6bb7..8178850 100755 | |||
22 | --- a/actions/actions.py | |||
23 | +++ b/actions/actions.py | |||
24 | @@ -1,45 +1,36 @@ | |||
25 | 1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python3 |
40 | 2 | # | 2 | """Actions module.""" |
27 | 3 | # Copyright 2016 Canonical Ltd | ||
28 | 4 | # | ||
29 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
30 | 6 | # you may not use this file except in compliance with the License. | ||
31 | 7 | # You may obtain a copy of the License at | ||
32 | 8 | # | ||
33 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
34 | 10 | # | ||
35 | 11 | # Unless required by applicable law or agreed to in writing, software | ||
36 | 12 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
37 | 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
38 | 14 | # See the License for the specific language governing permissions and | ||
39 | 15 | # limitations under the License. | ||
41 | 16 | 3 | ||
42 | 17 | import os | 4 | import os |
43 | 18 | import sys | 5 | import sys |
44 | 19 | 6 | ||
50 | 20 | sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib')) | 7 | from charmhelpers.core import hookenv |
46 | 21 | from charmhelpers.core import ( | ||
47 | 22 | hookenv, | ||
48 | 23 | host, | ||
49 | 24 | ) | ||
51 | 25 | 8 | ||
52 | 26 | from lib_logrotate import LogrotateHelper | ||
53 | 27 | from lib_cron import CronHelper | 9 | from lib_cron import CronHelper |
54 | 28 | 10 | ||
55 | 11 | from lib_logrotate import LogrotateHelper | ||
56 | 12 | |||
57 | 13 | sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib')) | ||
58 | 14 | |||
59 | 29 | hooks = hookenv.Hooks() | 15 | hooks = hookenv.Hooks() |
60 | 30 | logrotate = LogrotateHelper() | 16 | logrotate = LogrotateHelper() |
61 | 31 | cron = CronHelper() | 17 | cron = CronHelper() |
62 | 32 | 18 | ||
63 | 19 | |||
64 | 33 | @hooks.hook("update-logrotate-files") | 20 | @hooks.hook("update-logrotate-files") |
65 | 34 | def update_logrotate_files(): | 21 | def update_logrotate_files(): |
66 | 22 | """Update the logrotate files.""" | ||
67 | 35 | logrotate.read_config() | 23 | logrotate.read_config() |
68 | 36 | logrotate.modify_configs() | 24 | logrotate.modify_configs() |
69 | 37 | 25 | ||
70 | 26 | |||
71 | 38 | @hooks.hook("update-cronjob") | 27 | @hooks.hook("update-cronjob") |
72 | 39 | def update_cronjob(): | 28 | def update_cronjob(): |
73 | 29 | """Update the cronjob file.""" | ||
74 | 40 | cron.read_config() | 30 | cron.read_config() |
75 | 41 | cron.install_cronjob() | 31 | cron.install_cronjob() |
76 | 42 | 32 | ||
77 | 33 | |||
78 | 43 | if __name__ == "__main__": | 34 | if __name__ == "__main__": |
79 | 35 | """Main function.""" | ||
80 | 44 | hooks.execute(sys.argv) | 36 | hooks.execute(sys.argv) |
81 | 45 | |||
82 | diff --git a/lib/lib_cron.py b/lib/lib_cron.py | |||
83 | index 15bcdaf..f560084 100644 | |||
84 | --- a/lib/lib_cron.py | |||
85 | +++ b/lib/lib_cron.py | |||
86 | @@ -1,24 +1,27 @@ | |||
87 | 1 | """Cron helper module.""" | ||
88 | 1 | import os | 2 | import os |
90 | 2 | import re | 3 | |
91 | 3 | from lib_logrotate import LogrotateHelper | 4 | from lib_logrotate import LogrotateHelper |
92 | 4 | 5 | ||
93 | 6 | |||
94 | 5 | class CronHelper: | 7 | class CronHelper: |
95 | 6 | """Helper class for logrotate charm.""" | 8 | """Helper class for logrotate charm.""" |
96 | 7 | 9 | ||
97 | 8 | @classmethod | ||
98 | 9 | def __init__(self): | 10 | def __init__(self): |
101 | 10 | """Init function""" | 11 | """Init function.""" |
102 | 11 | self.cronjob_check_paths = [ "hourly", "daily", "weekly", "monthly" ] | 12 | self.cronjob_base_path = "/etc/cron." |
103 | 13 | self.cronjob_etc_config = "/etc/logrotate_cronjob_config" | ||
104 | 14 | self.cronjob_check_paths = ["hourly", "daily", "weekly", "monthly"] | ||
105 | 12 | self.cronjob_logrotate_cron_file = "charm-logrotate" | 15 | self.cronjob_logrotate_cron_file = "charm-logrotate" |
106 | 13 | 16 | ||
107 | 14 | |||
108 | 15 | @classmethod | ||
109 | 16 | def read_config(self): | 17 | def read_config(self): |
112 | 17 | """Config changed/install hooks dumps config out to disk, | 18 | """Read the configuration from the file. |
111 | 18 | Here we read that config to update the cronjob""" | ||
113 | 19 | 19 | ||
116 | 20 | config_file = open("/etc/logrotate_cronjob_config", "r") | 20 | Config changed/install hooks dumps config out to disk, |
117 | 21 | lines = config_file.read() | 21 | Here we read that config to update the cronjob. |
118 | 22 | """ | ||
119 | 23 | config_file = open(self.cronjob_etc_config, "r") | ||
120 | 24 | lines = config_file.read() | ||
121 | 22 | lines = lines.split('\n') | 25 | lines = lines.split('\n') |
122 | 23 | 26 | ||
123 | 24 | if lines[0] == 'True': | 27 | if lines[0] == 'True': |
124 | @@ -28,51 +31,54 @@ class CronHelper: | |||
125 | 28 | 31 | ||
126 | 29 | self.cronjob_frequency = int(self.cronjob_check_paths.index(lines[1])) | 32 | self.cronjob_frequency = int(self.cronjob_check_paths.index(lines[1])) |
127 | 30 | 33 | ||
128 | 31 | |||
129 | 32 | @classmethod | ||
130 | 33 | def install_cronjob(self): | 34 | def install_cronjob(self): |
133 | 34 | """If logrotate-cronjob config option is set to True | 35 | """Install the cron job task. |
132 | 35 | install cronjob. Otherwise cleanup""" | ||
134 | 36 | 36 | ||
135 | 37 | If logrotate-cronjob config option is set to True install cronjob, | ||
136 | 38 | otherwise cleanup. | ||
137 | 39 | """ | ||
138 | 37 | clean_up_file = self.cronjob_frequency if self.cronjob_enabled else -1 | 40 | clean_up_file = self.cronjob_frequency if self.cronjob_enabled else -1 |
139 | 38 | 41 | ||
140 | 39 | if self.cronjob_enabled is True: | 42 | if self.cronjob_enabled is True: |
141 | 40 | path_to_lib = os.path.realpath(__file__) | 43 | path_to_lib = os.path.realpath(__file__) |
144 | 41 | cron_file_path = "/etc/cron." + self.cronjob_check_paths[clean_up_file] \ | 44 | cron_file_path = self.cronjob_base_path\ |
145 | 42 | + "/" + self.cronjob_logrotate_cron_file | 45 | + self.cronjob_check_paths[clean_up_file]\ |
146 | 46 | + "/" + self.cronjob_logrotate_cron_file | ||
147 | 43 | 47 | ||
148 | 44 | # upgrade to template if logic increases | 48 | # upgrade to template if logic increases |
149 | 45 | cron_file = open(cron_file_path, 'w') | 49 | cron_file = open(cron_file_path, 'w') |
150 | 46 | cron_file.write("#!/bin/sh\n/usr/bin/python3 " + path_to_lib + "\n\n") | 50 | cron_file.write("#!/bin/sh\n/usr/bin/python3 " + path_to_lib + "\n\n") |
151 | 47 | cron_file.close() | 51 | cron_file.close() |
155 | 48 | os.chmod(cron_file_path,700) | 52 | os.chmod(cron_file_path, 700) |
153 | 49 | |||
154 | 50 | self.cleanup_cronjob(clean_up_file) | ||
156 | 51 | 53 | ||
157 | 54 | self.cleanup_cronjob(clean_up_file) | ||
158 | 52 | 55 | ||
166 | 53 | @classmethod | 56 | def cleanup_cronjob(self, frequency=-1): |
167 | 54 | def cleanup_cronjob(self, frequency = -1): | 57 | """Cleanup previous config.""" |
168 | 55 | """Cleanup previous config""" | 58 | if frequency == -1: |
169 | 56 | for i in range(4): | 59 | for check_path in self.cronjob_check_paths: |
170 | 57 | if frequency != i: | 60 | path = self.cronjob_base_path \ |
171 | 58 | path = "/etc/cron." + self.cronjob_check_paths[i] + "/" +\ | 61 | + check_path \ |
172 | 59 | self.cronjob_logrotate_cron_file | 62 | + "/" \ |
173 | 63 | + self.cronjob_logrotate_cron_file | ||
174 | 60 | if os.path.exists(path): | 64 | if os.path.exists(path): |
175 | 61 | os.remove(path) | 65 | os.remove(path) |
176 | 66 | if os.path.exists(self.cronjob_etc_config): | ||
177 | 67 | os.remove(self.cronjob_etc_config) | ||
178 | 62 | 68 | ||
179 | 63 | @classmethod | ||
180 | 64 | def update_logrotate_etc(self): | 69 | def update_logrotate_etc(self): |
182 | 65 | """Run logrotate update config""" | 70 | """Run logrotate update config.""" |
183 | 66 | logrotate = LogrotateHelper() | 71 | logrotate = LogrotateHelper() |
184 | 67 | logrotate.read_config() | 72 | logrotate.read_config() |
185 | 68 | logrotate.modify_configs() | 73 | logrotate.modify_configs() |
186 | 69 | 74 | ||
187 | 70 | 75 | ||
188 | 71 | def main(): | 76 | def main(): |
193 | 72 | cronHelper = CronHelper() | 77 | """Ran by cron.""" |
194 | 73 | cronHelper.read_config() | 78 | cronhelper = CronHelper() |
195 | 74 | cronHelper.update_logrotate_etc() | 79 | cronhelper.read_config() |
196 | 75 | cronHelper.install_cronjob() | 80 | cronhelper.update_logrotate_etc() |
197 | 81 | cronhelper.install_cronjob() | ||
198 | 76 | 82 | ||
199 | 77 | 83 | ||
200 | 78 | if __name__ == '__main__': | 84 | if __name__ == '__main__': |
201 | diff --git a/lib/lib_logrotate.py b/lib/lib_logrotate.py | |||
202 | index d823020..67245cb 100644 | |||
203 | --- a/lib/lib_logrotate.py | |||
204 | +++ b/lib/lib_logrotate.py | |||
205 | @@ -1,6 +1,8 @@ | |||
206 | 1 | """Logrotate module.""" | ||
207 | 1 | import os | 2 | import os |
208 | 2 | import re | 3 | import re |
209 | 3 | 4 | ||
210 | 5 | from charmhelpers.core import hookenv | ||
211 | 4 | 6 | ||
212 | 5 | LOGROTATE_DIR = "/etc/logrotate.d/" | 7 | LOGROTATE_DIR = "/etc/logrotate.d/" |
213 | 6 | 8 | ||
214 | @@ -8,27 +10,24 @@ LOGROTATE_DIR = "/etc/logrotate.d/" | |||
215 | 8 | class LogrotateHelper: | 10 | class LogrotateHelper: |
216 | 9 | """Helper class for logrotate charm.""" | 11 | """Helper class for logrotate charm.""" |
217 | 10 | 12 | ||
218 | 11 | @classmethod | ||
219 | 12 | def __init__(self): | 13 | def __init__(self): |
222 | 13 | """Init function""" | 14 | """Init function.""" |
223 | 14 | pass | 15 | self.retention = hookenv.config('logrotate-retention') |
224 | 15 | 16 | ||
225 | 16 | @classmethod | ||
226 | 17 | def read_config(self): | 17 | def read_config(self): |
229 | 18 | """Config changed/install hooks dumps config out to disk, | 18 | """Read changes from disk. |
228 | 19 | Here we read that config to update the cronjob""" | ||
230 | 20 | 19 | ||
231 | 20 | Config changed/install hooks dumps config out to disk, | ||
232 | 21 | Here we read that config to update the cronjob | ||
233 | 22 | """ | ||
234 | 21 | config_file = open("/etc/logrotate_cronjob_config", "r") | 23 | config_file = open("/etc/logrotate_cronjob_config", "r") |
235 | 22 | lines = config_file.read() | 24 | lines = config_file.read() |
236 | 23 | lines = lines.split('\n') | 25 | lines = lines.split('\n') |
237 | 24 | 26 | ||
238 | 25 | self.retention = int(lines[2]) | 27 | self.retention = int(lines[2]) |
239 | 26 | 28 | ||
240 | 27 | |||
241 | 28 | @classmethod | ||
242 | 29 | def modify_configs(self): | 29 | def modify_configs(self): |
243 | 30 | """Modify the logrotate config files.""" | 30 | """Modify the logrotate config files.""" |
244 | 31 | |||
245 | 32 | for config_file in os.listdir(LOGROTATE_DIR): | 31 | for config_file in os.listdir(LOGROTATE_DIR): |
246 | 33 | file_path = LOGROTATE_DIR + config_file | 32 | file_path = LOGROTATE_DIR + config_file |
247 | 34 | 33 | ||
248 | @@ -44,11 +43,8 @@ class LogrotateHelper: | |||
249 | 44 | logrotate_file.write(mod_contents) | 43 | logrotate_file.write(mod_contents) |
250 | 45 | logrotate_file.close() | 44 | logrotate_file.close() |
251 | 46 | 45 | ||
252 | 47 | |||
253 | 48 | @classmethod | ||
254 | 49 | def modify_content(self, content): | 46 | def modify_content(self, content): |
257 | 50 | """Helper function to edit the content of a logrotate file.""" | 47 | """Edit the content of a logrotate file.""" |
256 | 51 | |||
258 | 52 | # Split the contents in a logrotate file in separate entries (if | 48 | # Split the contents in a logrotate file in separate entries (if |
259 | 53 | # multiple are found in the file) and put in a list for further | 49 | # multiple are found in the file) and put in a list for further |
260 | 54 | # processing | 50 | # processing |
261 | @@ -66,7 +62,7 @@ class LogrotateHelper: | |||
262 | 66 | # the rotate option to the appropriate value | 62 | # the rotate option to the appropriate value |
263 | 67 | results = [] | 63 | results = [] |
264 | 68 | for item in items: | 64 | for item in items: |
266 | 69 | count = self.calculate_count(item) | 65 | count = self.calculate_count(item, self.retention) |
267 | 70 | rotate = 'rotate {}'.format(count) | 66 | rotate = 'rotate {}'.format(count) |
268 | 71 | result = re.sub(r'rotate \d+\.?[0-9]*', rotate, item) | 67 | result = re.sub(r'rotate \d+\.?[0-9]*', rotate, item) |
269 | 72 | results.append(result) | 68 | results.append(result) |
270 | @@ -75,10 +71,8 @@ class LogrotateHelper: | |||
271 | 75 | 71 | ||
272 | 76 | return results | 72 | return results |
273 | 77 | 73 | ||
274 | 78 | @classmethod | ||
275 | 79 | def modify_header(self, content): | 74 | def modify_header(self, content): |
278 | 80 | """Helper function to add Juju headers to the file.""" | 75 | """Add Juju headers to the file.""" |
277 | 81 | |||
279 | 82 | header = "# Configuration file maintained by Juju. Local changes may be overwritten" | 76 | header = "# Configuration file maintained by Juju. Local changes may be overwritten" |
280 | 83 | 77 | ||
281 | 84 | split = content.split('\n') | 78 | split = content.split('\n') |
282 | @@ -90,23 +84,22 @@ class LogrotateHelper: | |||
283 | 90 | return result | 84 | return result |
284 | 91 | 85 | ||
285 | 92 | @classmethod | 86 | @classmethod |
287 | 93 | def calculate_count(self, item): | 87 | def calculate_count(cls, item, retention): |
288 | 94 | """Calculate rotate based on rotation interval. Always round up.""" | 88 | """Calculate rotate based on rotation interval. Always round up.""" |
289 | 95 | |||
290 | 96 | # Fallback to default lowest retention - days | 89 | # Fallback to default lowest retention - days |
291 | 97 | # better to keep the logs than lose them | 90 | # better to keep the logs than lose them |
293 | 98 | count = self.retention | 91 | count = retention |
294 | 99 | # Daily 1:1 to configuration retention period (in days) | 92 | # Daily 1:1 to configuration retention period (in days) |
295 | 100 | if 'daily' in item: | 93 | if 'daily' in item: |
297 | 101 | count = self.retention | 94 | count = retention |
298 | 102 | # Weekly rounding up, as weeks are 7 days | 95 | # Weekly rounding up, as weeks are 7 days |
299 | 103 | if 'weekly' in item: | 96 | if 'weekly' in item: |
301 | 104 | count = int(round(self.retention/7)) | 97 | count = int(round(retention/7)) |
302 | 105 | # Monthly default 30 days and round up because of 28/31 days months | 98 | # Monthly default 30 days and round up because of 28/31 days months |
303 | 106 | if 'monthly' in item: | 99 | if 'monthly' in item: |
305 | 107 | count = int(round(self.retention/30)) | 100 | count = int(round(retention/30)) |
306 | 108 | # For every 360 days - add 1 year | 101 | # For every 360 days - add 1 year |
307 | 109 | if 'yearly' in item: | 102 | if 'yearly' in item: |
309 | 110 | count = self.retention // 360 + 1 if self.retention > 360 else 1 | 103 | count = retention // 360 + 1 if retention > 360 else 1 |
310 | 111 | 104 | ||
311 | 112 | return count | 105 | return count |
312 | diff --git a/reactive/logrotate.py b/reactive/logrotate.py | |||
313 | index 4a8d5c1..1810707 100644 | |||
314 | --- a/reactive/logrotate.py | |||
315 | +++ b/reactive/logrotate.py | |||
316 | @@ -1,36 +1,48 @@ | |||
319 | 1 | from lib_logrotate import LogrotateHelper | 1 | """Reactive charm hooks.""" |
318 | 2 | from lib_cron import CronHelper | ||
320 | 3 | from charmhelpers.core import hookenv | 2 | from charmhelpers.core import hookenv |
321 | 3 | |||
322 | 4 | from charms.reactive import set_flag, when, when_not | 4 | from charms.reactive import set_flag, when, when_not |
323 | 5 | 5 | ||
324 | 6 | from lib_cron import CronHelper | ||
325 | 7 | |||
326 | 8 | from lib_logrotate import LogrotateHelper | ||
327 | 9 | |||
328 | 10 | |||
329 | 11 | hooks = hookenv.Hooks() | ||
330 | 6 | logrotate = LogrotateHelper() | 12 | logrotate = LogrotateHelper() |
331 | 7 | cron = CronHelper() | 13 | cron = CronHelper() |
332 | 8 | 14 | ||
333 | 15 | |||
334 | 9 | @when_not('logrotate.installed') | 16 | @when_not('logrotate.installed') |
335 | 10 | def install_logrotate(): | 17 | def install_logrotate(): |
337 | 11 | dump_config_to_disk(); | 18 | """Install the logrotate charm.""" |
338 | 19 | dump_config_to_disk() | ||
339 | 12 | logrotate.read_config() | 20 | logrotate.read_config() |
340 | 13 | cron.read_config() | 21 | cron.read_config() |
341 | 14 | logrotate.modify_configs() | 22 | logrotate.modify_configs() |
342 | 15 | hookenv.status_set('active', 'Unit is ready.') | 23 | hookenv.status_set('active', 'Unit is ready.') |
343 | 16 | set_flag('logrotate.installed') | 24 | set_flag('logrotate.installed') |
345 | 17 | cron.install_cronjob(); | 25 | cron.install_cronjob() |
346 | 26 | |||
347 | 18 | 27 | ||
348 | 19 | @when('config.changed') | 28 | @when('config.changed') |
349 | 20 | def config_changed(): | 29 | def config_changed(): |
350 | 30 | """Run when configuration changes.""" | ||
351 | 21 | dump_config_to_disk() | 31 | dump_config_to_disk() |
352 | 22 | cron.read_config() | 32 | cron.read_config() |
353 | 23 | logrotate.read_config() | 33 | logrotate.read_config() |
354 | 24 | hookenv.status_set('maintenance', 'Modifying configs.') | 34 | hookenv.status_set('maintenance', 'Modifying configs.') |
355 | 25 | logrotate.modify_configs() | 35 | logrotate.modify_configs() |
356 | 26 | hookenv.status_set('active', 'Unit is ready.') | 36 | hookenv.status_set('active', 'Unit is ready.') |
358 | 27 | cron.install_cronjob(); | 37 | cron.install_cronjob() |
359 | 38 | |||
360 | 28 | 39 | ||
361 | 29 | def dump_config_to_disk(): | 40 | def dump_config_to_disk(): |
362 | 41 | """Dump configurations to disk.""" | ||
363 | 30 | cronjob_enabled = hookenv.config('logrotate-cronjob') | 42 | cronjob_enabled = hookenv.config('logrotate-cronjob') |
364 | 31 | cronjob_frequency = hookenv.config('logrotate-cronjob-frequency') | 43 | cronjob_frequency = hookenv.config('logrotate-cronjob-frequency') |
365 | 32 | logrotate_retention = hookenv.config('logrotate-retention') | 44 | logrotate_retention = hookenv.config('logrotate-retention') |
366 | 33 | with open('/etc/logrotate_cronjob_config', 'w+') as cronjob_config_file: | 45 | with open('/etc/logrotate_cronjob_config', 'w+') as cronjob_config_file: |
370 | 34 | cronjob_config_file.write(str(cronjob_enabled) + '\n') | 46 | cronjob_config_file.write(str(cronjob_enabled) + '\n') |
371 | 35 | cronjob_config_file.write(str(cronjob_frequency) + '\n') | 47 | cronjob_config_file.write(str(cronjob_frequency) + '\n') |
372 | 36 | cronjob_config_file.write(str(logrotate_retention) + '\n') | 48 | cronjob_config_file.write(str(logrotate_retention) + '\n') |
373 | diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py | |||
374 | index 56925ff..1b22cb8 100644 | |||
375 | --- a/tests/functional/conftest.py | |||
376 | +++ b/tests/functional/conftest.py | |||
377 | @@ -1,28 +1,32 @@ | |||
378 | 1 | #!/usr/bin/python3 | 1 | #!/usr/bin/python3 |
381 | 2 | ''' | 2 | """ |
382 | 3 | Reusable pytest fixtures for functional testing | 3 | Reusable pytest fixtures for functional testing. |
383 | 4 | 4 | ||
384 | 5 | Environment variables | 5 | Environment variables |
385 | 6 | --------------------- | 6 | --------------------- |
386 | 7 | 7 | ||
387 | 8 | test_preserve_model: | 8 | test_preserve_model: |
388 | 9 | if set, the testing model won't be torn down at the end of the testing session | 9 | if set, the testing model won't be torn down at the end of the testing session |
390 | 10 | ''' | 10 | """ |
391 | 11 | 11 | ||
392 | 12 | import asyncio | 12 | import asyncio |
393 | 13 | import os | 13 | import os |
394 | 14 | import uuid | ||
395 | 15 | import pytest | ||
396 | 16 | import subprocess | 14 | import subprocess |
397 | 15 | import uuid | ||
398 | 17 | 16 | ||
399 | 18 | from juju.controller import Controller | 17 | from juju.controller import Controller |
400 | 18 | |||
401 | 19 | from juju_tools import JujuTools | 19 | from juju_tools import JujuTools |
402 | 20 | 20 | ||
403 | 21 | import pytest | ||
404 | 22 | |||
405 | 21 | 23 | ||
406 | 22 | @pytest.fixture(scope='module') | 24 | @pytest.fixture(scope='module') |
407 | 23 | def event_loop(): | 25 | def event_loop(): |
410 | 24 | '''Override the default pytest event loop to allow for fixtures using a | 26 | """Override the default pytest event loop. |
411 | 25 | broader scope''' | 27 | |
412 | 28 | Do this too allow for fixtures using a broader scope. | ||
413 | 29 | """ | ||
414 | 26 | loop = asyncio.get_event_loop_policy().new_event_loop() | 30 | loop = asyncio.get_event_loop_policy().new_event_loop() |
415 | 27 | asyncio.set_event_loop(loop) | 31 | asyncio.set_event_loop(loop) |
416 | 28 | loop.set_debug(True) | 32 | loop.set_debug(True) |
417 | @@ -33,7 +37,7 @@ def event_loop(): | |||
418 | 33 | 37 | ||
419 | 34 | @pytest.fixture(scope='module') | 38 | @pytest.fixture(scope='module') |
420 | 35 | async def controller(): | 39 | async def controller(): |
422 | 36 | '''Connect to the current controller''' | 40 | """Connect to the current controller.""" |
423 | 37 | _controller = Controller() | 41 | _controller = Controller() |
424 | 38 | await _controller.connect_current() | 42 | await _controller.connect_current() |
425 | 39 | yield _controller | 43 | yield _controller |
426 | @@ -42,7 +46,7 @@ async def controller(): | |||
427 | 42 | 46 | ||
428 | 43 | @pytest.fixture(scope='module') | 47 | @pytest.fixture(scope='module') |
429 | 44 | async def model(controller): | 48 | async def model(controller): |
431 | 45 | '''This model lives only for the duration of the test''' | 49 | """Live only for the duration of the test.""" |
432 | 46 | model_name = "functest-{}".format(str(uuid.uuid4())[-12:]) | 50 | model_name = "functest-{}".format(str(uuid.uuid4())[-12:]) |
433 | 47 | _model = await controller.add_model(model_name, | 51 | _model = await controller.add_model(model_name, |
434 | 48 | cloud_name=os.getenv('PYTEST_CLOUD_NAME'), | 52 | cloud_name=os.getenv('PYTEST_CLOUD_NAME'), |
435 | @@ -62,5 +66,6 @@ async def model(controller): | |||
436 | 62 | 66 | ||
437 | 63 | @pytest.fixture(scope='module') | 67 | @pytest.fixture(scope='module') |
438 | 64 | async def jujutools(controller, model): | 68 | async def jujutools(controller, model): |
439 | 69 | """Juju tools.""" | ||
440 | 65 | tools = JujuTools(controller, model) | 70 | tools = JujuTools(controller, model) |
441 | 66 | return tools | 71 | return tools |
442 | diff --git a/tests/functional/juju_tools.py b/tests/functional/juju_tools.py | |||
443 | index 2fd501d..5e4a1cc 100644 | |||
444 | --- a/tests/functional/juju_tools.py | |||
445 | +++ b/tests/functional/juju_tools.py | |||
446 | @@ -1,22 +1,26 @@ | |||
447 | 1 | """Juju tools.""" | ||
448 | 2 | import base64 | ||
449 | 1 | import pickle | 3 | import pickle |
450 | 4 | |||
451 | 2 | import juju | 5 | import juju |
452 | 3 | import base64 | ||
453 | 4 | 6 | ||
454 | 5 | # from juju.errors import JujuError | 7 | # from juju.errors import JujuError |
455 | 6 | 8 | ||
456 | 7 | 9 | ||
457 | 8 | class JujuTools: | 10 | class JujuTools: |
458 | 11 | """Juju tools.""" | ||
459 | 12 | |||
460 | 9 | def __init__(self, controller, model): | 13 | def __init__(self, controller, model): |
461 | 14 | """Init.""" | ||
462 | 10 | self.controller = controller | 15 | self.controller = controller |
463 | 11 | self.model = model | 16 | self.model = model |
464 | 12 | 17 | ||
465 | 13 | async def run_command(self, cmd, target): | 18 | async def run_command(self, cmd, target): |
468 | 14 | ''' | 19 | """Run a command on a unit. |
467 | 15 | Runs a command on a unit. | ||
469 | 16 | 20 | ||
470 | 17 | :param cmd: Command to be run | 21 | :param cmd: Command to be run |
471 | 18 | :param unit: Unit object or unit name string | 22 | :param unit: Unit object or unit name string |
473 | 19 | ''' | 23 | """ |
474 | 20 | unit = ( | 24 | unit = ( |
475 | 21 | target | 25 | target |
476 | 22 | if isinstance(target, juju.unit.Unit) | 26 | if isinstance(target, juju.unit.Unit) |
477 | @@ -26,13 +30,12 @@ class JujuTools: | |||
478 | 26 | return action.results | 30 | return action.results |
479 | 27 | 31 | ||
480 | 28 | async def remote_object(self, imports, remote_cmd, target): | 32 | async def remote_object(self, imports, remote_cmd, target): |
483 | 29 | ''' | 33 | """Run command on target machine and returns a python object of the result. |
482 | 30 | Runs command on target machine and returns a python object of the result | ||
484 | 31 | 34 | ||
485 | 32 | :param imports: Imports needed for the command to run | 35 | :param imports: Imports needed for the command to run |
486 | 33 | :param remote_cmd: The python command to execute | 36 | :param remote_cmd: The python command to execute |
487 | 34 | :param target: Unit object or unit name string | 37 | :param target: Unit object or unit name string |
489 | 35 | ''' | 38 | """ |
490 | 36 | python3 = "python3 -c '{}'" | 39 | python3 = "python3 -c '{}'" |
491 | 37 | python_cmd = ('import pickle;' | 40 | python_cmd = ('import pickle;' |
492 | 38 | 'import base64;' | 41 | 'import base64;' |
493 | @@ -44,12 +47,11 @@ class JujuTools: | |||
494 | 44 | return pickle.loads(base64.b64decode(bytes(results['Stdout'][2:-1], 'utf8'))) | 47 | return pickle.loads(base64.b64decode(bytes(results['Stdout'][2:-1], 'utf8'))) |
495 | 45 | 48 | ||
496 | 46 | async def file_stat(self, path, target): | 49 | async def file_stat(self, path, target): |
499 | 47 | ''' | 50 | """Run stat on a file. |
498 | 48 | Runs stat on a file | ||
500 | 49 | 51 | ||
501 | 50 | :param path: File path | 52 | :param path: File path |
502 | 51 | :param target: Unit object or unit name string | 53 | :param target: Unit object or unit name string |
504 | 52 | ''' | 54 | """ |
505 | 53 | imports = 'import os;' | 55 | imports = 'import os;' |
506 | 54 | python_cmd = ('os.stat("{}")' | 56 | python_cmd = ('os.stat("{}")' |
507 | 55 | .format(path)) | 57 | .format(path)) |
508 | @@ -57,12 +59,11 @@ class JujuTools: | |||
509 | 57 | return await self.remote_object(imports, python_cmd, target) | 59 | return await self.remote_object(imports, python_cmd, target) |
510 | 58 | 60 | ||
511 | 59 | async def file_contents(self, path, target): | 61 | async def file_contents(self, path, target): |
514 | 60 | ''' | 62 | """Return the contents of a file. |
513 | 61 | Returns the contents of a file | ||
515 | 62 | 63 | ||
516 | 63 | :param path: File path | 64 | :param path: File path |
517 | 64 | :param target: Unit object or unit name string | 65 | :param target: Unit object or unit name string |
519 | 65 | ''' | 66 | """ |
520 | 66 | cmd = 'cat {}'.format(path) | 67 | cmd = 'cat {}'.format(path) |
521 | 67 | result = await self.run_command(cmd, target) | 68 | result = await self.run_command(cmd, target) |
522 | 68 | return result['Stdout'] | 69 | return result['Stdout'] |
523 | diff --git a/tests/functional/test_logrotate.py b/tests/functional/test_logrotate.py | |||
524 | index b4402be..100690f 100644 | |||
525 | --- a/tests/functional/test_logrotate.py | |||
526 | +++ b/tests/functional/test_logrotate.py | |||
527 | @@ -1,6 +1,8 @@ | |||
528 | 1 | #!/usr/bin/python3.6 | 1 | #!/usr/bin/python3.6 |
529 | 2 | """Main module for functional testing.""" | ||
530 | 2 | 3 | ||
531 | 3 | import os | 4 | import os |
532 | 5 | |||
533 | 4 | import pytest | 6 | import pytest |
534 | 5 | 7 | ||
535 | 6 | pytestmark = pytest.mark.asyncio | 8 | pytestmark = pytest.mark.asyncio |
536 | @@ -16,7 +18,7 @@ SERIES = ['xenial', | |||
537 | 16 | @pytest.fixture(scope='module', | 18 | @pytest.fixture(scope='module', |
538 | 17 | params=SERIES) | 19 | params=SERIES) |
539 | 18 | async def deploy_app(request, model): | 20 | async def deploy_app(request, model): |
541 | 19 | '''Deploys the logrotate charm as a subordinate of ubuntu''' | 21 | """Deploy the logrotate charm as a subordinate of ubuntu.""" |
542 | 20 | release = request.param | 22 | release = request.param |
543 | 21 | 23 | ||
544 | 22 | await model.deploy( | 24 | await model.deploy( |
545 | @@ -42,7 +44,7 @@ async def deploy_app(request, model): | |||
546 | 42 | 44 | ||
547 | 43 | @pytest.fixture(scope='module') | 45 | @pytest.fixture(scope='module') |
548 | 44 | async def unit(deploy_app): | 46 | async def unit(deploy_app): |
550 | 45 | '''Returns the logrotate unit we've deployed''' | 47 | """Return the logrotate unit we've deployed.""" |
551 | 46 | return deploy_app.units.pop() | 48 | return deploy_app.units.pop() |
552 | 47 | 49 | ||
553 | 48 | ######### | 50 | ######### |
554 | @@ -51,4 +53,5 @@ async def unit(deploy_app): | |||
555 | 51 | 53 | ||
556 | 52 | 54 | ||
557 | 53 | async def test_deploy(deploy_app): | 55 | async def test_deploy(deploy_app): |
558 | 56 | """Tst the deployment.""" | ||
559 | 54 | assert deploy_app.status == 'active' | 57 | assert deploy_app.status == 'active' |
560 | diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py | |||
561 | index 534e654..8520709 100644 | |||
562 | --- a/tests/unit/conftest.py | |||
563 | +++ b/tests/unit/conftest.py | |||
564 | @@ -1,11 +1,15 @@ | |||
565 | 1 | #!/usr/bin/python3 | 1 | #!/usr/bin/python3 |
566 | 2 | """Configurations for tests.""" | ||
567 | 3 | |||
568 | 2 | import mock | 4 | import mock |
569 | 5 | |||
570 | 3 | import pytest | 6 | import pytest |
571 | 4 | 7 | ||
572 | 5 | # If layer options are used, add this to ${fixture} | 8 | # If layer options are used, add this to ${fixture} |
573 | 6 | # and import layer in logrotate | 9 | # and import layer in logrotate |
574 | 7 | @pytest.fixture | 10 | @pytest.fixture |
575 | 8 | def mock_layers(monkeypatch): | 11 | def mock_layers(monkeypatch): |
576 | 12 | """Layers mock.""" | ||
577 | 9 | import sys | 13 | import sys |
578 | 10 | sys.modules['charms.layer'] = mock.Mock() | 14 | sys.modules['charms.layer'] = mock.Mock() |
579 | 11 | sys.modules['reactive'] = mock.Mock() | 15 | sys.modules['reactive'] = mock.Mock() |
580 | @@ -21,8 +25,10 @@ def mock_layers(monkeypatch): | |||
581 | 21 | 25 | ||
582 | 22 | monkeypatch.setattr('lib_logrotate.layer.options', options) | 26 | monkeypatch.setattr('lib_logrotate.layer.options', options) |
583 | 23 | 27 | ||
584 | 28 | |||
585 | 24 | @pytest.fixture | 29 | @pytest.fixture |
586 | 25 | def mock_hookenv_config(monkeypatch): | 30 | def mock_hookenv_config(monkeypatch): |
587 | 31 | """Hookenv mock.""" | ||
588 | 26 | import yaml | 32 | import yaml |
589 | 27 | 33 | ||
590 | 28 | def mock_config(): | 34 | def mock_config(): |
591 | @@ -39,17 +45,22 @@ def mock_hookenv_config(monkeypatch): | |||
592 | 39 | 45 | ||
593 | 40 | monkeypatch.setattr('lib_logrotate.hookenv.config', mock_config) | 46 | monkeypatch.setattr('lib_logrotate.hookenv.config', mock_config) |
594 | 41 | 47 | ||
595 | 48 | |||
596 | 42 | @pytest.fixture | 49 | @pytest.fixture |
597 | 43 | def mock_remote_unit(monkeypatch): | 50 | def mock_remote_unit(monkeypatch): |
598 | 51 | """Remote unit mock.""" | ||
599 | 44 | monkeypatch.setattr('lib_logrotate.hookenv.remote_unit', lambda: 'unit-mock/0') | 52 | monkeypatch.setattr('lib_logrotate.hookenv.remote_unit', lambda: 'unit-mock/0') |
600 | 45 | 53 | ||
601 | 46 | 54 | ||
602 | 47 | @pytest.fixture | 55 | @pytest.fixture |
603 | 48 | def mock_charm_dir(monkeypatch): | 56 | def mock_charm_dir(monkeypatch): |
604 | 57 | """Charm dir mock.""" | ||
605 | 49 | monkeypatch.setattr('lib_logrotate.hookenv.charm_dir', lambda: '/mock/charm/dir') | 58 | monkeypatch.setattr('lib_logrotate.hookenv.charm_dir', lambda: '/mock/charm/dir') |
606 | 50 | 59 | ||
607 | 60 | |||
608 | 51 | @pytest.fixture | 61 | @pytest.fixture |
609 | 52 | def logrotate(tmpdir, mock_hookenv_config, mock_charm_dir, monkeypatch): | 62 | def logrotate(tmpdir, mock_hookenv_config, mock_charm_dir, monkeypatch): |
610 | 63 | """Logrotate fixture.""" | ||
611 | 53 | from lib_logrotate import LogrotateHelper | 64 | from lib_logrotate import LogrotateHelper |
612 | 54 | helper = LogrotateHelper | 65 | helper = LogrotateHelper |
613 | 55 | 66 | ||
614 | diff --git a/tests/unit/test_logrotate.py b/tests/unit/test_logrotate.py | |||
615 | index 1f0ed9b..b01c7b5 100644 | |||
616 | --- a/tests/unit/test_logrotate.py | |||
617 | +++ b/tests/unit/test_logrotate.py | |||
618 | @@ -1,37 +1,45 @@ | |||
620 | 1 | from unittest.mock import patch | 1 | """Main unit test module.""" |
621 | 2 | |||
622 | 2 | 3 | ||
623 | 3 | class TestLogrotateHelper(): | 4 | class TestLogrotateHelper(): |
624 | 5 | """Main test class.""" | ||
625 | 6 | |||
626 | 4 | def test_pytest(self): | 7 | def test_pytest(self): |
627 | 8 | """Simple pytest.""" | ||
628 | 5 | assert True | 9 | assert True |
629 | 6 | 10 | ||
630 | 7 | |||
631 | 8 | def test_daily_retention_count(self, logrotate): | 11 | def test_daily_retention_count(self, logrotate): |
632 | 12 | """Test daily retention count.""" | ||
633 | 9 | logrotate.retention = 90 | 13 | logrotate.retention = 90 |
634 | 10 | contents = '/var/log/some.log {\n rotate 123\n daily\n}' | 14 | contents = '/var/log/some.log {\n rotate 123\n daily\n}' |
636 | 11 | count = logrotate.calculate_count(contents) | 15 | count = logrotate.calculate_count(contents, logrotate.retention) |
637 | 12 | assert count == 90 | 16 | assert count == 90 |
638 | 13 | 17 | ||
639 | 14 | def test_weekly_retention_count(self, logrotate): | 18 | def test_weekly_retention_count(self, logrotate): |
640 | 19 | """Test weekly retention count.""" | ||
641 | 15 | logrotate.retention = 21 | 20 | logrotate.retention = 21 |
642 | 16 | contents = '/var/log/some.log {\n rotate 123\n weekly\n}' | 21 | contents = '/var/log/some.log {\n rotate 123\n weekly\n}' |
644 | 17 | count = logrotate.calculate_count(contents) | 22 | count = logrotate.calculate_count(contents, logrotate.retention) |
645 | 18 | assert count == 3 | 23 | assert count == 3 |
646 | 19 | 24 | ||
647 | 20 | def test_monthly_retention_count(self, logrotate): | 25 | def test_monthly_retention_count(self, logrotate): |
648 | 26 | """Test monthly retention count.""" | ||
649 | 21 | logrotate.retention = 60 | 27 | logrotate.retention = 60 |
650 | 22 | contents = '/var/log/some.log {\n rotate 123\n monthly\n}' | 28 | contents = '/var/log/some.log {\n rotate 123\n monthly\n}' |
652 | 23 | count = logrotate.calculate_count(contents) | 29 | count = logrotate.calculate_count(contents, logrotate.retention) |
653 | 24 | assert count == 2 | 30 | assert count == 2 |
654 | 25 | 31 | ||
655 | 26 | def test_yearly_retention_count(self, logrotate): | 32 | def test_yearly_retention_count(self, logrotate): |
656 | 33 | """Test yearly retention count.""" | ||
657 | 27 | logrotate.retention = 180 | 34 | logrotate.retention = 180 |
658 | 28 | contents = '/var/log/some.log {\n rotate 123\n yearly\n}' | 35 | contents = '/var/log/some.log {\n rotate 123\n yearly\n}' |
660 | 29 | count = logrotate.calculate_count(contents) | 36 | count = logrotate.calculate_count(contents, logrotate.retention) |
661 | 30 | assert count == 1 | 37 | assert count == 1 |
662 | 31 | 38 | ||
663 | 32 | def test_modify_content(self, logrotate): | 39 | def test_modify_content(self, logrotate): |
664 | 40 | """Test the modify_content method.""" | ||
665 | 33 | logrotate.retention = 42 | 41 | logrotate.retention = 42 |
669 | 34 | contents = '/var/log/some.log {\n rotate 123\n daily\n}\n/var/log/other.log {\n rotate 456\n weekly\n}' | 42 | contents = '/log/some.log {\n rotate 123\n daily\n}\n/log/other.log {\n rotate 456\n weekly\n}' |
670 | 35 | mod_contents = logrotate.modify_content(contents) | 43 | mod_contents = logrotate.modify_content(logrotate, contents) |
671 | 36 | expected_contents = '/var/log/some.log {\n rotate 42\n daily\n}\n\n/var/log/other.log {\n rotate 6\n weekly\n}\n' | 44 | expected_contents = '/log/some.log {\n rotate 42\n daily\n}\n\n/log/other.log {\n rotate 6\n weekly\n}\n' |
672 | 37 | assert mod_contents == expected_contents | 45 | assert mod_contents == expected_contents |