RUN pip install -e /distcloud-client/distributedcloud-client\r
# in case git repo is not accessable\r
\r
+# RUN git clone --depth 1 --branch master https://github.com/cloudify-incubator/cloudify-helm-plugin.git /helmsdk\r
+\r
\r
COPY requirements.txt /tmp/\r
COPY requirements-stx.txt /tmp/\r
\r
RUN mkdir -p /src/o2app/\r
COPY o2app/ /src/o2app/\r
+\r
+RUN mkdir -p /src/helm_sdk/\r
+COPY helm_sdk/ /src/helm_sdk/\r
+\r
COPY setup.py /src/\r
\r
COPY configs/ /etc/o2/\r
\r
-# RUN pip install -e /src\r
+# RUN mkdir -p /helmsdk\r
+# COPY temp/helmsdk /helmsdk/\r
+# # RUN git clone --depth 1 --branch master https://github.com/cloudify-incubator/cloudify-helm-plugin.git helmsdk\r
+# COPY /helmsdk/helm_sdk /src/helm_sdk\r
\r
+# RUN pip install -e /src\r
COPY tests/ /tests/\r
\r
RUN apt-get install -y procps vim\r
\r
+RUN apt-get install -y curl\r
+RUN curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz;\r
+RUN tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; cp linux-amd64/helm /usr/local/bin\r
+\r
+RUN mkdir -p /etc/kubeconfig/\r
+COPY temp/kubeconfig/config /etc/kubeconfig/\r
+\r
WORKDIR /src\r
pytest tests/integration\r
pytest tests/e2e\r
```\r
+\r
+\r
+Test O2DMS with docker-compose\r
+==============================\r
+\r
+## setup account over INF and get token\r
+\r
+```sh\r
+USER="admin-user"\r
+NAMESPACE="kube-system"\r
+\r
+cat <<EOF > admin-login.yaml\r
+apiVersion: v1\r
+kind: ServiceAccount\r
+metadata:\r
+ name: ${USER}\r
+ namespace: kube-system\r
+---\r
+apiVersion: rbac.authorization.k8s.io/v1\r
+kind: ClusterRoleBinding\r
+metadata:\r
+ name: ${USER}\r
+roleRef:\r
+ apiGroup: rbac.authorization.k8s.io\r
+ kind: ClusterRole\r
+ name: cluster-admin\r
+subjects:\r
+- kind: ServiceAccount\r
+ name: ${USER}\r
+ namespace: kube-system\r
+EOF\r
+kubectl apply -f admin-login.yaml\r
+TOKEN_DATA=$(kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep ${USER} | awk '{print $1}') | grep "token:" | awk '{print $2}')\r
+\r
+```\r
+\r
+## setup remote cli to access kubenetes cluster over INF\r
+\r
+```sh\r
+sudo apt-get install -y apt-transport-https\r
+echo "deb http://mirrors.ustc.edu.cn/kubernetes/apt kubernetes-xenial main" | \\r
+sudo tee -a /etc/apt/sources.list.d/kubernetes.list\r
+sudo apt-get update\r
+sudo apt-get install -y kubectl\r
+\r
+source <(kubectl completion bash) # setup autocomplete in bash into the current shell, bash-completion package should be installed first.\r
+echo "source <(kubectl completion bash)" >> ~/.bashrc # add autocomplete permanently to your bash shell.\r
+\r
+https://get.helm.sh/helm-v3.5.3-linux-amd64.tar.gz\r
+tar xvf helm-v3.5.3-linux-amd64.tar.gz\r
+sudo cp linux-amd64/helm /usr/local/bin\r
+\r
+source <(helm completion bash)\r
+echo "source <(helm completion bash)" >> ~/.bashrc\r
+\r
+OAM_IP=<INF OAM IP>\r
+NAMESPACE=default\r
+TOKEN_DATA=<TOKEN_DATA from INF>\r
+\r
+USER="admin-user"\r
+\r
+kubectl config set-cluster inf-cluster --server=https://${OAM_IP}:6443 --insecure-skip-tls-verify\r
+kubectl config set-credentials ${USER} --token=$TOKEN_DATA\r
+kubectl config set-context ${USER}@inf-cluster --cluster=inf-cluster --user ${USER} --namespace=${NAMESPACE}\r
+kubectl config use-context ${USER}@inf-cluster\r
+\r
+kubectl get pods -A\r
+\r
+```\r
+\r
+\r
+## setup local repo: o2imsrepo\r
+\r
+```sh\r
+helm repo add chartmuseum https://chartmuseum.github.io/charts\r
+\r
+export NODE_IP=<INF OAM IP>\r
+\r
+cat <<EOF>chartmuseum-override.yaml\r
+env:\r
+ open:\r
+ DISABLE_API: false\r
+service:\r
+ type: NodePort\r
+ nodePort: 30330\r
+EOF\r
+helm install chartmuseumrepo chartmuseum/chartmuseum -f chartmuseum-override.yaml\r
+kubectl get pods\r
+Kubectl get services\r
+\r
+helm repo add o2imsrepo http://${NODE_IP}:30330\r
+helm repo update\r
+\r
+helm repo add bitnami https://charts.bitnami.com/bitnami\r
+helm repo update\r
+\r
+helm pull bitnami/mysql\r
+helm push mysql-8.8.16.tgz o2imsrepo\r
+helm repo update\r
+\r
+helm install my-release o2imsrepo/mysql\r
+kubectl get pods\r
+helm del my-release\r
+\r
+```\r
+\r
+\r
+\r
+## Verify CFW over INF: Test with cnf firewall-host-netdevice\r
+\r
+## Setup host netdevice over INF\r
+\r
+```sh\r
+ssh sysadmin@<inf oam IP>\r
+sudo ip link add name veth11 type veth peer name veth12\r
+sudo ip link add name veth21 type veth peer name veth22\r
+sudo ip link |grep veth\r
+exit\r
+```\r
+\r
+\r
+## verify CNF over INF\r
+```sh\r
+git clone https://github.com/biny993/firewall-host-netdevice.git\r
+\r
+cat <<EOF> cfw-hostdev-override.yaml\r
+\r
+image:\r
+ repository: ubuntu\r
+ tag: 18.04\r
+ pullPolicy: IfNotPresent\r
+\r
+resources:\r
+ cpu: 2\r
+ memory: 2Gi\r
+ hugepage: 256Mi\r
+\r
+#global vars for parent and subcharts.\r
+\r
+\r
+ unprotectedNetPortVpg: veth11\r
+ unprotectedNetPortVfw: veth12\r
+ unprotectedNetCidr: 10.10.1.0/24\r
+ unprotectedNetGwIp: 10.10.1.1\r
+\r
+ protectedNetPortVfw: veth21\r
+ protectedNetPortVsn: veth22\r
+ protectedNetCidr: 10.10.2.0/24\r
+ protectedNetGwIp: 10.10.2.1\r
+\r
+ vfwPrivateIp0: 10.10.1.1\r
+ vfwPrivateIp1: 10.10.2.1\r
+\r
+ vpgPrivateIp0: 10.10.1.2\r
+\r
+ vsnPrivateIp0: 10.10.2.2\r
+\r
+EOF\r
+\r
+helm install cfw1 firewall-host-netdevice -f cfw-hostdev-override.yaml\r
+kubectl get pods\r
+helm del cfw1\r
+```\r
+\r
+## push repo to o2imsrepo\r
+\r
+```sh\r
+tar -zcvf firewall-host-netdevice-1.0.0.tgz firewall-host-netdevice/\r
+helm push firewall-host-netdevice-1.0.0.tgz o2imsrepo\r
+helm repo update\r
+helm search repo firewall\r
+\r
+helm install cfw1 o2imsrepo/firewall-host-netdevice -f cfw-hostdev-override.yaml\r
+kubectl get pods\r
+helm del cfw1\r
+```\r
+\r
+## build docker image for o2 services\r
+```sh\r
+cd o2\r
+docker-compose build\r
+\r
+```\r
+\r
+## bootstrap o2 service with docker-compose\r
+```sh\r
+\r
+mkdir -p temp/kubeconfig/\r
+cp <your .kube/config> temp/kubeconfig/\r
+\r
+source ./admin_openrc.sh\r
+export K8S_KUBECONFIG=/etc/kubeconfig/config\r
+docker-compose up -d\r
+docker logs -f o2_redis_pubsub_1\r
+\r
+```\r
+\r
+## simiulate SMO to deploy CFW\r
+\r
+```sh\r
+\r
+curl --location --request GET 'http://localhost:5005/o2ims_infrastructureInventory/v1/deploymentManagers'\r
+export dmsId=<DMS ID>\r
+curl --location --request POST 'http://localhost:5005/o2dms/${dmsId}/O2dms_DeploymentLifecycle/NfDeploymentDescriptor' \\r
+--header 'Content-Type: application/json' \\r
+--data-raw '{\r
+ "name": "cfwdesc1",\r
+ "description": "demo nf deployment descriptor",\r
+ "artifactRepoUrl": "http://128.224.115.15:30330",\r
+ "artifactName": "firewall-host-netdevice",\r
+ "inputParams": \r
+ "{\n \"image\": {\n \"repository\": \"ubuntu\",\n \"tag\": 18.04,\n \"pullPolicy\": \"IfNotPresent\"\n },\n \"resources\": {\n \"cpu\": 2,\n \"memory\": \"2Gi\",\n \"hugepage\": \"256Mi\",\n \"unprotectedNetPortVpg\": \"veth11\",\n \"unprotectedNetPortVfw\": \"veth12\",\n \"unprotectedNetCidr\": \"10.10.1.0/24\",\n \"unprotectedNetGwIp\": \"10.10.1.1\",\n \"protectedNetPortVfw\": \"veth21\",\n \"protectedNetPortVsn\": \"veth22\",\n \"protectedNetCidr\": \"10.10.2.0/24\",\n \"protectedNetGwIp\": \"10.10.2.1\",\n \"vfwPrivateIp0\": \"10.10.1.1\",\n \"vfwPrivateIp1\": \"10.10.2.1\",\n \"vpgPrivateIp0\": \"10.10.1.2\",\n \"vsnPrivateIp0\": \"10.10.2.2\"\n }\n}",\r
+ "outputParams": "{\"output1\": 100}"\r
+}'\r
+\r
+curl --location --request GET 'http://localhost:5005/o2dms/${dmsId}/O2dms_DeploymentLifecycle/NfDeploymentDescriptor'\r
+\r
+curl --location --request POST 'http://localhost:5005/o2dms/${dmsId}/O2dms_DeploymentLifecycle/NfDeployment' \\r
+--header 'Content-Type: application/json' \\r
+--data-raw '{\r
+ "name": "cfw100",\r
+ "description": "demo nf deployment",\r
+ "descriptorId": "<NfDeploymentDescriptorId>",\r
+ "parentDeploymentId": ""\r
+}'\r
+\r
+curl --location --request GET 'http://localhost:5005/o2dms/${dmsId}/O2dms_DeploymentLifecycle/NfDeployment'\r
+\r
+export NfDeploymentId=<NfDeployment Id>\r
+\r
+```\r
+\r
+## check logs\r
+\r
+```sh\r
+docker logs -f o2_redis_pubsub_1\r
+kubectl get pods\r
+kubectl logs -f cfw100-sink-host-netdevice-59bf6fbd4b-845p4\r
+```\r
+\r
+## watch traffic stats\r
+\r
+open browswer with url: http://<NODE_IP>:30667\r
+\r
+\r
+## bring down CFW\r
+\r
+```sh\r
+curl --location --request DELETE 'http://localhost:5005/o2dms/${dmsId}/O2dms_DeploymentLifecycle/NfDeployment/${NfDeploymentId}'\r
+```\r
- OS_AUTH_URL=${OS_AUTH_URL}
- OS_USERNAME=${OS_USERNAME}
- OS_PASSWORD=${OS_PASSWORD}
+ - K8S_KUBECONFIG=${K8S_KUBECONFIG}
+ - K8S_APISERVER=${K8S_APISERVER}
+ - K8S_TOKEN=${K8S_TOKEN}
- LOGGING_CONFIG_LEVEL=DEBUG
volumes:
- ./configs:/configs
- ./o2dms:/o2dms
- ./o2common:/o2common
- ./o2app:/o2app
+ - ./helm_sdk:/helm_sdk
- ./tests:/tests
entrypoint:
- /bin/sh
- ./o2dms:/o2dms
- ./o2common:/o2common
- ./o2app:/o2app
+ - ./helm_sdk:/helm_sdk
- ./tests:/tests
entrypoint:
- /bin/sh
- ./o2dms:/o2dms
- ./o2common:/o2common
- ./o2app:/o2app
+ - ./helm_sdk:/helm_sdk
- ./tests:/tests
entrypoint:
- /bin/sh
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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 json
+
+from .exceptions import CloudifyHelmSDKError
+from helm_sdk.utils import (
+ run_subprocess,
+ prepare_parameter,
+ prepare_set_parameters,
+ validate_no_collisions_between_params_and_flags)
+
+# Helm cli flags names
+HELM_KUBECONFIG_FLAG = 'kubeconfig'
+HELM_KUBE_API_SERVER_FLAG = 'kube-apiserver'
+HELM_KUBE_TOKEN_FLAG = 'kube-token'
+HELM_VALUES_FLAG = 'values'
+APPEND_FLAG_STRING = '--{name}={value}'
+
+
+class Helm(object):
+
+ def __init__(self,
+ logger,
+ binary_path,
+ environment_variables
+ ):
+ self.binary_path = binary_path
+ self.logger = logger
+ if not isinstance(environment_variables, dict):
+ raise Exception(
+ "Unexpected type for environment variables (should be a "
+ "dict): {0}".format(type(
+ environment_variables)))
+
+ self.env = environment_variables
+
+ def execute(self, command, return_output=False):
+ return run_subprocess(
+ command,
+ self.logger,
+ cwd=None,
+ additional_env=self.env,
+ additional_args=None,
+ return_output=return_output)
+
+ def _helm_command(self, args):
+ cmd = [self.binary_path]
+ cmd.extend(args)
+ return cmd
+
+ @staticmethod
+ def handle_auth_params(cmd,
+ kubeconfig=None,
+ token=None,
+ apiserver=None):
+ """
+ Validation of authentication params.
+ Until helm will support --insecure, kubeconfig must be provided.
+ :param kubeconfig: Kubeconfig file path
+ :param: token: bearer token used for authentication.
+ :param: apiserver: the address and the port for the Kubernetes API
+ server.
+ """
+ if kubeconfig is None:
+ raise CloudifyHelmSDKError(
+ 'Must provide kubeconfig file path.')
+ else:
+ cmd.append(APPEND_FLAG_STRING.format(name=HELM_KUBECONFIG_FLAG,
+ value=kubeconfig))
+
+ if token:
+ cmd.append(APPEND_FLAG_STRING.format(name=HELM_KUBE_TOKEN_FLAG,
+ value=token))
+
+ if apiserver:
+ cmd.append(
+ APPEND_FLAG_STRING.format(name=HELM_KUBE_API_SERVER_FLAG,
+ value=apiserver))
+
+ def install(self,
+ name,
+ chart,
+ flags=None,
+ set_values=None,
+ values_file=None,
+ kubeconfig=None,
+ token=None,
+ apiserver=None,
+ **_):
+ """
+ Execute helm install command.
+ :param name: name for the created release.
+ :param chart: chart name to install.
+ :param flags: list of flags to add to the install command.
+ :param set_values: list of variables and their values for --set.
+ :param kubeconfig: path to kubeconfig file.
+ :param values_file: values file path.
+ :param token: bearer token used for authentication.
+ :param apiserver: the address and the port for the Kubernetes API
+ server.
+ :return output of install command.
+ """
+ cmd = ['install', name, chart, '--wait', '--output=json']
+ self.handle_auth_params(cmd, kubeconfig, token, apiserver)
+ if values_file:
+ cmd.append(APPEND_FLAG_STRING.format(name=HELM_VALUES_FLAG,
+ value=values_file))
+ flags = flags or []
+ validate_no_collisions_between_params_and_flags(flags)
+ cmd.extend([prepare_parameter(flag) for flag in flags])
+ set_arguments = set_values or []
+ cmd.extend(prepare_set_parameters(set_arguments))
+ output = self.execute(self._helm_command(cmd), True)
+ return json.loads(output)
+
+ def uninstall(self,
+ name,
+ flags=None,
+ kubeconfig=None,
+ token=None,
+ apiserver=None,
+ **_):
+ cmd = ['uninstall', name]
+ self.handle_auth_params(cmd, kubeconfig, token, apiserver)
+ flags = flags or []
+ validate_no_collisions_between_params_and_flags(flags)
+ cmd.extend([prepare_parameter(flag) for flag in flags])
+ self.execute(self._helm_command(cmd))
+
+ def repo_add(self,
+ name,
+ repo_url,
+ flags=None,
+ **_):
+ cmd = ['repo', 'add', name, repo_url]
+ flags = flags or []
+ cmd.extend([prepare_parameter(flag) for flag in flags])
+ self.execute(self._helm_command(cmd))
+
+ def repo_remove(self,
+ name,
+ flags=None,
+ **_):
+ cmd = ['repo', 'remove', name]
+ flags = flags or []
+ cmd.extend([prepare_parameter(flag) for flag in flags])
+ self.execute(self._helm_command(cmd))
+
+ def repo_list(self):
+ cmd = ['repo', 'list', '--output=json']
+ output = self.execute(self._helm_command(cmd), True)
+ return json.loads(output)
+
+ def repo_update(self, flags):
+ cmd = ['repo', 'update']
+ flags = flags or []
+ cmd.extend([prepare_parameter(flag) for flag in flags])
+ self.execute(self._helm_command(cmd))
+
+ def upgrade(self,
+ release_name,
+ chart=None,
+ flags=None,
+ set_values=None,
+ values_file=None,
+ kubeconfig=None,
+ token=None,
+ apiserver=None,
+ **_):
+ """
+ Execute helm upgrade command.
+ :param release_name: name of the release to upgrade.
+ :param chart: The chart to upgrade the release with.
+ The chart argument can be either: a chart reference('example/mariadb'),
+ a packaged chart, or a fully qualified URL.
+ :param flags: list of flags to add to the upgrade command.
+ :param set_values: list of variables and their values for --set.
+ :param kubeconfig: path to kubeconfig file.
+ :param values_file: values file path.
+ :param token: bearer token used for authentication.
+ :param apiserver: the address and the port for the Kubernetes API
+ server.
+ :return output of helm upgrade command.
+ """
+ if not chart:
+ raise CloudifyHelmSDKError(
+ 'Must provide chart for upgrade release.')
+ cmd = ['upgrade', release_name, chart, '--atomic', '-o=json']
+ self.handle_auth_params(cmd, kubeconfig, token, apiserver)
+ if values_file:
+ cmd.append(APPEND_FLAG_STRING.format(name=HELM_VALUES_FLAG,
+ value=values_file))
+ flags = flags or []
+ validate_no_collisions_between_params_and_flags(flags)
+ cmd.extend([prepare_parameter(flag) for flag in flags])
+ set_arguments = set_values or []
+ cmd.extend(prepare_set_parameters(set_arguments))
+ output = self.execute(self._helm_command(cmd), True)
+ return json.loads(output)
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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.
+
+"""Python 2 + 3 compatibility utils"""
+# flake8: noqa
+
+import sys
+
+PY2 = sys.version_info[0] == 2
+
+if PY2:
+ from StringIO import StringIO
+
+ exec("""
+def reraise(exception_type, value, traceback):
+ raise exception_type, value, traceback
+""")
+ text_type = unicode
+ exec("""
+def exec_(code, globs):
+ exec code in globs
+""")
+
+else:
+ import builtins
+ from io import StringIO
+
+ def reraise(exception_type,
+ value,
+ traceback):
+ raise value.with_traceback(traceback)
+
+ text_type = str
+ exec_ = getattr(builtins, 'exec')
+
+__all__ = [
+ 'PY2', 'StringIO', 'reraise', 'text_type', 'exec_']
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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.
+
+
+class CloudifyHelmSDKError(Exception):
+ """Generic Error for handling issues preparing
+ Helm command.
+ """
+ pass
--- /dev/null
+# Copyright (c) 2016-2020 Cloudify Platform 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 copy import deepcopy
+import re
+
+from ._compat import text_type
+
+OBFUSCATION_KEYWORDS = ('PASSWORD', 'SECRET', 'TOKEN',)
+OBFUSCATION_RE = re.compile(r'((password|secret|token)(:|=)\s*)(\S+)',
+ flags=re.IGNORECASE | re.MULTILINE)
+OBFUSCATED_SECRET = 'x' * 16
+
+
+def obfuscate_passwords(obj):
+ """Obfuscate passwords in dictionary or list of dictionaries.
+
+ Returns a copy of original object with elements potentially containing
+ passwords obfuscated. A copy.deepcopy() is used for copying dictionaries
+ but only when absolutely necessary. If a given object does not contain
+ any passwords, original is returned and deepcopy never performed.
+ """
+ if isinstance(obj, (text_type, bytes,)):
+ return OBFUSCATION_RE.sub('\\1{0}'.format(OBFUSCATED_SECRET), obj)
+ if isinstance(obj, list):
+ return [obfuscate_passwords(elem) for elem in obj]
+ if not isinstance(obj, dict):
+ return obj
+ result = obj
+ for k, v in list(result.items()):
+ if any(x for x in OBFUSCATION_KEYWORDS if x in k.upper()):
+ a_copy = deepcopy(result)
+ a_copy[k] = OBFUSCATED_SECRET
+ result = a_copy
+ if isinstance(v, (dict, list,)):
+ obfuscated_v = obfuscate_passwords(v)
+ if obfuscated_v is not v:
+ a_copy = deepcopy(result)
+ a_copy[k] = obfuscated_v
+ result = a_copy
+ return result
+
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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 logging
+import unittest
+
+from .. import Helm
+
+HELM_BINARY = '/tmp/helm'
+
+
+class HelmTestBase(unittest.TestCase):
+ def setUp(self):
+ super(HelmTestBase, self).setUp()
+ log = logging.getLogger('helm_log')
+ self.helm = Helm(log, HELM_BINARY, environment_variables={})
+
+ def tearDown(self):
+ self.helm = None
+ super(HelmTestBase, self).tearDown()
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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 mock
+
+from . import HelmTestBase, HELM_BINARY
+from helm_sdk.exceptions import CloudifyHelmSDKError
+
+mock_flags = [{'name': 'dry-run'},
+ {'name': 'timeout', 'value': '100'}]
+mock_set_args = [{'name': 'x', 'value': 'y'},
+ {'name': 'a', 'value': 'b'}]
+
+
+class HelmSDKTest(HelmTestBase):
+
+ def test_install_with_token_and_api(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.install('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ token='demotoken',
+ apiserver='https://1.0.0.0')
+
+ def test_install_with_kubeconfig(self):
+ mock_execute = mock.Mock(return_value='{"manifest":"resourceA"}')
+ self.helm.execute = mock_execute
+ out = self.helm.install('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ kubeconfig='/path/to/config')
+ cmd_expected = [HELM_BINARY, 'install', 'release1', 'my_chart',
+ '--wait', '--output=json',
+ '--kubeconfig=/path/to/config', '--dry-run',
+ '--timeout=100', '--set', 'x=y', '--set', 'a=b']
+ mock_execute.assert_called_once_with(cmd_expected, True)
+ self.assertEqual(out, {"manifest": "resourceA"})
+
+ def test_install_no_token_and_no_kubeconfig(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.install('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ apiserver='https://1.0.0.0')
+
+ def test_install_no_apiserver_and_no_kubeconfig(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.install('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ token='demotoken')
+
+ def test_uninstall_with_kubekonfig(self):
+ mock_execute = mock.Mock()
+ self.helm.execute = mock_execute
+ self.helm.uninstall('release1',
+ mock_flags,
+ kubeconfig='/path/to/config')
+ cmd_expected = [HELM_BINARY, 'uninstall', 'release1',
+ '--kubeconfig=/path/to/config', '--dry-run',
+ '--timeout=100']
+ mock_execute.assert_called_once_with(cmd_expected)
+
+ def test_uninstall_no_token_and_no_kubeconfig(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.uninstall('release1',
+ mock_flags,
+ apiserver='https://1.0.0.0')
+
+ def test_uninstall_no_apiserver_and_no_kubeconfig(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.uninstall('release1',
+ mock_flags,
+ token='demotoken')
+
+ def test_repo_add(self):
+ mock_execute = mock.Mock()
+ self.helm.execute = mock_execute
+ self.helm.repo_add('my_repo', 'https://github.com/repo')
+ cmd_expected = [HELM_BINARY, 'repo', 'add', 'my_repo',
+ 'https://github.com/repo']
+ mock_execute.assert_called_once_with(cmd_expected)
+
+ def test_repo_remove(self):
+ mock_execute = mock.Mock()
+ self.helm.execute = mock_execute
+ self.helm.repo_remove('my_repo')
+ cmd_expected = [HELM_BINARY, 'repo', 'remove', 'my_repo']
+ mock_execute.assert_called_once_with(cmd_expected)
+
+ def test_upgrade_with_token_and_api(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.upgrade('release1',
+ 'example/mariadb',
+ mock_flags,
+ mock_set_args,
+ token='demotoken',
+ apiserver='https://1.0.0.0')
+
+ def test_upgrade_with_kubeconfig(self):
+ mock_execute = mock.Mock(return_value='{"name":"release1"}')
+ self.helm.execute = mock_execute
+ out = self.helm.upgrade('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ kubeconfig='/path/to/config')
+ cmd_expected = [HELM_BINARY, 'upgrade', 'release1', 'my_chart',
+ '--atomic', '-o=json', '--kubeconfig=/path/to/config',
+ '--dry-run', '--timeout=100', '--set', 'x=y', '--set',
+ 'a=b']
+ mock_execute.assert_called_once_with(cmd_expected, True)
+ self.assertEqual(out, {"name": "release1"})
+
+ def test_upgrade_no_token_and_no_kubeconfig(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.upgrade('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ apiserver='https://1.0.0.0')
+
+ def test_upgrade_no_apiserver_and_no_kubeconfig(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide kubeconfig file path.'):
+ self.helm.upgrade('release1',
+ 'my_chart',
+ mock_flags,
+ mock_set_args,
+ token='demotoken')
+
+ def test_upgrade_no_chart(self):
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ 'Must provide chart for upgrade '
+ 'release.'):
+ self.helm.upgrade(release_name='release1',
+ flags=mock_flags,
+ set_values=mock_set_args,
+ kubeconfig='/path/to/config')
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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 unittest
+
+from helm_sdk.exceptions import CloudifyHelmSDKError
+from helm_sdk.utils import (
+ prepare_parameter,
+ prepare_set_parameters,
+ validate_no_collisions_between_params_and_flags)
+
+
+class TestUtils(unittest.TestCase):
+
+ def test_prepare_parameter(self):
+ param_dict = {'name': 'param1'}
+ self.assertEqual(prepare_parameter(param_dict), '--param1')
+ param_dict.update({'value': 'value1'})
+ self.assertEqual(prepare_parameter(param_dict), '--param1=value1')
+
+ def test_prepare_set_parameters(self):
+ set_no_val = [{'name': 'x'}]
+ with self.assertRaisesRegexp(
+ CloudifyHelmSDKError,
+ "\"set\" parameter name or value is missing"):
+ prepare_set_parameters(set_no_val)
+
+ with self.assertRaisesRegexp(
+ CloudifyHelmSDKError,
+ "\"set\" parameter name or value is missing"):
+ set_no_name = [{'value': 'y'}]
+ prepare_set_parameters(set_no_name)
+ # Now set_dict_no_val is a valid set parameter dictionary
+ valid_set_list = [{'name': 'x', 'value': 'y'}]
+ self.assertEqual(prepare_set_parameters(valid_set_list),
+ ['--set', 'x=y'])
+
+ def test_validate_no_collisions_between_params_and_flags(self):
+ fake_flags = [{'name': 'kube-apiserver', 'value': 'https://0.0.0.0'}]
+ with self.assertRaisesRegexp(CloudifyHelmSDKError,
+ "Please do not pass"):
+ validate_no_collisions_between_params_and_flags(fake_flags)
+ fake_flags = [{'name': 'debug'}]
+ self.assertEqual(
+ validate_no_collisions_between_params_and_flags(fake_flags),
+ None)
+ fake_flags = []
+ self.assertEqual(
+ validate_no_collisions_between_params_and_flags(fake_flags),
+ None)
--- /dev/null
+########
+# Copyright (c) 2019 Cloudify Platform 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 os
+import copy
+import threading
+import subprocess
+
+from helm_sdk.filters import obfuscate_passwords
+
+from helm_sdk._compat import StringIO, text_type
+from helm_sdk.exceptions import CloudifyHelmSDKError
+
+FLAGS_LIST_TO_VALIDATE = ['kube-apiserver', 'kube-token', 'kubeconfig']
+
+
+def run_subprocess(command,
+ logger,
+ cwd=None,
+ additional_env=None,
+ additional_args=None,
+ return_output=False):
+ if additional_args is None:
+ additional_args = {}
+ args_to_pass = copy.deepcopy(additional_args)
+ if additional_env:
+ passed_env = args_to_pass.setdefault('env', {})
+ passed_env.update(os.environ)
+ passed_env.update(additional_env)
+
+ logger.info(
+ "Running: command={cmd}, cwd={cwd}, additional_args={args}".format(
+ cmd=obfuscate_passwords(command),
+ cwd=cwd,
+ args=obfuscate_passwords(args_to_pass)))
+
+ process = subprocess.Popen(
+ args=command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdin=None,
+ cwd=cwd,
+ **args_to_pass)
+
+ if return_output:
+ stdout_consumer = CapturingOutputConsumer(
+ process.stdout)
+ else:
+ stdout_consumer = LoggingOutputConsumer(
+ process.stdout, logger, "<out> ")
+ stderr_consumer = LoggingOutputConsumer(
+ process.stderr, logger, "<err> ")
+
+ return_code = process.wait()
+ stdout_consumer.join()
+ stderr_consumer.join()
+
+ if return_code:
+ raise subprocess.CalledProcessError(return_code,
+ [obfuscate_passwords(cmd_element)
+ for cmd_element in command])
+
+ output = stdout_consumer.buffer.getvalue() if return_output else None
+ logger.info("Returning output:\n{0}".format(
+ obfuscate_passwords(output) if output is not None else '<None>'))
+
+ return output
+
+
+# Stolen from the script plugin, until this class
+# moves to a utils module in cloudify-common.
+class OutputConsumer(object):
+ def __init__(self, out):
+ self.out = out
+ self.consumer = threading.Thread(target=self.consume_output)
+ self.consumer.daemon = True
+
+ def consume_output(self):
+ for line in self.out:
+ self.handle_line(line)
+ self.out.close()
+
+ def handle_line(self, line):
+ raise NotImplementedError("Must be implemented by subclass.")
+
+ def join(self):
+ self.consumer.join()
+
+
+class LoggingOutputConsumer(OutputConsumer):
+ def __init__(self, out, logger, prefix):
+ OutputConsumer.__init__(self, out)
+ self.logger = logger
+ self.prefix = prefix
+ self.consumer.start()
+
+ def handle_line(self, line):
+ self.logger.info(
+ "{0}{1}".format(text_type(self.prefix),
+ obfuscate_passwords(
+ line.decode('utf-8').rstrip('\n'))))
+
+
+class CapturingOutputConsumer(OutputConsumer):
+ def __init__(self, out):
+ OutputConsumer.__init__(self, out)
+ self.buffer = StringIO()
+ self.consumer.start()
+
+ def handle_line(self, line):
+ self.buffer.write(line.decode('utf-8'))
+
+ def get_buffer(self):
+ return self.buffer
+
+
+def prepare_parameter(arg_dict):
+ """
+ Prepare single parameter.
+ :param arg_dict: dictionary with the name of the flag and value(optional)
+ :return: "--name=value" or -"-name"
+ """
+ try:
+ param_string = "--" + arg_dict["name"]
+ return param_string + '=' + arg_dict.get("value") if arg_dict.get(
+ "value") else param_string
+ except KeyError:
+ raise CloudifyHelmSDKError("Parameter name doesen't exist.")
+
+
+def prepare_set_parameters(set_values):
+ """
+ Prepare set parameters for install command.
+ :param set_values: list of dictionaries with the name of the variable to
+ set command and its value.
+ :return list like: ["--set", "name=value","--set",
+ """
+ set_list = []
+ for set_dict in set_values:
+ set_list.append('--set')
+ try:
+ set_list.append(set_dict["name"] + "=" + set_dict["value"])
+ except KeyError:
+ raise CloudifyHelmSDKError(
+ "\"set\" parameter name or value is missing.")
+ return set_list
+
+
+def validate_no_collisions_between_params_and_flags(flags):
+ if [flag for flag in flags if flag['name'] in FLAGS_LIST_TO_VALIDATE]:
+ raise CloudifyHelmSDKError(
+ 'Please do not pass {flags_list} under "flags" property,'
+ 'each of them has a known property.'.format(
+ flags_list=FLAGS_LIST_TO_VALIDATE))
logger.info("Redis pubsub starting")
bus = bootstrap.bootstrap()
pubsub = r.pubsub(ignore_subscribe_messages=True)
- pubsub.subscribe("NfDeploymentCreated")
+ pubsub.subscribe("NfDeploymentStateChanged")
for m in pubsub.listen():
try:
def handle_dms_changed(m, bus):
logger.info("handling %s", m)
channel = m['channel'].decode("UTF-8")
- if channel == "NfDeploymentCreated":
+ if channel == "NfDeploymentStateChanged":
datastr = m['data']
data = json.loads(datastr)
- logger.info('InstallNfDeployment with cmd:{}'.format(data))
- cmd = commands.InstallNfDeployment(NfDeploymentId = data['NfDeploymentId'])
+ logger.info('HandleNfDeploymentStateChanged with cmd:{}'.format(data))
+ cmd = commands.HandleNfDeploymentStateChanged(
+ NfDeploymentId = data['NfDeploymentId'],
+ FromState = data['FromState'],
+ ToState = data['ToState']
+ )
bus.handle(cmd)
else:
logger.info("unhandled:{}".format(channel))
- # data = json.loads(m["data"])
- # cmd = commands.UpdateDms(ref=data["dmsid"])
- # bus.handle(cmd)
if __name__ == "__main__":
# pylint: disable=unused-argument
from __future__ import annotations
-from o2dms.service.nfdeployment_handler import install_nfdeployment
-from o2dms.service.nfdeployment_handler import publish_nfdeployment_created
+
+from o2dms.service import nfdeployment_handler
# from dataclasses import asdict
from typing import List, Dict, Callable, Type
# TYPE_CHECKING
EVENT_HANDLERS = {
- o2dms_events.NfDeploymentCreated: [publish_nfdeployment_created]
-}
+ o2dms_events.NfDeploymentStateChanged: [
+ nfdeployment_handler.publish_nfdeployment_state_change
+ ]
+ # o2dms_events.NfDeploymentCreated: [
+ # nfdeployment_handler.publish_nfdeployment_created],
+ # o2dms_events.NfDeploymentInstalled: [
+ # nfdeployment_handler.publish_nfdeployment_installed],
+ # o2dms_events.NfDeploymentUninstalling: [
+ # nfdeployment_handler.publish_nfdeployment_uninstalling],
+ # o2dms_events.NfDeploymentUninstalled: [
+ # nfdeployment_handler.publish_nfdeployment_uninstalled]
+} # type: Dict[Type[events.Event], Callable]
COMMAND_HANDLERS = {
commands.UpdatePserverIf: pserver_if_handler.update_pserver_if,
commands.UpdatePserverIfPort: pserver_port_handler.update_pserver_port,
commands.UpdatePserverEth: pserver_eth_handler.update_pserver_eth,
- o2dms_cmmands.InstallNfDeployment: install_nfdeployment
+
+ o2dms_cmmands.HandleNfDeploymentStateChanged: \
+ nfdeployment_handler.handle_nfdeployment_statechanged,
+ o2dms_cmmands.InstallNfDeployment: \
+ nfdeployment_handler.install_nfdeployment,
+ o2dms_cmmands.UninstallNfDeployment: \
+ nfdeployment_handler.uninstall_nfdeployment,
+ o2dms_cmmands.DeleteNfDeployment: \
+ nfdeployment_handler.delete_nfdeployment,
} # type: Dict[Type[commands.Command], Callable]
os_client_args['os_region_name'] = 'RegionOne'
os_client_args['api_version'] = 1
return os_client_args
+
+
+def get_k8s_api_endpoint():
+ K8S_KUBECONFIG = os.environ.get("K8S_KUBECONFIG", None)
+ K8S_APISERVER = os.environ.get("K8S_APISERVER", None)
+ K8S_TOKEN = os.environ.get("K8S_TOKEN", None)
+ return K8S_KUBECONFIG, K8S_APISERVER, K8S_TOKEN
+
+
+def get_helm_cli():
+ return '/usr/local/bin/helm'
Column,\r
Integer,\r
String,\r
+ Text,\r
# Date,\r
DateTime,\r
# ForeignKey,\r
Column("deploymentManagerId", String(255)),\r
Column("name", String(255)),\r
Column("description", String(255)),\r
- Column("inputParams", String(255)),\r
+ Column("inputParams", Text()),\r
Column("outputParams", String(255)),\r
- Column("artifactUrl", String(255)),\r
+ Column("artifactRepoUrl", String(255)),\r
+ Column("artifactName", String(255)),\r
# Column("extensions", String(1024))\r
)\r
\r
'description': fields.String,
'inputParams': fields.String,
'outputParams': fields.String,
- 'artifactUrl': fields.String
+ 'artifactRepoUrl': fields.String,
+ 'artifactName': fields.String
}
)
{
'name': fields.String,
'description': fields.String,
- 'artifactUrl': fields.String,
+ 'artifactRepoUrl': fields.String,
+ 'artifactName': fields.String,
'inputParams': fields.String,
'outputParams': fields.String
}
{
'name': fields.String,
'description': fields.String,
- 'artifactUrl': fields.String,
+ 'artifactRepoUrl': fields.String,
+ 'artifactName': fields.String,
'inputParams': fields.String,
'outputParams': fields.String
}
\r
from sqlalchemy import select\r
import uuid\r
+from o2dms.domain.states import NfDeploymentState\r
from o2common.service import messagebus\r
-from o2dms.domain import events\r
from o2common.service import unit_of_work\r
from o2dms.adapter.orm import nfDeployment\r
from o2dms.api.dms_dto import DmsLcmNfDeploymentDTO\r
bus: messagebus.MessageBus):\r
\r
uow = bus.uow\r
+ id = str(uuid.uuid4())\r
with uow:\r
_check_duplication(input, uow)\r
_check_dependencies(input, uow)\r
- id = str(uuid.uuid4())\r
entity = NfDeployment(\r
id, input['name'], deploymentManagerId, input['description'],\r
input['descriptorId'], input['parentDeploymentId'])\r
uow.nfdeployments.add(entity)\r
+ entity.transit_state(NfDeploymentState.NotInstalled)\r
\r
- # publish event\r
- event = events.NfDeploymentCreated(NfDeploymentId=id)\r
+ # to be refactor later according to O2 DMS API design\r
+ entity.transit_state(NfDeploymentState.Installing)\r
uow.commit()\r
- bus.handle(event)\r
+ _handle_events(bus)\r
\r
return id\r
\r
def lcm_nfdeployment_update(\r
nfdeploymentid: str,\r
input: DmsLcmNfDeploymentDTO.NfDeployment_update,\r
- uow: unit_of_work.AbstractUnitOfWork):\r
+ bus: messagebus.MessageBus):\r
\r
+ uow = bus.uow\r
with uow:\r
- entity = uow.nfdeployments.get(nfdeploymentid)\r
+ entity: NfDeployment = uow.nfdeployments.get(nfdeploymentid)\r
entity.name = input['name']\r
entity.description = input['description']\r
entity.outputParams = input['parentDeploymentId']\r
+ entity.transit_state(NfDeploymentState.Updating)\r
uow.commit()\r
+ _handle_events(bus)\r
return True\r
\r
\r
-def lcm_nfdeployment_delete(\r
- nfdeploymentid: str, uow: unit_of_work.AbstractUnitOfWork):\r
+def _handle_events(bus: messagebus.MessageBus):\r
+ # handle events\r
+ events = bus.uow.collect_new_events()\r
+ for event in events:\r
+ bus.handle(event)\r
+ return True\r
+\r
\r
+def lcm_nfdeployment_uninstall(\r
+ nfdeploymentid: str,\r
+ bus: messagebus.MessageBus):\r
+\r
+ uow = bus.uow\r
with uow:\r
- uow.nfdeployments.delete(nfdeploymentid)\r
+ entity: NfDeployment = uow.nfdeployments.get(nfdeploymentid)\r
+ if entity.status == NfDeploymentState.Installed:\r
+ entity.transit_state(NfDeploymentState.Uninstalling)\r
+ elif entity.status == NfDeploymentState.Abnormal:\r
+ bus.uow.nfdeployments.delete(nfdeploymentid)\r
+ else:\r
+ entity.transit_state(NfDeploymentState.Abnormal)\r
uow.commit()\r
+ _handle_events(bus)\r
return True\r
\r
\r
+# def lcm_nfdeployment_delete(\r
+# nfdeploymentid: str,\r
+# bus: messagebus.MessageBus):\r
+\r
+# uow = bus.uow\r
+# with uow:\r
+# entity = uow.nfdeployments.get(nfdeploymentid)\r
+# if entity.status != NfDeploymentState.Initial:\r
+# raise Exception(\r
+# "NfDeployment {} is not in status to delete".format(\r
+# nfdeploymentid))\r
+# uow.nfdeployments.delete(nfdeploymentid)\r
+# entity.transit_state(NfDeploymentState.Deleted)\r
+# uow.commit()\r
+# return True\r
+\r
+\r
def _check_duplication(\r
input: DmsLcmNfDeploymentDTO,\r
uow: unit_of_work.AbstractUnitOfWork):\r
id = str(uuid.uuid4())\r
entity = NfDeploymentDesc(\r
id, input['name'], deploymentManagerId, input['description'],\r
- input['inputParams'], input['outputParams'], input['artifactUrl'])\r
+ input['inputParams'], input['outputParams'],\r
+ input['artifactRepoUrl'], input['artifactName'])\r
uow.nfdeployment_descs.add(entity)\r
uow.commit()\r
return id\r
entity.description = input['description']\r
entity.inputParams = input['inputParams']\r
entity.outputParams = input['outputParams']\r
- entity.artifactUrl = input['artifactUrl']\r
+ entity.artifactRepoUrl = input['artifactRepoUrl']\r
+ entity.artifactName = input['artifactName']\r
uow.commit()\r
return True\r
\r
bus = MessageBus.get_instance()
data = api_dms_lcm_v1.payload
dms_lcm_view.lcm_nfdeployment_update(
- nfDeploymentId, data, bus.uow)
+ nfDeploymentId, data, bus)
return {}, 201
except Exception as ex:
logger.warning("{}".format(str(ex)))
@api_dms_lcm_v1.response(204, 'NfDeployment deleted')
def delete(self, nfDeploymentId, deploymentManagerID):
bus = MessageBus.get_instance()
- with bus.uow:
- bus.uow.nfdeployments.delete(nfDeploymentId)
- bus.uow.commit()
+ result = dms_lcm_view\
+ .lcm_nfdeployment_uninstall(nfDeploymentId, bus)
+ if result is not None:
+ return result
+ api_dms_lcm_v1.abort(
+ 404, "NfDeployment {} doesn't exist".format(
+ nfDeploymentId))
return '', 204
# from datetime import date
# from typing import Optional
from dataclasses import dataclass
+from o2dms.domain.states import NfDeploymentState
+# from o2dms.domain.dms import NfDeployment
# from datetime import datetime
# from o2ims.domain.resource_type import ResourceTypeEnum
@dataclass
class InstallNfDeployment(Command):
NfDeploymentId: str
+
+
+@dataclass
+class UninstallNfDeployment(Command):
+ NfDeploymentId: str
+
+
+@dataclass
+class DeleteNfDeployment(Command):
+ NfDeploymentId: str
+
+
+@dataclass
+class HandleNfDeploymentStateChanged(Command):
+ NfDeploymentId: str
+ FromState: NfDeploymentState
+ ToState: NfDeploymentState
# limitations under the License.\r
\r
from __future__ import annotations\r
+from o2dms.domain import events\r
+from o2dms.domain.states import NfDeploymentState\r
\r
-from o2common.domain.base import AgRoot\r
+from o2common.domain.base import AgRoot, Serializer\r
\r
\r
-class NfDeploymentDesc(AgRoot):\r
+class NfDeploymentDesc(AgRoot, Serializer):\r
def __init__(self, id: str, name: str, dmsId: str, description: str = '',\r
inputParams: str = '', outputParams: str = '',\r
- artifacturl: str = '') -> None:\r
+ artifactRepoUrl: str = '', artifactName: str = '') -> None:\r
super().__init__()\r
self.id = id\r
self.version_number = 0\r
self.description = description\r
self.inputParams = inputParams\r
self.outputParams = outputParams\r
- self.artifactUrl = artifacturl\r
+ self.artifactRepoUrl = artifactRepoUrl\r
+ self.artifactName = artifactName\r
+ self.status = 0\r
# self.extensions = []\r
\r
\r
-class NfDeployment(AgRoot):\r
+class NfDeployment(AgRoot, Serializer):\r
def __init__(self, id: str, name: str, dmsId: str, description: str = '',\r
descriptorId: str = '', parentId: str = '',) -> None:\r
super().__init__()\r
self.description = description\r
self.descriptorId = descriptorId\r
self.parentDeploymentId = parentId\r
- self.status = 0\r
+ self.status = NfDeploymentState.Initial\r
+\r
+ def transit_state(self, state: NfDeploymentState):\r
+ if (self.status != state):\r
+ self._append_event(self.status, state)\r
+ self.status = state\r
+\r
+ def _append_event(self, fromState, toState):\r
+ if not hasattr(self, "events"):\r
+ self.events = []\r
+ self.events.append(\r
+ events.NfDeploymentStateChanged(\r
+ NfDeploymentId=self.id, FromState=fromState, ToState=toState))\r
\r
\r
-class NfOCloudVResource(AgRoot):\r
+class NfOCloudVResource(AgRoot, Serializer):\r
def __init__(self, id: str, name: str, dmsId: str, description: str = '',\r
descriptorId: str = '', nfDeploymentId: str = '',\r
vresourceType: int = 0,) -> None:\r
# pylint: disable=too-few-public-methods
from dataclasses import dataclass
+from o2dms.domain.states import NfDeploymentState
from o2common.domain.events import Event
@dataclass
-class NfDeploymentCreated(Event):
+class NfDeploymentStateChanged(Event):
NfDeploymentId: str
+ FromState: NfDeploymentState
+ ToState: NfDeploymentState
-@dataclass
-class NfDeploymentDeleted(Event):
- NfDeploymentId: str
+# @dataclass
+# class NfDeploymentCreated(Event):
+# NfDeploymentId: str
+
+
+# @dataclass
+# class NfDeploymentInstalled(Event):
+# NfDeploymentId: str
+
+
+# @dataclass
+# class NfDeploymentUninstalling(Event):
+# NfDeploymentId: str
+
+
+# @dataclass
+# class NfDeploymentUninstalled(Event):
+# NfDeploymentId: str
+
+
+# @dataclass
+# class NfDeploymentUpdating(Event):
+# NfDeploymentId: str
+
+
+# @dataclass
+# class NfDeploymentUpdated(Event):
+# NfDeploymentId: str
+
+
+# @dataclass
+# class NfDeploymentDeleted(Event):
+# NfDeploymentId: str
--- /dev/null
+\r
+# Copyright (C) 2021 Wind River Systems, Inc.\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License");\r
+# you may not use this file except in compliance with the License.\r
+# You may obtain a copy of the License at\r
+#\r
+# http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS,\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+# See the License for the specific language governing permissions and\r
+# limitations under the License.\r
+\r
+from __future__ import annotations\r
+from dataclasses import dataclass\r
+\r
+\r
+@dataclass\r
+class NfDeploymentState():\r
+ Initial = 0\r
+ NotInstalled = 1\r
+ Installing = 2\r
+ Installed = 3\r
+ Updating = 4\r
+ Uninstalling = 5\r
+ Abnormal = 6\r
+ Deleted = 7\r
# pylint: disable=unused-argument
from __future__ import annotations
-from o2dms.domain.commands import InstallNfDeployment
+from o2dms.domain.states import NfDeploymentState
+# from o2common.service import messagebus
+from o2dms.domain.dms import NfDeployment, NfDeploymentDesc
+from o2dms.domain import commands
from typing import Callable
from o2dms.domain import events
from o2common.service.unit_of_work import AbstractUnitOfWork
+from helm_sdk import Helm
+from ruamel import yaml
+import json
+from o2common.config import config
+from retry import retry
# if TYPE_CHECKING:
# from . import unit_of_work
from o2common.helper import o2logging
logger = o2logging.get_logger(__name__)
+LOCAL_HELM_BIN = config.get_helm_cli()
+K8S_KUBECONFIG, K8S_APISERVER, K8S_TOKEN = \
+ config.get_k8s_api_endpoint()
-def publish_nfdeployment_created(
- event: events.NfDeploymentCreated,
+def publish_nfdeployment_state_change(
+ event: events.NfDeploymentStateChanged,
publish: Callable,
):
- publish("NfDeploymentCreated", event)
- logger.debug("published NfDeploymentCreated: {}".format(
- event.NfDeploymentId))
+ publish("NfDeploymentStateChanged", event)
+ logger.debug(
+ "published NfDeploymentStateChanged: {}, state from {} to {}".format(
+ event.NfDeploymentId, event.FromState, event.ToState))
+
+
+def handle_nfdeployment_statechanged(
+ cmd: commands.HandleNfDeploymentStateChanged,
+ uow: AbstractUnitOfWork
+):
+ if cmd.FromState == NfDeploymentState.Initial:
+ if cmd.ToState == NfDeploymentState.NotInstalled:
+ cmd2 = commands.InstallNfDeployment(cmd.NfDeploymentId)
+ install_nfdeployment(cmd2, uow)
+ else:
+ logger.debug("Not insterested state change: {}".format(cmd))
+ elif cmd.FromState == NfDeploymentState.Installed:
+ if cmd.ToState == NfDeploymentState.Uninstalling:
+ cmd2 = commands.UninstallNfDeployment(cmd.NfDeploymentId)
+ uninstall_nfdeployment(cmd2, uow)
+ else:
+ logger.debug("Not insterested state change: {}".format(cmd))
+ elif cmd.FromState == NfDeploymentState.NotInstalled:
+ if cmd.ToState == NfDeploymentState.Initial:
+ cmd2 = commands.DeleteNfDeployment(cmd.NfDeploymentId)
+ delete_nfdeployment(cmd2, uow)
+ else:
+ logger.debug("Not insterested state change: {}".format(cmd))
+ else:
+ logger.debug("Not insterested state change: {}".format(cmd))
+
+
+# retry 10 seconds
+@retry(tries=20, max_delay=10000)
+def _retry_get_nfdeployment(
+ cmd: commands.InstallNfDeployment,
+ uow: AbstractUnitOfWork):
+ nfdeployment: NfDeployment = uow.nfdeployments.get(
+ cmd.NfDeploymentId)
+ if nfdeployment is None:
+ raise Exception("Cannot find NfDeployment: {}".format(
+ cmd.NfDeploymentId))
+ return nfdeployment
def install_nfdeployment(
- cmd: InstallNfDeployment,
+ cmd: commands.InstallNfDeployment,
uow: AbstractUnitOfWork
):
logger.info("install with NfDeploymentId: {}".format(
cmd.NfDeploymentId))
+ nfdeployment: NfDeployment = _retry_get_nfdeployment(cmd, uow)
+ if nfdeployment is None:
+ raise Exception("Cannot find NfDeployment: {}".format(
+ cmd.NfDeploymentId))
+ # get nfdeploymentdescriptor by descriptorId
+ desc: NfDeploymentDesc = uow.nfdeployment_descs.get(
+ nfdeployment.descriptorId)
+ if desc is None:
+ raise Exception(
+ "Cannot find NfDeploymentDescriptor:{} for NfDeployment:{}".format(
+ nfdeployment.descriptorId, nfdeployment.id
+ ))
+
+ # helm repo add
+ repourl = desc.artifactRepoUrl
+ helm = Helm(logger, LOCAL_HELM_BIN, environment_variables={})
+ repoName = None
+ try:
+ repolist = helm.repo_list()
+ for repo in repolist:
+ if repo['url'] == repourl:
+ repoName = repo['name']
+ break
+ except Exception:
+ # repoExisted
+ repoName = None
+
+ if not repoName:
+ repoName = "repo4{}".format(nfdeployment.name)
+ logger.debug("Trying to add repo:{}".format(repourl))
+ helm.repo_add(repoName, repourl)
+ helm.repo_update(None)
+
+ repolist = helm.repo_list()
+ logger.debug('repo list:{}'.format(repolist))
+
+ # helm install name chart
+ values_file_path = '/tmp/override_{}.yaml'.format(nfdeployment.name)
+ if len(desc.inputParams) > 0:
+ logger.info("dump override yaml:{}".format(values_file_path))
+ values = json.loads(desc.inputParams)
+ _create_values_file(values_file_path, values)
+ else:
+ values_file_path = None
+
+ logger.debug('Try to helm install {}/{} {} -f {}'.format(
+ repoName, nfdeployment.name, desc.artifactName, values_file_path))
+ tokens = desc.artifactName.split(':')
+ chartname = tokens[0]
+ myflags = None
+ # if (len(tokens) > 1):
+ # myflags = {"name": "version", "value": tokens[1]}
+ result = helm.install(
+ nfdeployment.name, "{}/{}".format(repoName, chartname), flags=myflags,
+ values_file=values_file_path, kubeconfig=K8S_KUBECONFIG,
+ token=K8S_TOKEN, apiserver=K8S_APISERVER)
+ logger.debug('result: {}'.format(result))
+
+ # in case success
+ with uow:
+ entity: NfDeployment = uow.nfdeployments.get(cmd.NfDeploymentId)
+ entity.transit_state(NfDeploymentState.Installed)
+ uow.commit()
+
+
+def _create_values_file(filePath: str, content: dict):
+ with open(filePath, "w", encoding="utf-8") as f:
+ yaml.dump(content, f, Dumper=yaml.RoundTripDumper)
+
+
+def uninstall_nfdeployment(
+ cmd: commands.UninstallNfDeployment,
+ uow: AbstractUnitOfWork
+):
+ logger.info("uninstall with NfDeploymentId: {}".format(
+ cmd.NfDeploymentId))
+ nfdeployment: NfDeployment = _retry_get_nfdeployment(cmd, uow)
+ if nfdeployment is None:
+ raise Exception("Cannot find NfDeployment: {}".format(
+ cmd.NfDeploymentId))
+ # get nfdeploymentdescriptor by descriptorId
+ desc: NfDeploymentDesc = uow.nfdeployment_descs.get(
+ nfdeployment.descriptorId)
+ if desc is None:
+ raise Exception(
+ "Cannot find NfDeploymentDescriptor:{} for NfDeployment:{}".format(
+ nfdeployment.descriptorId, nfdeployment.id
+ ))
+
+ helm = Helm(logger, LOCAL_HELM_BIN, environment_variables={})
+
+ logger.debug('Try to helm del {}'.format(
+ nfdeployment.name))
+ myflags = None
+ # if (len(tokens) > 1):
+ # myflags = {"name": "version", "value": tokens[1]}
+ result = helm.uninstall(
+ nfdeployment.name, flags=myflags,
+ kubeconfig=K8S_KUBECONFIG,
+ token=K8S_TOKEN, apiserver=K8S_APISERVER)
+ logger.debug('result: {}'.format(result))
+
+ # in case success
+
+ with uow:
+ entity: NfDeployment = uow.nfdeployments.get(cmd.NfDeploymentId)
+ entity.transit_state(NfDeploymentState.Initial)
+ # uow.nfdeployments.update(
+ # cmd.NfDeploymentId, status=NfDeploymentState.Initial)
+ uow.commit()
+
+
+def delete_nfdeployment(
+ cmd: commands.UninstallNfDeployment,
+ uow: AbstractUnitOfWork
+):
+ logger.info("delete with NfDeploymentId: {}".format(
+ cmd.NfDeploymentId))
+
+ # nfdeployment: NfDeployment = _retry_get_nfdeployment(cmd, uow)
+ with uow:
+ uow.nfdeployments.delete(cmd.NfDeploymentId)
+ uow.commit()
-e git+https://opendev.org/starlingx/distcloud-client.git@master#egg=distributedcloud-client&subdirectory=distributedcloud-client\r
-e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client#\r
+# -e git+https://github.com/cloudify-incubator/cloudify-helm-plugin.git@master#egg=helmsdk&subdirectory=helm_sdk\r
PrettyTable<0.8,>=0.7.2\r
\r
retry\r
+ruamel.yaml==0.17.17
\ No newline at end of file
cp -r /o2dms/* /src/o2dms
mkdir -p /src/o2app
cp -r /o2app/* /src/o2app
+mkdir -p /src/helm_sdk
+cp -r /helm_sdk/* /src/helm_sdk
+
pip install -e /src
export FLASK_APP=/o2app/entrypoints/flask_application.py
# pip install -e /src
# python /o2ims/entrypoints/resource_watcher.py
+# test only
+if [ ! -e '/usr/local/bin/helm' ]; then
+ apt-get install -y curl
+ curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz;
+ tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; cp linux-amd64/helm /usr/local/bin
+fi
+
mkdir -p /etc/o2
cp -r /configs/* /etc/o2/
mkdir -p /src/o2common
cp -r /o2dms/* /src/o2dms
mkdir -p /src/o2app
cp -r /o2app/* /src/o2app
+mkdir -p /src/helm_sdk
+cp -r /helm_sdk/* /src/helm_sdk
+
pip install -e /src
+
+
python /o2app/entrypoints/redis_eventconsumer.py
cp -r /o2dms/* /src/o2dms
mkdir -p /src/o2app
cp -r /o2app/* /src/o2app
+mkdir -p /src/helm_sdk
+cp -r /helm_sdk/* /src/helm_sdk
+
pip install -e /src
python /o2app/entrypoints/resource_watcher.py