From 2b15322c5f8b6aa2bdf7d36ca922d4ebb5f06cda Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 11 Nov 2016 15:26:46 +0300 Subject: [PATCH] Add support for rolling upgrade action Depends-On: I8700d499168b560e1cfeb309b4eaae1bd9ddb995 Change-Id: Ifb3b2f36735932bafa5fb8a71892b0638eb11a2e --- fuel_ccp_entrypoint/start_script.py | 93 +++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 94 insertions(+) diff --git a/fuel_ccp_entrypoint/start_script.py b/fuel_ccp_entrypoint/start_script.py index e99906a..9ded7a0 100644 --- a/fuel_ccp_entrypoint/start_script.py +++ b/fuel_ccp_entrypoint/start_script.py @@ -15,6 +15,7 @@ import etcd import jinja2 import json import netifaces +import pykube import six @@ -345,6 +346,80 @@ def run_daemon(cmd, user=None): raise RuntimeError("Process exited with code: %d" % proc.returncode) +def get_pykube_client(): + os.environ['KUBERNETES_SERVICE_HOST'] = 'kubernetes.default' + config = pykube.KubeConfig.from_service_account() + return pykube.HTTPClient(config) + + +def _reload_obj(obj, updated_dict): + obj.reload() + obj.obj = updated_dict + + +def get_pykube_object(object_dict, namespace, client): + obj_class = getattr(pykube, object_dict["kind"], None) + if obj_class is None: + raise RuntimeError('"%s" object is not supported, skipping.' + % object_dict['kind']) + + if not object_dict['kind'] == 'Namespace': + object_dict['metadata']['namespace'] = namespace + + return obj_class(client, object_dict) + +UPDATABLE_OBJECTS = ('ConfigMap', 'Deployment', 'Service') + + +def process_pykube_object(object_dict, namespace, client): + LOG.debug("Deploying %s: \"%s\"", + object_dict["kind"], object_dict["metadata"]["name"]) + + obj = get_pykube_object(object_dict, namespace, client) + + if obj.exists(): + LOG.debug('%s "%s" already exists', object_dict['kind'], + object_dict['metadata']['name']) + if object_dict['kind'] in UPDATABLE_OBJECTS: + if object_dict['kind'] == 'Service': + # Reload object and merge new and old fields + _reload_obj(obj, object_dict) + obj.update() + LOG.debug('%s "%s" has been updated', object_dict['kind'], + object_dict['metadata']['name']) + else: + obj.create() + LOG.debug('%s "%s" has been created', object_dict['kind'], + object_dict['metadata']['name']) + return obj + + +def wait_for_deployment(obj): + while True: + generation = obj.obj['metadata']['generation'] + observed_generation = obj.obj['status']['observedGeneration'] + if observed_generation >= generation: + break + LOG.info("Waiting for deployment %s to move to new generation") + time.sleep(4.2) + obj.reload() + + while True: + desired = obj.obj['spec']['replicas'] + status = obj.obj['status'] + updated = status['updatedReplicas'] + available = status['availableReplicas'] + current = status['replicas'] + if desired == updated == available == current: + break + LOG.info("Waiting for deployment %s: desired=%s, updated=%s," + " available=%s, current=%s", + obj.obj['metadata']['name'], + desired, updated, available, current) + time.sleep(4.2) + obj.reload() + + def get_workflow(role_name): workflow_path = WORKFLOW_PATH_TEMPLATE % role_name LOG.info("Getting workflow from %s", workflow_path) @@ -420,10 +495,13 @@ def do_provision(role_name): job = workflow.get("job") daemon = workflow.get("daemon") + roll = workflow.get("roll") if job: execute_job(workflow, job) elif daemon: execute_daemon(workflow, daemon) + elif roll is not None: + execute_roll(workflow, roll) else: LOG.error("Job or daemon is not specified in workflow") sys.exit(1) @@ -460,5 +538,20 @@ def execute_job(workflow, job): sys.exit(0) +def execute_roll(workflow, roll): + LOG.info("Running rolling upgrade of service %s", workflow["name"]) + namespace = VARIABLES["namespace"] + client = get_pykube_client() + deployments = [] + for object_dict in roll: + obj = process_pykube_object(object_dict, namespace, client) + if object_dict['kind'] == 'Deployment': + deployments.append(obj) + for obj in deployments: + wait_for_deployment(obj) + set_status_ready(workflow["name"]) + sys.exit(0) + + if __name__ == "__main__": main() diff --git a/requirements.txt b/requirements.txt index 05754fa..8842251 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ pbr>=1.6 # Apache-2.0 Jinja2>=2.8 # BSD License (3 clause) +pykube python-etcd>=0.4.3 # MIT License PyYAML>=3.10.0 # MIT six>=1.9.0 # MIT