Merge ~barryprice/charm-canonical-livepatch/+git/canonical-livepatch-charm:master into ~livepatch-charmers/charm-canonical-livepatch:master

Proposed by Barry Price
Status: Merged
Approved by: Barry Price
Approved revision: 62416fa29784df12e2a9842449ae5673dbe8f997
Merged at revision: 6308443772e8c506cde9b2b61caec86c443cae6f
Proposed branch: ~barryprice/charm-canonical-livepatch/+git/canonical-livepatch-charm:master
Merge into: ~livepatch-charmers/charm-canonical-livepatch:master
Diff against target: 187 lines (+90/-54)
1 file modified
files/check_canonical-livepatch.py (+90/-54)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+355822@code.launchpad.net

Commit message

Clearer check output, and warn if the client output format changes (i.e. if checkState or patchState go away).

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Stuart Bishop (stub) wrote :

Seems ok. Some comments inline.

review: Approve
Revision history for this message
Stuart Bishop (stub) wrote :

Yup, but see comments.

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 6308443772e8c506cde9b2b61caec86c443cae6f

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/files/check_canonical-livepatch.py b/files/check_canonical-livepatch.py
2index 7878169..87c7204 100755
3--- a/files/check_canonical-livepatch.py
4+++ b/files/check_canonical-livepatch.py
5@@ -5,70 +5,108 @@
6 import os
7 import nagios_plugin
8 from subprocess import check_output, call
9+from yaml import safe_load
10
11 supported_archs = ['x86_64']
12
13
14-##############################################################################
15-
16-def check_package_installed():
17+def check_snap_installed():
18+ """Confirm the snap is installed, raise an error if not"""
19 cmd = ['snap', 'list', 'canonical-livepatch']
20 try:
21 check_output(cmd, universal_newlines=True)
22 except Exception:
23- raise nagios_plugin.CriticalError("canonical-livepatch snap is not installed")
24+ raise nagios_plugin.CriticalError('canonical-livepatch snap is not installed')
25
26
27-##############################################################################
28+def load_status():
29+ """Load the cached status from disk, return it as a string"""
30+ livepatch_output_path = '/var/lib/nagios/canonical-livepatch-status.txt'
31
32-def check_vmlinuz():
33- vmlinuz = '/vmlinuz'
34- if os.path.exists(vmlinuz):
35- full_kernel_path = os.path.realpath(vmlinuz)
36- elif os.path.exists('/boot' + vmlinuz):
37- vmlinuz = '/boot' + vmlinuz
38- full_kernel_path = os.path.realpath(vmlinuz)
39- else:
40- return 'no /vmlinuz or /boot/vmlinuz'
41- kernel_filename = os.path.basename(full_kernel_path)
42- # remove 'vmlinuz'-' from start:
43- kernel_version = '-'.join(kernel_filename.split('-', 1)[1:])
44- # check for '-generic-pae' kernels that need two removes:
45- if '-generic-pae' in kernel_version:
46- kernel_version = '-'.join(kernel_version.split('-')[:-1])
47- # remove e.g. '-generic' from end:
48- kernel_version = '-'.join(kernel_version.split('-')[:-1])
49- return kernel_version.strip()
50+ with open(livepatch_output_path, 'r') as canonical_livepatch_log:
51+ livepatch_status = canonical_livepatch_log.read()
52+
53+ return livepatch_status
54+
55+
56+def check_serious_errors():
57+ """In serious error cases, the output will not be YAML"""
58+ livepatch_status = load_status()
59+
60+ # if it's empty, something's definitely wrong
61+ if livepatch_status == '':
62+ raise nagios_plugin.CriticalError('No output from canonical-livepatch status')
63+
64+ # in some cases, it's obviously not valid YAML
65+ try:
66+ livepatch_yaml = safe_load(livepatch_status)
67+ except Exception:
68+ raise nagios_plugin.CriticalError(livepatch_status.replace('\n', ' '))
69+
70+ # in other cases, it's less obvious until we try to parse
71+ try:
72+ livepatch_yaml['status']
73+ except KeyError:
74+ raise nagios_plugin.CriticalError(livepatch_status.replace('\n', ' '))
75
76
77-##############################################################################
78+def active_kernel_version():
79+ """Return the active kernel version, from livepatch's perspective"""
80+ livepatch_status = load_status()
81+ status_yaml = safe_load(livepatch_status)
82+ for kernel in status_yaml.get('status'):
83+ if kernel.get('running') is True:
84+ return kernel.get('kernel')
85+
86
87 def check_status():
88- livepatch_output_path = '/var/lib/nagios/canonical-livepatch-status.txt'
89- err_lines = []
90- wrn_lines = []
91+ """Check the cached status, raise an error if we find any issues"""
92+ livepatch_status = load_status()
93+ errors = []
94
95- with open(livepatch_output_path, 'r') as canonical_livepatch_log:
96- for line in canonical_livepatch_log:
97- line = line.strip()
98- if 'State:' in line:
99- if 'apply-failed' in line:
100- err_lines.append('Livepatch failed to apply patches.')
101- elif 'check-failed' in line:
102- err_lines.append('Livepatch failed to check the remote service for patches.')
103- elif 'unknown' in line:
104- err_lines.append('Livepatch reports an unknown error.')
105- elif 'kernel-upgrade-required' in line:
106- err_lines.append('A kernel upgrade (and reboot) is required.')
107- elif 'Machine is not enabled' in line:
108- err_lines.append('Machine is not enabled.')
109-
110- if err_lines:
111- err = " ".join(err_lines)
112- raise nagios_plugin.CriticalError(err)
113- elif wrn_lines:
114- wrn = " ".join(wrn_lines)
115- raise nagios_plugin.WarnError(wrn)
116+ status_yaml = safe_load(livepatch_status)
117+
118+ for kernel in status_yaml.get('status'):
119+ if kernel.get('running') is True:
120+ check_state = kernel.get('livepatch').get('checkState')
121+ patch_state = kernel.get('livepatch').get('patchState')
122+
123+ check_state_error = check_check_state(check_state)
124+ patch_state_error = check_patch_state(patch_state)
125+
126+ if check_state_error:
127+ errors.append(check_state_error)
128+ if patch_state_error:
129+ errors.append(patch_state_error)
130+
131+ if errors:
132+ raise nagios_plugin.CriticalError(' '.join(errors))
133+
134+
135+def check_check_state(check_state):
136+ """Check for issues with checkState, including unexpected output"""
137+ if check_state in ['checked', 'needs-check']:
138+ pass
139+ elif check_state == 'check-failed':
140+ return 'Livepatch failed to check the remote service for patches.'
141+ else:
142+ return 'Unknown check state: {}'.format(check_state)
143+
144+
145+def check_patch_state(patch_state):
146+ """Check for issues with patchState, including unexpected output"""
147+ if patch_state in ['applied', 'nothing-to-apply']:
148+ pass
149+ elif patch_state == 'unapplied':
150+ return 'Livepatch has not applied the downloaded patches.'
151+ elif patch_state == 'applied-with-bug':
152+ return 'Livepatch has detected a kernel bug while applying patches.'
153+ elif patch_state == 'apply-failed':
154+ return 'Livepatch failed to apply patches.'
155+ elif patch_state == 'kernel-upgrade-required':
156+ return 'A kernel upgrade (and reboot) is required.'
157+ else:
158+ return 'Unknown patch state: {}'.format(patch_state)
159
160
161 def lsb_release():
162@@ -101,8 +139,6 @@ def is_container():
163 return os.path.exists('/run/container_type')
164
165
166-##############################################################################
167-
168 def main():
169 arch = os.uname()[4]
170 if arch not in supported_archs:
171@@ -112,12 +148,12 @@ def main():
172 elif is_container():
173 print("canonical-livepatch not needed in OS containers.")
174 else:
175- nagios_plugin.try_check(check_package_installed)
176+ nagios_plugin.try_check(check_snap_installed)
177+ nagios_plugin.try_check(check_serious_errors)
178 nagios_plugin.try_check(check_status)
179- print("OK - canonical-livepatch seems to be installed and working")
180-
181+ kernel_version = active_kernel_version()
182+ print("OK - canonical-livepatch is active on kernel {}".format(kernel_version))
183
184-##############################################################################
185
186 if __name__ == "__main__":
187 main()

Subscribers

People subscribed via source and target branches