From ddf451199c7c8e19d7e4f4edce95706367bfbde9 Mon Sep 17 00:00:00 2001 From: Andrey Pavlov Date: Sat, 4 Mar 2017 17:54:49 +0000 Subject: [PATCH] Initial AppController integration Change-Id: Ic3dc48a93f2cc1a4d3ff3e513b5cfcc545289c6b --- fuel_ccp/config/kubernetes.py | 10 +++++++++ fuel_ccp/deploy.py | 27 +++++++++++++++++++++-- fuel_ccp/kubernetes.py | 30 +++++++++++++++++++++++--- fuel_ccp/templates.py | 40 +++++++++++++++++++++++++++++++++-- 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/fuel_ccp/config/kubernetes.py b/fuel_ccp/config/kubernetes.py index bc6361e6..f8f2c0ec 100644 --- a/fuel_ccp/config/kubernetes.py +++ b/fuel_ccp/config/kubernetes.py @@ -10,6 +10,9 @@ DEFAULTS = { 'password': None, 'cluster_domain': 'cluster.local', 'image_pull_policy': None, + 'appcontroller': { + "enabled": False + } }, } @@ -33,6 +36,13 @@ SCHEMA = { {'type': 'null'}, {'enum': ['Always', 'IfNotPresent', 'Never']}, ]}, + 'appcontroller': { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + "enabled": {'type': 'boolean'}, + }, + }, }, }, } diff --git a/fuel_ccp/deploy.py b/fuel_ccp/deploy.py index fa6b4416..41e5d23b 100644 --- a/fuel_ccp/deploy.py +++ b/fuel_ccp/deploy.py @@ -89,7 +89,7 @@ def _process_secrets(secrets): type, data) -def parse_role(component, topology, configmaps): +def parse_role(component, topology, configmaps, components_map): service_dir = component["service_dir"] role = component["service_content"] component_name = component["component_name"] @@ -114,6 +114,8 @@ def parse_role(component, topology, configmaps): yield _process_secrets(role.get("secrets")) workflows = _parse_workflows(service) + if CONF.kubernetes.appcontroller['enabled']: + yield create_dependencies(workflows, components_map) serialize_workflows(workflows) workflow_cm = _create_workflow(workflows, service_name) configmaps = configmaps + (files_cm, meta_cm, workflow_cm) @@ -627,6 +629,27 @@ def _create_registry_secret(): kubernetes.process_object(secret) +def _format_dependency(dep, components_map): + service_name, _, dep_name = dep.partition('/') + # FIXME in general this is not correct... + if dep_name in components_map: + kind = components_map[dep_name]['service_content']['service'].get( + 'kind', "deployment") + return "%s/%s" % (kind.lower(), service_name) + return "job/%s-%s" % (service_name, dep_name) + + +def create_dependencies(workflows, components_map): + for name, wf in six.iteritems(workflows): + child = _format_dependency(wf['workflow']['name'], components_map) + for dep in wf['workflow']['dependencies']: + parent = _format_dependency(dep, components_map) + dep_name = "-".join((child.partition("/")[-1], + parent.partition("/")[-1]))[:63].rstrip("-") + template = templates.serialize_dependency(dep_name, parent, child) + yield template + + def deploy_components(components_map, components): topology = _make_topology(CONF.nodes, CONF.roles, CONF.replicas) @@ -662,7 +685,7 @@ def deploy_components(components_map, components): for service_name in components: service = components_map[service_name] service["service_content"]['service']['exports_ctx'] = exports_ctx - objects_gen = parse_role(service, topology, configmaps) + objects_gen = parse_role(service, topology, configmaps, components_map) objects = list(itertools.chain.from_iterable(objects_gen)) component_name = service['component_name'] do_upgrade = component_name in upgrading_components diff --git a/fuel_ccp/kubernetes.py b/fuel_ccp/kubernetes.py index c8deb339..be31a7ac 100644 --- a/fuel_ccp/kubernetes.py +++ b/fuel_ccp/kubernetes.py @@ -2,6 +2,7 @@ import logging import os import pykube.exceptions +import pykube.objects import yaml from fuel_ccp import config @@ -94,8 +95,8 @@ def get_pykube_object(object_dict, namespace=None, client=None): namespace = CONF.kubernetes.namespace if client is None: client = get_client() - - obj_class = getattr(pykube, object_dict["kind"], None) + obj_class = getattr(pykube, object_dict["kind"], None) or globals().get( + object_dict["kind"], None) if obj_class is None: raise RuntimeError('"%s" object is not supported, skipping.' % object_dict['kind']) @@ -127,7 +128,6 @@ def process_object(object_dict, namespace=None, client=None): if CONF.action.dry_run: LOG.info(yaml.dump(object_dict, default_flow_style=False)) return - obj = get_pykube_object(object_dict, namespace=namespace, client=client) if obj.exists(): @@ -226,3 +226,27 @@ def get_configmap(name): return pykube.ConfigMap.objects(client).filter( namespace=CONF.kubernetes.namespace, selector="ccp=true").get_by_name(name) + + +class Dependency(pykube.objects.APIObject): + + version = "appcontroller.k8s/v1alpha1" + endpoint = "dependencies" + kind = "Dependency" + + def __init__(self, api, obj): + self.api = api + self.namespace = obj['metadata']['namespace'] + self.set_obj(obj) + + +class Definition(pykube.objects.APIObject): + + version = "appcontroller.k8s/v1alpha1" + endpoint = "definitions" + kind = "Definition" + + def __init__(self, api, obj): + self.api = api + self.namespace = obj['metadata']['namespace'] + self.set_obj(obj) diff --git a/fuel_ccp/templates.py b/fuel_ccp/templates.py index 70d7340b..6bc54b5d 100644 --- a/fuel_ccp/templates.py +++ b/fuel_ccp/templates.py @@ -29,6 +29,18 @@ def _get_readiness_cmd(role_name): return [PYTHON_PATH, ENTRYPOINT_PATH, "status", role_name] +def _wrap_with_definition(name, obj): + kind = obj['kind'].lower() + return { + "apiVersion": "appcontroller.k8s/v1alpha1", + "kind": "Definition", + "metadata": { + "name": "%s-%s" % (kind, name) + }, + kind: obj + } + + def serialize_namespace(name): return { "apiVersion": "v1", @@ -376,7 +388,7 @@ def serialize_volumes(service, for_job=None): def serialize_job(name, spec, component_name, app_name): - return { + job = { "apiVersion": "batch/v1", "kind": "Job", "metadata": { @@ -391,6 +403,9 @@ def serialize_job(name, spec, component_name, app_name): "template": spec } } + if CONF.kubernetes.appcontroller["enabled"]: + job = _wrap_with_definition(name, job) + return job def serialize_deployment(name, spec, annotations, replicas, component_name, @@ -423,12 +438,14 @@ def serialize_deployment(name, spec, annotations, replicas, component_name, } } } + if CONF.kubernetes.appcontroller["enabled"]: + deployment = _wrap_with_definition(name, deployment) return deployment def serialize_statefulset(name, spec, annotations, replicas, component_name): - return { + obj = { "apiVersion": "apps/v1beta1", "kind": "StatefulSet", "metadata": { @@ -451,6 +468,10 @@ def serialize_statefulset(name, spec, annotations, replicas, component_name): } } + if CONF.kubernetes.appcontroller["enabled"]: + obj = _wrap_with_definition(name, obj) + return obj + def serialize_affinity(service, topology): policy = { @@ -532,6 +553,9 @@ def serialize_service(name, ports, headless=False, annotations=None): else: obj["spec"]["clusterIP"] = "None" + if CONF.kubernetes.appcontroller["enabled"]: + obj = _wrap_with_definition(name, obj) + return obj @@ -576,3 +600,15 @@ def serialize_secret(name, type="Opaque", data={}): "type": type, "data": data } + + +def serialize_dependency(name, parent, child): + return { + "apiVersion": "appcontroller.k8s/v1alpha1", + "kind": "Dependency", + "metadata": { + "name": name + }, + "parent": parent, + "child": child + }