1. Add profile query parameter for IMS and DMS API, if the parameter exists, return k8s profile information
2. Add an API of the download k8s config file
Issue-ID: INF-264
Signed-off-by: Zhang Rong(Jon) <rong.zhang@windriver.com>
Change-Id: I5cba1290b5b1b7103cf6f5f88f5998ef1dc8b8fd
FROM python:3.10-slim-buster
-RUN apt-get update; apt-get install -y git gcc
+RUN apt-get update && apt-get install -y git gcc procps vim curl
# in case git repo is not accessable
# RUN mkdir -p /cgtsclient
COPY tests/ /tests/
-RUN apt-get install -y procps vim curl
-
RUN curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz;
RUN tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; cp linux-amd64/helm /usr/local/bin
FROM python:3.10-slim-buster
-RUN apt-get update; apt-get install -y git gcc
+RUN apt-get update && apt-get install -y git gcc \
+ vim curl procps
# in case git repo is not accessable
-RUN mkdir -p /cgtsclient
+RUN mkdir -p /cgtsclient && mkdir -p /distcloud-client
COPY temp/config /cgtsclient/
-RUN pip install -e cgtsclient/sysinv/cgts-client/cgts-client/
-
-RUN mkdir -p /distcloud-client
COPY temp/distcloud-client /distcloud-client/
-RUN pip install -e /distcloud-client/distributedcloud-client
-# in case git repo is not accessable
+RUN pip install -e cgtsclient/sysinv/cgts-client/cgts-client/ \
+ && pip install -e /distcloud-client/distributedcloud-client
+# in case git repo is not accessable
-COPY requirements.txt /tmp/
-COPY constraints.txt /tmp/
-
-RUN pip install -r /tmp/requirements.txt -c /tmp/constraints.txt
+COPY requirements.txt constraints.txt requirements-test.txt /tmp/
-COPY requirements-test.txt /tmp/
-RUN pip install -r /tmp/requirements-test.txt
+RUN pip install -r /tmp/requirements.txt -c /tmp/constraints.txt \
+ && pip install -r /tmp/requirements-test.txt
RUN mkdir -p /src
# RUN pip install -e /src
COPY tests/ /tests/
-RUN apt-get install -y procps vim
+#RUN apt-get install -y procps vim
-RUN apt-get install -y curl
+#RUN apt-get install -y curl
RUN curl -O https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz;
RUN tar -zxvf helm-v3.3.1-linux-amd64.tar.gz; cp linux-amd64/helm /usr/local/bin
ports:
- containerPort: 80
env:
- - name: API_HOST
- value: api
+ - name: API_HOST_EXTERNAL_FLOATING
+ value: {{ .Values.ocloud.API_HOST_EXTERNAL_FLOATING }}
- name: DB_HOST
value: postgres
- name: DB_PASSWORD
environment:
- DB_HOST=postgres
- DB_PASSWORD=o2ims123
- - API_HOST=api
+ - API_HOST_EXTERNAL_FLOATING=${API_HOST_EXTERNAL_FLOATING}
- REDIS_HOST=redis
- PYTHONDONTWRITEBYTECODE=1
- FLASK_APP=/o2app/entrypoints/flask_application.py
class DmsDTO:
+ profile = api_dms_lcm_v1.model("DMSGetDtoProfile", {
+ 'cluster_api_endpoint': fields.String(
+ attributes='cluster_api_endpoint'),
+ 'cluster_ca_cert': fields.String(attributes='cluster_ca_cert'),
+ 'admin_user': fields.String(attributes='admin_user'),
+ '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')
+ })
+
dms_get = api_dms_lcm_v1.model(
"DmsGetDto",
{
'supportedLocations': fields.String,
'capabilities': fields.String,
'capacity': fields.String,
+ 'profile': fields.Nested(profile, False, True),
}
)
# See the License for the specific language governing permissions and
# limitations under the License.
-from sqlalchemy import select
+import string
+import random
+import yaml
+from datetime import datetime
+
+# from sqlalchemy import select
from o2common.service import unit_of_work
-from o2ims.adapter.orm import deploymentmanager
+# from o2ims.adapter.orm import deploymentmanager
from o2common.helper import o2logging
+from o2common.config import config
logger = o2logging.get_logger(__name__)
def deployment_managers(uow: unit_of_work.AbstractUnitOfWork):
+ # with uow:
+ # res = uow.session.execute(select(deploymentmanager))
+ # return [dict(r) for r in res]
with uow:
- res = uow.session.execute(select(deploymentmanager))
- return [dict(r) for r in res]
+ li = uow.deployment_managers.list()
+ return [r.serialize() for r in li]
def deployment_manager_one(deploymentManagerId: str,
- uow: unit_of_work.AbstractUnitOfWork):
+ uow: unit_of_work.AbstractUnitOfWork,
+ profile: str = 'params'):
+ # with uow:
+ # res = uow.session.execute(select(deploymentmanager).where(
+ # deploymentmanager.c.deploymentManagerId == deploymentManagerId))
+ # first = res.first()
+ # return None if first is None else dict(first)
+ # with uow:
+ # first = uow.deployment_managers.get(deploymentManagerId)
+ # return first.serialize() if first is not None else None
with uow:
- res = uow.session.execute(select(deploymentmanager).where(
- deploymentmanager.c.deploymentManagerId == deploymentManagerId))
- first = res.first()
- return None if first is None else dict(first)
+ first = uow.deployment_managers.get(deploymentManagerId)
+ if first is None:
+ return first
+ result = first.serialize()
+
+ if "params" == profile:
+ pass
+ elif "file" == profile and result.hasattr("profile"):
+ p = result.pop("profile", None)
+ result["profile"] = _gen_kube_config(deploymentManagerId, p)
+ else:
+ result.pop("profile", None)
+
+ return result
+
+
+def _gen_kube_config(dmId: str, kubeconfig: dict) -> dict:
+
+ # KUBECONFIG environment variable
+ # reference:
+ # https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/
+ data = {
+ 'apiVersion': 'v1',
+ 'clusters': [
+ {
+ 'cluster': {
+ 'server':
+ kubeconfig.pop('cluster_api_endpoint', None),
+ 'certificate-authority-data':
+ kubeconfig.pop('cluster_ca_cert', None),
+ },
+ 'name': 'inf-cluster'
+ }],
+ 'contexts': [
+ {
+ 'context': {
+ 'cluster': 'inf-cluster',
+ 'user': 'kubernetes-admin'
+ },
+ 'name': 'kubernetes-admin@inf-cluster'
+ }
+ ],
+ 'current-context': 'kubernetes-admin@inf-cluster',
+ 'kind': 'Config',
+ 'preferences': {},
+ 'users': [
+ {
+ 'name': kubeconfig.pop('admin_user', None),
+ 'user': {
+ 'client-certificate-data':
+ kubeconfig.pop('admin_client_cert', None),
+ 'client-key-data':
+ kubeconfig.pop('admin_client_key', None),
+ }
+ }]
+ }
+
+ # Generate a random key for tmp kube config file
+ letters = string.ascii_uppercase
+ random_key = ''.join(random.choice(letters) for i in range(10))
+
+ # 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
+
+ # write down the yaml file of kubectl into tmp folder
+ with open('/tmp/kubeconfig_' + 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
+
+ return kubeconfig
# limitations under the License.
# from flask import jsonify
-from flask_restx import Resource
+from os.path import exists
+from flask import send_file
+from flask_restx import Resource, reqparse
from o2dms.api.dms_dto import DmsDTO
from o2dms.api import dms_lcm_view
# ---------- DeploymentManagers ---------- #
@api_dms_lcm_v1.route("/<deploymentManagerID>")
@api_dms_lcm_v1.param('deploymentManagerID', 'ID of the deployment manager')
+@api_dms_lcm_v1.param('profile', 'DMS profile',
+ location='args')
@api_dms_lcm_v1.response(404, 'Deployment manager not found')
class DmsGetRouter(Resource):
deploymentManagerID
))
bus = MessageBus.get_instance()
+
+ parser = reqparse.RequestParser()
+ parser.add_argument('profile', location='args')
+ args = parser.parse_args()
+
result = dms_lcm_view.deployment_manager_one(
- deploymentManagerID, bus.uow)
+ deploymentManagerID, bus.uow, args.profile)
if result is not None:
return result
api_dms_lcm_v1.abort(404, "Deployment manager {} doesn't exist".format(
deploymentManagerID))
+
+
+@api_dms_lcm_v1.route("/<deploymentManagerID>/download/<filename>")
+@api_dms_lcm_v1.param('deploymentManagerID',
+ 'ID of the deployment manager')
+@api_dms_lcm_v1.param('filename',
+ 'profile filename')
+@api_dms_lcm_v1.response(404, 'profile not found')
+class DeploymentManagerGetFileRouter(Resource):
+ def get(self, deploymentManagerID, filename):
+ path = "/tmp/kubeconfig_" + filename
+
+ if exists(path):
+ return send_file(path, as_attachment=True)
+ api_dms_lcm_v1.abort(
+ 404,
+ "Deployment manager {}'s Kube config file doesn't exist".
+ format(deploymentManagerID))
Column("supportedLocations", String(255)),
Column("capabilities", String(255)),
Column("capacity", String(255)),
+ Column("profile", Text())
# Column("extensions", String(1024))
)
def __init__(self, id: str, name: str, ocloudid: str,
dmsendpoint: str, description: str = '',
supportedLocations: str = '', capabilities: str = '',
- capacity: str = '') -> None:
+ capacity: str = '', profile: str = '') -> None:
super().__init__()
self.deploymentManagerId = id
self.version_number = 0
self.supportedLocations = supportedLocations
self.capabilities = capabilities
self.capacity = capacity
+ self.profile = profile
self.extensions = []
+ def serialize(self):
+ d = Serializer.serialize(self)
+
+ if 'profile' in d and d['profile'] != '':
+ d['profile'] = json.loads(d['profile'])
+
+ return d
+
class ResourcePool(AgRoot, Serializer):
def __init__(self, id: str, name: str, location: str,
# pylint: disable=unused-argument
from __future__ import annotations
+import base64
+import json
+
from o2ims.domain.stx_object import StxGenericModel
# from dataclasses import asdict
# from typing import List, Dict, Callable, Type
with uow:
dms = uow.deployment_managers.get(stxobj.id)
if not dms:
- logger.info("add dms:" + stxobj.name
+ logger.info("add dms: " + stxobj.name
+ " update_at: " + str(stxobj.updatetime)
+ " id: " + str(stxobj.id)
+ " hash: " + str(stxobj.hash))
else:
localmodel = dms
if is_outdated(localmodel, stxobj):
- logger.info("update a dms:" + stxobj.name
+ logger.info("update a dms: " + stxobj.name
+ " update_at: " + str(stxobj.updatetime)
+ " id: " + str(stxobj.id)
+ " hash: " + str(stxobj.hash))
supportedLocations = ''
capabilities = ''
capacity = ''
+ profile = _convert_content(stxobj.content)
localmodel = DeploymentManager(
stxobj.id, stxobj.name, ocloudid, dmsendpoint, description,
- supportedLocations, capabilities, capacity)
+ supportedLocations, capabilities, capacity, profile)
localmodel.createtime = stxobj.createtime
localmodel.updatetime = stxobj.updatetime
localmodel.hash = stxobj.hash
target.name = stxobj.name
target.createtime = stxobj.createtime
target.updatetime = stxobj.updatetime
- # ocloud.content = stxobj.content
target.hash = stxobj.hash
target.oCloudId = parentid
target.version_number = target.version_number + 1
+ target.profile = _convert_content(stxobj.content)
target.events = []
+
+
+def _convert_content(stxobj_content: str):
+ # Convert api retrun content to dict object
+ content = json.loads(stxobj_content)
+ admin_user = content["admin_user"]
+ cluster_api_endpoint = content["cluster_api_endpoint"]
+ cluster_ca_cert = _b64_encode_str(content["cluster_ca_cert"])
+ admin_client_cert = _b64_encode_str(content["admin_client_cert"])
+ admin_client_key = _b64_encode_str(content["admin_client_key"])
+ # admin_client_cert = base64.b64encode(
+ # bytes(content["admin_client_cert"], "utf-8"))
+ # admin_client_key = base64.b64encode(
+ # bytes(content["admin_client_key"], "utf-8"))
+ profile = {
+ "admin_user": admin_user,
+ "cluster_api_endpoint": cluster_api_endpoint,
+ "cluster_ca_cert": cluster_ca_cert,
+ "admin_client_cert": admin_client_cert,
+ "admin_client_key": admin_client_key
+ }
+
+ return json.dumps(profile)
+
+
+def _b64_encode_str(msg: str, encode: str = 'utf-8') -> str:
+ msg_bytes = msg.encode('utf-8')
+ base64_bytes = base64.b64encode(msg_bytes)
+ base64_msg = base64_bytes.decode('utf-8')
+ return base64_msg
return api_ims_inventory_v1.model(
'ResourceGetDto' + str(iteration_number), resource_json_mapping)
- def _recursive_resource_mapping(self, iteration_number=2):
- resource_json_mapping = {
- 'resourceId': fields.String(required=True,
- description='Resource ID'),
- 'resourceTypeId': fields.String,
- 'resourcePoolId': fields.String,
+ # def _recursive_resource_mapping(self, iteration_number=2):
+ # resource_json_mapping = {
+ # 'resourceId': fields.String(required=True,
+ # description='Resource ID'),
+ # 'resourceTypeId': fields.String,
+ # 'resourcePoolId': fields.String,
+ # 'name': fields.String,
+ # 'parentId': fields.String,
+ # 'description': fields.String,
+ # }
+ # if iteration_number:
+ # resource_json_mapping['children'] = fields.List(
+ # fields.Nested(self._recursive_resource_mapping(
+ # iteration_number-1)))
+ # # print(type(resource_json_mapping['children']))
+ # if resource_json_mapping['children'] is None:
+ # del resource_json_mapping['children']
+ # return resource_json_mapping
+
+ # def get_resource_get(self):
+ # return api_ims_inventory_v1.model(
+ # 'ResourceGetDto',
+ # {
+ # 'resourceId': fields.String(required=True,
+ # description='Resource ID'),
+ # 'resourceTypeId': fields.String,
+ # 'resourcePoolId': fields.String,
+ # 'name': fields.String,
+ # 'parentId': fields.String,
+ # 'description': fields.String,
+ # 'children': fields.List(fields.Nested(
+ # self._recursive_resource_mapping()))
+ # }
+ # )
+
+
+class DeploymentManagerDTO:
+
+ deployment_manager_list = api_ims_inventory_v1.model(
+ "DeploymentManagerListDto",
+ {
+ 'deploymentManagerId': fields.String(
+ required=True,
+ description='Deployment manager ID'),
'name': fields.String,
- 'parentId': fields.String,
'description': fields.String,
+ 'deploymentManagementServiceEndpoint': fields.String,
+ 'supportedLocations': fields.String,
+ 'capabilities': fields.String,
+ 'capacity': fields.String,
}
- if iteration_number:
- resource_json_mapping['children'] = fields.List(
- fields.Nested(self._recursive_resource_mapping(
- iteration_number-1)))
- # print(type(resource_json_mapping['children']))
- if resource_json_mapping['children'] is None:
- del resource_json_mapping['children']
- return resource_json_mapping
-
- def get_resource_get(self):
- return api_ims_inventory_v1.model(
- 'ResourceGetDto',
- {
- 'resourceId': fields.String(required=True,
- description='Resource ID'),
- 'resourceTypeId': fields.String,
- 'resourcePoolId': fields.String,
- 'name': fields.String,
- 'parentId': fields.String,
- 'description': fields.String,
- 'children': fields.List(fields.Nested(
- self._recursive_resource_mapping()))
- }
- )
-
+ )
-class DeploymentManagerDTO:
+ profile = api_ims_inventory_v1.model("DeploymentManagerGetDtoProfile", {
+ 'cluster_api_endpoint': fields.String(
+ attributes='cluster_api_endpoint'),
+ 'cluster_ca_cert': fields.String(attributes='cluster_ca_cert'),
+ 'admin_user': fields.String(attributes='admin_user'),
+ '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')
+ })
deployment_manager_get = api_ims_inventory_v1.model(
"DeploymentManagerGetDto",
'supportedLocations': fields.String,
'capabilities': fields.String,
'capacity': fields.String,
+ 'profile': fields.Nested(profile, False, True),
}
)
# See the License for the specific language governing permissions and
# limitations under the License.
-from flask_restx import Resource
-from flask_restx import reqparse
+from flask_restx import Resource, reqparse
from o2common.service.messagebus import MessageBus
from o2ims.views import ocloud_view
@api_ims_inventory_v1.route("/deploymentManagers")
class DeploymentManagersListRouter(Resource):
- model = DeploymentManagerDTO.deployment_manager_get
+ model = DeploymentManagerDTO.deployment_manager_list
@api_ims_inventory_v1.marshal_list_with(model)
def get(self):
@api_ims_inventory_v1.route("/deploymentManagers/<deploymentManagerID>")
@api_ims_inventory_v1.param('deploymentManagerID',
'ID of the deployment manager')
+@api_ims_inventory_v1.param('profile', 'DMS profile',
+ location='args')
@api_ims_inventory_v1.response(404, 'Deployment manager not found')
class DeploymentManagerGetRouter(Resource):
@api_ims_inventory_v1.doc('Get deployment manager')
@api_ims_inventory_v1.marshal_with(model)
def get(self, deploymentManagerID):
+ parser = reqparse.RequestParser()
+ parser.add_argument('profile', location='args')
+ args = parser.parse_args()
result = ocloud_view.deployment_manager_one(
- deploymentManagerID, bus.uow)
+ deploymentManagerID, bus.uow, args.profile)
if result is not None:
return result
api_ims_inventory_v1.abort(
# limitations under the License.
import uuid
+import yaml
+import random
+import string
+from datetime import datetime
from o2common.service import unit_of_work
from o2ims.views.ocloud_dto import SubscriptionDTO
from o2ims.domain.subscription_obj import Subscription
from o2common.helper import o2logging
+from o2common.config import config
logger = o2logging.get_logger(__name__)
def deployment_manager_one(deploymentManagerId: str,
- uow: unit_of_work.AbstractUnitOfWork):
+ uow: unit_of_work.AbstractUnitOfWork,
+ profile: str = 'params'):
with uow:
first = uow.deployment_managers.get(deploymentManagerId)
- return first.serialize() if first is not None else None
+ if first is None:
+ return first
+ result = first.serialize()
+
+ if "params" == profile:
+ pass
+ elif "file" == profile and result.hasattr("profile"):
+ p = result.pop("profile", None)
+ result["profile"] = _gen_kube_config(deploymentManagerId, p)
+ else:
+ result.pop("profile", None)
+
+ return result
+
+
+def _gen_kube_config(dmId: str, kubeconfig: dict) -> dict:
+
+ # KUBECONFIG environment variable
+ # reference:
+ # https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/
+ data = {
+ 'apiVersion': 'v1',
+ 'clusters': [
+ {
+ 'cluster': {
+ 'server':
+ kubeconfig.pop('cluster_api_endpoint', None),
+ 'certificate-authority-data':
+ kubeconfig.pop('cluster_ca_cert', None),
+ },
+ 'name': 'inf-cluster'
+ }],
+ 'contexts': [
+ {
+ 'context': {
+ 'cluster': 'inf-cluster',
+ 'user': 'kubernetes-admin'
+ },
+ 'name': 'kubernetes-admin@inf-cluster'
+ }
+ ],
+ 'current-context': 'kubernetes-admin@inf-cluster',
+ 'kind': 'Config',
+ 'preferences': {},
+ 'users': [
+ {
+ 'name': kubeconfig.pop('admin_user', None),
+ 'user': {
+ 'client-certificate-data':
+ kubeconfig.pop('admin_client_cert', None),
+ 'client-key-data':
+ kubeconfig.pop('admin_client_key', None),
+ }
+ }]
+ }
+
+ # Generate a random key for tmp kube config file
+ letters = string.ascii_uppercase
+ random_key = ''.join(random.choice(letters) for i in range(10))
+
+ # 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
+
+ # write down the yaml file of kubectl into tmp folder
+ with open('/tmp/kubeconfig_' + 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
+
+ return kubeconfig
def subscriptions(uow: unit_of_work.AbstractUnitOfWork):