From 63b197b11b12a536254352f134262ad90f31593b Mon Sep 17 00:00:00 2001 From: "Chen, Jackie" Date: Mon, 15 Jun 2020 14:28:29 -0400 Subject: [PATCH] Adding policy manager and a1 sdnc vth included otf-helm with this commit. used for otf database deployments removed mock serever from both vths Change-Id: Ic1ca76eb89da3d79b100331ae1e11fb98c91113a Signed-off-by: Chen, Jackie --- a1-policy-manager-vth/.environ | 9 + a1-policy-manager-vth/Jenkinsfile | 158 +++ a1-policy-manager-vth/app/__init__.py | 11 + a1-policy-manager-vth/app/errors/__init__.py | 6 + .../app/errors/bad_request_exception.py | 21 + a1-policy-manager-vth/app/helpers/__init__.py | 12 + a1-policy-manager-vth/app/helpers/action_helper.py | 54 + a1-policy-manager-vth/app/helpers/error_helper.py | 51 + a1-policy-manager-vth/app/helpers/policy_helper.py | 163 +++ .../app/helpers/response_helper.py | 221 ++++ a1-policy-manager-vth/app/helpers/ric_helper.py | 37 + .../app/helpers/service_helper.py | 78 ++ a1-policy-manager-vth/app/helpers/time_helper.py | 24 + a1-policy-manager-vth/app/models/__init__.py | 6 + a1-policy-manager-vth/app/routes/__init__.py | 19 + a1-policy-manager-vth/app/routes/errors.py | 33 + a1-policy-manager-vth/app/routes/info.py | 76 ++ a1-policy-manager-vth/app/routes/policy.py | 225 ++++ a1-policy-manager-vth/app/routes/ric.py | 12 + a1-policy-manager-vth/app/routes/service.py | 16 + a1-policy-manager-vth/config.ini | 14 + a1-policy-manager-vth/doc/a1-documentation.docx | Bin 0 -> 84646 bytes a1-policy-manager-vth/docker/Dockerfile | 18 + a1-policy-manager-vth/docker/container-tag.yaml | 15 + .../helm/a1-policy-manager-vth/.helmignore | 21 + .../helm/a1-policy-manager-vth/Chart.yaml | 5 + .../templates/deployment.yaml | 137 +++ .../a1-policy-manager-vth/templates/secret.yaml | 9 + .../a1-policy-manager-vth/templates/service.yaml | 18 + .../helm/a1-policy-manager-vth/values.yaml | 23 + a1-policy-manager-vth/pip-requirements.txt | 6 + a1-policy-manager-vth/run.py | 52 + a1-sdnc-vth/.environ | 9 + a1-sdnc-vth/Jenkinsfile | 158 +++ a1-sdnc-vth/app/__init__.py | 11 + a1-sdnc-vth/app/errors/__init__.py | 6 + a1-sdnc-vth/app/errors/bad_request_exception.py | 21 + a1-sdnc-vth/app/helpers/__init__.py | 12 + a1-sdnc-vth/app/helpers/action_helper.py | 42 + a1-sdnc-vth/app/helpers/error_helper.py | 51 + a1-sdnc-vth/app/helpers/policy_helper.py | 163 +++ a1-sdnc-vth/app/helpers/response_helper.py | 218 ++++ a1-sdnc-vth/app/helpers/ric_helper.py | 37 + a1-sdnc-vth/app/helpers/service_helper.py | 78 ++ a1-sdnc-vth/app/helpers/time_helper.py | 24 + a1-sdnc-vth/app/models/__init__.py | 6 + a1-sdnc-vth/app/routes/__init__.py | 19 + a1-sdnc-vth/app/routes/errors.py | 33 + a1-sdnc-vth/app/routes/info.py | 76 ++ a1-sdnc-vth/app/routes/policy.py | 225 ++++ a1-sdnc-vth/app/routes/ric.py | 12 + a1-sdnc-vth/app/routes/service.py | 16 + a1-sdnc-vth/config.ini | 14 + a1-sdnc-vth/doc/a1-documentation.docx | Bin 0 -> 53241 bytes a1-sdnc-vth/docker/Dockerfile | 18 + a1-sdnc-vth/docker/container-tag.yaml | 15 + a1-sdnc-vth/helm/a1-sdnc-vth/.helmignore | 21 + a1-sdnc-vth/helm/a1-sdnc-vth/Chart.yaml | 5 + .../helm/a1-sdnc-vth/templates/deployment.yaml | 137 +++ a1-sdnc-vth/helm/a1-sdnc-vth/templates/secret.yaml | 8 + .../helm/a1-sdnc-vth/templates/service.yaml | 18 + a1-sdnc-vth/helm/a1-sdnc-vth/values.yaml | 23 + a1-sdnc-vth/pip-requirements.txt | 6 + a1-sdnc-vth/run.py | 51 + otf-helm/.gitignore | 1 + otf-helm/deploy.sh | 0 otf-helm/otf/.helmignore | 22 + otf-helm/otf/Chart.yaml | 21 + .../otf/charts/databases/charts/mongodb/Chart.yaml | 20 + .../databases/charts/mongodb/scripts/groups.json | 46 + .../databases/charts/mongodb/scripts/init_db.sh | 5 + .../databases/charts/mongodb/scripts/users.json | 28 + .../charts/mongodb/templates/configmap.yaml | 12 + .../charts/mongodb/templates/deployment.yaml | 72 ++ .../charts/mongodb/templates/ingress.yaml | 30 + .../databases/charts/mongodb/templates/secret.yaml | 8 + .../charts/mongodb/templates/service.yaml | 19 + .../charts/databases/charts/mongodb/values.yaml | 510 ++++++++ .../otf/charts/databases/charts/mysqldb/Chart.yaml | 17 + .../databases/charts/mysqldb/scripts/init_db.sh | 6 + .../charts/mysqldb/scripts/mysql_engine_7.10.0.sql | 1298 ++++++++++++++++++++ .../mysqldb/scripts/mysql_identity_7.10.0.sql | 103 ++ .../charts/mysqldb/templates/configmap.yaml | 12 + .../charts/mysqldb/templates/deployment.yaml | 97 ++ .../charts/mysqldb/templates/ingress.yaml | 30 + .../databases/charts/mysqldb/templates/secret.yaml | 8 + .../charts/mysqldb/templates/service.yaml | 19 + .../charts/databases/charts/mysqldb/values.yaml | 233 ++++ otf-helm/otf/values/development.yaml | 1 + otf-helm/readme.md | 5 + 90 files changed, 5706 insertions(+) create mode 100644 a1-policy-manager-vth/.environ create mode 100644 a1-policy-manager-vth/Jenkinsfile create mode 100644 a1-policy-manager-vth/app/__init__.py create mode 100644 a1-policy-manager-vth/app/errors/__init__.py create mode 100644 a1-policy-manager-vth/app/errors/bad_request_exception.py create mode 100644 a1-policy-manager-vth/app/helpers/__init__.py create mode 100644 a1-policy-manager-vth/app/helpers/action_helper.py create mode 100644 a1-policy-manager-vth/app/helpers/error_helper.py create mode 100644 a1-policy-manager-vth/app/helpers/policy_helper.py create mode 100644 a1-policy-manager-vth/app/helpers/response_helper.py create mode 100644 a1-policy-manager-vth/app/helpers/ric_helper.py create mode 100644 a1-policy-manager-vth/app/helpers/service_helper.py create mode 100644 a1-policy-manager-vth/app/helpers/time_helper.py create mode 100644 a1-policy-manager-vth/app/models/__init__.py create mode 100644 a1-policy-manager-vth/app/routes/__init__.py create mode 100644 a1-policy-manager-vth/app/routes/errors.py create mode 100644 a1-policy-manager-vth/app/routes/info.py create mode 100644 a1-policy-manager-vth/app/routes/policy.py create mode 100644 a1-policy-manager-vth/app/routes/ric.py create mode 100644 a1-policy-manager-vth/app/routes/service.py create mode 100644 a1-policy-manager-vth/config.ini create mode 100644 a1-policy-manager-vth/doc/a1-documentation.docx create mode 100644 a1-policy-manager-vth/docker/Dockerfile create mode 100644 a1-policy-manager-vth/docker/container-tag.yaml create mode 100644 a1-policy-manager-vth/helm/a1-policy-manager-vth/.helmignore create mode 100644 a1-policy-manager-vth/helm/a1-policy-manager-vth/Chart.yaml create mode 100644 a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/deployment.yaml create mode 100644 a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/secret.yaml create mode 100644 a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/service.yaml create mode 100644 a1-policy-manager-vth/helm/a1-policy-manager-vth/values.yaml create mode 100644 a1-policy-manager-vth/pip-requirements.txt create mode 100644 a1-policy-manager-vth/run.py create mode 100644 a1-sdnc-vth/.environ create mode 100644 a1-sdnc-vth/Jenkinsfile create mode 100644 a1-sdnc-vth/app/__init__.py create mode 100644 a1-sdnc-vth/app/errors/__init__.py create mode 100644 a1-sdnc-vth/app/errors/bad_request_exception.py create mode 100644 a1-sdnc-vth/app/helpers/__init__.py create mode 100644 a1-sdnc-vth/app/helpers/action_helper.py create mode 100644 a1-sdnc-vth/app/helpers/error_helper.py create mode 100644 a1-sdnc-vth/app/helpers/policy_helper.py create mode 100644 a1-sdnc-vth/app/helpers/response_helper.py create mode 100644 a1-sdnc-vth/app/helpers/ric_helper.py create mode 100644 a1-sdnc-vth/app/helpers/service_helper.py create mode 100644 a1-sdnc-vth/app/helpers/time_helper.py create mode 100644 a1-sdnc-vth/app/models/__init__.py create mode 100644 a1-sdnc-vth/app/routes/__init__.py create mode 100644 a1-sdnc-vth/app/routes/errors.py create mode 100644 a1-sdnc-vth/app/routes/info.py create mode 100644 a1-sdnc-vth/app/routes/policy.py create mode 100644 a1-sdnc-vth/app/routes/ric.py create mode 100644 a1-sdnc-vth/app/routes/service.py create mode 100644 a1-sdnc-vth/config.ini create mode 100644 a1-sdnc-vth/doc/a1-documentation.docx create mode 100644 a1-sdnc-vth/docker/Dockerfile create mode 100644 a1-sdnc-vth/docker/container-tag.yaml create mode 100644 a1-sdnc-vth/helm/a1-sdnc-vth/.helmignore create mode 100644 a1-sdnc-vth/helm/a1-sdnc-vth/Chart.yaml create mode 100644 a1-sdnc-vth/helm/a1-sdnc-vth/templates/deployment.yaml create mode 100644 a1-sdnc-vth/helm/a1-sdnc-vth/templates/secret.yaml create mode 100644 a1-sdnc-vth/helm/a1-sdnc-vth/templates/service.yaml create mode 100644 a1-sdnc-vth/helm/a1-sdnc-vth/values.yaml create mode 100644 a1-sdnc-vth/pip-requirements.txt create mode 100644 a1-sdnc-vth/run.py create mode 100644 otf-helm/.gitignore create mode 100644 otf-helm/deploy.sh create mode 100644 otf-helm/otf/.helmignore create mode 100644 otf-helm/otf/Chart.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/Chart.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/scripts/groups.json create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/scripts/init_db.sh create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/scripts/users.json create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/templates/configmap.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/templates/deployment.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/templates/ingress.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/templates/secret.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/templates/service.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mongodb/values.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/Chart.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/scripts/init_db.sh create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_engine_7.10.0.sql create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_identity_7.10.0.sql create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/templates/configmap.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/templates/deployment.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/templates/ingress.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/templates/secret.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/templates/service.yaml create mode 100644 otf-helm/otf/charts/databases/charts/mysqldb/values.yaml create mode 100644 otf-helm/otf/values/development.yaml create mode 100644 otf-helm/readme.md diff --git a/a1-policy-manager-vth/.environ b/a1-policy-manager-vth/.environ new file mode 100644 index 0000000..0b3fa87 --- /dev/null +++ b/a1-policy-manager-vth/.environ @@ -0,0 +1,9 @@ +#if using dotenv change file name to .env and set env variables below +USER=String +PW=String +AUTH=Boolean +PROXY=Boolean +HTTP=String +HTTPS=String +API_URL=String +API_PORT=Int diff --git a/a1-policy-manager-vth/Jenkinsfile b/a1-policy-manager-vth/Jenkinsfile new file mode 100644 index 0000000..3244ca2 --- /dev/null +++ b/a1-policy-manager-vth/Jenkinsfile @@ -0,0 +1,158 @@ +#!/usr/bin/env groovy + +/* Copyright (c) 2019 AT&T Intellectual Property. # +# # +# 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. # +##############################################################################*/ + + +properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [ + [$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"], + [$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "m13591_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "com-att-ecomp-otf-dev"] +]]]) + + + echo "Build branch: ${env.BRANCH_NAME}" + + node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="a1-policy-manager-vth"; + VERSION="0.0.1-SNAPSHOT"; + NAMESPACE="com.att.ecomp.otf" //TODO change back to org-otf-oran when done testing + DOCKER_REGISTRY="dockercentral.it.att.com:5100" + + if( ENV.equalsIgnoreCase("dev") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".dev" + "/" + ARTIFACT_ID + ":" + VERSION + + } + if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID + ":" + VERSION + + } + + if( ENV.equalsIgnoreCase("st") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID + ":" + VERSION + + } + + echo "Artifact: " + IMAGE_NAME + + withEnv(["PATH=${env.PATH}:${env.WORKSPACE}/linux-amd64", "HELM_HOME=${env.WORKSPACE}"]) { + + echo "PATH=${env.PATH}" + echo "HELM_HOME=${env.HELM_HOME}" + + if (PHASES.contains("BUILD")){ + dir("./a1-policy-manager-vth"){ + + stage 'Publish Artifact' + + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + + echo "Artifact: " + IMAGE_NAME + + sh """ + docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD + docker build -t $IMAGE_NAME . + docker push $IMAGE_NAME + """ + } + } + + } + + if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY")) { + + stage 'Init Helm' + + //check if helm exists if not install + if(fileExists('linux-amd64/helm')){ + sh """ + echo "helm is already installed" + """ + } + else{ + //download helm + sh """ + echo "installing helm" + wget https://storage.googleapis.com/kubernetes-helm/helm-v2.14.3-linux-amd64.tar.gz + tar -xf helm-v2.14.3-linux-amd64.tar.gz + rm helm-v2.14.3-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('a1-policy-manager-vth/helm'){ + //check if charts are valid, and then perform dry run, if successful then upgrade/install charts + + if (PHASES.contains("UNDEPLOY") ) { + stage 'Undeploy' + + sh """ + helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID + """ + } + + //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace + if (PHASES.contains("DEPLOY") ){ + stage 'Deploy' + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + + sh """ + echo "Validate Yaml" + helm lint $ARTIFACT_ID + + echo "View Helm Templates" + helm template $ARTIFACT_ID --set appName=$ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set env=$ENV \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE + + echo "Perform Dry Run Of Install" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set env=$ENV \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE + + + echo "Helm Install/Upgrade" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set env=$ENV \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE + + """ + } + } + + } + } + } + + } + } diff --git a/a1-policy-manager-vth/app/__init__.py b/a1-policy-manager-vth/app/__init__.py new file mode 100644 index 0000000..14b5496 --- /dev/null +++ b/a1-policy-manager-vth/app/__init__.py @@ -0,0 +1,11 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Import everything using star, methods or anything that should not be + used by the outside modules should not be imported on the nested + __init__ files. +""" +from .routes import * +from .errors import * +from .models import * +from .helpers import * diff --git a/a1-policy-manager-vth/app/errors/__init__.py b/a1-policy-manager-vth/app/errors/__init__.py new file mode 100644 index 0000000..b491f42 --- /dev/null +++ b/a1-policy-manager-vth/app/errors/__init__.py @@ -0,0 +1,6 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Only imort methods that can be used and are used by outside modules +""" +from .bad_request_exception import BadRequestException diff --git a/a1-policy-manager-vth/app/errors/bad_request_exception.py b/a1-policy-manager-vth/app/errors/bad_request_exception.py new file mode 100644 index 0000000..a3e3d22 --- /dev/null +++ b/a1-policy-manager-vth/app/errors/bad_request_exception.py @@ -0,0 +1,21 @@ +""" +Args: +Returns: +Examples: +""" +class BadRequestException(Exception): + """ + Args: + Returns: + Examples: + """ + def __init__(self, status_code=406, message="Not Acceptable Response"): + cases = { + 401:"Unauthorized", + 403:"Forbidden", + 404:"Not Found", + 423:"Not Operational" + } + super().__init__(cases.get(status_code, message)) + self.status_code = status_code + self.message = message diff --git a/a1-policy-manager-vth/app/helpers/__init__.py b/a1-policy-manager-vth/app/helpers/__init__.py new file mode 100644 index 0000000..3313af8 --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/__init__.py @@ -0,0 +1,12 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Only imort methods that can be used and are used by outside modules +""" +from .error_helper import * +from .response_helper import * +from .time_helper import * +from .policy_helper import * +from .service_helper import * +from .ric_helper import * +from .action_helper import * diff --git a/a1-policy-manager-vth/app/helpers/action_helper.py b/a1-policy-manager-vth/app/helpers/action_helper.py new file mode 100644 index 0000000..f952e76 --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/action_helper.py @@ -0,0 +1,54 @@ +import json +import ast +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException +import requests + +def execute_action(request, response_dict, config): + headers = ResponseHelper.create_headers(); + request_data = request.json + action_request = request_data.get("action").lower() + method = request_data.get("method").upper() + creds = ResponseHelper.get_credentials(request_data, config) + + proxies = ResponseHelper.get_proxies(config) + action = "services/keepalive" if action_request == "keepalive" else action_request + url = ResponseHelper.create_url(config=config, uri_path="/"+action) +# ret_url = request.args.get('retURL') + + + json_req = ast.literal_eval(request_data["action_data"]["jsonBody"]) + query_params = ast.literal_eval(request_data["action_data"]["query"]) + current_app.logger.info("Requesting Url: {}, params: {}, body: {}, auth: {}, proxies: {}".format(url, query_params, json_req, creds, proxies)) + try: + if(method == "GET"): + res = requests.get(url, proxies=proxies, auth=creds, headers=headers, params=query_params, json=json_req) + elif(method == "POST"): + res = requests.post(url, proxies=proxies, auth=creds, headers=headers, params=query_params, json=json_req) + elif(method == "PUT"): + res = requests.put(url, proxies=proxies, auth=creds, headers=headers, params=query_params, json=json_req) + elif(method == "DELETE"): + res = requests.delete(url, proxies=proxies, auth=creds, headers=headers, params=query_params, json=json_req) + else: + raise BadRequestException(406, "Method Not Supported") + response = { + "status_code":res.status_code, + "result": res.json() + } + except(json.decoder.JSONDecodeError): + response = { + "status_code":res.status_code, + "result": res.reason + } + except requests.exceptions.RequestException: + response = { + "status_code":504, + "result": "Something Happned" + } + finally: + response_dict['vthResponse']['resultData'] = response + # if ret_url is not None: + # ResponseHelper.sendCallback(ret_url,response_dict) + # return '',200 + return response_dict diff --git a/a1-policy-manager-vth/app/helpers/error_helper.py b/a1-policy-manager-vth/app/helpers/error_helper.py new file mode 100644 index 0000000..b34cedf --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/error_helper.py @@ -0,0 +1,51 @@ +from flask import current_app +import datetime +""" +Args: +Returns: +Examples: +""" + +def error_dic(error, status_code, response_message="Something went wrong, vth encountered an error"): + """ + Args: + Returns: + Examples: + """ + message = [str(x) for x in error.args] + error_log={ + "error":{ + "type": error.__class__.__name__, + "message": message + } + } + response_data = { + "vthResponse": { + "testDurationMS": 0, + 'dateTimeUTC': str(datetime.datetime.now()), + "abstractMessage": "Failed", + "error":response_message, + "status_code": status_code, + "resultData": {} + } + } + current_app.logger.error(error_log) + return response_data + +def error_dic2(error, status_code=500): + """ + Args: + Returns: + Examples: + """ + message = [str(x) for x in error.args] + response = { + "status_code" : status_code, + "success": False, + "error":{ + "type": error.__class__.__name__, + "message": message + } + } + return response + diff --git a/a1-policy-manager-vth/app/helpers/policy_helper.py b/a1-policy-manager-vth/app/helpers/policy_helper.py new file mode 100644 index 0000000..ea4fedc --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/policy_helper.py @@ -0,0 +1,163 @@ +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException +import requests + +def get_policy_using_get(request, response_dict, config): + json_data = request.get_json() + if 'id' not in json_data: raise BadRequestException(406, "Request is missing id") + param = {'id': json_data['id']} + creds = ResponseHelper.get_credentials(json_data, config) + url = ResponseHelper.create_url(config=config, uri_path="/policy") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + + return response_dict +def put_policy_using_put(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + + current_app.logger.info("creds: {}".format(creds)) + + required = {'id', 'jsonBody', 'ric', 'service'} + param_keys = {'id', 'ric', 'service'} + optional = {"type"} + data_keys = param_keys.copy() + keys = set(json_data.keys()) + if not required <= keys: + raise BadRequestException(406, "Request is missing required values {}".format(required)) + if optional <= keys: data_keys.update(optional) + param = {} + body = {} + for key in data_keys: + param[key] = json_data[key] + body['jsonBody'] = json_data['jsonBody'] + + url = ResponseHelper.create_url(config=config, uri_path="/policy") + res = requests.put(url, auth=creds, params=param, json=body) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict +def delete_policy_using_delete(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'id'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + param = {'id': json_data['id']} + + url = ResponseHelper.create_url(config=config, uri_path="/policy") + res = requests.delete(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict + +def get_policy_ids_using_get(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + param = { + "ric":json_data["ric"] if "ric" in json_data else "", + "service":json_data["service"] if "service" in json_data else "", + "type":json_data["type"] if "type" in json_data else "" + } + + url = ResponseHelper.create_url(config=config, uri_path="/policy_ids") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict + +def get_policy_schema_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'id'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + param = {'id': json_data['id']} + + url = ResponseHelper.create_url(config=config, uri_path="/policy_schema") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict +def get_policy_schemas_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + param = { + "ric":json_data['ric'] if 'ric' in json_data else "" + } + #api_response = requests.put(url, credentials=creds, params=param) + + url = ResponseHelper.create_url(config=config, uri_path="/policy_schemas") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict +def get_policy_status_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'id'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + param = { + "id":json_data["id"] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def get_policy_types_using_get(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + param = { + 'ric': json_data['ric'] if 'ric' in json_data else "" + } + + url = ResponseHelper.create_url(config=config, uri_path="/a1-p/policytypes") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict + diff --git a/a1-policy-manager-vth/app/helpers/response_helper.py b/a1-policy-manager-vth/app/helpers/response_helper.py new file mode 100644 index 0000000..2058347 --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/response_helper.py @@ -0,0 +1,221 @@ +import ast +import requests +from configparser import ConfigParser +import os +import datetime +import json +from flask import request, jsonify, current_app +from app.helpers.time_helper import unix_time_millis +from app.errors.bad_request_exception import BadRequestException +import requests + +""" + Module Info: +""" +def create_headers(enable_cache=True, content_type="application/json", connection="Keep-Alive"): + headers = {'Cache-Control':'no-cache, no-store, must-revalidate', "Pragma":"no-cache", "Expires":"0"} if not enable_cache else {} + headers['content-type'] = content_type + headers['connection'] = connection + return headers +def create_url(config=None, uri_path = "/", url_string=None): + return config['api']['base_url'] +":"+ config['api']['port']+uri_path if url_string is None else url_string + +def valid_string_json(string, response_message="Invalid json string in query or jsonBody, format requires quoted json object e.g. \"{'key':'value, key2:{'innerKey':'innerValue'}}\""): + try: + string_to_dict = ast.literal_eval(string) + except(Exception): + raise BadRequestException(406, response_message) + return True +def route_check(config=None, get_function=None, post_function=None, put_function=None, delete_function=None): + """ + Info: + Since all routes do the same pre-check and have a similar skeleton, this function just refactored the pre-check for code reuse + Arguments (**kwargs): pass in the specified key(s) and method(s) that handle the type of method, method must be allowed by route decorator + get_function => type: function + put_function => type: function + delete_function => type: function + Returns: + returns the return of the function call, typically a jsonified response. + you can capture response in a var and execute logic or you can just return the function call/response + E.G.: + response = route_check(post_function = handle_post) + return route_check(get_function = handle_get, post_function = handle_post) + """ + if not request.is_json: raise BadRequestException(406, "Invalid Json Request") + + + response_dict = vth_response_dic() + start_time = unix_time_millis(datetime.datetime.now()) + status_code = 200 + ret_url = request.args.get('retURL') + + query = "" + json_body = "" + request_data = request.json + json_keys = set(request_data) + action_request = request_data.get("action").lower() + valid_actions = {"policies", "policy", "policy_ids", "policy_schema", "policy_schemas", "policy_status", "policy_types", "ric", "rics", "service", "services", "keepalive", "status" } + required_keys = {"action", "method", "auth", "action_data"} + + #check for valid action and json request contains required keys + if not required_keys <= json_keys: raise BadRequestException(406, "Json request is missing required keys {}".format(required_keys)) + if not action_request in valid_actions: raise BadRequestException(406, "Action is not supported {}".format(action_request)) + #check request's action_data key contains required keys + if 'query' not in request.json['action_data']: raise BadRequestException(406, "action_data must contain query and jsonBody ") + if 'jsonBody' not in request.json['action_data']: raise BadRequestException(406, "action_data must contain query and jsonBody") + + query = request.json['action_data']['query'] if 'query' in request.json['action_data'] else "" + json_body = request.json['action_data']['jsonBody'] if 'jsonBody' in request.json['action_data'] else "" + + + if valid_string_json(query) and valid_string_json(json_body): + if(request.method == 'GET'): + response_dict = get_function(request, response_dict, config) + elif(request.method == 'POST'): + response_dict = post_function(request, response_dict, config) + elif(request.method == 'PUT'): + response_dict = put_function(request, response_dict, config) + elif(request.method == 'DELETE'): + response_dict = delete_function(request, response_dict, config) + else: + raise BadRequestException(406, "Invalid JSON Strings") + end_time = unix_time_millis(datetime.datetime.now()) + response_dict['vthResponse']['testDurationMS'] = end_time-start_time + if ret_url is not None: + sendCallback(ret_url,response_dict) + return '',200 + return jsonify(response_dict), status_code + +def get_proxies(config): + proxy_enabled = config.getboolean('resource', 'proxy_enabled') + req_proxies = { + 'http': None, + 'https': None + } + if not proxy_enabled: + return None + else: + req_proxies['http'] = config['resource']['http_proxy'] + req_proxies['https'] = config['resource']['https_proxy'] + return req_proxies +def get_credentials(json_data, config): + auth_enabled = config.getboolean('auth', 'creds_enabled') + if not auth_enabled: + return None + else: + username = config['auth']['username'] if 'username' not in json_data['auth'] else json_data['auth']['username'] + password = config['auth']['password'] if 'password' not in json_data['auth'] else json_data['auth']['password'] + return (username, password) +def vth_response_dic(): + """ + Args: + Returns: + Examples: + """ + response_data = { + "vthResponse": { + "testDurationMS": "", + 'dateTimeUTC': str(datetime.datetime.now()), + "abstractMessage": "Success", + "resultData": {} + } + } + return response_data + +def sendCallback(url, data): + try: + if type(data) is not dict: + data = {"msg": data} + current_app.logger.info("sending callback") + requests.post(url, json=data) + except Exception as e: + current_app.logger.info(e) + return + +def get_request_data(request): + if not request.is_json: + raise ValueError("request must be json") + requestData = request.get_json() + return requestData + + +def valid_json(data): + + try: + _ = json.loads(data) + except ValueError as e: + return False + return True +def get_config(config_file_name): + config = ConfigParser(os.environ) + config.read(config_file_name) + return config + +def validate_request(request_data, isPublish=True): + return + missing_params = [] + + if 'topic_name' not in request_data: + missing_params.append("topic_name") + if isPublish: + if 'data' not in request_data: + missing_params.append('data') + else: + if 'consumer_group' not in request_data: + missing_params.append('consumer_group') + if 'consumer_id' not in request_data: + missing_params.append('consumer_id') + + if missing_params: + err_msg = '{} request requires the following: '.format('publish' if isPublish else 'subscribe') + err_msg += ','.join(missing_params) + raise KeyError(err_msg) + + +def build_url(config, request_data, is_publish=True): + if is_publish: + base_path = config['resource']['base_address'] + config['resource']['publish'] + topic_name = request_data['topic_name'] + publish_address = base_path.format(topic_name=topic_name) + return publish_address + + base_path = config['resource']['base_address'] + config['resource']['subscribe'] + topic_name = request_data['topic_name'] + consumer_group = request_data['consumer_group'] + consumer_id = request_data['consumer_id'] + subscribe_address = base_path.format(topic_name=topic_name, consumer_group=consumer_group, consumer_id=consumer_id) + if ('timeout' in request_data): + subscribe_address = (subscribe_address + '?timeout={}').format(request_data['timeout']) + return subscribe_address + + +def send_request(url, config, is_subscribe_request=False, payload=None): + # setup default values + auth_enabled = config.getboolean('auth', 'auth_enabled') + proxy_enabled = config.getboolean('resource', 'proxy_enabled') + username = '' + password = '' + req_proxies = { + 'http': None, + 'https': None + } + # place proxy and authentication information + if auth_enabled: + username = config['auth']['username'] + password = config['auth']['password'] + if proxy_enabled: + req_proxies['http'] = config['resource']['http_proxy'] + req_proxies['https'] = config['resource']['https_proxy'] + + # for subscribe request + if is_subscribe_request: + return requests.get(url, + auth=(username, password) if auth_enabled else None, + proxies=req_proxies if proxy_enabled else None) + # for publish request + req_headers = {'Content-type': 'application/json'} + return requests.post(url, + json=payload, + auth=(username, password) if auth_enabled else None, + proxies=req_proxies if proxy_enabled else None, + headers=req_headers) diff --git a/a1-policy-manager-vth/app/helpers/ric_helper.py b/a1-policy-manager-vth/app/helpers/ric_helper.py new file mode 100644 index 0000000..47d55c2 --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/ric_helper.py @@ -0,0 +1,37 @@ +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException + +def get_ric_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'managedElementId'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'managedElementId': json_data['managedElementId'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def get_rics_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + param = { + "policyType": json_data["policyType"] if "policyType" in json_data else "" + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict diff --git a/a1-policy-manager-vth/app/helpers/service_helper.py b/a1-policy-manager-vth/app/helpers/service_helper.py new file mode 100644 index 0000000..16d9b92 --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/service_helper.py @@ -0,0 +1,78 @@ + +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException + +def get_services_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + param = { + 'name': json_data['name'] if 'name' in json_data else "" + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def delete_services_using_delete(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'name'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'name': json_data['name'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def put_service_using_put(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'registrationInfo'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'registrationInfo': json_data['registrationInfo'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict + +def keep_alive_service_using_put(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'name'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'name': json_data['name'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict diff --git a/a1-policy-manager-vth/app/helpers/time_helper.py b/a1-policy-manager-vth/app/helpers/time_helper.py new file mode 100644 index 0000000..b882d0b --- /dev/null +++ b/a1-policy-manager-vth/app/helpers/time_helper.py @@ -0,0 +1,24 @@ +""" + Module Info: +""" +import datetime + +def unix_time_millis(d_time): + """ + Args: + Returns: + Examples: + """ + epoch = datetime.datetime.utcfromtimestamp(0) + return (d_time - epoch).total_seconds() * 1000.0 + +def timed_function(func): + """ + Args: + Returns: + Examples: + """ + start_time = unix_time_millis(datetime.datetime.now()) + func() + end_time = unix_time_millis(datetime.datetime.now()) + return end_time - start_time diff --git a/a1-policy-manager-vth/app/models/__init__.py b/a1-policy-manager-vth/app/models/__init__.py new file mode 100644 index 0000000..52319a0 --- /dev/null +++ b/a1-policy-manager-vth/app/models/__init__.py @@ -0,0 +1,6 @@ + +""" + Module Info: + Anything imported to this file will be available to outside modules. + Only imort methods that can be used and are used by outside modules +""" diff --git a/a1-policy-manager-vth/app/routes/__init__.py b/a1-policy-manager-vth/app/routes/__init__.py new file mode 100644 index 0000000..89419e1 --- /dev/null +++ b/a1-policy-manager-vth/app/routes/__init__.py @@ -0,0 +1,19 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Routes need to be exported to be usable, if removed, routes will not be found and response + will be a 500. + ROUTE order matters, because ROUTE is like a global var used by all the other modules + it needs to be above them all +""" +from flask import Blueprint +from app.helpers.response_helper import get_config + +ROUTES = Blueprint('routes', __name__) +config = get_config("config.ini") + +from .policy import * +from .ric import * +from .service import * +from .info import * +from .errors import ERRORS diff --git a/a1-policy-manager-vth/app/routes/errors.py b/a1-policy-manager-vth/app/routes/errors.py new file mode 100644 index 0000000..43e1ec1 --- /dev/null +++ b/a1-policy-manager-vth/app/routes/errors.py @@ -0,0 +1,33 @@ +""" +Module Info: +""" +from flask import jsonify, current_app, Blueprint +from app.helpers.error_helper import error_dic +from app.errors.bad_request_exception import BadRequestException +import traceback + +ERRORS = Blueprint('errors', __name__) + +@ERRORS.app_errorhandler(BadRequestException) +def handle_bad_request(error): + """ + Args: + Returns: + Examples: + """ + current_app.logger.info(error) + response = error_dic(error, error.status_code, error.message) + print(traceback.format_exc()) + return jsonify(response), error.status_code + +@ERRORS.app_errorhandler(Exception) +def handle_error(error): + """ + Args: + Returns: + Examples: + """ + status_code = 500 + response = error_dic(error, status_code) + print(traceback.format_exc()) + return jsonify(response), status_code diff --git a/a1-policy-manager-vth/app/routes/info.py b/a1-policy-manager-vth/app/routes/info.py new file mode 100644 index 0000000..7090cf3 --- /dev/null +++ b/a1-policy-manager-vth/app/routes/info.py @@ -0,0 +1,76 @@ +""" +Args: +Returns: +Examples: +""" +import json +import datetime +from flask import current_app, jsonify, request +import time +import requests +from app.errors.bad_request_exception import BadRequestException +from app.helpers.time_helper import unix_time_millis, timed_function +from app.helpers.response_helper import vth_response_dic +from app.helpers import response_helper as ResponseHelper +from app.helpers import action_helper as Info +from . import config, ROUTES + + +@ROUTES.route("/handle_action", methods=['POST']) +def handle_action_request(): + return ResponseHelper.route_check(config=config, post_function = Info.execute_action) + + +@ROUTES.route("/", methods=['GET']) +def get_base(): + """ + Args: + Returns: + Examples: + """ + current_app.logger.info(request.method) + response = vth_response_dic() + data = current_app.url_map + rules = [] + methods_list = [] + for rule in data.iter_rules(): + ma = {rule.rule:[]} + for val in rule.methods: + if (val != "OPTIONS") and (val !="HEAD"): + #print(val) + ma[rule.rule].append(val) + rules.append(ma) + + # methods_set.add(rule.methods) + #print(rule.methods) + #print(rules) + response["vthResponse"]["resultData"] = rules + #current_app.logger.info(current_app.url_map) + current_app.logger.debug("hit health point") + return jsonify(response) + +@ROUTES.route("/health", methods=['GET']) +def get_health(): + """ + Args: + Returns: + Examples: + """ + current_app.logger.debug("hit health point") + return "UP" + +@ROUTES.route("/status", methods=['GET']) +def get_status(): + """ + Args: + Returns: + Examples: + """ + suma = lambda: time.sleep(1) + #current_app.logger.info(current_app.url_map) + current_app.logger.info(unix_time_millis(datetime.datetime.now())) + current_app.logger.info(timed_function(suma)) + current_app.logger.debug("some stuff") + #raise Exception("some error") + raise BadRequestException() + return "Running" diff --git a/a1-policy-manager-vth/app/routes/policy.py b/a1-policy-manager-vth/app/routes/policy.py new file mode 100644 index 0000000..588397f --- /dev/null +++ b/a1-policy-manager-vth/app/routes/policy.py @@ -0,0 +1,225 @@ + +import datetime +import json +import logging +from logging import FileHandler +import os + +import requests +from flask import Flask, request, jsonify +from . import config, ROUTES +from app.helpers import policy_helper as Policy +from app.helpers import response_helper as ResponseHelper +from app.errors.bad_request_exception import BadRequestException + + + +def sendCallback(url, data): + try: + if type(data) is not dict: + data = {"msg": data} + app.logger.info("sending callback") + requests.post(url, json=data) + except Exception as e: + app.logger.info(e) + return + +def unix_time_millis(dt): + epoch = datetime.datetime.utcfromtimestamp(0) + return (dt - epoch).total_seconds() * 1000.0 + + +def route_check2(get_function=None, post_function=None, put_function=None, delete_function=None): + """ + Info: + Since all routes do the same pre-check and have a similar skeleton, this function just refactored the pre-check for code reuse + Arguments (**kwargs): pass in the specified key(s) and method(s) that handle the type of method, method must be allowed by route decorator + get_function => type: function + put_function => type: function + delete_function => type: function + Returns: + returns the return of the function call, typically a jsonified response. + you can capture response in a var and execute logic or you can just return the function call/response + E.G.: + response = route_check(post_function = handle_post) + return route_check(get_function = handle_get, post_function = handle_post) + """ + response_dict = ResponseHelper.vth_response_dic() + start_time = unix_time_millis(datetime.datetime.now()) + status_code = 200 + if request.is_json and ResponseHelper.valid_json(request.data): + if(request.method == 'GET'): + response_dict = get_function(request, response_dict, config) + elif(request.method == 'POST'): + response_dict = post_function(request, response_dict, config) + elif(request.method == 'PUT'): + response_dict = put_function(request, response_dict, config) + elif(request.method == 'DELETE'): + response_dict = delete_function(request, response_dict, config) + else: + raise BadRequestException(406, "Invalid Json") + end_time = unix_time_millis(datetime.datetime.now()) + response_dict['vthResponse']['testDurationMS'] = end_time-start_time + return jsonify(response_dict), status_code + + +@ROUTES.route("/policies", methods=['GET']) +def policies(): + pass + +@ROUTES.route("/policy", methods=['GET', 'PUT', 'DELETE']) +def handle_policy(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_using_get, put_function = Policy.put_policy_using_put, delete_function=Policy.delete_policy_using_delete) + + +@ROUTES.route("/policy_ids", methods=['GET']) +def handle_policy_ids(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_ids_using_get) + +@ROUTES.route("/policy_schemas", methods=['GET']) +def handle_policy_schemas(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_schemas_using_get) + +@ROUTES.route("/policy_schema", methods=['GET']) +def handle_policy_schema(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_schema_using_get) + +@ROUTES.route("/policy_status", methods=['GET']) +def handle_policy_status(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_status_using_get) + +@ROUTES.route("/policy_types", methods=['GET']) +def handle_policy_types(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_types_using_get) + + +@ROUTES.route("/", methods=['POST']) +def executeRicRequest(): + response_data = { + 'vthResponse': { + 'testDuration': '', + 'dateTimeUTC': str(datetime.datetime.now()), + 'abstractMessage': '', + 'resultData': {} + } + } + + startTime = unix_time_millis(datetime.datetime.now()) + ret_url = request.args.get('retURL') + try: + if not request.is_json: + raise ValueError("request must be json") + + requestData = request.get_json() + + app.logger.info("A1 requestData:" + str(requestData)) + + action = requestData['action'].lower() + _check_incoming_request(requestData) + + os.environ['NO_PROXY'] = '127.0.0.1' # TODO testing purpose w/ mock server. Needs to remove on final version + with open('config.json') as configFile: + config = json.load(configFile) + + baseAddress = config['base_address'] + if action == 'health_check' or action == 'list_policy': + res = requests.get(baseAddress + config['actions_path'][action]) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + if action == 'health_check': + response_data['vthResponse']['resultData']['resultOutput'] = res.text + else: + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + elif action == 'list_policy_instance': + res = requests.get(baseAddress + config['actions_path'][action] + .format(policy_type_id=requestData['policy_type_id'])) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + elif action == 'get_policy_instance_status': + res = requests.get(baseAddress + config['actions_path'][action] + .format(policy_type_id=requestData['policy_type_id'], + policy_instance_id=requestData['policy_instance_id'])) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + elif action == 'edit_policy': + res = _send_edit_request(requestData, config) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + if requestData['request_type'].lower() == 'get' and res.status_code == 200: + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + else: + response_data['vthResponse']['resultData']['resultOutput'] = res.text + elif action == 'edit_policy_instance': + res = _send_edit_request(requestData, config) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + if requestData['request_type'].lower() == 'get' and res.status_code == 200: + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + else: + response_data['vthResponse']['resultData']['resultOutput'] = res.text + + except Exception as ex: + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + response_data['vthResponse']['testDuration'] = totalTime + response_data['vthResponse']['abstractMessage'] = str(ex) + return jsonify(response_data) + + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + + response_data['vthResponse']['testDuration'] = totalTime + + if ret_url is not None: + sendCallback(ret_url, response_data) + return '', 200 + + return jsonify(response_data), 200 + + +def _send_edit_request(request_data, config): + baseAddress = config['base_address'] + path = '' + action = request_data['action'] + policy_type_id = request_data['policy_type_id'] + request_type = request_data['request_type'] + if action == "edit_policy": + path = baseAddress + config['actions_path'][action].format(policy_type_id=policy_type_id) + if action == 'edit_policy_instance': + instance_id = request_data['policy_instance_id'] + path = baseAddress + config['actions_path'][action].format(policy_type_id=policy_type_id, + policy_instance_id=instance_id) + if request_type == 'get': + return requests.get(path) + if request_type == 'put': + payload = request_data['payload'] + return requests.put(path, payload) + if request_type == 'delete': + return requests.delete(path) + + +def _check_incoming_request(requestData): # check if the request is valid + if 'action' not in requestData: + raise KeyError('no action was specify') + + action = requestData['action'].lower() + edit_actions = ['edit_policy', 'edit_policy_instance'] + requires_policy_id = ['edit_policy', 'list_policy_instance' + , 'edit_policy_instance', 'get_policy_instance_status'] + requires_policy_instance_id = ['edit_policy_instance', 'get_policy_instance_status'] + possible_actions = ['health_check', 'list_policy', 'edit_policy', 'list_policy_instance' + , 'edit_policy_instance', 'get_policy_instance_status'] + possible_request_type = ['get', 'put', 'delete'] + + if action not in possible_actions: + raise KeyError("invalid action") + if action in edit_actions: # request type is required + if 'request_type' not in requestData: + raise KeyError('this action: ' + action + ' requires a request type') + if requestData['request_type'] not in possible_request_type: + raise KeyError('this request_type: ' + requestData['request_type'] + ' is not valid') + if requestData['request_type'] == 'put' and 'payload' not in requestData: + raise KeyError('put request requires a payload') + if action in requires_policy_id: + if 'policy_type_id' not in requestData: + raise KeyError('this action: ' + action + ' requires a policy_type_id') + if action in requires_policy_instance_id: + if 'policy_instance_id' not in requestData: + raise KeyError('this action: ' + action + ' requires a policy_instance_id') diff --git a/a1-policy-manager-vth/app/routes/ric.py b/a1-policy-manager-vth/app/routes/ric.py new file mode 100644 index 0000000..8441ac6 --- /dev/null +++ b/a1-policy-manager-vth/app/routes/ric.py @@ -0,0 +1,12 @@ + +from app.helpers import response_helper as ResponseHelper +from app.helpers import ric_helper as Ric +from . import config, ROUTES + +@ROUTES.route("/ric", methods=['GET']) +def handle_ric(): + return ResponseHelper.route_check(config=config, get_function=Ric.get_ric_using_get) + +@ROUTES.route("/rics", methods=['GET']) +def handle_rics(): + return ResponseHelper.route_check(config=config, get_function=Ric.get_rics_using_get) diff --git a/a1-policy-manager-vth/app/routes/service.py b/a1-policy-manager-vth/app/routes/service.py new file mode 100644 index 0000000..e06bf94 --- /dev/null +++ b/a1-policy-manager-vth/app/routes/service.py @@ -0,0 +1,16 @@ +from app.helpers import response_helper as ResponseHelper +from app.helpers import service_helper as Service +from . import config, ROUTES + +@ROUTES.route("/services", methods=['GET', 'DELETE']) +def handleS_services(): + return ResponseHelper.route_check(config=config, get_function=Service.get_services_using_get, delete_function=Service.delete_services_using_delete) + + +@ROUTES.route("/service", methods=['PUT']) +def handle_service(): + return ResponseHelper.route_check(config=config, put_function=Service.put_service_using_put) + +@ROUTES.route("/services/keepalive", methods=['PUT']) +def handle_services_keepalive(): + return ResponseHelper.route_check(config=config, put_function=Service.keep_alive_service_using_put) diff --git a/a1-policy-manager-vth/config.ini b/a1-policy-manager-vth/config.ini new file mode 100644 index 0000000..e9bc817 --- /dev/null +++ b/a1-policy-manager-vth/config.ini @@ -0,0 +1,14 @@ +[auth] +creds_enabled= %(USE_CRED)s +username = %(USER)s +password = %(PW)s +[api] +base_url= %(API_URL)s +port= %(API_PORT)s +[resource] +proxy_enabled = %(USE_PROXY)s +https_proxy= %(HTTPS)s +http_proxy= %(HTTP)s +base_address = %(API_URL)s +publish = /{topic_name} +subscribe = /{topic_name}/{consumer_group}/{consumer_id} diff --git a/a1-policy-manager-vth/doc/a1-documentation.docx b/a1-policy-manager-vth/doc/a1-documentation.docx new file mode 100644 index 0000000000000000000000000000000000000000..dada0b5178f1a6a0f36865b59f78c955fba7742f GIT binary patch literal 84646 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�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 literal 0 HcmV?d00001 diff --git a/a1-policy-manager-vth/docker/Dockerfile b/a1-policy-manager-vth/docker/Dockerfile new file mode 100644 index 0000000..960368c --- /dev/null +++ b/a1-policy-manager-vth/docker/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.7.4 + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD app app +ADD config.ini config.ini +ADD run.py run.py + +RUN mkdir -p /otf/logs + +RUN python -m pip install --proxy http://one.proxy.att.com:8080 -r pip-requirements.txt + +ENV USER=default_user +ENV PW=default_pass + + +ENTRYPOINT ["python", "run.py"] diff --git a/a1-policy-manager-vth/docker/container-tag.yaml b/a1-policy-manager-vth/docker/container-tag.yaml new file mode 100644 index 0000000..ee078db --- /dev/null +++ b/a1-policy-manager-vth/docker/container-tag.yaml @@ -0,0 +1,15 @@ +--- +# Copyright (c) 2019 AT&T Intellectual Property. +# +# 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. +tag: 0.0.1 diff --git a/a1-policy-manager-vth/helm/a1-policy-manager-vth/.helmignore b/a1-policy-manager-vth/helm/a1-policy-manager-vth/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/a1-policy-manager-vth/helm/a1-policy-manager-vth/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/a1-policy-manager-vth/helm/a1-policy-manager-vth/Chart.yaml b/a1-policy-manager-vth/helm/a1-policy-manager-vth/Chart.yaml new file mode 100644 index 0000000..66e0b29 --- /dev/null +++ b/a1-policy-manager-vth/helm/a1-policy-manager-vth/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the a1 policy manager Virtual Test Head +name: a1-policy-manager-vth +version: 0.0.1 diff --git a/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/deployment.yaml b/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/deployment.yaml new file mode 100644 index 0000000..89d053d --- /dev/null +++ b/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/deployment.yaml @@ -0,0 +1,137 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: {{ .Values.appName}} + namespace: {{.Values.namespace}} + labels: + app: {{ .Values.appName}} + version: {{.Values.version}} +spec: + revisionHistoryLimit: 1 + minReadySeconds: 10 + strategy: + # indicate which strategy we want for rolling update + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + replicas: {{ .Values.replicas}} + selector: + matchLabels: + app: {{ .Values.appName}} + version: {{.Values.version}} + template: + metadata: + labels: + app: {{ .Values.appName}} + version: {{.Values.version}} + spec: + serviceAccount: default + volumes: + - name: {{ .Values.appName}}-cert-volume + secret: + secretName: {{.Values.sharedCert}} + optional: true + items: + - key: PEM_CERT + path: otf.pem + - key: PEM_KEY + path: privateKey.pem +# {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}} TODO UNCOMMENT WHEN PUSHING TO ORAN +# {{else}} +# - name: logging-pvc +# persistentVolumeClaim: +# {{if eq .Values.env "prod"}} +# claimName: {{ .Values.pvc.prod | quote }} +# {{ else }} +# claimName: {{ .Values.pvc.dev | quote }} +# {{ end }} +# {{end}} + containers: + - name: {{ .Values.appName}} + image: {{ .Values.image}} + imagePullPolicy: Always + ports: + - name: http + containerPort: 6000 + nodePort: {{.Values.nodePort}} + protocol: TCP +# {{ if eq .Values.env "st"}} TODO UNCOMMENT FOR ORAN? +# resources: +# limits: +# memory: "512Mi" +# cpu: "500m" +# requests: +# memory: "256Mi" +# cpu: "100m" +# {{else}} +# resources: +# limits: +# memory: "1Gi" +# cpu: "1" +# requests: +# memory: "1Gi" +# cpu: "1" +# {{end}} + env: + - name: NAMESPACE + value: {{.Values.namespace}} + - name: APP_NAME + value: {{ .Values.appName}} + - name: APP_VERSION + value: {{.Values.version}} + - name: USE_CRED + value: {{.Values.auth.enabled | quote }} + - name: USER + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: api_user + optional: true + - name: PW + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: api_pass + optional: true + - name: USE_PROXY + value: {{.Values.proxy.enabled | quote }} + - name: HTTPS + value: {{.Values.proxy.https | quote }} + - name: HTTP + value: {{.Values.proxy.http | quote }} + - name: API_URL + value: {{.Values.api.base_url}} + - name: API_PORT + value: {{.Values.api.port | quote }} + volumeMounts: + - name: {{.Values.appName}}-cert-volume + mountPath: /opt/cert +# {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}} +# {{else}} +# - name: logging-pvc +# mountPath: "/otf/logs" +# {{end}} + livenessProbe: + httpGet: + path: {{.Values.health}} + port: http + scheme: HTTP + httpHeaders: + - name: X-Custom-Header + value: Alive + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: {{.Values.health}} + port: http + scheme: HTTP + httpHeaders: + - name: X-Custom-Header + value: Ready + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + restartPolicy: Always diff --git a/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/secret.yaml b/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/secret.yaml new file mode 100644 index 0000000..1aabe3d --- /dev/null +++ b/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/secret.yaml @@ -0,0 +1,9 @@ + +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + api_user: {{ .Values.auth.user | b64enc }} + api_pass: {{ .Values.auth.pw | b64enc }} diff --git a/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/service.yaml b/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/service.yaml new file mode 100644 index 0000000..291f9fc --- /dev/null +++ b/a1-policy-manager-vth/helm/a1-policy-manager-vth/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.appName }} + namespace: {{ .Values.namespace}} + labels: + app: {{ .Values.appName }} + version: {{ .Values.version}} +spec: + type: NodePort + ports: + - name: http + port: 6000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/a1-policy-manager-vth/helm/a1-policy-manager-vth/values.yaml b/a1-policy-manager-vth/helm/a1-policy-manager-vth/values.yaml new file mode 100644 index 0000000..5c3e5a0 --- /dev/null +++ b/a1-policy-manager-vth/helm/a1-policy-manager-vth/values.yaml @@ -0,0 +1,23 @@ +appName: a1-policy-manager-vth +env: dev +version: 0.0.1-SNAPSHOT +image: dockercentral.it.att.com:5100/com.att.ecomp.otf.dev/a1-policy-manager-vth:0.0.1-SNAPSHOT +namespace: com-att-ecomp-otf-dev #org-oran-otf +nodePort: 32330 +replicas: 1 +health : /otf/vth/oran/a1/v1/health +sharedCert: otf-cert-secret-builder +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv +auth: + enabled: true + user: user + pw: pw +proxy: + enabled: false + http: + https: +api: + base_url: http://njcdtl08rg9907.itservices.sbc.com + port: 3000 diff --git a/a1-policy-manager-vth/pip-requirements.txt b/a1-policy-manager-vth/pip-requirements.txt new file mode 100644 index 0000000..d25f478 --- /dev/null +++ b/a1-policy-manager-vth/pip-requirements.txt @@ -0,0 +1,6 @@ +flask +flask-cors +FLASK +FLASK-CORS +requests +configparser diff --git a/a1-policy-manager-vth/run.py b/a1-policy-manager-vth/run.py new file mode 100644 index 0000000..613ff4f --- /dev/null +++ b/a1-policy-manager-vth/run.py @@ -0,0 +1,52 @@ +""" +# Copyright (c) 2019 AT&T Intellectual Property. # +# # +# 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. # +################################################################################ +# File name: a1-policy-manager-vth.py # +# Description: vth for A1 service # +# Date created: 04/22/2020 # +# Last modified: 04/30/2020 # +# Python Version: 3.7.4 # +# Author: Raul Gomez (rg9907) # +# Email: rg9907@att.com # +################################################################################ +""" +import logging +from logging import FileHandler +from flask import Flask +from flask.logging import create_logger +from app.routes import ROUTES, ERRORS +#from dotenv import load_dotenv + +#load dev env vars +#load_dotenv() +# redirect http to https +APP = Flask(__name__) +LOG = create_logger(APP) + +# Prevents print statement every time an endpoint is triggered. +logging.getLogger("werkzeug").setLevel(logging.DEBUG) +#logging.getLogger("werkzeug").setLevel(logging.WARNING) +APP.register_blueprint(ERRORS) +APP.register_blueprint(ROUTES, url_prefix="/otf/vth/oran/a1/v1") + +if __name__ == '__main__': + LOG_HANDLER = FileHandler('a1-policy-manager.log', mode='a') + LOG_HANDLER.setLevel(logging.INFO) + LOG.setLevel(logging.INFO) + LOG.addHandler(LOG_HANDLER) + #context = ('opt/cert/otf.pem', 'opt/cert/privateKey.pem') + # app.run(debug = False, host = '0.0.0.0', port = 5000, ssl_context = context) + APP.run(debug=False, host='0.0.0.0', port=6000) + #APP.run(debug=False, host='0.0.0.0', port=6000, ssl_context = context) diff --git a/a1-sdnc-vth/.environ b/a1-sdnc-vth/.environ new file mode 100644 index 0000000..0b3fa87 --- /dev/null +++ b/a1-sdnc-vth/.environ @@ -0,0 +1,9 @@ +#if using dotenv change file name to .env and set env variables below +USER=String +PW=String +AUTH=Boolean +PROXY=Boolean +HTTP=String +HTTPS=String +API_URL=String +API_PORT=Int diff --git a/a1-sdnc-vth/Jenkinsfile b/a1-sdnc-vth/Jenkinsfile new file mode 100644 index 0000000..a9bfbaa --- /dev/null +++ b/a1-sdnc-vth/Jenkinsfile @@ -0,0 +1,158 @@ +#!/usr/bin/env groovy + +/* Copyright (c) 2019 AT&T Intellectual Property. # +# # +# 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. # +##############################################################################*/ + + +properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [ + [$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"], + [$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "m13591_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "com-att-ecomp-otf-dev"] +]]]) + + + echo "Build branch: ${env.BRANCH_NAME}" + + node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="a1-sdnc-vth"; + VERSION="0.0.1-SNAPSHOT"; + NAMESPACE="com.att.ecomp.otf" //TODO change back to org-otf-oran when done testing + DOCKER_REGISTRY="dockercentral.it.att.com:5100" + + if( ENV.equalsIgnoreCase("dev") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".dev" + "/" + ARTIFACT_ID + ":" + VERSION + + } + if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID + ":" + VERSION + + } + + if( ENV.equalsIgnoreCase("st") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID + ":" + VERSION + + } + + echo "Artifact: " + IMAGE_NAME + + withEnv(["PATH=${env.PATH}:${env.WORKSPACE}/linux-amd64", "HELM_HOME=${env.WORKSPACE}"]) { + + echo "PATH=${env.PATH}" + echo "HELM_HOME=${env.HELM_HOME}" + + if (PHASES.contains("BUILD")){ + dir("./a1-sdnc-vth"){ + stage 'Publish Artifact' + + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + + echo "Artifact: " + IMAGE_NAME + + sh """ + docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD + docker build -t $IMAGE_NAME . + docker push $IMAGE_NAME + """ + } + } + + + } + + if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY")) { + + stage 'Init Helm' + + //check if helm exists if not install + if(fileExists('linux-amd64/helm')){ + sh """ + echo "helm is already installed" + """ + } + else{ + //download helm + sh """ + echo "installing helm" + wget https://storage.googleapis.com/kubernetes-helm/helm-v2.14.3-linux-amd64.tar.gz + tar -xf helm-v2.14.3-linux-amd64.tar.gz + rm helm-v2.14.3-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('a1-sdnc-vth/helm'){ + //check if charts are valid, and then perform dry run, if successful then upgrade/install charts + + if (PHASES.contains("UNDEPLOY") ) { + stage 'Undeploy' + + sh """ + helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID + """ + } + + //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace + if (PHASES.contains("DEPLOY") ){ + stage 'Deploy' + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + + sh """ + echo "Validate Yaml" + helm lint $ARTIFACT_ID + + echo "View Helm Templates" + helm template $ARTIFACT_ID --set appName=$ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set env=$ENV \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE + + echo "Perform Dry Run Of Install" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set env=$ENV \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE + + + echo "Helm Install/Upgrade" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set env=$ENV \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE + + """ + } + } + + } + } + } + + } + } diff --git a/a1-sdnc-vth/app/__init__.py b/a1-sdnc-vth/app/__init__.py new file mode 100644 index 0000000..14b5496 --- /dev/null +++ b/a1-sdnc-vth/app/__init__.py @@ -0,0 +1,11 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Import everything using star, methods or anything that should not be + used by the outside modules should not be imported on the nested + __init__ files. +""" +from .routes import * +from .errors import * +from .models import * +from .helpers import * diff --git a/a1-sdnc-vth/app/errors/__init__.py b/a1-sdnc-vth/app/errors/__init__.py new file mode 100644 index 0000000..b491f42 --- /dev/null +++ b/a1-sdnc-vth/app/errors/__init__.py @@ -0,0 +1,6 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Only imort methods that can be used and are used by outside modules +""" +from .bad_request_exception import BadRequestException diff --git a/a1-sdnc-vth/app/errors/bad_request_exception.py b/a1-sdnc-vth/app/errors/bad_request_exception.py new file mode 100644 index 0000000..a3e3d22 --- /dev/null +++ b/a1-sdnc-vth/app/errors/bad_request_exception.py @@ -0,0 +1,21 @@ +""" +Args: +Returns: +Examples: +""" +class BadRequestException(Exception): + """ + Args: + Returns: + Examples: + """ + def __init__(self, status_code=406, message="Not Acceptable Response"): + cases = { + 401:"Unauthorized", + 403:"Forbidden", + 404:"Not Found", + 423:"Not Operational" + } + super().__init__(cases.get(status_code, message)) + self.status_code = status_code + self.message = message diff --git a/a1-sdnc-vth/app/helpers/__init__.py b/a1-sdnc-vth/app/helpers/__init__.py new file mode 100644 index 0000000..3313af8 --- /dev/null +++ b/a1-sdnc-vth/app/helpers/__init__.py @@ -0,0 +1,12 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Only imort methods that can be used and are used by outside modules +""" +from .error_helper import * +from .response_helper import * +from .time_helper import * +from .policy_helper import * +from .service_helper import * +from .ric_helper import * +from .action_helper import * diff --git a/a1-sdnc-vth/app/helpers/action_helper.py b/a1-sdnc-vth/app/helpers/action_helper.py new file mode 100644 index 0000000..a4f7a3a --- /dev/null +++ b/a1-sdnc-vth/app/helpers/action_helper.py @@ -0,0 +1,42 @@ +import json +import ast +from app.helpers import response_helper as ResponseHelper +from flask import current_app, jsonify +from app.errors.bad_request_exception import BadRequestException +import requests + + +def execute_action(request, response_dict, config): + headers = ResponseHelper.create_headers(); + request_data = request.json + action_request = request_data.get("action").lower() + + creds = ResponseHelper.get_credentials(request_data, config) + proxies = ResponseHelper.get_proxies(config) + url = ResponseHelper.create_url(config=config, uri_path="/restconf/operations/A1-ADAPTER-API:"+action_request) +# ret_url = request.args.get('retURL') + + json_req = ast.literal_eval(request_data["action_data"]["jsonBody"]) + current_app.logger.info("Requesting Url: {}, body: {}, auth: {}, proxies: {}".format(url, json_req, creds, proxies)) + try: + res = requests.post(url, proxies=proxies, auth=creds, headers=headers, json=json_req) + response = { + "status_code":res.status_code, + "result": res.json() + } + except(json.decoder.JSONDecodeError): + response = { + "status_code":res.status_code, + "result": res.reason + } + except requests.exceptions.RequestException: + response = { + "status_code":504, + "result": "Something Happned" + } + finally: + response_dict['vthResponse']['resultData'] = response +# if ret_url is not None: +# ResponseHelper.sendCallback(ret_url,response_dict) +# return '',200 + return response_dict diff --git a/a1-sdnc-vth/app/helpers/error_helper.py b/a1-sdnc-vth/app/helpers/error_helper.py new file mode 100644 index 0000000..b34cedf --- /dev/null +++ b/a1-sdnc-vth/app/helpers/error_helper.py @@ -0,0 +1,51 @@ +from flask import current_app +import datetime +""" +Args: +Returns: +Examples: +""" + +def error_dic(error, status_code, response_message="Something went wrong, vth encountered an error"): + """ + Args: + Returns: + Examples: + """ + message = [str(x) for x in error.args] + error_log={ + "error":{ + "type": error.__class__.__name__, + "message": message + } + } + response_data = { + "vthResponse": { + "testDurationMS": 0, + 'dateTimeUTC': str(datetime.datetime.now()), + "abstractMessage": "Failed", + "error":response_message, + "status_code": status_code, + "resultData": {} + } + } + current_app.logger.error(error_log) + return response_data + +def error_dic2(error, status_code=500): + """ + Args: + Returns: + Examples: + """ + message = [str(x) for x in error.args] + response = { + "status_code" : status_code, + "success": False, + "error":{ + "type": error.__class__.__name__, + "message": message + } + } + return response + diff --git a/a1-sdnc-vth/app/helpers/policy_helper.py b/a1-sdnc-vth/app/helpers/policy_helper.py new file mode 100644 index 0000000..ea4fedc --- /dev/null +++ b/a1-sdnc-vth/app/helpers/policy_helper.py @@ -0,0 +1,163 @@ +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException +import requests + +def get_policy_using_get(request, response_dict, config): + json_data = request.get_json() + if 'id' not in json_data: raise BadRequestException(406, "Request is missing id") + param = {'id': json_data['id']} + creds = ResponseHelper.get_credentials(json_data, config) + url = ResponseHelper.create_url(config=config, uri_path="/policy") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + + return response_dict +def put_policy_using_put(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + + current_app.logger.info("creds: {}".format(creds)) + + required = {'id', 'jsonBody', 'ric', 'service'} + param_keys = {'id', 'ric', 'service'} + optional = {"type"} + data_keys = param_keys.copy() + keys = set(json_data.keys()) + if not required <= keys: + raise BadRequestException(406, "Request is missing required values {}".format(required)) + if optional <= keys: data_keys.update(optional) + param = {} + body = {} + for key in data_keys: + param[key] = json_data[key] + body['jsonBody'] = json_data['jsonBody'] + + url = ResponseHelper.create_url(config=config, uri_path="/policy") + res = requests.put(url, auth=creds, params=param, json=body) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict +def delete_policy_using_delete(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'id'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + param = {'id': json_data['id']} + + url = ResponseHelper.create_url(config=config, uri_path="/policy") + res = requests.delete(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict + +def get_policy_ids_using_get(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + param = { + "ric":json_data["ric"] if "ric" in json_data else "", + "service":json_data["service"] if "service" in json_data else "", + "type":json_data["type"] if "type" in json_data else "" + } + + url = ResponseHelper.create_url(config=config, uri_path="/policy_ids") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict + +def get_policy_schema_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'id'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + param = {'id': json_data['id']} + + url = ResponseHelper.create_url(config=config, uri_path="/policy_schema") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict +def get_policy_schemas_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + param = { + "ric":json_data['ric'] if 'ric' in json_data else "" + } + #api_response = requests.put(url, credentials=creds, params=param) + + url = ResponseHelper.create_url(config=config, uri_path="/policy_schemas") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict +def get_policy_status_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'id'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + param = { + "id":json_data["id"] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def get_policy_types_using_get(request, response_dict, config): + json_data = request.get_json() + creds = ResponseHelper.get_credentials(json_data, config) + param = { + 'ric': json_data['ric'] if 'ric' in json_data else "" + } + + url = ResponseHelper.create_url(config=config, uri_path="/a1-p/policytypes") + res = requests.get(url, auth=creds, params=param) + response = { + "status_code":res.status_code, + "result": res.json() + } + response_dict['vthResponse']['resultData'] = response + return response_dict + diff --git a/a1-sdnc-vth/app/helpers/response_helper.py b/a1-sdnc-vth/app/helpers/response_helper.py new file mode 100644 index 0000000..833598f --- /dev/null +++ b/a1-sdnc-vth/app/helpers/response_helper.py @@ -0,0 +1,218 @@ +import ast +import requests +from configparser import ConfigParser +import os +import datetime +import json +from flask import request, jsonify, current_app +from app.helpers.time_helper import unix_time_millis +from app.errors.bad_request_exception import BadRequestException + +""" + Module Info: +""" +def create_headers(enable_cache=True, content_type="application/json", connection="Keep-Alive"): + headers = {'Cache-Control':'no-cache, no-store, must-revalidate', "Pragma":"no-cache", "Expires":"0"} if not enable_cache else {} + headers['content-type'] = content_type + headers['connection'] = connection + return headers +def create_url(config=None, uri_path = "/", url_string=None): + return config['api']['base_url'] +":"+ config['api']['port']+uri_path if url_string is None else url_string + +def valid_string_json(string, response_message="Invalid json string in query or jsonBody, format requires quoted json object e.g. \"{'key':'value, key2:{'innerKey':'innerValue'}}\""): + try: + string_to_dict = ast.literal_eval(string) + except(Exception): + raise BadRequestException(406, response_message) + return True +def route_check(config=None, get_function=None, post_function=None, put_function=None, delete_function=None): + """ + Info: + Since all routes do the same pre-check and have a similar skeleton, this function just refactored the pre-check for code reuse + Arguments (**kwargs): pass in the specified key(s) and method(s) that handle the type of method, method must be allowed by route decorator + get_function => type: function + put_function => type: function + delete_function => type: function + Returns: + returns the return of the function call, typically a jsonified response. + you can capture response in a var and execute logic or you can just return the function call/response + E.G.: + response = route_check(post_function = handle_post) + return route_check(get_function = handle_get, post_function = handle_post) + """ + if not request.is_json: raise BadRequestException(406, "Invalid Json Request") + + response_dict = vth_response_dic() + start_time = unix_time_millis(datetime.datetime.now()) + status_code = 200 + ret_url = request.args.get('retURL') + + query = "" + json_body = "" + request_data = request.json + json_keys = set(request_data) + action_request = request_data.get("action").lower() + valid_actions = {"geta1policytype", "geta1policy", "puta1policy", "deletea1policy", "geta1policystatus"} + required_keys = {"action", "auth", "action_data"} + + #check for valid action and json request contains required keys + if not required_keys <= json_keys: raise BadRequestException(406, "Json request is missing required keys {}".format(required_keys)) + if not action_request in valid_actions: raise BadRequestException(406, "Action is not supported {}".format(action_request)) + #check request's action_data key contains required keys + if 'query' not in request.json['action_data']: raise BadRequestException(406, "action_data must contain query and jsonBody ") + if 'jsonBody' not in request.json['action_data']: raise BadRequestException(406, "action_data must contain query and jsonBody") + + query = request.json['action_data']['query'] if 'query' in request.json['action_data'] else "" + json_body = request.json['action_data']['jsonBody'] if 'jsonBody' in request.json['action_data'] else "" + + if valid_string_json(query) and valid_string_json(json_body): + if(request.method == 'GET'): + response_dict = get_function(request, response_dict, config) + elif(request.method == 'POST'): + response_dict = post_function(request, response_dict, config) + elif(request.method == 'PUT'): + response_dict = put_function(request, response_dict, config) + elif(request.method == 'DELETE'): + response_dict = delete_function(request, response_dict, config) + else: + raise BadRequestException(406, "Invalid JSON Strings") + end_time = unix_time_millis(datetime.datetime.now()) + response_dict['vthResponse']['testDurationMS'] = end_time-start_time + if ret_url is not None: + sendCallback(ret_url,response_dict) + return '',200 + return jsonify(response_dict), status_code + +def get_proxies(config): + proxy_enabled = config.getboolean('resource', 'proxy_enabled') + req_proxies = { + 'http': None, + 'https': None + } + if not proxy_enabled: + return None + else: + req_proxies['http'] = config['resource']['http_proxy'] + req_proxies['https'] = config['resource']['https_proxy'] + return req_proxies +def get_credentials(json_data, config): + auth_enabled = config.getboolean('auth', 'creds_enabled') + if not auth_enabled: + return None + else: + username = config['auth']['username'] if 'username' not in json_data['auth'] else json_data['auth']['username'] + password = config['auth']['password'] if 'password' not in json_data['auth'] else json_data['auth']['password'] + return (username, password) +def vth_response_dic(): + """ + Args: + Returns: + Examples: + """ + response_data = { + "vthResponse": { + "testDurationMS": "", + 'dateTimeUTC': str(datetime.datetime.now()), + "abstractMessage": "Success", + "resultData": {} + } + } + return response_data +#TODO data is data from callback and not my json response +def sendCallback(url, data): + try: + if type(data) is not dict: + data = {"msg": data} + current_app.logger.info("sending callback") + requests.post(url, json=data) + except Exception as e: + current_app.logger.info(e) + return + +def get_request_data(request): + if not request.is_json: + raise ValueError("request must be json") + requestData = request.get_json() + return requestData + + +def valid_json(data): + + try: + _ = json.loads(data) + except ValueError as e: + return False + return True +def get_config(config_file_name): + config = ConfigParser(os.environ) + config.read(config_file_name) + return config + +def validate_request(request_data, isPublish=True): + return + missing_params = [] + + if 'topic_name' not in request_data: + missing_params.append("topic_name") + if isPublish: + if 'data' not in request_data: + missing_params.append('data') + else: + if 'consumer_group' not in request_data: + missing_params.append('consumer_group') + if 'consumer_id' not in request_data: + missing_params.append('consumer_id') + + if missing_params: + err_msg = '{} request requires the following: '.format('publish' if isPublish else 'subscribe') + err_msg += ','.join(missing_params) + raise KeyError(err_msg) + + +def build_url(config, request_data, is_publish=True): + if is_publish: + base_path = config['resource']['base_address'] + config['resource']['publish'] + topic_name = request_data['topic_name'] + publish_address = base_path.format(topic_name=topic_name) + return publish_address + + base_path = config['resource']['base_address'] + config['resource']['subscribe'] + topic_name = request_data['topic_name'] + consumer_group = request_data['consumer_group'] + consumer_id = request_data['consumer_id'] + subscribe_address = base_path.format(topic_name=topic_name, consumer_group=consumer_group, consumer_id=consumer_id) + if ('timeout' in request_data): + subscribe_address = (subscribe_address + '?timeout={}').format(request_data['timeout']) + return subscribe_address + + +def send_request(url, config, is_subscribe_request=False, payload=None): + # setup default values + auth_enabled = config.getboolean('auth', 'auth_enabled') + proxy_enabled = config.getboolean('resource', 'proxy_enabled') + username = '' + password = '' + req_proxies = { + 'http': None, + 'https': None + } + # place proxy and authentication information + if auth_enabled: + username = config['auth']['username'] + password = config['auth']['password'] + if proxy_enabled: + req_proxies['http'] = config['resource']['http_proxy'] + req_proxies['https'] = config['resource']['https_proxy'] + + # for subscribe request + if is_subscribe_request: + return requests.get(url, + auth=(username, password) if auth_enabled else None, + proxies=req_proxies if proxy_enabled else None) + # for publish request + req_headers = {'Content-type': 'application/json'} + return requests.post(url, + json=payload, + auth=(username, password) if auth_enabled else None, + proxies=req_proxies if proxy_enabled else None, + headers=req_headers) diff --git a/a1-sdnc-vth/app/helpers/ric_helper.py b/a1-sdnc-vth/app/helpers/ric_helper.py new file mode 100644 index 0000000..47d55c2 --- /dev/null +++ b/a1-sdnc-vth/app/helpers/ric_helper.py @@ -0,0 +1,37 @@ +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException + +def get_ric_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'managedElementId'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'managedElementId': json_data['managedElementId'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def get_rics_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + param = { + "policyType": json_data["policyType"] if "policyType" in json_data else "" + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict diff --git a/a1-sdnc-vth/app/helpers/service_helper.py b/a1-sdnc-vth/app/helpers/service_helper.py new file mode 100644 index 0000000..16d9b92 --- /dev/null +++ b/a1-sdnc-vth/app/helpers/service_helper.py @@ -0,0 +1,78 @@ + +from app.helpers import response_helper as ResponseHelper +from flask import current_app +from app.errors.bad_request_exception import BadRequestException + +def get_services_using_get(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + param = { + 'name': json_data['name'] if 'name' in json_data else "" + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def delete_services_using_delete(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'name'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'name': json_data['name'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict +def put_service_using_put(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'registrationInfo'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'registrationInfo': json_data['registrationInfo'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict + +def keep_alive_service_using_put(request, response_dict, config): + json_data = request.get_json() + #username = config['auth']['username'] if 'username' not in json_data else json_data['username'] + #password = config['auth']['password'] if 'password' not in json_data else json_data['password'] + #creds = (username, password) + creds = ResponseHelper.get_credentials(json_data, config) + current_app.logger.info("creds: {}".format(creds)) + + keys = set(json_data.keys()) + required = {'name'} + if not required <= keys: raise BadRequestException(406, "Request is missing required values {}".format(required)) + + param = { + 'name': json_data['name'] + } + + response_dict['vthResponse']['resultData'] = param + #api_response = requests.get(url, credentials=creds, params=param) + return response_dict diff --git a/a1-sdnc-vth/app/helpers/time_helper.py b/a1-sdnc-vth/app/helpers/time_helper.py new file mode 100644 index 0000000..b882d0b --- /dev/null +++ b/a1-sdnc-vth/app/helpers/time_helper.py @@ -0,0 +1,24 @@ +""" + Module Info: +""" +import datetime + +def unix_time_millis(d_time): + """ + Args: + Returns: + Examples: + """ + epoch = datetime.datetime.utcfromtimestamp(0) + return (d_time - epoch).total_seconds() * 1000.0 + +def timed_function(func): + """ + Args: + Returns: + Examples: + """ + start_time = unix_time_millis(datetime.datetime.now()) + func() + end_time = unix_time_millis(datetime.datetime.now()) + return end_time - start_time diff --git a/a1-sdnc-vth/app/models/__init__.py b/a1-sdnc-vth/app/models/__init__.py new file mode 100644 index 0000000..52319a0 --- /dev/null +++ b/a1-sdnc-vth/app/models/__init__.py @@ -0,0 +1,6 @@ + +""" + Module Info: + Anything imported to this file will be available to outside modules. + Only imort methods that can be used and are used by outside modules +""" diff --git a/a1-sdnc-vth/app/routes/__init__.py b/a1-sdnc-vth/app/routes/__init__.py new file mode 100644 index 0000000..89419e1 --- /dev/null +++ b/a1-sdnc-vth/app/routes/__init__.py @@ -0,0 +1,19 @@ +""" + Module Info: + Anything imported to this file will be available to outside modules. + Routes need to be exported to be usable, if removed, routes will not be found and response + will be a 500. + ROUTE order matters, because ROUTE is like a global var used by all the other modules + it needs to be above them all +""" +from flask import Blueprint +from app.helpers.response_helper import get_config + +ROUTES = Blueprint('routes', __name__) +config = get_config("config.ini") + +from .policy import * +from .ric import * +from .service import * +from .info import * +from .errors import ERRORS diff --git a/a1-sdnc-vth/app/routes/errors.py b/a1-sdnc-vth/app/routes/errors.py new file mode 100644 index 0000000..43e1ec1 --- /dev/null +++ b/a1-sdnc-vth/app/routes/errors.py @@ -0,0 +1,33 @@ +""" +Module Info: +""" +from flask import jsonify, current_app, Blueprint +from app.helpers.error_helper import error_dic +from app.errors.bad_request_exception import BadRequestException +import traceback + +ERRORS = Blueprint('errors', __name__) + +@ERRORS.app_errorhandler(BadRequestException) +def handle_bad_request(error): + """ + Args: + Returns: + Examples: + """ + current_app.logger.info(error) + response = error_dic(error, error.status_code, error.message) + print(traceback.format_exc()) + return jsonify(response), error.status_code + +@ERRORS.app_errorhandler(Exception) +def handle_error(error): + """ + Args: + Returns: + Examples: + """ + status_code = 500 + response = error_dic(error, status_code) + print(traceback.format_exc()) + return jsonify(response), status_code diff --git a/a1-sdnc-vth/app/routes/info.py b/a1-sdnc-vth/app/routes/info.py new file mode 100644 index 0000000..7090cf3 --- /dev/null +++ b/a1-sdnc-vth/app/routes/info.py @@ -0,0 +1,76 @@ +""" +Args: +Returns: +Examples: +""" +import json +import datetime +from flask import current_app, jsonify, request +import time +import requests +from app.errors.bad_request_exception import BadRequestException +from app.helpers.time_helper import unix_time_millis, timed_function +from app.helpers.response_helper import vth_response_dic +from app.helpers import response_helper as ResponseHelper +from app.helpers import action_helper as Info +from . import config, ROUTES + + +@ROUTES.route("/handle_action", methods=['POST']) +def handle_action_request(): + return ResponseHelper.route_check(config=config, post_function = Info.execute_action) + + +@ROUTES.route("/", methods=['GET']) +def get_base(): + """ + Args: + Returns: + Examples: + """ + current_app.logger.info(request.method) + response = vth_response_dic() + data = current_app.url_map + rules = [] + methods_list = [] + for rule in data.iter_rules(): + ma = {rule.rule:[]} + for val in rule.methods: + if (val != "OPTIONS") and (val !="HEAD"): + #print(val) + ma[rule.rule].append(val) + rules.append(ma) + + # methods_set.add(rule.methods) + #print(rule.methods) + #print(rules) + response["vthResponse"]["resultData"] = rules + #current_app.logger.info(current_app.url_map) + current_app.logger.debug("hit health point") + return jsonify(response) + +@ROUTES.route("/health", methods=['GET']) +def get_health(): + """ + Args: + Returns: + Examples: + """ + current_app.logger.debug("hit health point") + return "UP" + +@ROUTES.route("/status", methods=['GET']) +def get_status(): + """ + Args: + Returns: + Examples: + """ + suma = lambda: time.sleep(1) + #current_app.logger.info(current_app.url_map) + current_app.logger.info(unix_time_millis(datetime.datetime.now())) + current_app.logger.info(timed_function(suma)) + current_app.logger.debug("some stuff") + #raise Exception("some error") + raise BadRequestException() + return "Running" diff --git a/a1-sdnc-vth/app/routes/policy.py b/a1-sdnc-vth/app/routes/policy.py new file mode 100644 index 0000000..588397f --- /dev/null +++ b/a1-sdnc-vth/app/routes/policy.py @@ -0,0 +1,225 @@ + +import datetime +import json +import logging +from logging import FileHandler +import os + +import requests +from flask import Flask, request, jsonify +from . import config, ROUTES +from app.helpers import policy_helper as Policy +from app.helpers import response_helper as ResponseHelper +from app.errors.bad_request_exception import BadRequestException + + + +def sendCallback(url, data): + try: + if type(data) is not dict: + data = {"msg": data} + app.logger.info("sending callback") + requests.post(url, json=data) + except Exception as e: + app.logger.info(e) + return + +def unix_time_millis(dt): + epoch = datetime.datetime.utcfromtimestamp(0) + return (dt - epoch).total_seconds() * 1000.0 + + +def route_check2(get_function=None, post_function=None, put_function=None, delete_function=None): + """ + Info: + Since all routes do the same pre-check and have a similar skeleton, this function just refactored the pre-check for code reuse + Arguments (**kwargs): pass in the specified key(s) and method(s) that handle the type of method, method must be allowed by route decorator + get_function => type: function + put_function => type: function + delete_function => type: function + Returns: + returns the return of the function call, typically a jsonified response. + you can capture response in a var and execute logic or you can just return the function call/response + E.G.: + response = route_check(post_function = handle_post) + return route_check(get_function = handle_get, post_function = handle_post) + """ + response_dict = ResponseHelper.vth_response_dic() + start_time = unix_time_millis(datetime.datetime.now()) + status_code = 200 + if request.is_json and ResponseHelper.valid_json(request.data): + if(request.method == 'GET'): + response_dict = get_function(request, response_dict, config) + elif(request.method == 'POST'): + response_dict = post_function(request, response_dict, config) + elif(request.method == 'PUT'): + response_dict = put_function(request, response_dict, config) + elif(request.method == 'DELETE'): + response_dict = delete_function(request, response_dict, config) + else: + raise BadRequestException(406, "Invalid Json") + end_time = unix_time_millis(datetime.datetime.now()) + response_dict['vthResponse']['testDurationMS'] = end_time-start_time + return jsonify(response_dict), status_code + + +@ROUTES.route("/policies", methods=['GET']) +def policies(): + pass + +@ROUTES.route("/policy", methods=['GET', 'PUT', 'DELETE']) +def handle_policy(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_using_get, put_function = Policy.put_policy_using_put, delete_function=Policy.delete_policy_using_delete) + + +@ROUTES.route("/policy_ids", methods=['GET']) +def handle_policy_ids(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_ids_using_get) + +@ROUTES.route("/policy_schemas", methods=['GET']) +def handle_policy_schemas(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_schemas_using_get) + +@ROUTES.route("/policy_schema", methods=['GET']) +def handle_policy_schema(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_schema_using_get) + +@ROUTES.route("/policy_status", methods=['GET']) +def handle_policy_status(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_status_using_get) + +@ROUTES.route("/policy_types", methods=['GET']) +def handle_policy_types(): + return ResponseHelper.route_check(config=config, get_function = Policy.get_policy_types_using_get) + + +@ROUTES.route("/", methods=['POST']) +def executeRicRequest(): + response_data = { + 'vthResponse': { + 'testDuration': '', + 'dateTimeUTC': str(datetime.datetime.now()), + 'abstractMessage': '', + 'resultData': {} + } + } + + startTime = unix_time_millis(datetime.datetime.now()) + ret_url = request.args.get('retURL') + try: + if not request.is_json: + raise ValueError("request must be json") + + requestData = request.get_json() + + app.logger.info("A1 requestData:" + str(requestData)) + + action = requestData['action'].lower() + _check_incoming_request(requestData) + + os.environ['NO_PROXY'] = '127.0.0.1' # TODO testing purpose w/ mock server. Needs to remove on final version + with open('config.json') as configFile: + config = json.load(configFile) + + baseAddress = config['base_address'] + if action == 'health_check' or action == 'list_policy': + res = requests.get(baseAddress + config['actions_path'][action]) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + if action == 'health_check': + response_data['vthResponse']['resultData']['resultOutput'] = res.text + else: + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + elif action == 'list_policy_instance': + res = requests.get(baseAddress + config['actions_path'][action] + .format(policy_type_id=requestData['policy_type_id'])) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + elif action == 'get_policy_instance_status': + res = requests.get(baseAddress + config['actions_path'][action] + .format(policy_type_id=requestData['policy_type_id'], + policy_instance_id=requestData['policy_instance_id'])) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + elif action == 'edit_policy': + res = _send_edit_request(requestData, config) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + if requestData['request_type'].lower() == 'get' and res.status_code == 200: + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + else: + response_data['vthResponse']['resultData']['resultOutput'] = res.text + elif action == 'edit_policy_instance': + res = _send_edit_request(requestData, config) + response_data['vthResponse']['resultData']['statusCode'] = res.status_code + if requestData['request_type'].lower() == 'get' and res.status_code == 200: + response_data['vthResponse']['resultData']['resultOutput'] = res.json() + else: + response_data['vthResponse']['resultData']['resultOutput'] = res.text + + except Exception as ex: + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + response_data['vthResponse']['testDuration'] = totalTime + response_data['vthResponse']['abstractMessage'] = str(ex) + return jsonify(response_data) + + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + + response_data['vthResponse']['testDuration'] = totalTime + + if ret_url is not None: + sendCallback(ret_url, response_data) + return '', 200 + + return jsonify(response_data), 200 + + +def _send_edit_request(request_data, config): + baseAddress = config['base_address'] + path = '' + action = request_data['action'] + policy_type_id = request_data['policy_type_id'] + request_type = request_data['request_type'] + if action == "edit_policy": + path = baseAddress + config['actions_path'][action].format(policy_type_id=policy_type_id) + if action == 'edit_policy_instance': + instance_id = request_data['policy_instance_id'] + path = baseAddress + config['actions_path'][action].format(policy_type_id=policy_type_id, + policy_instance_id=instance_id) + if request_type == 'get': + return requests.get(path) + if request_type == 'put': + payload = request_data['payload'] + return requests.put(path, payload) + if request_type == 'delete': + return requests.delete(path) + + +def _check_incoming_request(requestData): # check if the request is valid + if 'action' not in requestData: + raise KeyError('no action was specify') + + action = requestData['action'].lower() + edit_actions = ['edit_policy', 'edit_policy_instance'] + requires_policy_id = ['edit_policy', 'list_policy_instance' + , 'edit_policy_instance', 'get_policy_instance_status'] + requires_policy_instance_id = ['edit_policy_instance', 'get_policy_instance_status'] + possible_actions = ['health_check', 'list_policy', 'edit_policy', 'list_policy_instance' + , 'edit_policy_instance', 'get_policy_instance_status'] + possible_request_type = ['get', 'put', 'delete'] + + if action not in possible_actions: + raise KeyError("invalid action") + if action in edit_actions: # request type is required + if 'request_type' not in requestData: + raise KeyError('this action: ' + action + ' requires a request type') + if requestData['request_type'] not in possible_request_type: + raise KeyError('this request_type: ' + requestData['request_type'] + ' is not valid') + if requestData['request_type'] == 'put' and 'payload' not in requestData: + raise KeyError('put request requires a payload') + if action in requires_policy_id: + if 'policy_type_id' not in requestData: + raise KeyError('this action: ' + action + ' requires a policy_type_id') + if action in requires_policy_instance_id: + if 'policy_instance_id' not in requestData: + raise KeyError('this action: ' + action + ' requires a policy_instance_id') diff --git a/a1-sdnc-vth/app/routes/ric.py b/a1-sdnc-vth/app/routes/ric.py new file mode 100644 index 0000000..8441ac6 --- /dev/null +++ b/a1-sdnc-vth/app/routes/ric.py @@ -0,0 +1,12 @@ + +from app.helpers import response_helper as ResponseHelper +from app.helpers import ric_helper as Ric +from . import config, ROUTES + +@ROUTES.route("/ric", methods=['GET']) +def handle_ric(): + return ResponseHelper.route_check(config=config, get_function=Ric.get_ric_using_get) + +@ROUTES.route("/rics", methods=['GET']) +def handle_rics(): + return ResponseHelper.route_check(config=config, get_function=Ric.get_rics_using_get) diff --git a/a1-sdnc-vth/app/routes/service.py b/a1-sdnc-vth/app/routes/service.py new file mode 100644 index 0000000..e06bf94 --- /dev/null +++ b/a1-sdnc-vth/app/routes/service.py @@ -0,0 +1,16 @@ +from app.helpers import response_helper as ResponseHelper +from app.helpers import service_helper as Service +from . import config, ROUTES + +@ROUTES.route("/services", methods=['GET', 'DELETE']) +def handleS_services(): + return ResponseHelper.route_check(config=config, get_function=Service.get_services_using_get, delete_function=Service.delete_services_using_delete) + + +@ROUTES.route("/service", methods=['PUT']) +def handle_service(): + return ResponseHelper.route_check(config=config, put_function=Service.put_service_using_put) + +@ROUTES.route("/services/keepalive", methods=['PUT']) +def handle_services_keepalive(): + return ResponseHelper.route_check(config=config, put_function=Service.keep_alive_service_using_put) diff --git a/a1-sdnc-vth/config.ini b/a1-sdnc-vth/config.ini new file mode 100644 index 0000000..e9bc817 --- /dev/null +++ b/a1-sdnc-vth/config.ini @@ -0,0 +1,14 @@ +[auth] +creds_enabled= %(USE_CRED)s +username = %(USER)s +password = %(PW)s +[api] +base_url= %(API_URL)s +port= %(API_PORT)s +[resource] +proxy_enabled = %(USE_PROXY)s +https_proxy= %(HTTPS)s +http_proxy= %(HTTP)s +base_address = %(API_URL)s +publish = /{topic_name} +subscribe = /{topic_name}/{consumer_group}/{consumer_id} diff --git a/a1-sdnc-vth/doc/a1-documentation.docx b/a1-sdnc-vth/doc/a1-documentation.docx new file mode 100644 index 0000000000000000000000000000000000000000..335e2a0d44665c9ec5815223dfae5e2cc02277f6 GIT binary patch literal 53241 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 Z2;}r@E7`Y*fq)d6b*lnKwMkg0+ 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 l6#vi3>EGgT!~YWhYpRlygaq{DXSxRo + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + annotations: {} + +## Configure the ingress resource that allows you to access the +## MongoDB installation. Set up the URL +## ref: http://kubernetes.io/docs/user-guide/ingress/ +## +ingress: + ## Set to true to enable ingress record generation + enabled: false + + ## Set this to true in order to add the corresponding annotations for cert-manager + certManager: false + + ## Ingress annotations done as key:value pairs + ## For a full list of possible ingress annotations, please see + ## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md + ## + ## If tls is set to true, annotation ingress.kubernetes.io/secure-backends: "true" will automatically be set + ## If certManager is set to true, annotation kubernetes.io/tls-acme: "true" will automatically be set + annotations: + # kubernetes.io/ingress.class: nginx + + ## The list of hostnames to be covered with this ingress record. + ## Most likely this will be just one host, but in the event more hosts are needed, this is an array + hosts: + - name: mongodb.local + path: / + + ## The tls configuration for the ingress + ## see: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls + tls: + - hosts: + - mongodb.local + secretName: mongodb.local-tls + + secrets: + ## If you're providing your own certificates, please use this to add the certificates as secrets + ## key and certificate should start with -----BEGIN CERTIFICATE----- or + ## -----BEGIN RSA PRIVATE KEY----- + ## + ## name should line up with a tlsSecret set further up + ## If you're using cert-manager, this is unneeded, as it will create the secret for you if it is not set + ## + ## It is also possible to create and manage the certificates outside of this helm chart + ## Please see README.md for more information + # - name: airflow.local-tls + # key: + # certificate: + +## Configure the options for init containers to be run before the main app containers +## are started. All init containers are run sequentially and must exit without errors +## for the next one to be started. +## ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ +# extraInitContainers: | +# - name: do-something +# image: busybox +# command: ['do', 'something'] + +## Configure extra options for liveness and readiness probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) +livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +# Define custom config map with init scripts +initConfigMap: {} +# name: "init-config-map" + +## Entries for the MongoDB config file. For documentation of all options, see: +## http://docs.mongodb.org/manual/reference/configuration-options/ +## +configmap: +# # where and how to store data. +# storage: +# dbPath: /bitnami/mongodb/data/db +# journal: +# enabled: true +# directoryPerDB: false +# # where to write logging data. +# systemLog: +# destination: file +# quiet: false +# logAppend: true +# logRotate: reopen +# path: /opt/bitnami/mongodb/logs/mongodb.log +# verbosity: 0 +# # network interfaces +# net: +# port: 27017 +# unixDomainSocket: +# enabled: true +# pathPrefix: /opt/bitnami/mongodb/tmp +# ipv6: false +# bindIpAll: true +# # replica set options +# #replication: +# #replSetName: replicaset +# #enableMajorityReadConcern: true +# # process management options +# processManagement: +# fork: false +# pidFilePath: /opt/bitnami/mongodb/tmp/mongodb.pid +# # set parameter options +# setParameter: +# enableLocalhostAuthBypass: true +# # security options +# security: +# authorization: disabled +# #keyFile: /opt/bitnami/mongodb/conf/keyfile + +## Prometheus Exporter / Metrics +## +metrics: + enabled: false + + image: + registry: docker.io + repository: bitnami/mongodb-exporter + tag: 0.10.0-debian-10-r41 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## String with extra arguments to the metrics exporter + ## ref: https://github.com/percona/mongodb_exporter/blob/master/mongodb_exporter.go + extraArgs: "" + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Metrics exporter liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + livenessProbe: + enabled: false + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + enabled: false + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + + ## Metrics exporter pod Annotation + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9216" + + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md + serviceMonitor: + ## If the operator is installed in your cluster, set to true to create a Service Monitor Entry + enabled: false + + ## Specify a namespace if needed + # namespace: monitoring + + ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + additionalLabels: {} + + ## Specify Metric Relabellings to add to the scrape endpoint + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint + # relabellings: + + alerting: + ## Define individual alerting rules as required + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#rulegroup + ## https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + rules: {} + + ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Prometheus Rules to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + additionalLabels: {} diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/Chart.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/Chart.yaml new file mode 100644 index 0000000..e803431 --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +name: mysql +version: 1.6.3 +appVersion: 5.7.28 +description: Fast, reliable, scalable, and easy to use open-source relational database + system. +keywords: +- mysql +- database +- sql +home: https://www.mysql.com/ +icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png +sources: +- https://github.com/kubernetes/charts +- https://github.com/docker-library/mysql +maintainers: +engine: gotpl diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/scripts/init_db.sh b/otf-helm/otf/charts/databases/charts/mysqldb/scripts/init_db.sh new file mode 100644 index 0000000..9b748ca --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/scripts/init_db.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +sleep 20; +mysql -u otfuser otf_camunda -pToday.123 < /data/scripts/mysql_engine_7.10.0.sql +mysql -u otfuser otf_camunda -pToday.123 < /data/scripts/mysql_identity_7.10.0.sql + diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_engine_7.10.0.sql b/otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_engine_7.10.0.sql new file mode 100644 index 0000000..aefe0cb --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_engine_7.10.0.sql @@ -0,0 +1,1298 @@ +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +create table ACT_GE_PROPERTY ( + NAME_ varchar(64), + VALUE_ varchar(300), + REV_ integer, + primary key (NAME_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +insert into ACT_GE_PROPERTY +values ('schema.version', 'fox', 1); + +insert into ACT_GE_PROPERTY +values ('schema.history', 'create(fox)', 1); + +insert into ACT_GE_PROPERTY +values ('next.dbid', '1', 1); + +insert into ACT_GE_PROPERTY +values ('deployment.lock', '0', 1); + +insert into ACT_GE_PROPERTY +values ('history.cleanup.job.lock', '0', 1); + +insert into ACT_GE_PROPERTY +values ('startup.lock', '0', 1); + +create table ACT_GE_BYTEARRAY ( + ID_ varchar(64), + REV_ integer, + NAME_ varchar(255), + DEPLOYMENT_ID_ varchar(64), + BYTES_ LONGBLOB, + GENERATED_ TINYINT, + TENANT_ID_ varchar(64), + TYPE_ integer, + CREATE_TIME_ datetime, + ROOT_PROC_INST_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RE_DEPLOYMENT ( + ID_ varchar(64), + NAME_ varchar(255), + DEPLOY_TIME_ timestamp, + SOURCE_ varchar(255), + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_EXECUTION ( + ID_ varchar(64), + REV_ integer, + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + BUSINESS_KEY_ varchar(255), + PARENT_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + SUPER_EXEC_ varchar(64), + SUPER_CASE_EXEC_ varchar(64), + CASE_INST_ID_ varchar(64), + ACT_ID_ varchar(255), + ACT_INST_ID_ varchar(64), + IS_ACTIVE_ TINYINT, + IS_CONCURRENT_ TINYINT, + IS_SCOPE_ TINYINT, + IS_EVENT_SCOPE_ TINYINT, + SUSPENSION_STATE_ integer, + CACHED_ENT_STATE_ integer, + SEQUENCE_COUNTER_ bigint, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_JOB ( + ID_ varchar(64) NOT NULL, + REV_ integer, + TYPE_ varchar(255) NOT NULL, + LOCK_EXP_TIME_ timestamp NULL, + LOCK_OWNER_ varchar(255), + EXCLUSIVE_ boolean, + EXECUTION_ID_ varchar(64), + PROCESS_INSTANCE_ID_ varchar(64), + PROCESS_DEF_ID_ varchar(64), + PROCESS_DEF_KEY_ varchar(255), + RETRIES_ integer, + EXCEPTION_STACK_ID_ varchar(64), + EXCEPTION_MSG_ varchar(4000), + DUEDATE_ timestamp NULL, + REPEAT_ varchar(255), + HANDLER_TYPE_ varchar(255), + HANDLER_CFG_ varchar(4000), + DEPLOYMENT_ID_ varchar(64), + SUSPENSION_STATE_ integer NOT NULL DEFAULT 1, + JOB_DEF_ID_ varchar(64), + PRIORITY_ bigint NOT NULL DEFAULT 0, + SEQUENCE_COUNTER_ bigint, + TENANT_ID_ varchar(64), + CREATE_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_JOBDEF ( + ID_ varchar(64) NOT NULL, + REV_ integer, + PROC_DEF_ID_ varchar(64), + PROC_DEF_KEY_ varchar(255), + ACT_ID_ varchar(255), + JOB_TYPE_ varchar(255) NOT NULL, + JOB_CONFIGURATION_ varchar(255), + SUSPENSION_STATE_ integer, + JOB_PRIORITY_ bigint, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RE_PROCDEF ( + ID_ varchar(64) not null, + REV_ integer, + CATEGORY_ varchar(255), + NAME_ varchar(255), + KEY_ varchar(255) not null, + VERSION_ integer not null, + DEPLOYMENT_ID_ varchar(64), + RESOURCE_NAME_ varchar(4000), + DGRM_RESOURCE_NAME_ varchar(4000), + HAS_START_FORM_KEY_ TINYINT, + SUSPENSION_STATE_ integer, + TENANT_ID_ varchar(64), + VERSION_TAG_ varchar(64), + HISTORY_TTL_ integer, + STARTABLE_ boolean NOT NULL default TRUE, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_TASK ( + ID_ varchar(64), + REV_ integer, + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + CASE_EXECUTION_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + CASE_DEF_ID_ varchar(64), + NAME_ varchar(255), + PARENT_TASK_ID_ varchar(64), + DESCRIPTION_ varchar(4000), + TASK_DEF_KEY_ varchar(255), + OWNER_ varchar(255), + ASSIGNEE_ varchar(255), + DELEGATION_ varchar(64), + PRIORITY_ integer, + CREATE_TIME_ timestamp, + DUE_DATE_ datetime, + FOLLOW_UP_DATE_ datetime, + SUSPENSION_STATE_ integer, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_IDENTITYLINK ( + ID_ varchar(64), + REV_ integer, + GROUP_ID_ varchar(255), + TYPE_ varchar(255), + USER_ID_ varchar(255), + TASK_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_VARIABLE ( + ID_ varchar(64) not null, + REV_ integer, + TYPE_ varchar(255) not null, + NAME_ varchar(255) not null, + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + CASE_EXECUTION_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + TASK_ID_ varchar(64), + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + VAR_SCOPE_ varchar(64) not null, + SEQUENCE_COUNTER_ bigint, + IS_CONCURRENT_LOCAL_ TINYINT, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_EVENT_SUBSCR ( + ID_ varchar(64) not null, + REV_ integer, + EVENT_TYPE_ varchar(255) not null, + EVENT_NAME_ varchar(255), + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + ACTIVITY_ID_ varchar(255), + CONFIGURATION_ varchar(255), + CREATED_ timestamp not null, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_INCIDENT ( + ID_ varchar(64) not null, + REV_ integer not null, + INCIDENT_TIMESTAMP_ timestamp not null, + INCIDENT_MSG_ varchar(4000), + INCIDENT_TYPE_ varchar(255) not null, + EXECUTION_ID_ varchar(64), + ACTIVITY_ID_ varchar(255), + PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + CAUSE_INCIDENT_ID_ varchar(64), + ROOT_CAUSE_INCIDENT_ID_ varchar(64), + CONFIGURATION_ varchar(255), + TENANT_ID_ varchar(64), + JOB_DEF_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_AUTHORIZATION ( + ID_ varchar(64) not null, + REV_ integer not null, + TYPE_ integer not null, + GROUP_ID_ varchar(255), + USER_ID_ varchar(255), + RESOURCE_TYPE_ integer not null, + RESOURCE_ID_ varchar(255), + PERMS_ integer, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_FILTER ( + ID_ varchar(64) not null, + REV_ integer not null, + RESOURCE_TYPE_ varchar(255) not null, + NAME_ varchar(255) not null, + OWNER_ varchar(255), + QUERY_ LONGTEXT not null, + PROPERTIES_ LONGTEXT, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_METER_LOG ( + ID_ varchar(64) not null, + NAME_ varchar(64) not null, + REPORTER_ varchar(255), + VALUE_ bigint, + TIMESTAMP_ timestamp, + MILLISECONDS_ bigint DEFAULT 0, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_EXT_TASK ( + ID_ varchar(64) not null, + REV_ integer not null, + WORKER_ID_ varchar(255), + TOPIC_NAME_ varchar(255), + RETRIES_ integer, + ERROR_MSG_ varchar(4000), + ERROR_DETAILS_ID_ varchar(64), + LOCK_EXP_TIME_ timestamp NULL, + SUSPENSION_STATE_ integer, + EXECUTION_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + PROC_DEF_KEY_ varchar(255), + ACT_ID_ varchar(255), + ACT_INST_ID_ varchar(64), + TENANT_ID_ varchar(64), + PRIORITY_ bigint NOT NULL DEFAULT 0, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_RU_BATCH ( + ID_ varchar(64) not null, + REV_ integer not null, + TYPE_ varchar(255), + TOTAL_JOBS_ integer, + JOBS_CREATED_ integer, + JOBS_PER_SEED_ integer, + INVOCATIONS_PER_JOB_ integer, + SEED_JOB_DEF_ID_ varchar(64), + BATCH_JOB_DEF_ID_ varchar(64), + MONITOR_JOB_DEF_ID_ varchar(64), + SUSPENSION_STATE_ integer, + CONFIGURATION_ varchar(255), + TENANT_ID_ varchar(64), + CREATE_USER_ID_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create index ACT_IDX_EXEC_ROOT_PI on ACT_RU_EXECUTION(ROOT_PROC_INST_ID_); +create index ACT_IDX_EXEC_BUSKEY on ACT_RU_EXECUTION(BUSINESS_KEY_); +create index ACT_IDX_EXEC_TENANT_ID on ACT_RU_EXECUTION(TENANT_ID_); +create index ACT_IDX_TASK_CREATE on ACT_RU_TASK(CREATE_TIME_); +create index ACT_IDX_TASK_ASSIGNEE on ACT_RU_TASK(ASSIGNEE_); +create index ACT_IDX_TASK_TENANT_ID on ACT_RU_TASK(TENANT_ID_); +create index ACT_IDX_IDENT_LNK_USER on ACT_RU_IDENTITYLINK(USER_ID_); +create index ACT_IDX_IDENT_LNK_GROUP on ACT_RU_IDENTITYLINK(GROUP_ID_); +create index ACT_IDX_EVENT_SUBSCR_CONFIG_ on ACT_RU_EVENT_SUBSCR(CONFIGURATION_); +create index ACT_IDX_EVENT_SUBSCR_TENANT_ID on ACT_RU_EVENT_SUBSCR(TENANT_ID_); +create index ACT_IDX_VARIABLE_TASK_ID on ACT_RU_VARIABLE(TASK_ID_); +create index ACT_IDX_VARIABLE_TENANT_ID on ACT_RU_VARIABLE(TENANT_ID_); +create index ACT_IDX_ATHRZ_PROCEDEF on ACT_RU_IDENTITYLINK(PROC_DEF_ID_); +create index ACT_IDX_INC_CONFIGURATION on ACT_RU_INCIDENT(CONFIGURATION_); +create index ACT_IDX_INC_TENANT_ID on ACT_RU_INCIDENT(TENANT_ID_); +-- CAM-5914 +create index ACT_IDX_JOB_EXECUTION_ID on ACT_RU_JOB(EXECUTION_ID_); +-- this index needs to be limited in mysql see CAM-6938 +create index ACT_IDX_JOB_HANDLER on ACT_RU_JOB(HANDLER_TYPE_(100),HANDLER_CFG_(155)); +create index ACT_IDX_JOB_PROCINST on ACT_RU_JOB(PROCESS_INSTANCE_ID_); +create index ACT_IDX_JOB_TENANT_ID on ACT_RU_JOB(TENANT_ID_); +create index ACT_IDX_JOBDEF_TENANT_ID on ACT_RU_JOBDEF(TENANT_ID_); + +-- new metric milliseconds column +CREATE INDEX ACT_IDX_METER_LOG_MS ON ACT_RU_METER_LOG(MILLISECONDS_); +CREATE INDEX ACT_IDX_METER_LOG_NAME_MS ON ACT_RU_METER_LOG(NAME_, MILLISECONDS_); +CREATE INDEX ACT_IDX_METER_LOG_REPORT ON ACT_RU_METER_LOG(NAME_, REPORTER_, MILLISECONDS_); + +-- old metric timestamp column +CREATE INDEX ACT_IDX_METER_LOG_TIME ON ACT_RU_METER_LOG(TIMESTAMP_); +CREATE INDEX ACT_IDX_METER_LOG ON ACT_RU_METER_LOG(NAME_, TIMESTAMP_); + +create index ACT_IDX_EXT_TASK_TOPIC on ACT_RU_EXT_TASK(TOPIC_NAME_); +create index ACT_IDX_EXT_TASK_TENANT_ID on ACT_RU_EXT_TASK(TENANT_ID_); +create index ACT_IDX_EXT_TASK_PRIORITY ON ACT_RU_EXT_TASK(PRIORITY_); +create index ACT_IDX_EXT_TASK_ERR_DETAILS ON ACT_RU_EXT_TASK(ERROR_DETAILS_ID_); +create index ACT_IDX_AUTH_GROUP_ID on ACT_RU_AUTHORIZATION(GROUP_ID_); +create index ACT_IDX_JOB_JOB_DEF_ID on ACT_RU_JOB(JOB_DEF_ID_); + +alter table ACT_GE_BYTEARRAY + add constraint ACT_FK_BYTEARR_DEPL + foreign key (DEPLOYMENT_ID_) + references ACT_RE_DEPLOYMENT (ID_); + +alter table ACT_RU_EXECUTION + add constraint ACT_FK_EXE_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION (ID_) on delete cascade on update cascade; + +alter table ACT_RU_EXECUTION + add constraint ACT_FK_EXE_PARENT + foreign key (PARENT_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_EXECUTION + add constraint ACT_FK_EXE_SUPER + foreign key (SUPER_EXEC_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_EXECUTION + add constraint ACT_FK_EXE_PROCDEF + foreign key (PROC_DEF_ID_) + references ACT_RE_PROCDEF (ID_); + +alter table ACT_RU_IDENTITYLINK + add constraint ACT_FK_TSKASS_TASK + foreign key (TASK_ID_) + references ACT_RU_TASK (ID_); + +alter table ACT_RU_IDENTITYLINK + add constraint ACT_FK_ATHRZ_PROCEDEF + foreign key (PROC_DEF_ID_) + references ACT_RE_PROCDEF(ID_); + +alter table ACT_RU_TASK + add constraint ACT_FK_TASK_EXE + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_TASK + add constraint ACT_FK_TASK_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_TASK + add constraint ACT_FK_TASK_PROCDEF + foreign key (PROC_DEF_ID_) + references ACT_RE_PROCDEF (ID_); + +alter table ACT_RU_VARIABLE + add constraint ACT_FK_VAR_EXE + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_VARIABLE + add constraint ACT_FK_VAR_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION(ID_); + +alter table ACT_RU_VARIABLE + add constraint ACT_FK_VAR_BYTEARRAY + foreign key (BYTEARRAY_ID_) + references ACT_GE_BYTEARRAY (ID_); + +alter table ACT_RU_JOB + add constraint ACT_FK_JOB_EXCEPTION + foreign key (EXCEPTION_STACK_ID_) + references ACT_GE_BYTEARRAY (ID_); + +alter table ACT_RU_EVENT_SUBSCR + add constraint ACT_FK_EVENT_EXEC + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION(ID_); + +alter table ACT_RU_INCIDENT + add constraint ACT_FK_INC_EXE + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_INCIDENT + add constraint ACT_FK_INC_PROCINST + foreign key (PROC_INST_ID_) + references ACT_RU_EXECUTION (ID_); + +alter table ACT_RU_INCIDENT + add constraint ACT_FK_INC_PROCDEF + foreign key (PROC_DEF_ID_) + references ACT_RE_PROCDEF (ID_); + +alter table ACT_RU_INCIDENT + add constraint ACT_FK_INC_CAUSE + foreign key (CAUSE_INCIDENT_ID_) + references ACT_RU_INCIDENT (ID_) on delete cascade on update cascade; + +alter table ACT_RU_INCIDENT + add constraint ACT_FK_INC_RCAUSE + foreign key (ROOT_CAUSE_INCIDENT_ID_) + references ACT_RU_INCIDENT (ID_) on delete cascade on update cascade; + +alter table ACT_RU_EXT_TASK + add constraint ACT_FK_EXT_TASK_ERROR_DETAILS + foreign key (ERROR_DETAILS_ID_) + references ACT_GE_BYTEARRAY (ID_); + +create index ACT_IDX_INC_JOB_DEF on ACT_RU_INCIDENT(JOB_DEF_ID_); +alter table ACT_RU_INCIDENT + add constraint ACT_FK_INC_JOB_DEF + foreign key (JOB_DEF_ID_) + references ACT_RU_JOBDEF (ID_); + +alter table ACT_RU_AUTHORIZATION + add constraint ACT_UNIQ_AUTH_USER + unique (USER_ID_,TYPE_,RESOURCE_TYPE_,RESOURCE_ID_); + +alter table ACT_RU_AUTHORIZATION + add constraint ACT_UNIQ_AUTH_GROUP + unique (GROUP_ID_,TYPE_,RESOURCE_TYPE_,RESOURCE_ID_); + +alter table ACT_RU_VARIABLE + add constraint ACT_UNIQ_VARIABLE + unique (VAR_SCOPE_, NAME_); + +alter table ACT_RU_EXT_TASK + add constraint ACT_FK_EXT_TASK_EXE + foreign key (EXECUTION_ID_) + references ACT_RU_EXECUTION (ID_); + +create index ACT_IDX_BATCH_SEED_JOB_DEF ON ACT_RU_BATCH(SEED_JOB_DEF_ID_); +alter table ACT_RU_BATCH + add constraint ACT_FK_BATCH_SEED_JOB_DEF + foreign key (SEED_JOB_DEF_ID_) + references ACT_RU_JOBDEF (ID_); + +create index ACT_IDX_BATCH_MONITOR_JOB_DEF ON ACT_RU_BATCH(MONITOR_JOB_DEF_ID_); +alter table ACT_RU_BATCH + add constraint ACT_FK_BATCH_MONITOR_JOB_DEF + foreign key (MONITOR_JOB_DEF_ID_) + references ACT_RU_JOBDEF (ID_); + +create index ACT_IDX_BATCH_JOB_DEF ON ACT_RU_BATCH(BATCH_JOB_DEF_ID_); +alter table ACT_RU_BATCH + add constraint ACT_FK_BATCH_JOB_DEF + foreign key (BATCH_JOB_DEF_ID_) + references ACT_RU_JOBDEF (ID_); + +-- indexes for deadlock problems - https://app.camunda.com/jira/browse/CAM-2567 -- +create index ACT_IDX_INC_CAUSEINCID on ACT_RU_INCIDENT(CAUSE_INCIDENT_ID_); +create index ACT_IDX_INC_EXID on ACT_RU_INCIDENT(EXECUTION_ID_); +create index ACT_IDX_INC_PROCDEFID on ACT_RU_INCIDENT(PROC_DEF_ID_); +create index ACT_IDX_INC_PROCINSTID on ACT_RU_INCIDENT(PROC_INST_ID_); +create index ACT_IDX_INC_ROOTCAUSEINCID on ACT_RU_INCIDENT(ROOT_CAUSE_INCIDENT_ID_); +-- index for deadlock problem - https://app.camunda.com/jira/browse/CAM-4440 -- +create index ACT_IDX_AUTH_RESOURCE_ID on ACT_RU_AUTHORIZATION(RESOURCE_ID_); +-- index to prevent deadlock on fk constraint - https://app.camunda.com/jira/browse/CAM-5440 -- +create index ACT_IDX_EXT_TASK_EXEC on ACT_RU_EXT_TASK(EXECUTION_ID_); + +-- indexes to improve deployment +create index ACT_IDX_BYTEARRAY_ROOT_PI on ACT_GE_BYTEARRAY(ROOT_PROC_INST_ID_); +create index ACT_IDX_BYTEARRAY_RM_TIME on ACT_GE_BYTEARRAY(REMOVAL_TIME_); +create index ACT_IDX_BYTEARRAY_NAME on ACT_GE_BYTEARRAY(NAME_); +create index ACT_IDX_DEPLOYMENT_NAME on ACT_RE_DEPLOYMENT(NAME_); +create index ACT_IDX_DEPLOYMENT_TENANT_ID on ACT_RE_DEPLOYMENT(TENANT_ID_); +create index ACT_IDX_JOBDEF_PROC_DEF_ID ON ACT_RU_JOBDEF(PROC_DEF_ID_); +create index ACT_IDX_JOB_HANDLER_TYPE ON ACT_RU_JOB(HANDLER_TYPE_); +create index ACT_IDX_EVENT_SUBSCR_EVT_NAME ON ACT_RU_EVENT_SUBSCR(EVENT_NAME_); +create index ACT_IDX_PROCDEF_DEPLOYMENT_ID ON ACT_RE_PROCDEF(DEPLOYMENT_ID_); +create index ACT_IDX_PROCDEF_TENANT_ID ON ACT_RE_PROCDEF(TENANT_ID_); +create index ACT_IDX_PROCDEF_VER_TAG ON ACT_RE_PROCDEF(VERSION_TAG_); +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +-- create case definition table -- +create table ACT_RE_CASE_DEF ( + ID_ varchar(64) not null, + REV_ integer, + CATEGORY_ varchar(255), + NAME_ varchar(255), + KEY_ varchar(255) not null, + VERSION_ integer not null, + DEPLOYMENT_ID_ varchar(64), + RESOURCE_NAME_ varchar(4000), + DGRM_RESOURCE_NAME_ varchar(4000), + TENANT_ID_ varchar(64), + HISTORY_TTL_ integer, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- create case execution table -- +create table ACT_RU_CASE_EXECUTION ( + ID_ varchar(64) NOT NULL, + REV_ integer, + CASE_INST_ID_ varchar(64), + SUPER_CASE_EXEC_ varchar(64), + SUPER_EXEC_ varchar(64), + BUSINESS_KEY_ varchar(255), + PARENT_ID_ varchar(64), + CASE_DEF_ID_ varchar(64), + ACT_ID_ varchar(255), + PREV_STATE_ integer, + CURRENT_STATE_ integer, + REQUIRED_ boolean, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- create case sentry part table -- + +create table ACT_RU_CASE_SENTRY_PART ( + ID_ varchar(64) NOT NULL, + REV_ integer, + CASE_INST_ID_ varchar(64), + CASE_EXEC_ID_ varchar(64), + SENTRY_ID_ varchar(255), + TYPE_ varchar(255), + SOURCE_CASE_EXEC_ID_ varchar(64), + STANDARD_EVENT_ varchar(255), + SOURCE_ varchar(255), + VARIABLE_EVENT_ varchar(255), + VARIABLE_NAME_ varchar(255), + SATISFIED_ boolean, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- create index on business key -- +create index ACT_IDX_CASE_EXEC_BUSKEY on ACT_RU_CASE_EXECUTION(BUSINESS_KEY_); + +-- https://app.camunda.com/jira/browse/CAM-9165 +create index ACT_IDX_CASE_EXE_CASE_INST on ACT_RU_CASE_EXECUTION(CASE_INST_ID_); + +-- create foreign key constraints on ACT_RU_CASE_EXECUTION -- +alter table ACT_RU_CASE_EXECUTION + add constraint ACT_FK_CASE_EXE_CASE_INST + foreign key (CASE_INST_ID_) + references ACT_RU_CASE_EXECUTION(ID_) on delete cascade on update cascade; + +alter table ACT_RU_CASE_EXECUTION + add constraint ACT_FK_CASE_EXE_PARENT + foreign key (PARENT_ID_) + references ACT_RU_CASE_EXECUTION(ID_); + +alter table ACT_RU_CASE_EXECUTION + add constraint ACT_FK_CASE_EXE_CASE_DEF + foreign key (CASE_DEF_ID_) + references ACT_RE_CASE_DEF(ID_); + +-- create foreign key constraints on ACT_RU_VARIABLE -- +alter table ACT_RU_VARIABLE + add constraint ACT_FK_VAR_CASE_EXE + foreign key (CASE_EXECUTION_ID_) + references ACT_RU_CASE_EXECUTION(ID_); + +alter table ACT_RU_VARIABLE + add constraint ACT_FK_VAR_CASE_INST + foreign key (CASE_INST_ID_) + references ACT_RU_CASE_EXECUTION(ID_); + +-- create foreign key constraints on ACT_RU_TASK -- +alter table ACT_RU_TASK + add constraint ACT_FK_TASK_CASE_EXE + foreign key (CASE_EXECUTION_ID_) + references ACT_RU_CASE_EXECUTION(ID_); + +alter table ACT_RU_TASK + add constraint ACT_FK_TASK_CASE_DEF + foreign key (CASE_DEF_ID_) + references ACT_RE_CASE_DEF(ID_); + +-- create foreign key constraints on ACT_RU_CASE_SENTRY_PART -- +alter table ACT_RU_CASE_SENTRY_PART + add constraint ACT_FK_CASE_SENTRY_CASE_INST + foreign key (CASE_INST_ID_) + references ACT_RU_CASE_EXECUTION(ID_); + +alter table ACT_RU_CASE_SENTRY_PART + add constraint ACT_FK_CASE_SENTRY_CASE_EXEC + foreign key (CASE_EXEC_ID_) + references ACT_RU_CASE_EXECUTION(ID_); + +create index ACT_IDX_CASE_DEF_TENANT_ID on ACT_RE_CASE_DEF(TENANT_ID_); +create index ACT_IDX_CASE_EXEC_TENANT_ID on ACT_RU_CASE_EXECUTION(TENANT_ID_); +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +-- create decision definition table -- +create table ACT_RE_DECISION_DEF ( + ID_ varchar(64) not null, + REV_ integer, + CATEGORY_ varchar(255), + NAME_ varchar(255), + KEY_ varchar(255) not null, + VERSION_ integer not null, + DEPLOYMENT_ID_ varchar(64), + RESOURCE_NAME_ varchar(4000), + DGRM_RESOURCE_NAME_ varchar(4000), + DEC_REQ_ID_ varchar(64), + DEC_REQ_KEY_ varchar(255), + TENANT_ID_ varchar(64), + HISTORY_TTL_ integer, + VERSION_TAG_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- create decision requirements definition table -- +create table ACT_RE_DECISION_REQ_DEF ( + ID_ varchar(64) NOT NULL, + REV_ integer, + CATEGORY_ varchar(255), + NAME_ varchar(255), + KEY_ varchar(255) NOT NULL, + VERSION_ integer NOT NULL, + DEPLOYMENT_ID_ varchar(64), + RESOURCE_NAME_ varchar(4000), + DGRM_RESOURCE_NAME_ varchar(4000), + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +alter table ACT_RE_DECISION_DEF + add constraint ACT_FK_DEC_REQ + foreign key (DEC_REQ_ID_) + references ACT_RE_DECISION_REQ_DEF(ID_); + +create index ACT_IDX_DEC_DEF_TENANT_ID on ACT_RE_DECISION_DEF(TENANT_ID_); +create index ACT_IDX_DEC_DEF_REQ_ID on ACT_RE_DECISION_DEF(DEC_REQ_ID_); +create index ACT_IDX_DEC_REQ_DEF_TENANT_ID on ACT_RE_DECISION_REQ_DEF(TENANT_ID_); + +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +create table ACT_HI_PROCINST ( + ID_ varchar(64) not null, + PROC_INST_ID_ varchar(64) not null, + BUSINESS_KEY_ varchar(255), + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64) not null, + START_TIME_ datetime not null, + END_TIME_ datetime, + REMOVAL_TIME_ datetime, + DURATION_ bigint, + START_USER_ID_ varchar(255), + START_ACT_ID_ varchar(255), + END_ACT_ID_ varchar(255), + SUPER_PROCESS_INSTANCE_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + SUPER_CASE_INSTANCE_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + DELETE_REASON_ varchar(4000), + TENANT_ID_ varchar(64), + STATE_ varchar(255), + primary key (ID_), + unique (PROC_INST_ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_ACTINST ( + ID_ varchar(64) not null, + PARENT_ACT_INST_ID_ varchar(64), + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64) not null, + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64) not null, + EXECUTION_ID_ varchar(64) not null, + ACT_ID_ varchar(255) not null, + TASK_ID_ varchar(64), + CALL_PROC_INST_ID_ varchar(64), + CALL_CASE_INST_ID_ varchar(64), + ACT_NAME_ varchar(255), + ACT_TYPE_ varchar(255) not null, + ASSIGNEE_ varchar(64), + START_TIME_ datetime not null, + END_TIME_ datetime, + DURATION_ bigint, + ACT_INST_STATE_ integer, + SEQUENCE_COUNTER_ bigint, + TENANT_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_TASKINST ( + ID_ varchar(64) not null, + TASK_DEF_KEY_ varchar(255), + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + CASE_DEF_KEY_ varchar(255), + CASE_DEF_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + CASE_EXECUTION_ID_ varchar(64), + ACT_INST_ID_ varchar(64), + NAME_ varchar(255), + PARENT_TASK_ID_ varchar(64), + DESCRIPTION_ varchar(4000), + OWNER_ varchar(255), + ASSIGNEE_ varchar(255), + START_TIME_ datetime not null, + END_TIME_ datetime, + DURATION_ bigint, + DELETE_REASON_ varchar(4000), + PRIORITY_ integer, + DUE_DATE_ datetime, + FOLLOW_UP_DATE_ datetime, + TENANT_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_VARINST ( + ID_ varchar(64) not null, + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + ACT_INST_ID_ varchar(64), + CASE_DEF_KEY_ varchar(255), + CASE_DEF_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + CASE_EXECUTION_ID_ varchar(64), + TASK_ID_ varchar(64), + NAME_ varchar(255) not null, + VAR_TYPE_ varchar(100), + CREATE_TIME_ datetime, + REV_ integer, + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + TENANT_ID_ varchar(64), + STATE_ varchar(20), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_DETAIL ( + ID_ varchar(64) not null, + TYPE_ varchar(255) not null, + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + CASE_DEF_KEY_ varchar(255), + CASE_DEF_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + CASE_EXECUTION_ID_ varchar(64), + TASK_ID_ varchar(64), + ACT_INST_ID_ varchar(64), + VAR_INST_ID_ varchar(64), + NAME_ varchar(255) not null, + VAR_TYPE_ varchar(255), + REV_ integer, + TIME_ datetime not null, + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + SEQUENCE_COUNTER_ bigint, + TENANT_ID_ varchar(64), + OPERATION_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_IDENTITYLINK ( + ID_ varchar(64) not null, + TIMESTAMP_ timestamp not null, + TYPE_ varchar(255), + USER_ID_ varchar(255), + GROUP_ID_ varchar(255), + TASK_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + OPERATION_TYPE_ varchar(64), + ASSIGNER_ID_ varchar(64), + PROC_DEF_KEY_ varchar(255), + TENANT_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_COMMENT ( + ID_ varchar(64) not null, + TYPE_ varchar(255), + TIME_ datetime not null, + USER_ID_ varchar(255), + TASK_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + ACTION_ varchar(255), + MESSAGE_ varchar(4000), + FULL_MSG_ LONGBLOB, + TENANT_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_ATTACHMENT ( + ID_ varchar(64) not null, + REV_ integer, + USER_ID_ varchar(255), + NAME_ varchar(255), + DESCRIPTION_ varchar(4000), + TYPE_ varchar(255), + TASK_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + URL_ varchar(4000), + CONTENT_ID_ varchar(64), + TENANT_ID_ varchar(64), + CREATE_TIME_ datetime, + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_OP_LOG ( + ID_ varchar(64) not null, + DEPLOYMENT_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + PROC_DEF_KEY_ varchar(255), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + CASE_DEF_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + CASE_EXECUTION_ID_ varchar(64), + TASK_ID_ varchar(64), + JOB_ID_ varchar(64), + JOB_DEF_ID_ varchar(64), + BATCH_ID_ varchar(64), + USER_ID_ varchar(255), + TIMESTAMP_ timestamp not null, + OPERATION_TYPE_ varchar(64), + OPERATION_ID_ varchar(64), + ENTITY_TYPE_ varchar(30), + PROPERTY_ varchar(64), + ORG_VALUE_ varchar(4000), + NEW_VALUE_ varchar(4000), + TENANT_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_INCIDENT ( + ID_ varchar(64) not null, + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + CREATE_TIME_ timestamp not null, + END_TIME_ timestamp null, + INCIDENT_MSG_ varchar(4000), + INCIDENT_TYPE_ varchar(255) not null, + ACTIVITY_ID_ varchar(255), + CAUSE_INCIDENT_ID_ varchar(64), + ROOT_CAUSE_INCIDENT_ID_ varchar(64), + CONFIGURATION_ varchar(255), + INCIDENT_STATE_ integer, + TENANT_ID_ varchar(64), + JOB_DEF_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_JOB_LOG ( + ID_ varchar(64) not null, + TIMESTAMP_ timestamp not null, + JOB_ID_ varchar(64) not null, + JOB_DUEDATE_ timestamp NULL, + JOB_RETRIES_ integer, + JOB_PRIORITY_ bigint NOT NULL DEFAULT 0, + JOB_EXCEPTION_MSG_ varchar(4000), + JOB_EXCEPTION_STACK_ID_ varchar(64), + JOB_STATE_ integer, + JOB_DEF_ID_ varchar(64), + JOB_DEF_TYPE_ varchar(255), + JOB_DEF_CONFIGURATION_ varchar(255), + ACT_ID_ varchar(255), + EXECUTION_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROCESS_INSTANCE_ID_ varchar(64), + PROCESS_DEF_ID_ varchar(64), + PROCESS_DEF_KEY_ varchar(255), + DEPLOYMENT_ID_ varchar(64), + SEQUENCE_COUNTER_ bigint, + TENANT_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_BATCH ( + ID_ varchar(64) not null, + TYPE_ varchar(255), + TOTAL_JOBS_ integer, + JOBS_PER_SEED_ integer, + INVOCATIONS_PER_JOB_ integer, + SEED_JOB_DEF_ID_ varchar(64), + MONITOR_JOB_DEF_ID_ varchar(64), + BATCH_JOB_DEF_ID_ varchar(64), + TENANT_ID_ varchar(64), + CREATE_USER_ID_ varchar(255), + START_TIME_ datetime not null, + END_TIME_ datetime, + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_EXT_TASK_LOG ( + ID_ varchar(64) not null, + TIMESTAMP_ timestamp not null, + EXT_TASK_ID_ varchar(64) not null, + RETRIES_ integer, + TOPIC_NAME_ varchar(255), + WORKER_ID_ varchar(255), + PRIORITY_ bigint not null default 0, + ERROR_MSG_ varchar(4000), + ERROR_DETAILS_ID_ varchar(64), + ACT_ID_ varchar(255), + ACT_INST_ID_ varchar(64), + EXECUTION_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + PROC_DEF_ID_ varchar(64), + PROC_DEF_KEY_ varchar(255), + TENANT_ID_ varchar(64), + STATE_ integer, + REV_ integer, + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create index ACT_IDX_HI_PRO_INST_END on ACT_HI_PROCINST(END_TIME_); +create index ACT_IDX_HI_PRO_I_BUSKEY on ACT_HI_PROCINST(BUSINESS_KEY_); +create index ACT_IDX_HI_PRO_INST_TENANT_ID on ACT_HI_PROCINST(TENANT_ID_); +create index ACT_IDX_HI_PRO_INST_PROC_DEF_KEY on ACT_HI_PROCINST(PROC_DEF_KEY_); +create index ACT_IDX_HI_PRO_INST_PROC_TIME on ACT_HI_PROCINST(START_TIME_, END_TIME_); +create index ACT_IDX_HI_PI_PDEFID_END_TIME on ACT_HI_PROCINST(PROC_DEF_ID_, END_TIME_); +create index ACT_IDX_HI_PRO_INST_ROOT_PI on ACT_HI_PROCINST(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_PRO_INST_RM_TIME on ACT_HI_PROCINST(REMOVAL_TIME_); + +create index ACT_IDX_HI_ACTINST_ROOT_PI on ACT_HI_ACTINST(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_ACT_INST_START on ACT_HI_ACTINST(START_TIME_); +create index ACT_IDX_HI_ACT_INST_END on ACT_HI_ACTINST(END_TIME_); +create index ACT_IDX_HI_ACT_INST_PROCINST on ACT_HI_ACTINST(PROC_INST_ID_, ACT_ID_); +create index ACT_IDX_HI_ACT_INST_COMP on ACT_HI_ACTINST(EXECUTION_ID_, ACT_ID_, END_TIME_, ID_); +create index ACT_IDX_HI_ACT_INST_STATS on ACT_HI_ACTINST(PROC_DEF_ID_, PROC_INST_ID_, ACT_ID_, END_TIME_, ACT_INST_STATE_); +create index ACT_IDX_HI_ACT_INST_TENANT_ID on ACT_HI_ACTINST(TENANT_ID_); +create index ACT_IDX_HI_ACT_INST_PROC_DEF_KEY on ACT_HI_ACTINST(PROC_DEF_KEY_); +create index ACT_IDX_HI_AI_PDEFID_END_TIME on ACT_HI_ACTINST(PROC_DEF_ID_, END_TIME_); +create index ACT_IDX_HI_ACT_INST_RM_TIME on ACT_HI_ACTINST(REMOVAL_TIME_); + +create index ACT_IDX_HI_TASKINST_ROOT_PI on ACT_HI_TASKINST(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_TASK_INST_TENANT_ID on ACT_HI_TASKINST(TENANT_ID_); +create index ACT_IDX_HI_TASK_INST_PROC_DEF_KEY on ACT_HI_TASKINST(PROC_DEF_KEY_); +create index ACT_IDX_HI_TASKINST_PROCINST on ACT_HI_TASKINST(PROC_INST_ID_); +create index ACT_IDX_HI_TASKINSTID_PROCINST on ACT_HI_TASKINST(ID_,PROC_INST_ID_); +create index ACT_IDX_HI_TASK_INST_RM_TIME on ACT_HI_TASKINST(REMOVAL_TIME_); +create index ACT_IDX_HI_TASK_INST_START on ACT_HI_TASKINST(START_TIME_); +create index ACT_IDX_HI_TASK_INST_END on ACT_HI_TASKINST(END_TIME_); + +create index ACT_IDX_HI_DETAIL_ROOT_PI on ACT_HI_DETAIL(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_DETAIL_PROC_INST on ACT_HI_DETAIL(PROC_INST_ID_); +create index ACT_IDX_HI_DETAIL_ACT_INST on ACT_HI_DETAIL(ACT_INST_ID_); +create index ACT_IDX_HI_DETAIL_CASE_INST on ACT_HI_DETAIL(CASE_INST_ID_); +create index ACT_IDX_HI_DETAIL_CASE_EXEC on ACT_HI_DETAIL(CASE_EXECUTION_ID_); +create index ACT_IDX_HI_DETAIL_TIME on ACT_HI_DETAIL(TIME_); +create index ACT_IDX_HI_DETAIL_NAME on ACT_HI_DETAIL(NAME_); +create index ACT_IDX_HI_DETAIL_TASK_ID on ACT_HI_DETAIL(TASK_ID_); +create index ACT_IDX_HI_DETAIL_TENANT_ID on ACT_HI_DETAIL(TENANT_ID_); +create index ACT_IDX_HI_DETAIL_PROC_DEF_KEY on ACT_HI_DETAIL(PROC_DEF_KEY_); +create index ACT_IDX_HI_DETAIL_BYTEAR on ACT_HI_DETAIL(BYTEARRAY_ID_); +create index ACT_IDX_HI_DETAIL_RM_TIME on ACT_HI_DETAIL(REMOVAL_TIME_); +create index ACT_IDX_HI_DETAIL_TASK_BYTEAR on ACT_HI_DETAIL(BYTEARRAY_ID_, TASK_ID_); + +create index ACT_IDX_HI_IDENT_LNK_ROOT_PI on ACT_HI_IDENTITYLINK(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_IDENT_LNK_USER on ACT_HI_IDENTITYLINK(USER_ID_); +create index ACT_IDX_HI_IDENT_LNK_GROUP on ACT_HI_IDENTITYLINK(GROUP_ID_); +create index ACT_IDX_HI_IDENT_LNK_TENANT_ID on ACT_HI_IDENTITYLINK(TENANT_ID_); +create index ACT_IDX_HI_IDENT_LNK_PROC_DEF_KEY on ACT_HI_IDENTITYLINK(PROC_DEF_KEY_); +create index ACT_IDX_HI_IDENT_LINK_TASK on ACT_HI_IDENTITYLINK(TASK_ID_); +create index ACT_IDX_HI_IDENT_LINK_RM_TIME on ACT_HI_IDENTITYLINK(REMOVAL_TIME_); + +create index ACT_IDX_HI_VARINST_ROOT_PI on ACT_HI_VARINST(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_PROCVAR_PROC_INST on ACT_HI_VARINST(PROC_INST_ID_); +create index ACT_IDX_HI_PROCVAR_NAME_TYPE on ACT_HI_VARINST(NAME_, VAR_TYPE_); +create index ACT_IDX_HI_CASEVAR_CASE_INST on ACT_HI_VARINST(CASE_INST_ID_); +create index ACT_IDX_HI_VAR_INST_TENANT_ID on ACT_HI_VARINST(TENANT_ID_); +create index ACT_IDX_HI_VAR_INST_PROC_DEF_KEY on ACT_HI_VARINST(PROC_DEF_KEY_); +create index ACT_IDX_HI_VARINST_BYTEAR on ACT_HI_VARINST(BYTEARRAY_ID_); +create index ACT_IDX_HI_VARINST_RM_TIME on ACT_HI_VARINST(REMOVAL_TIME_); + +create index ACT_IDX_HI_INCIDENT_TENANT_ID on ACT_HI_INCIDENT(TENANT_ID_); +create index ACT_IDX_HI_INCIDENT_PROC_DEF_KEY on ACT_HI_INCIDENT(PROC_DEF_KEY_); +create index ACT_IDX_HI_INCIDENT_ROOT_PI on ACT_HI_INCIDENT(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_INCIDENT_PROCINST on ACT_HI_INCIDENT(PROC_INST_ID_); +create index ACT_IDX_HI_INCIDENT_RM_TIME on ACT_HI_INCIDENT(REMOVAL_TIME_); + +create index ACT_IDX_HI_JOB_LOG_ROOT_PI on ACT_HI_JOB_LOG(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_JOB_LOG_PROCINST on ACT_HI_JOB_LOG(PROCESS_INSTANCE_ID_); +create index ACT_IDX_HI_JOB_LOG_PROCDEF on ACT_HI_JOB_LOG(PROCESS_DEF_ID_); +create index ACT_IDX_HI_JOB_LOG_TENANT_ID on ACT_HI_JOB_LOG(TENANT_ID_); +create index ACT_IDX_HI_JOB_LOG_JOB_DEF_ID on ACT_HI_JOB_LOG(JOB_DEF_ID_); +create index ACT_IDX_HI_JOB_LOG_PROC_DEF_KEY on ACT_HI_JOB_LOG(PROCESS_DEF_KEY_); +create index ACT_IDX_HI_JOB_LOG_EX_STACK on ACT_HI_JOB_LOG(JOB_EXCEPTION_STACK_ID_); +create index ACT_IDX_HI_JOB_LOG_RM_TIME on ACT_HI_JOB_LOG(REMOVAL_TIME_); + +create index ACT_HI_BAT_RM_TIME on ACT_HI_BATCH(REMOVAL_TIME_); + +create index ACT_HI_EXT_TASK_LOG_ROOT_PI on ACT_HI_EXT_TASK_LOG(ROOT_PROC_INST_ID_); +create index ACT_HI_EXT_TASK_LOG_PROCINST on ACT_HI_EXT_TASK_LOG(PROC_INST_ID_); +create index ACT_HI_EXT_TASK_LOG_PROCDEF on ACT_HI_EXT_TASK_LOG(PROC_DEF_ID_); +create index ACT_HI_EXT_TASK_LOG_PROC_DEF_KEY on ACT_HI_EXT_TASK_LOG(PROC_DEF_KEY_); +create index ACT_HI_EXT_TASK_LOG_TENANT_ID on ACT_HI_EXT_TASK_LOG(TENANT_ID_); +create index ACT_IDX_HI_EXTTASKLOG_ERRORDET on ACT_HI_EXT_TASK_LOG(ERROR_DETAILS_ID_); +create index ACT_HI_EXT_TASK_LOG_RM_TIME on ACT_HI_EXT_TASK_LOG(REMOVAL_TIME_); + +create index ACT_IDX_HI_OP_LOG_ROOT_PI on ACT_HI_OP_LOG(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_OP_LOG_PROCINST on ACT_HI_OP_LOG(PROC_INST_ID_); +create index ACT_IDX_HI_OP_LOG_PROCDEF on ACT_HI_OP_LOG(PROC_DEF_ID_); +create index ACT_IDX_HI_OP_LOG_TASK on ACT_HI_OP_LOG(TASK_ID_); +create index ACT_IDX_HI_OP_LOG_RM_TIME on ACT_HI_OP_LOG(REMOVAL_TIME_); +create index ACT_IDX_HI_OP_LOG_TIMESTAMP on ACT_HI_OP_LOG(TIMESTAMP_); + +create index ACT_IDX_HI_ATTACHMENT_CONTENT on ACT_HI_ATTACHMENT(CONTENT_ID_); +create index ACT_IDX_HI_ATTACHMENT_ROOT_PI on ACT_HI_ATTACHMENT(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_ATTACHMENT_PROCINST on ACT_HI_ATTACHMENT(PROC_INST_ID_); +create index ACT_IDX_HI_ATTACHMENT_TASK on ACT_HI_ATTACHMENT(TASK_ID_); +create index ACT_IDX_HI_ATTACHMENT_RM_TIME on ACT_HI_ATTACHMENT(REMOVAL_TIME_); + +create index ACT_IDX_HI_COMMENT_TASK on ACT_HI_COMMENT(TASK_ID_); +create index ACT_IDX_HI_COMMENT_ROOT_PI on ACT_HI_COMMENT(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_COMMENT_PROCINST on ACT_HI_COMMENT(PROC_INST_ID_); +create index ACT_IDX_HI_COMMENT_RM_TIME on ACT_HI_COMMENT(REMOVAL_TIME_); +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +create table ACT_HI_CASEINST ( + ID_ varchar(64) not null, + CASE_INST_ID_ varchar(64) not null, + BUSINESS_KEY_ varchar(255), + CASE_DEF_ID_ varchar(64) not null, + CREATE_TIME_ datetime not null, + CLOSE_TIME_ datetime, + DURATION_ bigint, + STATE_ integer, + CREATE_USER_ID_ varchar(255), + SUPER_CASE_INSTANCE_ID_ varchar(64), + SUPER_PROCESS_INSTANCE_ID_ varchar(64), + TENANT_ID_ varchar(64), + primary key (ID_), + unique (CASE_INST_ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_HI_CASEACTINST ( + ID_ varchar(64) not null, + PARENT_ACT_INST_ID_ varchar(64), + CASE_DEF_ID_ varchar(64) not null, + CASE_INST_ID_ varchar(64) not null, + CASE_ACT_ID_ varchar(255) not null, + TASK_ID_ varchar(64), + CALL_PROC_INST_ID_ varchar(64), + CALL_CASE_INST_ID_ varchar(64), + CASE_ACT_NAME_ varchar(255), + CASE_ACT_TYPE_ varchar(255), + CREATE_TIME_ datetime not null, + END_TIME_ datetime, + DURATION_ bigint, + STATE_ integer, + REQUIRED_ boolean, + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create index ACT_IDX_HI_CAS_I_CLOSE on ACT_HI_CASEINST(CLOSE_TIME_); +create index ACT_IDX_HI_CAS_I_BUSKEY on ACT_HI_CASEINST(BUSINESS_KEY_); +create index ACT_IDX_HI_CAS_I_TENANT_ID on ACT_HI_CASEINST(TENANT_ID_); +create index ACT_IDX_HI_CAS_A_I_CREATE on ACT_HI_CASEACTINST(CREATE_TIME_); +create index ACT_IDX_HI_CAS_A_I_END on ACT_HI_CASEACTINST(END_TIME_); +create index ACT_IDX_HI_CAS_A_I_COMP on ACT_HI_CASEACTINST(CASE_ACT_ID_, END_TIME_, ID_); +create index ACT_IDX_HI_CAS_A_I_CASEINST on ACT_HI_CASEACTINST(CASE_INST_ID_, CASE_ACT_ID_); +create index ACT_IDX_HI_CAS_A_I_TENANT_ID on ACT_HI_CASEACTINST(TENANT_ID_); +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +-- create history decision instance table -- +create table ACT_HI_DECINST ( + ID_ varchar(64) NOT NULL, + DEC_DEF_ID_ varchar(64) NOT NULL, + DEC_DEF_KEY_ varchar(255) NOT NULL, + DEC_DEF_NAME_ varchar(255), + PROC_DEF_KEY_ varchar(255), + PROC_DEF_ID_ varchar(64), + PROC_INST_ID_ varchar(64), + CASE_DEF_KEY_ varchar(255), + CASE_DEF_ID_ varchar(64), + CASE_INST_ID_ varchar(64), + ACT_INST_ID_ varchar(64), + ACT_ID_ varchar(255), + EVAL_TIME_ datetime not null, + REMOVAL_TIME_ datetime, + COLLECT_VALUE_ double, + USER_ID_ varchar(255), + ROOT_DEC_INST_ID_ varchar(64), + ROOT_PROC_INST_ID_ varchar(64), + DEC_REQ_ID_ varchar(64), + DEC_REQ_KEY_ varchar(255), + TENANT_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- create history decision input table -- +create table ACT_HI_DEC_IN ( + ID_ varchar(64) NOT NULL, + DEC_INST_ID_ varchar(64) NOT NULL, + CLAUSE_ID_ varchar(64), + CLAUSE_NAME_ varchar(255), + VAR_TYPE_ varchar(100), + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + TENANT_ID_ varchar(64), + CREATE_TIME_ datetime, + ROOT_PROC_INST_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +-- create history decision output table -- +create table ACT_HI_DEC_OUT ( + ID_ varchar(64) NOT NULL, + DEC_INST_ID_ varchar(64) NOT NULL, + CLAUSE_ID_ varchar(64), + CLAUSE_NAME_ varchar(255), + RULE_ID_ varchar(64), + RULE_ORDER_ integer, + VAR_NAME_ varchar(255), + VAR_TYPE_ varchar(100), + BYTEARRAY_ID_ varchar(64), + DOUBLE_ double, + LONG_ bigint, + TEXT_ varchar(4000), + TEXT2_ varchar(4000), + TENANT_ID_ varchar(64), + CREATE_TIME_ datetime, + ROOT_PROC_INST_ID_ varchar(64), + REMOVAL_TIME_ datetime, + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + + +create index ACT_IDX_HI_DEC_INST_ID on ACT_HI_DECINST(DEC_DEF_ID_); +create index ACT_IDX_HI_DEC_INST_KEY on ACT_HI_DECINST(DEC_DEF_KEY_); +create index ACT_IDX_HI_DEC_INST_PI on ACT_HI_DECINST(PROC_INST_ID_); +create index ACT_IDX_HI_DEC_INST_CI on ACT_HI_DECINST(CASE_INST_ID_); +create index ACT_IDX_HI_DEC_INST_ACT on ACT_HI_DECINST(ACT_ID_); +create index ACT_IDX_HI_DEC_INST_ACT_INST on ACT_HI_DECINST(ACT_INST_ID_); +create index ACT_IDX_HI_DEC_INST_TIME on ACT_HI_DECINST(EVAL_TIME_); +create index ACT_IDX_HI_DEC_INST_TENANT_ID on ACT_HI_DECINST(TENANT_ID_); +create index ACT_IDX_HI_DEC_INST_ROOT_ID on ACT_HI_DECINST(ROOT_DEC_INST_ID_); +create index ACT_IDX_HI_DEC_INST_REQ_ID on ACT_HI_DECINST(DEC_REQ_ID_); +create index ACT_IDX_HI_DEC_INST_REQ_KEY on ACT_HI_DECINST(DEC_REQ_KEY_); +create index ACT_IDX_HI_DEC_INST_ROOT_PI on ACT_HI_DECINST(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_DEC_INST_RM_TIME on ACT_HI_DECINST(REMOVAL_TIME_); + +create index ACT_IDX_HI_DEC_IN_INST on ACT_HI_DEC_IN(DEC_INST_ID_); +create index ACT_IDX_HI_DEC_IN_CLAUSE on ACT_HI_DEC_IN(DEC_INST_ID_, CLAUSE_ID_); +create index ACT_IDX_HI_DEC_IN_ROOT_PI on ACT_HI_DEC_IN(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_DEC_IN_RM_TIME on ACT_HI_DEC_IN(REMOVAL_TIME_); + +create index ACT_IDX_HI_DEC_OUT_INST on ACT_HI_DEC_OUT(DEC_INST_ID_); +create index ACT_IDX_HI_DEC_OUT_RULE on ACT_HI_DEC_OUT(RULE_ORDER_, CLAUSE_ID_); +create index ACT_IDX_HI_DEC_OUT_ROOT_PI on ACT_HI_DEC_OUT(ROOT_PROC_INST_ID_); +create index ACT_IDX_HI_DEC_OUT_RM_TIME on ACT_HI_DEC_OUT(REMOVAL_TIME_); diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_identity_7.10.0.sql b/otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_identity_7.10.0.sql new file mode 100644 index 0000000..113240d --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/scripts/mysql_identity_7.10.0.sql @@ -0,0 +1,103 @@ +-- +-- Copyright © 2012 - 2018 camunda services GmbH and various authors (info@camunda.com) +-- +-- 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. +-- + +create table ACT_ID_GROUP ( + ID_ varchar(64), + REV_ integer, + NAME_ varchar(255), + TYPE_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_ID_MEMBERSHIP ( + USER_ID_ varchar(64), + GROUP_ID_ varchar(64), + primary key (USER_ID_, GROUP_ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_ID_USER ( + ID_ varchar(64), + REV_ integer, + FIRST_ varchar(255), + LAST_ varchar(255), + EMAIL_ varchar(255), + PWD_ varchar(255), + SALT_ varchar(255), + LOCK_EXP_TIME_ timestamp NULL, + ATTEMPTS_ integer, + PICTURE_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_ID_INFO ( + ID_ varchar(64), + REV_ integer, + USER_ID_ varchar(64), + TYPE_ varchar(64), + KEY_ varchar(255), + VALUE_ varchar(255), + PASSWORD_ LONGBLOB, + PARENT_ID_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_ID_TENANT ( + ID_ varchar(64), + REV_ integer, + NAME_ varchar(255), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +create table ACT_ID_TENANT_MEMBER ( + ID_ varchar(64) not null, + TENANT_ID_ varchar(64) not null, + USER_ID_ varchar(64), + GROUP_ID_ varchar(64), + primary key (ID_) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_bin; + +alter table ACT_ID_MEMBERSHIP + add constraint ACT_FK_MEMB_GROUP + foreign key (GROUP_ID_) + references ACT_ID_GROUP (ID_); + +alter table ACT_ID_MEMBERSHIP + add constraint ACT_FK_MEMB_USER + foreign key (USER_ID_) + references ACT_ID_USER (ID_); + +alter table ACT_ID_TENANT_MEMBER + add constraint ACT_UNIQ_TENANT_MEMB_USER + unique (TENANT_ID_, USER_ID_); + +alter table ACT_ID_TENANT_MEMBER + add constraint ACT_UNIQ_TENANT_MEMB_GROUP + unique (TENANT_ID_, GROUP_ID_); + +alter table ACT_ID_TENANT_MEMBER + add constraint ACT_FK_TENANT_MEMB + foreign key (TENANT_ID_) + references ACT_ID_TENANT (ID_); + +alter table ACT_ID_TENANT_MEMBER + add constraint ACT_FK_TENANT_MEMB_USER + foreign key (USER_ID_) + references ACT_ID_USER (ID_); + +alter table ACT_ID_TENANT_MEMBER + add constraint ACT_FK_TENANT_MEMB_GROUP + foreign key (GROUP_ID_) + references ACT_ID_GROUP (ID_); diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/templates/configmap.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/templates/configmap.yaml new file mode 100644 index 0000000..858345f --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/templates/configmap.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name : {{ .Values.global.mysqldb.appName }}-init-scripts + +data: + {{- $files := .Files }} + {{- range $key, $value := .Files }} + {{- if hasPrefix "scripts/" $key }} {{/* only when in scripts/ */}} + {{ $key | trimPrefix "scripts/" }}: {{ $files.Get $key | quote }} {{/* adapt $key as desired */}} + {{- end }} + {{- end }} diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/templates/deployment.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/templates/deployment.yaml new file mode 100644 index 0000000..41f8d33 --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/templates/deployment.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment + +metadata: + name: {{ .Values.global.mysqldb.appName }} + namespace: {{.Values.global.mysqldb.namespace }} + labels: + app: {{ .Values.global.mysqldb.appName }} + version: {{.Values.global.mysqldb.version }} + +spec: + strategy: + # indicate which strategy we want for rolling update + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + replicas: {{ .Values.global.mysqldb.replicas }} + selector: + matchLabels: + app: {{ .Values.global.mysqldb.appName }} + version: {{.Values.global.mysqldb.version }} + template: + metadata: + labels: + app: {{ .Values.global.mysqldb.appName }} + version: {{.Values.global.mysqldb.version }} + spec: + serviceAccountName: default + containers: + - name: {{ .Values.global.mysqldb.appName }} + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + image: {{ .Values.global.mysqldb.image.image }}:{{ .Values.global.mysqldb.image.tag }} + imagePullPolicy: Always + resources: + limits: + memory: {{ .Values.global.mysqldb.resources.limits.memory }} + cpu: {{ .Values.global.mysqldb.resources.limits.cpu }} + requests: + memory: {{ .Values.global.mysqldb.resources.requests.memory }} + cpu: {{ .Values.global.mysqldb.resources.requests.cpu }} + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.global.mysqldb.appName }} + key: mysql_root_password + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.global.mysqldb.appName }} + key: mysql_password + - name: MYSQL_USER + value: {{ .Values.global.mysqldb.mysqlUser | quote }} + - name: MYSQL_DATABASE + value: {{ .Values.global.mysqldb.mysqlDatabase | quote }} + - name: TZ + value: {{ .Values.timezone }} + ports: + - name: {{ .Values.global.mysqldb.appName }} + containerPort: 3306 + livenessProbe: + exec: + command: + - sh + - -c + - "mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}" + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + readinessProbe: + exec: + command: + - sh + - -c + - "mysqladmin ping -u root -p${MYSQL_ROOT_PASSWORD}" + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + lifecycle: + postStart: + exec: + command: ["/bin/bash", "-c", "cd data/scripts;./init_db.sh"] + volumeMounts: + - name: custom-init-scripts + mountPath: /data/scripts + volumes: + - name: custom-init-scripts + configMap: + name: {{ .Values.global.mysqldb.appName }}-init-scripts + defaultMode: 0755 + restartPolicy: Always + diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/templates/ingress.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/templates/ingress.yaml new file mode 100644 index 0000000..4abb02c --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/templates/ingress.yaml @@ -0,0 +1,30 @@ +# Need Updates to configure the connectivity when we deploy other microservices to connect to mysqldb + +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ .Values.global.mysqldb.appName }} + namespace: {{.Values.global.mysqldb.namespace }} + labels: + app: {{ .Values.global.mysqldb.appName }} + version: {{.Values.global.mysqldb.version }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:$service_port; + grpc_set_header l5d-dst-override $service_name.$namespace.svc.cluster.local:$service_port; +spec: + tls: + - hosts: + - {{ .Values.global.mysqldb.nodeApi.host }} + secretName: {{.Values.global.mysqldb.certName }} + rules: + - host: {{ .Values.global.mysqldb.nodeApi.host }} + http: + paths: + - path: /mysqldb/(.*) + backend: + serviceName: {{ .Values.global.mysqldb.appName }} + servicePort: {{ .Values.global.mysqldb.port }} \ No newline at end of file diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/templates/secret.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/templates/secret.yaml new file mode 100644 index 0000000..80834a7 --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/templates/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.global.mysqldb.appName }} +type: opaque +data: + mysql_root_password: {{ .Values.global.mysqldb.mysqlRootPassword | b64enc }} + mysql_password: {{ .Values.global.mysqldb.mysqlPassword | b64enc }} diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/templates/service.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/templates/service.yaml new file mode 100644 index 0000000..7a9c79a --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.mysqldb.appName }} + namespace: {{ .Values.global.mysqldb.namespace }} + labels: + app: {{ .Values.global.mysqldb.appName }} + version: {{ .Values.global.mysqldb.version }} + annotations: + service.beta.kubernetes.io/azure-load-balancer-internal: "true" +spec: + type: LoadBalancer + ports: + - port: {{ .Values.global.mysqldb.port }} + protocol: TCP + targetPort: {{ .Values.global.mysqldb.targetPort }} + selector: + app: {{ .Values.global.mysqldb.appName }} + version: {{ .Values.global.mysqldb.version }} diff --git a/otf-helm/otf/charts/databases/charts/mysqldb/values.yaml b/otf-helm/otf/charts/databases/charts/mysqldb/values.yaml new file mode 100644 index 0000000..1c0457b --- /dev/null +++ b/otf-helm/otf/charts/databases/charts/mysqldb/values.yaml @@ -0,0 +1,233 @@ +# Values yaml file for reference from the github. - currently not used. + +## mysql image version +## ref: https://hub.docker.com/r/library/mysql/tags/ +## +image: "mysql" +imageTag: "5.7.26" + +strategy: + type: Recreate + +busybox: + image: "busybox" + tag: "1.29.3" + +testFramework: + enabled: true + image: "dduportal/bats" + tag: "0.4.0" + +## Specify password for root user +## +## Default: random 10 character string +# mysqlRootPassword: testing + +## Create a database user +## +# mysqlUser: +## Default: random 10 character string +# mysqlPassword: + +## Allow unauthenticated access, uncomment to enable +## +# mysqlAllowEmptyPassword: true + +## Create a database +## +# mysqlDatabase: + +## Specify an imagePullPolicy (Required) +## It's recommended to change this to 'Always' if the image tag is 'latest' +## ref: http://kubernetes.io/docs/user-guide/images/#updating-images +## +imagePullPolicy: IfNotPresent + +## Additionnal arguments that are passed to the MySQL container. +## For example use --default-authentication-plugin=mysql_native_password if older clients need to +## connect to a MySQL 8 instance. +args: [] + +extraVolumes: | + # - name: extras + # emptyDir: {} + +extraVolumeMounts: | + # - name: extras + # mountPath: /usr/share/extras + # readOnly: true + +extraInitContainers: | + # - name: do-something + # image: busybox + # command: ['do', 'something'] + +# Optionally specify an array of imagePullSecrets. +# Secrets must be manually created in the namespace. +# ref: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod +# imagePullSecrets: + # - name: myRegistryKeySecretName + +## Node selector +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector +nodeSelector: {} + +## Affinity +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +affinity: {} + +## Tolerations for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +livenessProbe: + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + +readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + +## Persist data to a persistent volume +persistence: + enabled: true + ## database data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessMode: ReadWriteOnce + size: 8Gi + annotations: {} + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## Security context +securityContext: + enabled: false + runAsUser: 999 + fsGroup: 999 + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + requests: + memory: 256Mi + cpu: 100m + +# Custom mysql configuration files path +configurationFilesPath: /etc/mysql/conf.d/ + +# Custom mysql configuration files used to override default mysql settings +configurationFiles: {} +# mysql.cnf: |- +# [mysqld] +# skip-name-resolve +# ssl-ca=/ssl/ca.pem +# ssl-cert=/ssl/server-cert.pem +# ssl-key=/ssl/server-key.pem + +# Custom mysql init SQL files used to initialize the database +initializationFiles: {} +# first-db.sql: |- +# CREATE DATABASE IF NOT EXISTS first DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; +# second-db.sql: |- +# CREATE DATABASE IF NOT EXISTS second DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; + +metrics: + enabled: false + image: prom/mysqld-exporter + imageTag: v0.10.0 + imagePullPolicy: IfNotPresent + resources: {} + annotations: {} + # prometheus.io/scrape: "true" + # prometheus.io/port: "9104" + livenessProbe: + initialDelaySeconds: 15 + timeoutSeconds: 5 + readinessProbe: + initialDelaySeconds: 5 + timeoutSeconds: 1 + flags: [] + serviceMonitor: + enabled: false + additionalLabels: {} + +## Configure the service +## ref: http://kubernetes.io/docs/user-guide/services/ +service: + annotations: {} + ## Specify a service type + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types + type: ClusterIP + port: 3306 + # nodePort: 32000 + # loadBalancerIP: + +## Pods Service Account +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the mariadb.fullname template + # name: + +ssl: + enabled: false + secret: mysql-ssl-certs + certificates: +# - name: mysql-ssl-certs +# ca: |- +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- +# cert: |- +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- +# key: |- +# -----BEGIN RSA PRIVATE KEY----- +# ... +# -----END RSA PRIVATE KEY----- + +## Populates the 'TZ' system timezone environment variable +## ref: https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html +## +## Default: nil (mysql will use image's default timezone, normally UTC) +## Example: 'Australia/Sydney' +# timezone: + +# Deployment Annotations +deploymentAnnotations: {} + +# To be added to the database server pod(s) +podAnnotations: {} +podLabels: {} + +## Set pod priorityClassName +# priorityClassName: {} + + +## Init container resources defaults +initContainer: + resources: + requests: + memory: 10Mi + cpu: 10m diff --git a/otf-helm/otf/values/development.yaml b/otf-helm/otf/values/development.yaml new file mode 100644 index 0000000..894d035 --- /dev/null +++ b/otf-helm/otf/values/development.yaml @@ -0,0 +1 @@ +#These are the development environment specific values diff --git a/otf-helm/readme.md b/otf-helm/readme.md new file mode 100644 index 0000000..c9db864 --- /dev/null +++ b/otf-helm/readme.md @@ -0,0 +1,5 @@ +Open Test Framework + +Use these heml charts to deploy otf + +helm install otf \ No newline at end of file -- 2.16.6