marketplace/deployer_util/wait_for_ready.py (149 lines of code) (raw):
#!/usr/bin/env python3
#
# Copyright 2018 Google LLC
#
# 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.
import time
import log_util as log
from argparse import ArgumentParser
from bash_util import Command
_PROG_HELP = "Wait for the application to get ready into a ready state"
def main():
parser = ArgumentParser(description=_PROG_HELP)
parser.add_argument('--name')
parser.add_argument('--namespace')
parser.add_argument('--timeout', type=float)
args = parser.parse_args()
log.info("Wait {} seconds for the application '{}' to get into ready state",
args.timeout, args.name)
previous_healthy = False
min_time_before_healthy = 30
poll_interval = 4
application = Command(
'''
kubectl get "applications.app.k8s.io/{}"
--namespace="{}"
--output=json
'''.format(args.name, args.namespace),
print_call=True).json()
top_level_kinds = [
kind['kind'] for kind in application['spec']['componentKinds']
]
poll_start_time = time.time()
while True:
top_level_resources = []
for kind in top_level_kinds:
resources = Command('''
kubectl get "{}"
--namespace="{}"
--selector app.kubernetes.io/name="{}"
--output=json
'''.format(kind, args.namespace, args.name)).json()
top_level_resources.extend(resources['items'])
if len(top_level_resources) == 0:
raise Exception("ERROR no top level resources found")
log.info("Top level resources: {}", len(top_level_resources))
healthy = True
for resource in top_level_resources:
healthy = is_healthy(resource)
if not healthy:
break
if previous_healthy != healthy:
log.info(
"Initialization: Found applications.app.k8s.io/{} ready status to be {}.",
args.name, healthy)
previous_healthy = healthy
if healthy:
log.info("Wait {} seconds to make sure app stays in healthy state.",
min_time_before_healthy)
healthy_start_time = time.time()
if healthy:
elapsed_healthy_time = time.time() - healthy_start_time
if elapsed_healthy_time > min_time_before_healthy:
break
if time.time() - poll_start_time > args.timeout:
raise Exception(
"ERROR Application did not get ready before timeout of {} seconds"
.format(args.timeout))
time.sleep(poll_interval)
def is_healthy(resource):
if resource['kind'] == "Deployment":
return is_deployment_ready(resource)
if resource['kind'] == "StatefulSet":
return is_sts_ready(resource)
if resource['kind'] == "Pod":
return is_pod_ready(resource)
if resource['kind'] == "Job":
return is_job_ready(resource)
if resource['kind'] == "PersistentVolumeClaim":
return is_pvc_ready(resource)
if resource['kind'] == "Service":
return is_service_ready(resource)
if resource['kind'] == "Ingress":
return is_ingress_ready(resource)
# TODO(ruela): Handle more resource types.
return True
def is_deployment_ready(resource):
if total_replicas(resource) == ready_replicas(resource):
return True
log.info("Deployment '{}' replicas are not ready: {}/{}", name(resource),
ready_replicas(resource), total_replicas(resource))
return False
def is_sts_ready(resource):
if total_replicas(resource) == ready_replicas(resource):
return True
log.info("StatefulSet '{}' replicas are not ready: {}/{}", name(resource),
ready_replicas(resource), total_replicas(resource))
return False
def is_pod_ready(resource):
if status_condition_is_true('Ready', resource):
return True
log.info("Pod/{} is not ready.", name(resource))
return False
def is_job_ready(resource):
# Don't wait for Deployer.
if is_deployer_job(resource):
return True
if status_condition_is_true('Complete', resource):
return True
log.info("Job/{} is not ready.", name(resource))
return False
def is_pvc_ready(resource):
if phase(resource) == "Bound":
return True
log.info("pvc/{} phase is '{}'. Expected: 'Bound'", name(resource),
phase(resource))
return False
def is_service_ready(resource):
if resource['spec']['type'] != "LoadBalancer":
return True
if service_ip(resource):
return True
log.info("service/{} service ip is not ready.", name(resource))
return False
def is_ingress_ready(resource):
if 'ingress' in resource['status']['loadBalancer']:
return True
log.info("Ingress/{} is not ready.", name(resource))
return False
def is_deployer_job(resource):
if 'app.kubernetes.io/component' in labels(resource):
return (labels(resource)['app.kubernetes.io/component'] ==
'deployer.marketplace.cloud.google.com')
return False
def name(resource):
return resource['metadata']['name']
def labels(resource):
return resource['metadata']['labels']
def total_replicas(resource):
return resource['spec']['replicas']
def status_condition_is_true(condition_type, resource):
for condition in resource.get('status', {}).get('conditions', []):
if condition['type'] == condition_type:
return condition['status'] == 'True'
return False
def ready_replicas(resource):
if not 'readyReplicas' in resource['status']:
return 0
return resource['status']['readyReplicas']
def service_ip(resource):
if not 'ingress' in resource['status']['loadBalancer']:
return ""
return resource['status']['loadBalancer']['ingress']
def phase(resource):
return resource['status']['phase']
if __name__ == "__main__":
main()