diff options
Diffstat (limited to 'cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common')
8 files changed, 584 insertions, 0 deletions
diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/__init__.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/__init__.py new file mode 100644 index 0000000000..19a30ba43d --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/__init__.py @@ -0,0 +1,14 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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. diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/constants.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/constants.py new file mode 100644 index 0000000000..493a44f16f --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/constants.py @@ -0,0 +1,20 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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. + +HELM_URL = 'https://kubernetes-helm.storage.googleapis.com/helm-canary-linux-amd64.tar.gz' +OOM_GIT_URL = 'https://gerrit.onap.org/r/oom.git' + +RT_HELM_CLI_PATH = "helm_cli_path" +RT_APPS_ROOT_PATH = "app_root_path" diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/deployment_result.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/deployment_result.py new file mode 100644 index 0000000000..48d49e0403 --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/deployment_result.py @@ -0,0 +1,27 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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. + +from cloudify import ctx + + +def save_deployment_result(key): + result = ctx.instance.runtime_properties['kubernetes'] + ctx.instance.runtime_properties[key] = result + ctx.instance.runtime_properties['kubernetes'] = {} + + +def set_deployment_result(key): + result = ctx.instance.runtime_properties.pop(key) + ctx.instance.runtime_properties['kubernetes'] = result
\ No newline at end of file diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/helm.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/helm.py new file mode 100644 index 0000000000..4404f6f832 --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/helm.py @@ -0,0 +1,62 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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 urllib +import tarfile +import os +import tempfile +from git import Repo + +def get_helm_path(url): + tarball = _fetch_helm(url) + helm_dir = _get_tmp_file_name() + _untar_helm_archive(tarball, helm_dir) + helm_binary_path = _find_file('helm', helm_dir) + return helm_binary_path + + +def get_apps_root_path(git_url): + dst_repo_path = _get_tmp_file_name() + Repo.clone_from(git_url, dst_repo_path) + apps_root = format(dst_repo_path) + return apps_root + +def _fetch_helm(url): + dst_tar_path = _get_tmp_file_name() + + file = urllib.URLopener() + file.retrieve(url, dst_tar_path) + + return dst_tar_path + +def _untar_helm_archive(tar_path, helm_dir): + helm_tar = tarfile.open(tar_path) + helm_tar.extractall(helm_dir) + helm_tar.close() + + +def _find_file(filename, base_path): + for root, dirs, files in os.walk(base_path): + for name in files: + if name == filename: + return os.path.abspath(os.path.join(root, name)) + + raise Exception('Cannot find helm binary') + + +def _get_tmp_file_name(): + return '{}/{}'.format(tempfile._get_default_tempdir(), next(tempfile._get_candidate_names())) + + diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/init_pod.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/init_pod.py new file mode 100644 index 0000000000..1376818b7b --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/init_pod.py @@ -0,0 +1,63 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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. + +from cloudify import ctx +import yaml + +import constants +import resources_services + +SERVICES_FILE_PARTS_SEPARATOR = '---' + + +def do_create_init_pod(): + ctx.logger.info('Creating init pod') + + yaml_config = resources_services.render_chart( + ctx.node.properties["init_pod"], + _retrieve_root_path(), + _retrieve_helm_cli_path() + ) + yaml_content_part = yaml_config.split(SERVICES_FILE_PARTS_SEPARATOR)[2] + enhanced_yaml = _add_openstack_envs(yaml_content_part) + + resources_services.create_resource(enhanced_yaml) + + ctx.logger.info('Init pod created successfully') + + +def do_delete_init_pod(): + ctx.logger.info('Deleting init pod') + + ctx.logger.info('Init pod deleted successfully') + +def _add_openstack_envs(yaml_content): + input_dict = yaml.load(yaml_content) + + container_dict = input_dict['spec']['containers'][0] + container_dict.pop('envFrom') + + openstack_envs = ctx.node.properties["openstack_envs"] + for item in openstack_envs.items(): + ctx.logger.debug("adding item = {}".format(item)) + container_dict['env'].append(item) + + return input_dict + +def _retrieve_root_path(): + return ctx.instance.runtime_properties.get(constants.RT_APPS_ROOT_PATH, None) + +def _retrieve_helm_cli_path(): + return ctx.instance.runtime_properties.get(constants.RT_HELM_CLI_PATH, None)
\ No newline at end of file diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/namespace.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/namespace.py new file mode 100644 index 0000000000..d1336768ac --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/namespace.py @@ -0,0 +1,101 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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 cloudify_kubernetes.tasks as kubernetes_plugin +from cloudify import ctx +from cloudify.exceptions import NonRecoverableError + +import deployment_result + + +def do_create_namespace(): + namespace = _retrieve_namespace() + ctx.logger.info('Creating namespace: {0}'.format(namespace)) + + namespace_resource_template = _prepare_namespace_resource_template( + namespace + ) + + ctx.logger.debug( + 'Kubernetes object which will be deployed: {0}' + .format(namespace_resource_template) + ) + + kubernetes_plugin.custom_resource_create(**namespace_resource_template) + deployment_result.save_deployment_result('namespace') + ctx.logger.info('Namespace created successfully') + + +def do_delete_namespace(): + namespace = _retrieve_namespace() + ctx.logger.info('Deleting namespace: {0}'.format(namespace)) + + namespace_resource_template = _prepare_namespace_resource_template( + namespace + ) + + ctx.logger.debug( + 'Kubernetes object which will be deleted: {0}' + .format(namespace_resource_template) + ) + + deployment_result.set_deployment_result('namespace') + kubernetes_plugin.custom_resource_delete(**namespace_resource_template) + ctx.logger.info('Namespace deleted successfully') + + + +def _retrieve_namespace(): + + default_namespace = ctx.node.properties.get('options', {}).get('namespace') + namespace = ctx.node.properties.get('namespace', default_namespace) + + if not namespace: + raise NonRecoverableError( + 'Namespace is not defined (node={})'.format(ctx.node.name) + ) + + return namespace + + +def _prepare_namespace_resource_template(name): + return { + 'definition': { + 'apiVersion': 'v1', + 'kind': 'Namespace', + 'metadata': { + 'name': name, + 'labels': { + 'name': name + }, + }, + }, + 'api_mapping': { + 'create': { + 'api': 'CoreV1Api', + 'method': 'create_namespace', + 'payload': 'V1Namespace' + }, + 'read': { + 'api': 'CoreV1Api', + 'method': 'read_namespace', + }, + 'delete': { + 'api': 'CoreV1Api', + 'method': 'delete_namespace', + 'payload': 'V1DeleteOptions' + } + } + } diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/resources_services.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/resources_services.py new file mode 100644 index 0000000000..268068f00c --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/resources_services.py @@ -0,0 +1,230 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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 subprocess + +import cloudify_kubernetes.tasks as kubernetes_plugin +import yaml +from cloudify import ctx +from cloudify.exceptions import NonRecoverableError + +import constants +import deployment_result +import time +import ast +import json +import base64 + +SERVICES_FILE_PARTS_SEPARATOR = '---' + + +def create_resoruces(): + ctx.logger.info('Creating resources') + apps_path = _retrieve_root_path() + + if not apps_path: + ctx.logger.warn( + 'Apps dir is not defined. Skipping!' + ) + + return + + helm_app = ctx.node.properties.get('path', None) + + yaml_file = prepare_content(helm_app) + + yaml_content_parts = yaml_file.split(SERVICES_FILE_PARTS_SEPARATOR) + + for yaml_content_part in yaml_content_parts: + if yaml_content_part: + yaml_content = _apply_readiness_workaround(yaml_content_part) + if yaml_content: + create_resource(yaml_content) + + ctx.logger.info('Resource created successfully') + +def delete_resoruces(): + + ctx.logger.info('Deleting resources') + apps_path = _retrieve_root_path() + + if not apps_path: + ctx.logger.warn( + 'Apps dir is not defined. Skipping!' + ) + return + + helm_app = ctx.node.properties.get('path', None) + + yaml_file = prepare_content(helm_app) + + yaml_content_parts = yaml_file.split(SERVICES_FILE_PARTS_SEPARATOR) + + for yaml_content_part in yaml_content_parts: + if yaml_content_part: + yaml_content = _apply_readiness_workaround(yaml_content_part) + if yaml_content: + delete_resource(yaml_content) + + ctx.logger.info('Resources deleted successfully') + + +def prepare_content(resource): + helm_path = _retrieve_helm_cli_path() + yaml_file = render_chart(resource, _retrieve_root_path(), helm_path) + + return yaml_file + + +def create_resource(yaml_content_dict): + ctx.logger.debug("Loading yaml: {}".format(yaml_content_dict)) + + if yaml_content_dict.get('kind', '') == 'PersistentVolumeClaim': + ctx.logger.debug("PersistentVolumeClaim custom handling") + kubernetes_plugin.custom_resource_create(definition=yaml_content_dict, api_mapping=_get_persistent_volume_mapping_claim_api()) + else: + kubernetes_plugin.resource_create(definition=yaml_content_dict) + + deployment_result.save_deployment_result('resource_{0}'.format(yaml_content_dict['metadata']['name'])) + +def delete_resource(yaml_content_dict): + ctx.logger.debug("Loading yaml: {}".format(yaml_content_dict)) + + deployment_result.save_deployment_result('resource_{0}'.format(yaml_content_dict['metadata']['name'])) + if yaml_content_dict.get('kind', '') == 'PersistentVolumeClaim': + ctx.logger.debug("PersistentVolumeClaim custom handling") + kubernetes_plugin.custom_resource_delete(definition=yaml_content_dict, api_mapping=_get_persistent_volume_mapping_claim_api()) + else: + kubernetes_plugin.resource_delete(definition=yaml_content_dict) + + +def render_chart(app, app_root_path, helm_cli_path): + app_chart_path = "{}/{}/".format(app_root_path, app) + ctx.logger.debug('App chart path = {}'.format(app_chart_path)) + return _exec_helm_template(helm_cli_path, app_chart_path) + + +def _exec_helm_template(helm_path, chart): + cmd = '{0} template {1}'.format(helm_path, chart) + ctx.logger.debug('Executing helm template cmd: {}'.format(cmd)) + rendered = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE).stdout.read().decode() + + return rendered + +def _get_persistent_volume_mapping_claim_api(): + api_mapping = { + 'create' : { + 'api': 'CoreV1Api', + 'method': 'create_namespaced_persistent_volume_claim', + 'payload': 'V1PersistentVolumeClaim' + }, + 'read' : { + 'api': 'CoreV1Api', + 'method': 'read_namespaced_persistent_volume_claim', + }, + 'delete': { + 'api': 'CoreV1Api', + 'method': 'delete_namespaced_persistent_volume_claim', + 'payload': 'V1DeleteOptions' + } + } + + return api_mapping + + +def _apply_readiness_workaround(yaml_file): + b64_env = _get_k8s_b64_env() + + input_dict = yaml.load(yaml_file) + + try: + init_containers = input_dict['spec']['template']['metadata']['annotations'][ + 'pod.beta.kubernetes.io/init-containers'] + init_cont_list = eval(init_containers) + + new_init_cont_list = list() + new_cont = None + for init_cont in init_cont_list: + if "oomk8s/readiness-check" in init_cont['image']: + init_cont['image'] = "clfy/oomk8s-cfy-readiness-check:1.0.1" + #init_cont['imagePullPolicy'] = "IfNotPresent" + init_cont['env'].append(b64_env) + new_cont = init_cont + new_init_cont_list.append(json.dumps(init_cont)) + + new_payload = ",".join(new_init_cont_list) + + if new_cont: + input_dict['spec']['template']['metadata']['annotations'].pop('pod.beta.kubernetes.io/init-containers') + input_dict['spec']['template']['metadata']['annotations']['pod.beta.kubernetes.io/init-containers'] = '[{}]'.format(new_payload) + + + except KeyError as ke: + ctx.logger.debug('Readiness section is not found.') + + return input_dict + + +def _get_k8s_b64(): + target_relationship = _retrieve_managed_by_master() + + k8s_config = target_relationship.node.properties.get('configuration').get('file_content') + + if not k8s_config: + raise Exception("Cannot find kubernetes config") + + k8s_config_plain = yaml.dump(k8s_config, allow_unicode=True) + + k8s_config_b64 = base64.b64encode(k8s_config_plain) + + return k8s_config_b64 + + +def _get_k8s_b64_env(): + env = dict() + env['name'] = 'K8S_CONFIG_B64' + env['value'] = _get_k8s_b64() + return env + + +def _retrieve_root_path(): + target_relationship = _retrieve_depends_on() + + apps_root_path = target_relationship.instance.runtime_properties.get(constants.RT_APPS_ROOT_PATH, None) + + ctx.logger.debug("Retrived apps root path = {}".format(apps_root_path)) + + return apps_root_path + +def _retrieve_helm_cli_path(): + target_relationship = _retrieve_depends_on() + + helm_cli_path = target_relationship.instance.runtime_properties.get(constants.RT_HELM_CLI_PATH, None) + + ctx.logger.debug("Retrived helm clis path = {}".format(helm_cli_path)) + + return helm_cli_path + +def _retrieve_depends_on(): + result = None + for relationship in ctx.instance.relationships: + if relationship.type == 'cloudify.relationships.depends_on': + return relationship.target + +def _retrieve_managed_by_master(): + result = None + for relationship in ctx.instance.relationships: + if relationship.type == 'cloudify.kubernetes.relationships.managed_by_master': + return relationship.target diff --git a/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/workarounds.py b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/workarounds.py new file mode 100644 index 0000000000..fe3e892c5b --- /dev/null +++ b/cloudify-onap/plugins/onap-installation-plugin/k8s_installer/common/workarounds.py @@ -0,0 +1,67 @@ +######## +# Copyright (c) 2017 GigaSpaces Technologies Ltd. All rights reserved +# +# 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. + +from cloudify import ctx +from cloudify.exceptions import NonRecoverableError + +from fabric import api as fabric_api + +def _retrieve_namespace(): + namespace = ctx.node.properties.get( + 'namespace', + ctx.node.properties + .get('options', {}) + .get('namespace', None) + ) + + if not namespace: + raise NonRecoverableError( + 'Namespace is not defined (node={})'.format(ctx.node.name) + ) + + return namespace + + +def configure_secret(): + namespace = _retrieve_namespace() + ctx.logger.info( + 'Configuring docker secrets for namespace: {0}'.format(namespace) + ) + + command = 'kubectl create secret ' \ + 'docker-registry onap-docker-registry-key ' \ + '--docker-server=nexus3.onap.org:10001 ' \ + '--docker-username=docker ' \ + '--docker-password=docker ' \ + '--docker-email=email@email.com ' \ + '--namespace={0}'.format(namespace) + + ctx.logger.info('Command "{0}" will be executed'.format(command)) + + with fabric_api.settings( + **ctx.node.properties.get('ssh_credentials')): + fabric_api.run(command) + + ctx.logger.info('Docker secrets configured successfully') + + +def _get_fabric_env(): + result = dict() + + result['host_string'] = ctx.node.properties.get('ssh_credentials')['host_string'] + result['user'] = ctx.node.properties.get('ssh_credentials')['user'] + result['key'] = ctx.node.properties.get('ssh_credentials')['key'] + + return result |