/
status.py
266 lines (215 loc) · 8.7 KB
/
status.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# Copyright 2018 Red Hat
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
CLI interface for kuryr status commands.
"""
from __future__ import print_function
import sys
import textwrap
import traceback
import prettytable
import os_vif
from os_vif.objects import base
from oslo_config import cfg
from oslo_serialization import jsonutils
from kuryr_kubernetes import clients
from kuryr_kubernetes import config
from kuryr_kubernetes import constants
from kuryr_kubernetes import exceptions
from kuryr_kubernetes import objects
from kuryr_kubernetes.objects import vif
from kuryr_kubernetes import version
CONF = config.CONF
UPGRADE_CHECK_SUCCESS = 0
UPGRADE_CHECK_WARNING = 1
UPGRADE_CHECK_FAILURE = 2
UPGRADE_CHECK_MSG_MAP = {
UPGRADE_CHECK_SUCCESS: 'Success',
UPGRADE_CHECK_WARNING: 'Warning',
UPGRADE_CHECK_FAILURE: 'Failure',
}
class UpgradeCheckResult(object):
"""Class used for 'kuryr-k8s-status upgrade check' results.
The 'code' attribute is an UpgradeCheckCode enum.
The 'details' attribute is a message generally only used for
checks that result in a warning or failure code. The details should provide
information on what issue was discovered along with any remediation.
"""
def __init__(self, code, details=None):
super(UpgradeCheckResult, self).__init__()
self.code = code
self.details = details
def get_details(self):
if self.details is not None:
# wrap the text on the details to 60 characters
return '\n'.join(textwrap.wrap(self.details, 60,
subsequent_indent=' ' * 9))
class UpgradeCommands(object):
def __init__(self):
self.check_methods = {
'Pod annotations': self._check_annotations, # Stein
}
clients.setup_kubernetes_client()
self.k8s = clients.get_kubernetes_client()
def _get_annotation(self, pod):
annotations = pod['metadata']['annotations']
if constants.K8S_ANNOTATION_VIF not in annotations:
# NOTE(dulek): We ignore pods without annotation, those
# probably are hostNetworking.
return None
k_ann = annotations[constants.K8S_ANNOTATION_VIF]
k_ann = jsonutils.loads(k_ann)
obj = base.VersionedObject.obj_from_primitive(k_ann)
return obj
def _check_annotations(self):
old_count = 0
malformed_count = 0
pods = self.k8s.get('/api/v1/pods')['items']
for pod in pods:
try:
obj = self._get_annotation(pod)
if not obj:
# NOTE(dulek): We ignore pods without annotation, those
# probably are hostNetworking.
continue
except Exception:
# TODO(dulek): We might want to print this exception.
malformed_count += 1
continue
if obj.obj_name() != objects.vif.PodState.obj_name():
old_count += 1
if malformed_count == 0 and old_count == 0:
return UpgradeCheckResult(0, 'All annotations are updated.')
elif malformed_count > 0 and old_count == 0:
msg = ('You have %d malformed Kuryr pod annotations in your '
'deployment. This is not blocking the upgrade, but '
'consider investigating it.' % malformed_count)
return UpgradeCheckResult(1, msg)
elif old_count > 0:
msg = ('You have %d Kuryr pod annotations in old format. You need '
'to run `kuryr-k8s-status upgrade update-annotations` '
'before proceeding with the upgrade.' % old_count)
return UpgradeCheckResult(2, msg)
def upgrade_check(self):
check_results = []
t = prettytable.PrettyTable(['Upgrade Check Results'],
hrules=prettytable.ALL)
t.align = 'l'
for name, method in self.check_methods.items():
result = method()
check_results.append(result)
cell = (
'Check: %(name)s\n'
'Result: %(result)s\n'
'Details: %(details)s' %
{
'name': name,
'result': UPGRADE_CHECK_MSG_MAP[result.code],
'details': result.get_details(),
}
)
t.add_row([cell])
print(t)
return max(res.code for res in check_results)
def _convert_annotations(self, test_fn, update_fn):
updated_count = 0
not_updated_count = 0
malformed_count = 0
pods = self.k8s.get('/api/v1/pods')['items']
for pod in pods:
try:
obj = self._get_annotation(pod)
if not obj:
# NOTE(dulek): We ignore pods without annotation, those
# probably are hostNetworking.
continue
except Exception:
malformed_count += 1
continue
if test_fn(obj):
obj = update_fn(obj)
serialized = obj.obj_to_primitive()
try:
ann = {
constants.K8S_ANNOTATION_VIF:
jsonutils.dumps(serialized)
}
self.k8s.annotate(
pod['metadata']['selfLink'], ann,
pod['metadata']['resourceVersion'])
except exceptions.K8sClientException:
print('Error when updating annotation for pod %s/%s' %
(pod['metadata']['namespace'],
pod['metadata']['name']))
not_updated_count += 1
updated_count += 1
t = prettytable.PrettyTable(['Stat', 'Number'],
hrules=prettytable.ALL)
t.align = 'l'
cells = [['Updated annotations', updated_count],
['Malformed annotations', malformed_count],
['Annotations left', not_updated_count]]
for cell in cells:
t.add_row(cell)
print(t)
def update_annotations(self):
def test_fn(obj):
return obj.obj_name() != objects.vif.PodState.obj_name()
def update_fn(obj):
return vif.PodState(default_vif=obj)
self._convert_annotations(test_fn, update_fn)
def downgrade_annotations(self):
def test_fn(obj):
return obj.obj_name() == objects.vif.PodState.obj_name()
def update_fn(obj):
return obj.default_vif
self._convert_annotations(test_fn, update_fn)
def print_version():
print(version.version_info.version_string())
def add_parsers(subparsers):
upgrade_cmds = UpgradeCommands()
upgrade = subparsers.add_parser(
'upgrade', help='Actions related to upgrades between releases.')
sub = upgrade.add_subparsers()
check = sub.add_parser('check', help='Check if upgrading is possible.')
check.set_defaults(action_fn=upgrade_cmds.upgrade_check)
ann_update = sub.add_parser(
'update-annotations',
help='Update annotations in K8s API to newest version.')
ann_update.set_defaults(action_fn=upgrade_cmds.update_annotations)
ann_downgrade = sub.add_parser(
'downgrade-annotations',
help='Downgrade annotations in K8s API to previous version (useful '
'when reverting a failed upgrade).')
ann_downgrade.set_defaults(action_fn=upgrade_cmds.downgrade_annotations)
version_action = subparsers.add_parser('version')
version_action.set_defaults(action_fn=print_version)
def main():
opt = cfg.SubCommandOpt(
'category', title='command',
description='kuryr-k8s-status command or category to execute',
handler=add_parsers)
conf = cfg.ConfigOpts()
conf.register_cli_opt(opt)
conf(sys.argv[1:])
os_vif.initialize()
objects.register_locally_defined_vifs()
try:
return conf.category.action_fn()
except Exception:
print('Error:\n%s' % traceback.format_exc())
# This is 255 so it's not confused with the upgrade check exit codes.
return 255
if __name__ == '__main__':
main()