blob: 448263d58406a160839c9b6bead68e8873469545 [file] [log] [blame]
Milan Verespej1a230472019-03-20 13:51:40 +01001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# COPYRIGHT NOTICE STARTS HERE
5
6# Copyright 2019 © Samsung Electronics Co., Ltd.
7#
8# Licensed under the Apache License, Version 2.0 (the "License");
9# you may not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS,
16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19
20# COPYRIGHT NOTICE ENDS HERE
21
22
23from __future__ import print_function
24import sys
25import argparse
26import yaml
27import requests
28import subprocess
29import datetime
30from time import sleep
31from os.path import expanduser
32from itertools import chain
33import csv
34from requests.packages.urllib3.exceptions import InsecureRequestWarning
35
36
37def add_resource_kind(resources, kind):
38 for item in resources:
39 item['kind'] = kind
40 return resources
41
42def get_resources(server, namespace, api, kind, ssl_verify=False):
43 url = '/'.join([server, api, 'namespaces', namespace, kind])
44 try:
45 req = requests.get(url, verify=ssl_verify)
46 except requests.exceptions.ConnectionError as err:
47 sys.exit('Could not connect to {}'.format(server))
48 json = req.json()
49 # kind is <resource>List in response so [:-4] removes 'List' from value
50 return add_resource_kind(json['items'], json['kind'][:-4])
51
52def pods_by_parent(pods, parent):
53 for pod in pods:
54 if pod['metadata']['labels']['app'] == parent:
55 yield pod
56
57def k8s_controller_ready(k8s_controller):
58 if k8s_controller['kind'] == 'Job':
59 return k8s_controller['status'].get('succeeded', 0) == k8s_controller['spec']['completions']
60 return k8s_controller['status'].get('readyReplicas', 0) == k8s_controller['spec']['replicas']
61
62def get_not_ready(data):
63 return [x for x in data if not k8s_controller_ready(x)]
64
65def get_apps(data):
66 return [x['metadata']['labels']['app'] for x in data]
67
68def get_names(data):
69 return [x['metadata']['name'] for x in data]
70
71def pod_ready(pod):
72 try:
73 return [x['status'] for x in pod['status']['conditions']
74 if x['type'] == 'Ready'][0] == 'True'
75 except (KeyError, IndexError):
76 return False
77
78def not_ready_pods(pods):
79 for pod in pods:
80 if not pod_ready(pod):
81 yield pod
82
83def analyze_k8s_controllers(resources_data):
84 resources = {'total_count': len(resources_data)}
85 resources['not_ready_list'] = get_apps(get_not_ready(resources_data))
86 resources['ready_count'] = resources['total_count'] - len(resources['not_ready_list'])
87
88 return resources
89
90def get_k8s_controllers(namespace, k8s_url):
91 k8s_controllers = {}
92
93 k8s_controllers['deployments'] = {'data': get_resources(k8s_url, namespace,
94 'apis/apps/v1', 'deployments')}
95 k8s_controllers['deployments'].update(analyze_k8s_controllers(k8s_controllers['deployments']['data']))
96
97 k8s_controllers['statefulsets'] = {'data': get_resources(k8s_url, namespace,
98 'apis/apps/v1', 'statefulsets')}
99 k8s_controllers['statefulsets'].update(analyze_k8s_controllers(k8s_controllers['statefulsets']['data']))
100
101 k8s_controllers['jobs'] = {'data': get_resources(k8s_url, namespace,
102 'apis/batch/v1', 'jobs')}
103 k8s_controllers['jobs'].update(analyze_k8s_controllers(k8s_controllers['jobs']['data']))
104
105 not_ready_controllers = chain.from_iterable(
106 k8s_controllers[x]['not_ready_list'] for x in k8s_controllers)
107
108 return k8s_controllers, list(not_ready_controllers)
109
110def get_k8s_url(kube_config):
111 # TODO: Get login info
112 with open(kube_config) as f:
113 config = yaml.load(f)
114 # TODO: Support cluster by name
115 return config['clusters'][0]['cluster']['server']
116
117def exec_healthcheck(hp_script, namespace):
118 try:
119 hc = subprocess.check_output(
120 ['sh', hp_script, namespace, 'health'],
121 stderr=subprocess.STDOUT)
122 return 0, hc.output
123 except subprocess.CalledProcessError as err:
124 return err.returncode, err.output
125
126def check_readiness(k8s_url, namespace, verbosity):
127 k8s_controllers, not_ready_controllers = get_k8s_controllers(namespace, k8s_url)
128
129 # check pods only when it is explicitly wanted (judging readiness by deployment status)
130 if verbosity > 1:
131 pods = get_resources(k8s_url, namespace, 'api/v1', 'pods')
132 unready_pods = chain.from_iterable(
133 get_names(not_ready_pods(
134 pods_by_parent(pods, x)))
135 for x in not_ready_controllers)
136 else:
137 unready_pods = []
138
139 print_status(verbosity, k8s_controllers, unready_pods)
140 return not not_ready_controllers
141
142def check_in_loop(k8s_url, namespace, max_time, sleep_time, verbosity):
143 max_end_time = datetime.datetime.now() + datetime.timedelta(minutes=max_time)
144 ready = False
145 while datetime.datetime.now() < max_end_time:
146 ready = check_readiness(k8s_url, namespace, verbosity)
147 if ready:
148 return ready
149 sleep(sleep_time)
150 return ready
151
152def check_helm_releases():
153 helm = subprocess.check_output(['helm', 'ls'])
154 if helm == '':
155 sys.exit('No Helm releases detected.')
156 helm_releases = csv.DictReader(
157 map(lambda x: x.replace(' ', ''), helm.split('\n')),
158 delimiter='\t')
159 failed_releases = [release['NAME'] for release in helm_releases
160 if release['STATUS'] == 'FAILED']
161 return helm, failed_releases
162
163
164def create_ready_string(ready, total, prefix):
165 return '{:12} {}/{}'.format(prefix, ready, total)
166
167def print_status(verbosity, resources, not_ready_pods):
168 ready_strings = []
169 ready = {k: v['ready_count'] for k,v in resources.items()}
170 count = {k: v['total_count'] for k,v in resources.items()}
171 if verbosity > 0:
172 ready_strings += [
173 create_ready_string(ready[k], count[k], k.capitalize()) for k in ready
174 ]
175 total_ready = sum(ready.values())
176 total_count = sum(count.values())
177 ready_strings.append(create_ready_string(total_ready, total_count, 'Ready'))
178 status_strings = ['\n'.join(ready_strings)]
179 if verbosity > 1:
180 if not_ready_pods:
181 status_strings.append('\nWaiting for pods:\n{}'.format('\n'.join(not_ready_pods)))
182 else:
183 status_strings.append('\nAll pods are ready!')
184 print('\n'.join(status_strings), '\n')
185
186def parse_args():
187 parser = argparse.ArgumentParser(description='Monitor ONAP deployment progress')
188 parser.add_argument('--namespace', '-n', default='onap',
189 help='Kubernetes namespace of ONAP')
190 parser.add_argument('--server', '-s', help='address of Kubernetes cluster')
191 parser.add_argument('--kubeconfig', '-c',
192 default=expanduser('~') + '/.kube/config',
193 help='path to .kube/config file')
194 parser.add_argument('--health-path', '-hp', help='path to ONAP robot ete-k8s.sh')
195 parser.add_argument('--no-helm', action='store_true', help='Do not check Helm')
196 parser.add_argument('--check-frequency', '-w', default=300, type=int,
197 help='time between readiness checks in seconds')
198 parser.add_argument('--max-time', '-t', default=120, type=int,
199 help='max time to run readiness checks in minutes')
200 parser.add_argument('--single-run', '-1', action='store_true',
201 help='run check loop only once')
202 parser.add_argument('-v', dest='verbosity', action='count', default=0,
203 help='increase output verbosity, e.g. -vv is more verbose than -v')
204
205 return parser.parse_args()
206
207def main():
208 args = parse_args()
209
210 if not args.no_helm:
211 try:
212 helm_output, failed_releases = check_helm_releases()
213 if failed_releases:
214 print('Deployment of {} failed.'.format(','.join(failed_releases)))
215 sys.exit(1)
216 elif args.verbosity > 1:
217 print(helm_output)
218 except IOError as err:
219 sys.exit(err.strerror)
220
221 requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
222 k8s_url = args.server if args.server is not None else get_k8s_url(args.kubeconfig)
223
224 ready = False
225 if args.single_run:
226 ready = check_readiness(k8s_url, args.namespace, args.verbosity)
227 else:
228 if not check_in_loop(k8s_url, args.namespace, args.max_time, args.check_frequency, args.verbosity):
229 # Double-check last 5 minutes and write verbosely in case it is not ready
230 ready = check_readiness(k8s_url, args.namespace, 2)
231
232 if args.health_path is not None:
233 try:
234 hc_rc, hc_output = exec_healthcheck(args.health_path, args.namespace)
235 except IOError as err:
236 sys.exit(err.strerror)
237 if args.verbosity > 1 or hc_rc > 0:
238 print(hc_output.decode('utf-8'))
239 sys.exit(hc_rc)
240
241 if not ready:
242 sys.exit('Deployment is not ready')
243
244if __name__ == '__main__':
245 main()