2 # -*- coding: utf-8 -*-
4 # COPYRIGHT NOTICE STARTS HERE
6 # Copyright 2019 © Samsung Electronics Co., Ltd.
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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.
20 # COPYRIGHT NOTICE ENDS HERE
23 from __future__ import print_function
30 from time import sleep
31 from os.path import expanduser
32 from itertools import chain
34 from requests.packages.urllib3.exceptions import InsecureRequestWarning
37 def add_resource_kind(resources, kind):
38 for item in resources:
42 def get_resources(server, namespace, api, kind, ssl_verify=False):
43 url = '/'.join([server, api, 'namespaces', namespace, kind])
45 req = requests.get(url, verify=ssl_verify)
46 except requests.exceptions.ConnectionError as err:
47 sys.exit('Could not connect to {}'.format(server))
49 # kind is <resource>List in response so [:-4] removes 'List' from value
50 return add_resource_kind(json['items'], json['kind'][:-4])
52 def pods_by_parent(pods, parent):
54 if pod['metadata']['labels']['app'] == parent:
57 def 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']
62 def get_not_ready(data):
63 return [x for x in data if not k8s_controller_ready(x)]
66 return [x['metadata']['labels']['app'] for x in data]
69 return [x['metadata']['name'] for x in data]
73 return [x['status'] for x in pod['status']['conditions']
74 if x['type'] == 'Ready'][0] == 'True'
75 except (KeyError, IndexError):
78 def not_ready_pods(pods):
80 if not pod_ready(pod):
83 def 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'])
90 def get_k8s_controllers(namespace, k8s_url):
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']))
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']))
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']))
105 not_ready_controllers = chain.from_iterable(
106 k8s_controllers[x]['not_ready_list'] for x in k8s_controllers)
108 return k8s_controllers, list(not_ready_controllers)
110 def 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']
117 def exec_healthcheck(hp_script, namespace):
119 hc = subprocess.check_output(
120 ['sh', hp_script, namespace, 'health'],
121 stderr=subprocess.STDOUT)
123 except subprocess.CalledProcessError as err:
124 return err.returncode, err.output
126 def check_readiness(k8s_url, namespace, verbosity):
127 k8s_controllers, not_ready_controllers = get_k8s_controllers(namespace, k8s_url)
129 # check pods only when it is explicitly wanted (judging readiness by deployment status)
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)
139 print_status(verbosity, k8s_controllers, unready_pods)
140 return not not_ready_controllers
142 def check_in_loop(k8s_url, namespace, max_time, sleep_time, verbosity):
143 max_end_time = datetime.datetime.now() + datetime.timedelta(minutes=max_time)
145 while datetime.datetime.now() < max_end_time:
146 ready = check_readiness(k8s_url, namespace, verbosity)
152 def check_helm_releases():
153 helm = subprocess.check_output(['helm', 'ls'])
155 sys.exit('No Helm releases detected.')
156 helm_releases = csv.DictReader(
157 map(lambda x: x.replace(' ', ''), helm.split('\n')),
159 failed_releases = [release['NAME'] for release in helm_releases
160 if release['STATUS'] == 'FAILED']
161 return helm, failed_releases
164 def create_ready_string(ready, total, prefix):
165 return '{:12} {}/{}'.format(prefix, ready, total)
167 def print_status(verbosity, resources, not_ready_pods):
169 ready = {k: v['ready_count'] for k,v in resources.items()}
170 count = {k: v['total_count'] for k,v in resources.items()}
173 create_ready_string(ready[k], count[k], k.capitalize()) for k in ready
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)]
181 status_strings.append('\nWaiting for pods:\n{}'.format('\n'.join(not_ready_pods)))
183 status_strings.append('\nAll pods are ready!')
184 print('\n'.join(status_strings), '\n')
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')
205 return parser.parse_args()
212 helm_output, failed_releases = check_helm_releases()
214 print('Deployment of {} failed.'.format(','.join(failed_releases)))
216 elif args.verbosity > 1:
218 except IOError as err:
219 sys.exit(err.strerror)
221 requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
222 k8s_url = args.server if args.server is not None else get_k8s_url(args.kubeconfig)
226 ready = check_readiness(k8s_url, args.namespace, args.verbosity)
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)
232 if args.health_path is not None:
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'))
242 sys.exit('Deployment is not ready')
244 if __name__ == '__main__':