From ad15ec6b732868c3ecedb9cd84450462bafa3320 Mon Sep 17 00:00:00 2001 From: "Zhang Rong(Jon)" Date: Fri, 29 Jul 2022 00:06:41 +0800 Subject: [PATCH] HelmCLI: support SOL018 Helm CLI ssh access Issue-ID: INF-280 Signed-off-by: Zhang Rong(Jon) Change-Id: Ifab09cbf2f5c5c608594c9be76260c46e98ec68f --- .gitignore | 1 + Dockerfile | 2 +- Dockerfile.localtest | 2 +- charts/resources/scripts/init/o2_helmcli_start.sh | 15 +++++++ charts/templates/deployment.yaml | 21 ++++++++++ charts/templates/service.yaml | 16 +++++++ charts/values.yaml | 1 + docker-compose.yml | 51 ++++++++--------------- o2common/config/config.py | 14 +++++++ o2ims/views/ocloud_dto.py | 5 +++ o2ims/views/ocloud_view.py | 48 ++++++++++++++------- requirements-test.txt | 2 +- requirements.txt | 7 +++- tests/o2app-helmcli-entry.sh | 15 +++++++ tests/unit/test_ocloud.py | 10 +++++ 15 files changed, 157 insertions(+), 53 deletions(-) create mode 100644 charts/resources/scripts/init/o2_helmcli_start.sh create mode 100644 tests/o2app-helmcli-entry.sh diff --git a/.gitignore b/.gitignore index e103593..6ae62d8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ __pycache__ .coverage temp docs/_build/ +configs/kubeconfig_*.config diff --git a/Dockerfile b/Dockerfile index 84ebb52..8c33fbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.10-slim-buster -RUN apt-get update && apt-get install -y git gcc procps vim curl +RUN apt-get update && apt-get install -y git gcc procps vim curl ssh # in case git repo is not accessable # RUN mkdir -p /cgtsclient diff --git a/Dockerfile.localtest b/Dockerfile.localtest index 3a3a7ea..a46b178 100644 --- a/Dockerfile.localtest +++ b/Dockerfile.localtest @@ -1,7 +1,7 @@ FROM python:3.10-slim-buster RUN apt-get update && apt-get install -y git gcc \ - vim curl procps + vim curl procps ssh # in case git repo is not accessable RUN mkdir -p /cgtsclient && mkdir -p /distcloud-client diff --git a/charts/resources/scripts/init/o2_helmcli_start.sh b/charts/resources/scripts/init/o2_helmcli_start.sh new file mode 100644 index 0000000..006b121 --- /dev/null +++ b/charts/resources/scripts/init/o2_helmcli_start.sh @@ -0,0 +1,15 @@ +apt-get update && apt-get install ssh -y + +if [ -z "${HELM_USER_PASSWD}" ]; +then + HELM_USER_PASSWD=St8rlingX* +fi +useradd helm +passwd helm << EOF +${HELM_USER_PASSWD} +${HELM_USER_PASSWD} +EOF + +service ssh restart + +tail -f /dev/null \ No newline at end of file diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml index 0f0b433..9da4fba 100644 --- a/charts/templates/deployment.yaml +++ b/charts/templates/deployment.yaml @@ -130,12 +130,33 @@ spec: value: "1" - name: REDIS_HOST value: redis + - name: HELM_USER_PASSWD + value: {{ .Values.ocloud.HELM_USER_PASSWD }} command: ["/bin/bash", "/opt/o2api_start.sh"] volumeMounts: - name: scripts mountPath: /opt + - name: configs + mountPath: /configs + - name: helmcli + image: "{{ .Values.o2ims.image.repository }}:{{ .Values.o2ims.image.tag }}" + ports: + - containerPort: 22 + env: + - name: API_HOST_EXTERNAL_FLOATING + value: {{ .Values.ocloud.API_HOST_EXTERNAL_FLOATING }} + - name: HELM_USER_PASSWD + value: {{ .Values.ocloud.HELM_USER_PASSWD }} + command: ["/bin/bash", "/opt/o2_helmcli_start.sh"] + volumeMounts: + - name: scripts + mountPath: /opt + - name: configs + mountPath: /configs volumes: - name: scripts configMap: name: {{ .Chart.Name }}-scripts-configmap + - name: configs + emptyDir: {} --- diff --git a/charts/templates/service.yaml b/charts/templates/service.yaml index 990f78b..8e59597 100644 --- a/charts/templates/service.yaml +++ b/charts/templates/service.yaml @@ -29,3 +29,19 @@ spec: selector: app: o2api --- +apiVersion: v1 +kind: Service +metadata: + name: helmcli + namespace: {{ .Values.global.namespace }} +spec: + type: NodePort + ports: + - name: helmcli + port: 10022 + targetPort: 22 + nodePort: 30022 + protocol: TCP + selector: + app: o2api +--- \ No newline at end of file diff --git a/charts/values.yaml b/charts/values.yaml index d58033b..c343088 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -43,3 +43,4 @@ ocloud: OS_PASSWORD: "" K8S_KUBECONFIG: "" API_HOST_EXTERNAL_FLOATING: "" + HELM_USER_PASSWD: "St8rlingX*" diff --git a/docker-compose.yml b/docker-compose.yml index ad3d857..ca73018 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,9 +19,6 @@ services: - 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 @@ -35,6 +32,23 @@ services: - /bin/sh - /tests/o2app-redis-entry.sh + helm_cli: + image: o2imsdms + depends_on: + - watcher + environment: + - LOGGING_CONFIG_LEVEL=DEBUG + - HELM_USER_PASSWD=St8rlingX* + volumes: + - ./configs:/configs + - ./helm_sdk:/helm_sdk + - ./tests:/tests + entrypoint: + - /bin/sh + - /tests/o2app-helmcli-entry.sh + ports: + - "10022:22" + api: image: o2imsdms depends_on: @@ -52,6 +66,7 @@ services: - OS_USERNAME=${OS_USERNAME} - OS_PASSWORD=${OS_PASSWORD} - LOGGING_CONFIG_LEVEL=DEBUG + - HELM_USER_PASSWD=St8rlingX* volumes: - ./configs:/configs - ./o2ims:/o2ims @@ -95,31 +110,6 @@ services: - /bin/sh - /tests/o2app-watcher-entry.sh - mock_smo: - build: - context: ./mock_smo - dockerfile: Dockerfile - image: mock-smo - depends_on: - - mock_smo_redis - environment: - - API_HOST=api - - REDIS_HOST=mock_smo_redis - - MOCK_SMO_HOST=mock_smo - - PYTHONDONTWRITEBYTECODE=1 - - FLASK_APP=/mock_smo/entrypoints/mock_smo.py - - FLASK_DEBUG=1 - - PYTHONUNBUFFERED=1 - - LOGGING_CONFIG_LEVEL=DEBUG - volumes: - - ./mock_smo/etc:/tmp/etc - - ./mock_smo/mock_smo:/mock_smo - entrypoint: - - /bin/sh - - /src/o2app-mock-smo.sh - ports: - - "5001:80" - postgres: image: postgres:9.6 environment: @@ -132,8 +122,3 @@ services: image: redis:alpine ports: - "63791:6379" - - mock_smo_redis: - image: redis:alpine - ports: - - "63792:6379" diff --git a/o2common/config/config.py b/o2common/config/config.py index 8f44fa2..64bf3eb 100644 --- a/o2common/config/config.py +++ b/o2common/config/config.py @@ -207,3 +207,17 @@ def gen_k8s_config_dict(cluster_api_endpoint, cluster_ca_cert, admin_user, } return data + + +def get_helmcli_access(): + host_external = os.environ.get("API_HOST_EXTERNAL_FLOATING") + host = "127.0.0.1" if host_external is None or host_external == '' \ + else host_external + port = "10022" if host_external is None or host_external == '' \ + else "50022" + + helm_host_with_port = host+':'+port + helm_user = 'helm' + helm_pass = os.environ.get("HELM_USER_PASSWD") + + return helm_host_with_port, helm_user, helm_pass diff --git a/o2ims/views/ocloud_dto.py b/o2ims/views/ocloud_dto.py index dacfcf1..651de97 100644 --- a/o2ims/views/ocloud_dto.py +++ b/o2ims/views/ocloud_dto.py @@ -159,6 +159,11 @@ class DeploymentManagerDTO: 'admin_client_cert': fields.String(attributes='admin_client_cert'), 'admin_client_key': fields.String(attributes='admin_client_key'), # 'kube_config_file': fields.String(attributes='kube_config_file') + 'helmcli_host_with_port': fields.String( + attributes='helmcli_host_with_port'), + 'helmcli_username': fields.String(attributes='helmcli_username'), + 'helmcli_password': fields.String(attributes='helmcli_password'), + 'helmcli_kubeconfig': fields.String(attributes='helmcli_kubeconfig'), }) deployment_manager_get = api_ims_inventory_v1.model( diff --git a/o2ims/views/ocloud_view.py b/o2ims/views/ocloud_view.py index 94d8601..83be178 100644 --- a/o2ims/views/ocloud_view.py +++ b/o2ims/views/ocloud_view.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import filecmp +import os.path import uuid import yaml -import random -import string from datetime import datetime +import shutil from o2common.service import unit_of_work from o2ims.views.ocloud_dto import SubscriptionDTO @@ -116,16 +117,27 @@ def deployment_manager_one(deploymentManagerId: str, return None profile_data = result.pop("profile", None) - result['profileName'] = 'default' + result['profileName'] = profile - if "sol018" == profile: - result['profileName'] = profile + if "default" == profile: + pass + elif "sol018" == profile: result['deploymentManagementServiceEndpoint'] = \ profile_data['cluster_api_endpoint'] result['profileData'] = profile_data - # elif "file" == profile and result.hasattr("profile"): - # p = result.pop("profile", None) - # result["profile"] = _gen_kube_config(deploymentManagerId, p) + elif "sol018_helmcli" == profile: + result['deploymentManagementServiceEndpoint'] = \ + profile_data['cluster_api_endpoint'] + + helmcli_profile = dict() + helmcli_profile["helmcli_host_with_port"], helmcli_profile[ + "helmcli_username"], helmcli_profile["helmcli_password"] = \ + config.get_helmcli_access() + helmcli_profile["helmcli_kubeconfig"] = _gen_kube_config( + deploymentManagerId, profile_data) + result['profileData'] = helmcli_profile + else: + return None return result @@ -141,21 +153,27 @@ def _gen_kube_config(dmId: str, kubeconfig: dict) -> dict: ) # Generate a random key for tmp kube config file - letters = string.ascii_uppercase - random_key = ''.join(random.choice(letters) for i in range(10)) + # letters = string.ascii_uppercase + # random_key = ''.join(random.choice(letters) for i in range(10)) + name_key = dmId[:8] # Get datetime of now as tag of the tmp file current_time = datetime.now().strftime("%Y%m%d%H%M%S") - tmp_file_name = random_key + "_" + current_time + tmp_file_name = 'kubeconfig_' + name_key + "_" + current_time + kube_config_name = 'kubeconfig_' + name_key + '.config' # write down the yaml file of kubectl into tmp folder - with open('/tmp/kubeconfig_' + tmp_file_name, 'w') as file: + with open('/tmp/' + tmp_file_name, 'w') as file: yaml.dump(data, file) - kubeconfig["kube_config_file"] = config.get_api_url() + \ - config.get_o2dms_api_base() + "/" + dmId + "/download/" + tmp_file_name + # generate the kube config file if not exist or update the file if it + # changes + if not os.path.exists('/configs/' + kube_config_name) or not \ + filecmp.cmp('/tmp/'+tmp_file_name, '/configs/'+kube_config_name): + shutil.move(os.path.join('/tmp', tmp_file_name), + os.path.join('/configs', kube_config_name)) - return kubeconfig + return '/configs/'+kube_config_name def subscriptions(uow: unit_of_work.AbstractUnitOfWork): diff --git a/requirements-test.txt b/requirements-test.txt index da66309..c5ae7f0 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -12,4 +12,4 @@ mock tenacity # -e git+https://opendev.org/starlingx/distcloud-client.git@master#egg=distributedcloud-client&subdirectory=distributedcloud-client -# -e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client +# -e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f1fc633..3d03fdd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,10 +8,13 @@ PyYAML>=5.4.1 Cython>=3.0a1 - httplib2 babel PrettyTable<0.8,>=0.7.2 retry -ruamel.yaml==0.17.17 \ No newline at end of file +ruamel.yaml==0.17.17 + +# https://github.com/python-restx/flask-restx/issues/460 +# Workaround for this issue +werkzeug<=2.1.2 \ No newline at end of file diff --git a/tests/o2app-helmcli-entry.sh b/tests/o2app-helmcli-entry.sh new file mode 100644 index 0000000..e00a00b --- /dev/null +++ b/tests/o2app-helmcli-entry.sh @@ -0,0 +1,15 @@ +apt-get update && apt-get install ssh -y + +if [ -z "${HELM_USER_PASSWD}" ]; +then + HELM_USER_PASSWD=St8rlingX +fi +useradd helm +passwd helm << EOF +${HELM_USER_PASSWD} +${HELM_USER_PASSWD} +EOF + +service ssh restart + +tail -f /dev/null \ No newline at end of file diff --git a/tests/unit/test_ocloud.py b/tests/unit/test_ocloud.py index dbfa65f..461fa41 100644 --- a/tests/unit/test_ocloud.py +++ b/tests/unit/test_ocloud.py @@ -303,6 +303,16 @@ def test_view_deployment_manager_one(mock_uow): assert str(deployment_manager_res.get( "profileName")) == profileName + # profile wrong name + profileName = 'wrong_profile' + session.return_value.query.return_value.filter_by.return_value.first.\ + return_value.serialize.return_value['profile'] = { + "cluster_api_endpoint": cluster_endpoint + } + deployment_manager_res = ocloud_view.deployment_manager_one( + deployment_manager_id1, uow, profile=profileName) + assert deployment_manager_res is None + def test_view_subscriptions(mock_uow): session, uow = mock_uow -- 2.16.6