From: Rohan Patel Date: Mon, 23 Sep 2019 19:04:19 +0000 (-0400) Subject: adding vth code as well as aaf and cert config X-Git-Tag: 0.0.1~15 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?p=it%2Fotf.git;a=commitdiff_plain;h=f49bd1efeaaddd4891c1f329b18d8cfb28b3e75b adding vth code as well as aaf and cert config Change-Id: Ib137099ccac3109b9b38deab1c42ffade5d6a303 Signed-off-by: Rohan Patel --- diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..aa73a11 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,28 @@ +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the "Software License"); +you may not use this software except in compliance with the Software +License. You may obtain a copy of the Software License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the Software License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Software License for the specific language governing permissions +and limitations under the Software License. + + + +Unless otherwise specified, all documentation contained herein is licensed +under the Creative Commons License, Attribution 4.0 Intl. (the +"Documentation License"); you may not use this documentation except in +compliance with the Documentation License. You may obtain a copy of the +Documentation License at + +https://creativecommons.org/licenses/by/4.0/ + +Unless required by applicable law or agreed to in writing, documentation +distributed under the Documentation License is distributed on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the Documentation License for the specific language governing +permissions and limitations under the Documentation License. \ No newline at end of file diff --git a/otf-aaf-credential-generator/Jenkinsfile b/otf-aaf-credential-generator/Jenkinsfile new file mode 100644 index 0000000..c2d0b5c --- /dev/null +++ b/otf-aaf-credential-generator/Jenkinsfile @@ -0,0 +1,112 @@ +#!/usr/bin/env groovy + + +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: "id"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-onap-otf"] +]]]) + + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + ARTIFACT_ID="otf-aaf-credential-generator" + echo "Tiller Namespace: " + TILLER_NAMESPACE + + withEnv(["PATH=${env.PATH}:${tool 'jdk180'}:${env.WORKSPACE}/linux-amd64", "JAVA_HOME=${tool 'jdk180'}","HELM_HOME=${env.WORKSPACE}"]) { + + echo "PATH=${env.PATH}" + echo "JAVA_HOME=${env.JAVA_HOME}" + echo "HELM_HOME=${env.HELM_HOME}" + + wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [ + [fileId: 'maven-settings.xml', variable: 'MAVEN_SETTINGS'] + ]]) { + + 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.8.2-linux-amd64.tar.gz + tar -xf helm-v2.8.2-linux-amd64.tar.gz + rm helm-v2.8.2-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('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')]) { + + //Note: KEYFILE_PATH must be within helm/$ARTIFACT_ID and must be relative path starting from that directory + sh """ + + echo "Remove old key info" + rm -rf keyfile; + rm -rf digest.txt + rm -rf $ARTIFACT_ID/keyfile + + ls + echo "Create keyfile and digest" + java -jar ../aaf_cadi_core_2.1.10_SNAPSHOT.jar keygen keyfile + java -jar ../aaf_cadi_core_2.1.10_SNAPSHOT.jar digest $PASSWORD keyfile > digest.txt 2>&1 + + cp keyfile $ARTIFACT_ID + KEYFILE_PATH=keyfile + DIGEST="enc:" + DIGEST+=\$(cat digest.txt) + echo \$DIGEST + echo "Validate Yaml" + helm lint $ARTIFACT_ID + + echo "View Helm Templates" + helm template $ARTIFACT_ID --set Secret.aafId=$USERNAME --set Secret.aafPassword=\$DIGEST --set Secret.aafMechPassword=$PASSWORD --set keyfilePath=\$KEYFILE_PATH + + echo "Perform Dry Run Of Install" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID --set Secret.aafId=$USERNAME --set Secret.aafPassword=\$DIGEST --set Secret.aafMechPassword=$PASSWORD --set keyfilePath=\$KEYFILE_PATH + + echo "Helm Install/Upgrade" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID --set Secret.aafId=$USERNAME --set Secret.aafPassword=\$DIGEST --set Secret.aafMechPassword=$PASSWORD --set keyfilePath=\$KEYFILE_PATH + + rm -rf $ARTIFACT_ID/keyfile + rm -rf keyfile + rm -rf digest.txt + """ + } + } + + } + } + } + } + } +} \ No newline at end of file diff --git a/otf-aaf-credential-generator/aaf_cadi_core_2.1.10_SNAPSHOT.jar b/otf-aaf-credential-generator/aaf_cadi_core_2.1.10_SNAPSHOT.jar new file mode 100644 index 0000000..9c7b105 Binary files /dev/null and b/otf-aaf-credential-generator/aaf_cadi_core_2.1.10_SNAPSHOT.jar differ diff --git a/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/.helmignore b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/.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/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/Chart.yaml b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/Chart.yaml new file mode 100644 index 0000000..985ce22 --- /dev/null +++ b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for OTF secret shared secret files +name: otf-aaf-credential-generator +version: 0.1.0 diff --git a/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/templates/secret.yaml b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/templates/secret.yaml new file mode 100644 index 0000000..44a4be1 --- /dev/null +++ b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/templates/secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + cadi_keyfile: {{ .Files.Get .Values.keyfilePath | b64enc }} + aaf_id: {{ .Values.Secret.aafId | b64enc}} + aaf_password: {{ .Values.Secret.aafPassword | b64enc}} + aaf_mech_password: {{ .Values.Secret.aafMechPassword | b64enc}} + keyfile_secret_path: {{.Values.Secret.keyfileSecretPath | b64enc}} diff --git a/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/values.yaml b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/values.yaml new file mode 100644 index 0000000..003e3ba --- /dev/null +++ b/otf-aaf-credential-generator/helm/otf-aaf-credential-generator/values.yaml @@ -0,0 +1,9 @@ +appName: otf-aaf-credential-generator + +keyfilePath: keyfile + +Secret: + keyfileSecretPath: /opt/secret/keyfile + aafId: id + aafPassword: pass + aafMechPassword: mechPass \ No newline at end of file diff --git a/otf-cert-secret-builder/Jenkinsfile b/otf-cert-secret-builder/Jenkinsfile new file mode 100644 index 0000000..e9f31ff --- /dev/null +++ b/otf-cert-secret-builder/Jenkinsfile @@ -0,0 +1,137 @@ +#!/usr/bin/env groovy + + +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: "id"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-onar-otf"], + [$class: 'hudson.model.StringParameterDefinition', name: 'PKCS12_CERT', defaultValue: "otf_ssl_pkcs12_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'PKCS12_KEY', defaultValue: "server_ssl_key_store_password"], + [$class: 'hudson.model.StringParameterDefinition', name: 'PEM_CERT', defaultValue: "otf_ssl_pem_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'PEM_KEY', defaultValue: "otf_ssl_pem_key_dev"] + + +]]]) + + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + ARTIFACT_ID="otf-cert-secret-builder" + echo "Tiller Namespace: " + TILLER_NAMESPACE + + withEnv(["PATH=${env.PATH}:${tool 'jdk180'}:${env.WORKSPACE}/linux-amd64", "JAVA_HOME=${tool 'jdk180'}","HELM_HOME=${env.WORKSPACE}"]) { + + echo "PATH=${env.PATH}" + echo "JAVA_HOME=${env.JAVA_HOME}" + echo "HELM_HOME=${env.HELM_HOME}" + + wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [ + [fileId: 'maven-settings.xml', variable: 'MAVEN_SETTINGS'] + ]]) { + + 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.8.2-linux-amd64.tar.gz + tar -xf helm-v2.8.2-linux-amd64.tar.gz + rm helm-v2.8.2-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('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'), + file(credentialsId: PKCS12_CERT, variable: 'VAR_PKCS12_CERT'), + string(credentialsId: PKCS12_KEY, variable: 'VAR_PKCS12_KEY'), + file(credentialsId: PEM_CERT, variable: 'VAR_PEM_CERT'), + file(credentialsId: PEM_KEY, variable: 'VAR_PEM_KEY'), + file(credentialsId: 'PRIVATE_KEY', variable: 'VAR_PRIVATE_KEY'), + usernamePassword(credentialsId: 'PRIVATE_KEY_USER_PASS', usernameVariable: 'PRIVATE_KEY_USERNAME', passwordVariable: 'PRIVATE_KEY_PASSPHRASE') + ]) { + + sh """ + + cp $VAR_PKCS12_CERT $ARTIFACT_ID + cp $VAR_PEM_CERT $ARTIFACT_ID + cp $VAR_PEM_KEY $ARTIFACT_ID + cp $VAR_PRIVATE_KEY $ARTIFACT_ID + FILE_PKCS12_CERT=`basename $VAR_PKCS12_CERT` + FILE_PEM_CERT=`basename $VAR_PEM_CERT` + FILE_PEM_KEY=`basename $VAR_PEM_KEY` + FILE_PRIVATE_KEY=`basename $VAR_PRIVATE_KEY` + + echo "Validate Yaml" + helm lint $ARTIFACT_ID + + echo "View Helm Templates" + helm template $ARTIFACT_ID \ + --set Secret.PKCS12_CERT=\$FILE_PKCS12_CERT \ + --set Secret.PKCS12_KEY=$VAR_PKCS12_KEY \ + --set Secret.PEM_CERT=\$FILE_PEM_CERT \ + --set Secret.PEM_KEY=\$FILE_PEM_KEY \ + --set Secret.privateKey.key=\$FILE_PRIVATE_KEY \ + --set Secret.privateKey.username=$PRIVATE_KEY_USERNAME \ + --set Secret.privateKey.passphrase=$PRIVATE_KEY_PASSPHRASE \ + + echo "Perform Dry Run Of Install" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \ + --set Secret.PKCS12_CERT=\$FILE_PKCS12_CERT \ + --set Secret.PKCS12_KEY=$VAR_PKCS12_KEY \ + --set Secret.PEM_CERT=\$FILE_PEM_CERT \ + --set Secret.PEM_KEY=\$FILE_PEM_KEY \ + --set Secret.privateKey.key=\$FILE_PRIVATE_KEY \ + --set Secret.privateKey.username=$PRIVATE_KEY_USERNAME \ + --set Secret.privateKey.passphrase=$PRIVATE_KEY_PASSPHRASE \ + + echo "Helm Install/Upgrade" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \ + --set Secret.PKCS12_CERT=\$FILE_PKCS12_CERT \ + --set Secret.PKCS12_KEY=$VAR_PKCS12_KEY \ + --set Secret.PEM_CERT=\$FILE_PEM_CERT \ + --set Secret.PEM_KEY=\$FILE_PEM_KEY \ + --set Secret.privateKey.key=\$FILE_PRIVATE_KEY \ + --set Secret.privateKey.username=$PRIVATE_KEY_USERNAME \ + --set Secret.privateKey.passphrase=$PRIVATE_KEY_PASSPHRASE \ + + """ + } + } + + } + } + } + } + } +} diff --git a/otf-cert-secret-builder/helm/otf-cert-secret-builder/.helmignore b/otf-cert-secret-builder/helm/otf-cert-secret-builder/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/otf-cert-secret-builder/helm/otf-cert-secret-builder/.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/otf-cert-secret-builder/helm/otf-cert-secret-builder/Chart.yaml b/otf-cert-secret-builder/helm/otf-cert-secret-builder/Chart.yaml new file mode 100644 index 0000000..aada663 --- /dev/null +++ b/otf-cert-secret-builder/helm/otf-cert-secret-builder/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for OTF certificate secret shared secret files +name: otf-cert-secret-builder +version: 0.1.0 diff --git a/otf-cert-secret-builder/helm/otf-cert-secret-builder/templates/secret.yaml b/otf-cert-secret-builder/helm/otf-cert-secret-builder/templates/secret.yaml new file mode 100644 index 0000000..2179d4e --- /dev/null +++ b/otf-cert-secret-builder/helm/otf-cert-secret-builder/templates/secret.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + PKCS12_CERT: {{ .Files.Get .Values.Secret.PKCS12_CERT | b64enc }} + PKCS12_KEY: {{ .Values.Secret.PKCS12_KEY | b64enc}} + PEM_CERT: {{ .Files.Get .Values.Secret.PEM_CERT | b64enc}} + PEM_KEY: {{ .Files.Get .Values.Secret.PEM_KEY | b64enc}} + private_key: {{ .Files.Get .Values.Secret.privateKey.key | b64enc}} + private_key_username: {{ .Values.Secret.privateKey.username | b64enc}} + private_key_passphrase: {{ .Values.Secret.privateKey.passphrase | b64enc}} diff --git a/otf-cert-secret-builder/helm/otf-cert-secret-builder/values.yaml b/otf-cert-secret-builder/helm/otf-cert-secret-builder/values.yaml new file mode 100644 index 0000000..6898616 --- /dev/null +++ b/otf-cert-secret-builder/helm/otf-cert-secret-builder/values.yaml @@ -0,0 +1,12 @@ +appName: otf-cert-secret-builder + +Secret: + PKCS12_CERT: temp + PKCS12_KEY: temp + PEM_CERT: temp + PEM_KEY: temp + SSH_PRIVATE_KEY: temp + privateKey: + username: id + passphrase: passphrase + key: tmp diff --git a/otf-ping-test-head/.gitignore b/otf-ping-test-head/.gitignore new file mode 100644 index 0000000..28ca722 --- /dev/null +++ b/otf-ping-test-head/.gitignore @@ -0,0 +1,5 @@ +otf.pem +privateKey.pem +*.log +*.pem +/otf diff --git a/otf-ping-test-head/Dockerfile b/otf-ping-test-head/Dockerfile new file mode 100644 index 0000000..0ebb8f8 --- /dev/null +++ b/otf-ping-test-head/Dockerfile @@ -0,0 +1,17 @@ +FROM python:2.7 + +ARG HTTP_PROXY="localhost:8080" +ARG HTTPS_PROXY="localhost:8080" +ARG http_proxy="localhost:8080" +ARG https_proxy="localhost:8080" + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD otfPingTestHead.py otfPingTestHead.py + +RUN mkdir -p /otf/logs + +RUN python -m pip install -r pip-requirements.txt + +ENTRYPOINT ["python", "otfPingTestHead.py"] diff --git a/otf-ping-test-head/Jenkinsfile b/otf-ping-test-head/Jenkinsfile new file mode 100644 index 0000000..8143fcf --- /dev/null +++ b/otf-ping-test-head/Jenkinsfile @@ -0,0 +1,141 @@ +#!/usr/bin/env groovy + + +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: "id_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-oran-otf"] +]]]) + + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="otf-ping-test-head"; + VERSION="Blitzcrank.1.1"; + NAMESPACE="org-oran-otf" + DOCKER_REGISTRY="registry.hub.docker.io" + + if( ENV.equalsIgnoreCase("dev") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + "/" + 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")){ + + 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.8.2-linux-amd64.tar.gz + tar -xf helm-v2.8.2-linux-amd64.tar.gz + rm helm-v2.8.2-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('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/otf-ping-test-head/LICENSE.txt b/otf-ping-test-head/LICENSE.txt new file mode 100644 index 0000000..aa73a11 --- /dev/null +++ b/otf-ping-test-head/LICENSE.txt @@ -0,0 +1,28 @@ +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the "Software License"); +you may not use this software except in compliance with the Software +License. You may obtain a copy of the Software License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the Software License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Software License for the specific language governing permissions +and limitations under the Software License. + + + +Unless otherwise specified, all documentation contained herein is licensed +under the Creative Commons License, Attribution 4.0 Intl. (the +"Documentation License"); you may not use this documentation except in +compliance with the Documentation License. You may obtain a copy of the +Documentation License at + +https://creativecommons.org/licenses/by/4.0/ + +Unless required by applicable law or agreed to in writing, documentation +distributed under the Documentation License is distributed on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the Documentation License for the specific language governing +permissions and limitations under the Documentation License. \ No newline at end of file diff --git a/otf-ping-test-head/helm/otf-ping-test-head/.helmignore b/otf-ping-test-head/helm/otf-ping-test-head/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/otf-ping-test-head/helm/otf-ping-test-head/.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/otf-ping-test-head/helm/otf-ping-test-head/Chart.yaml b/otf-ping-test-head/helm/otf-ping-test-head/Chart.yaml new file mode 100644 index 0000000..75d7003 --- /dev/null +++ b/otf-ping-test-head/helm/otf-ping-test-head/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the Ping Server Virtual Test Head +name: otf-ping-test-head +version: 0.0.1 diff --git a/otf-ping-test-head/helm/otf-ping-test-head/templates/deployment.yaml b/otf-ping-test-head/helm/otf-ping-test-head/templates/deployment.yaml new file mode 100644 index 0000000..b6cc154 --- /dev/null +++ b/otf-ping-test-head/helm/otf-ping-test-head/templates/deployment.yaml @@ -0,0 +1,113 @@ +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")}} + {{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: 5000 + nodePort: {{.Values.nodePort}} + protocol: TCP + {{ if eq .Values.env "st"}} + 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}} + 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: HTTPS + httpHeaders: + - name: X-Custom-Header + value: Alive + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: {{.Values.health}} + port: http + scheme: HTTPS + httpHeaders: + - name: X-Custom-Header + value: Ready + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + restartPolicy: Always diff --git a/otf-ping-test-head/helm/otf-ping-test-head/templates/service.yaml b/otf-ping-test-head/helm/otf-ping-test-head/templates/service.yaml new file mode 100644 index 0000000..f3bcfab --- /dev/null +++ b/otf-ping-test-head/helm/otf-ping-test-head/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: 5000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/otf-ping-test-head/helm/otf-ping-test-head/values.yaml b/otf-ping-test-head/helm/otf-ping-test-head/values.yaml new file mode 100644 index 0000000..bb24ca1 --- /dev/null +++ b/otf-ping-test-head/helm/otf-ping-test-head/values.yaml @@ -0,0 +1,12 @@ +appName: otf-ping-test-head +env: dev +version: 0.0.1-SNAPSHOT +image: otf-ping-test-head:0.0.1-SNAPSHOT +namespace: org-oran-otf +nodePort: 32223 +replicas: 1 +health : /otf/vth/ping/v1/health +sharedCert: otf-cert-secret-builder +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv diff --git a/otf-ping-test-head/opt/cert/certInfo.txt b/otf-ping-test-head/opt/cert/certInfo.txt new file mode 100644 index 0000000..bb8fa5f --- /dev/null +++ b/otf-ping-test-head/opt/cert/certInfo.txt @@ -0,0 +1 @@ +add certs here. requires two pem files (Cert and key) diff --git a/otf-ping-test-head/otfPingTestHead.py b/otf-ping-test-head/otfPingTestHead.py new file mode 100644 index 0000000..1163034 --- /dev/null +++ b/otf-ping-test-head/otfPingTestHead.py @@ -0,0 +1,196 @@ +# 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. # +################################################################################ + +from flask import Flask, request, make_response, jsonify, g +import json +import uuid +import traceback +import pyping +import paramiko +import socket +import os +import subprocess +import datetime +import logging +from logging import FileHandler + +#redirect http to https +app = Flask(__name__) + + +# Prevents print statement every time an endpoint is triggered. +logging.getLogger("werkzeug").setLevel(logging.WARNING) + +def unix_time_millis(dt): + epoch = datetime.datetime.utcfromtimestamp(0) + return (dt - epoch).total_seconds() * 1000.0 + +def pingServer(targetHost): + try: + response = subprocess.check_output( + ['ping', '-c', '1', targetHost], # execute the ping command + stderr = subprocess.STDOUT, # retrieve all the output + universal_newlines = True # return as string + ) + except subprocess.CalledProcessError as e: + app.logger.error(e) + app.logger.error('failed getting response from ' + str(targetHost)) + response = None + + return response + +@app.route("/otf/vth/ping/v1/health", methods = ['GET']) +def getHealth(): + return "UP" + +@app.route('/otf/vth/sample/v1', methods = ['POST']) +def sample(): + startTime = unix_time_millis(datetime.datetime.now()) + responseData = { + "vthResponse": { + "testDurationMS": "", + "dateTimeUTC": "", + "abstractMessage": "Success", + "resultData": {} + } + } + responseData['vthResponse']['dateTimeUTC'] = str(datetime.datetime.now()) + endTime = unix_time_millis(datetime.datetime.now()) + responseData['vthResponse']['testDurationMS'] = endTime - startTime + responseData['vthResponse']['resultData']['result'] = "Executed test successfully in " + str(responseData['vthResponse']['testDurationMS']) + " milliseconds." + app.logger.info('hit sample endpoint. response: ' + str(responseData)) + return jsonify(responseData) + +@app.route('/otf/vth/ping/v1', methods = ['POST']) +def testHead(): + responseData = { + "vthResponse": { + "testDurationMS": "", + "dateTimeUTC": "", + "abstractMessage": "", + "resultData": {} + } + } + + responseData['vthResponse']['dateTimeUTC'] = str(datetime.datetime.now()) + startTime = unix_time_millis(datetime.datetime.now()) + + try: + if not request.is_json: + raise ValueError('Request must be a valid JSON object.') + + requestData = request.get_json() + app.logger.info('ping endpoint. request: ' + str(requestData)) + + if 'vthInput' in requestData: + vthInput = requestData['vthInput'] + expectedKeys = ['vthName', 'testConfig', 'testData'] + receivedKeys = vthInput.keys(); + testData = "" + testConfig = "" + + if sorted(expectedKeys) == sorted(receivedKeys): + testData = vthInput['testData'] + + # Check if a target host is provided. + if 'targetHost' not in testData: + raise KeyError('targetHost is required to ping server.') + + # Check if the target host IP address is in the correct format. + # This excludes IPv6. Use IPy to check both IPv6/IPv4. + try: + socket.inet_aton(testData['targetHost']) + except socket.error: + raise ValueError('Invalid IP address assigned to targetHost') + + # Don't use a jump server by default. + if 'useJumpServer' not in testData: + testData['useJumpServer'] = False + else: + raise ValueError('Missing one or more expected keys: {expectedKeys}.'.format(expectedKeys = expectedKeys)) + + if testData['useJumpServer'] == False: + responseData['vthResponse']['resultData']['result'] = pingServer(testData['targetHost']) + else: + testConfig = vthInput['testConfig'] + + if 'jumpServer' not in testConfig: + raise KeyError('Cannot use jump server when jumpServer key is missing.') + + jumpServer = testConfig['jumpServer'] + + if 'host' not in testConfig['jumpServer']: + raise KeyError('Missing host value in jumpServer.') + + host = testConfig['jumpServer']['host'] + + if 'credentials' not in jumpServer: + raise KeyError('Missing credentials in jumpServer.') + + credentials = jumpServer['credentials'] + + if 'username' not in credentials: + raise KeyError('Missing username in credentials.') + + username = credentials['username'] + + if 'password' not in credentials: + raise KeyError('Missing password in credentials.') + + password = credentials['password'] + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(host, username = username, password = password) + command = "ping -c 1 " + testData['targetHost'] + ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command) + output = ssh_stdout.read() + error = ssh_stderr.read() + + responseData['vthResponse']['resultData']['result'] = output + else: + raise KeyError('Missing vthInput parameter(s)') + + # record the end time of the test + endTime = unix_time_millis(datetime.datetime.now()) + + # Calculate the total duration of the test + totalTime = endTime - startTime + + # Set the test duration in the result + responseData['vthResponse']['testDurationMS'] = totalTime + + responseData['vthResponse']['abstractMessage'] = 'Result from pinging {host}'.format(host = testData['targetHost']) + app.logger.info('ping endpoint. response: ' + str(responseData)) + + return jsonify(responseData) + except Exception as e: + app.logger.info(e) + responseData['vthResponse']['abstractMessage'] = str(e) + resp = make_response(json.dumps(responseData)) + endTime = unix_time_millis(datetime.datetime.now()) + + totalTime = endTime - startTime + return resp + +if __name__ == '__main__': + logHandler = FileHandler('otf/logs/pingVTH.log', mode='a') + # logHandler = FileHandler('pingVTH.log', mode='a') + logHandler.setLevel(logging.INFO) + app.logger.setLevel(logging.INFO) + app.logger.addHandler(logHandler) + 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 = 5000) diff --git a/otf-ping-test-head/pip-requirements.txt b/otf-ping-test-head/pip-requirements.txt new file mode 100644 index 0000000..4a744ee --- /dev/null +++ b/otf-ping-test-head/pip-requirements.txt @@ -0,0 +1,6 @@ +flask +flask-cors +pyping +paramiko +FLASK +FLASK-CORS diff --git a/otf-robot-test-head/.gitignore b/otf-robot-test-head/.gitignore new file mode 100644 index 0000000..d5f7c3d --- /dev/null +++ b/otf-robot-test-head/.gitignore @@ -0,0 +1,211 @@ +# Application-specific stuff +files/ +temp/ +opt/cert/*.pem +*.log + +envScript.sh + +# Created by https://www.gitignore.io/api/python,pycharm +# Edit at https://www.gitignore.io/?templates=python,pycharm + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### Python Patch ### +.venv/ + +# End of https://www.gitignore.io/api/python,pycharm diff --git a/otf-robot-test-head/Dockerfile b/otf-robot-test-head/Dockerfile new file mode 100644 index 0000000..7358b21 --- /dev/null +++ b/otf-robot-test-head/Dockerfile @@ -0,0 +1,18 @@ +FROM python:2.7 + +ARG HTTP_PROXY="http://localhost:8080" +ARG HTTPS_PROXY="http://localhost:8080" +ARG http_proxy="http://localhost:8080" +ARG https_proxy="http://localhost:8080" + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD run.py run.py +ADD app app + +RUN python -m pip install -r pip-requirements.txt + +RUN mkdir -p /otf/logs + +ENTRYPOINT ["python", "run.py"] \ No newline at end of file diff --git a/otf-robot-test-head/Jenkinsfile b/otf-robot-test-head/Jenkinsfile new file mode 100644 index 0000000..16fa274 --- /dev/null +++ b/otf-robot-test-head/Jenkinsfile @@ -0,0 +1,150 @@ +#!/usr/bin/env groovy + + +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: "id_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-oran-otf"] +]]]) + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="otf-robot-test-head"; + VERSION="Blitzcrank.1.1"; + NAMESPACE="org.oran.otf" + DOCKER_REGISTRY="registry.hub.docker.io" + + if( ENV.equalsIgnoreCase("dev") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + "/" + 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")){ + + 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.8.2-linux-amd64.tar.gz + tar -xf helm-v2.8.2-linux-amd64.tar.gz + rm helm-v2.8.2-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('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'), + usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME_MONGO', passwordVariable: 'PASSWORD_MONGO') + ]) { + + sh """ + echo "Validate Yaml" + helm lint $ARTIFACT_ID + + echo "View Helm Templates" + helm template $ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE \ + --set env=$ENV \ + --set mongo.username=$USERNAME_MONGO \ + --set mongo.password=$PASSWORD_MONGO + + 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 image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE \ + --set env=$ENV \ + --set mongo.username=$USERNAME_MONGO \ + --set mongo.password=$PASSWORD_MONGO + + + echo "Helm Install/Upgrade" + helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \ + --set appName=$ARTIFACT_ID \ + --set version=$VERSION \ + --set image=$IMAGE_NAME \ + --set namespace=$TILLER_NAMESPACE \ + --set env=$ENV \ + --set mongo.username=$USERNAME_MONGO \ + --set mongo.password=$PASSWORD_MONGO + + """ + } + } + + } + } + } + + } +} diff --git a/otf-robot-test-head/LICENSE.txt b/otf-robot-test-head/LICENSE.txt new file mode 100644 index 0000000..aa73a11 --- /dev/null +++ b/otf-robot-test-head/LICENSE.txt @@ -0,0 +1,28 @@ +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the "Software License"); +you may not use this software except in compliance with the Software +License. You may obtain a copy of the Software License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the Software License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Software License for the specific language governing permissions +and limitations under the Software License. + + + +Unless otherwise specified, all documentation contained herein is licensed +under the Creative Commons License, Attribution 4.0 Intl. (the +"Documentation License"); you may not use this documentation except in +compliance with the Documentation License. You may obtain a copy of the +Documentation License at + +https://creativecommons.org/licenses/by/4.0/ + +Unless required by applicable law or agreed to in writing, documentation +distributed under the Documentation License is distributed on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the Documentation License for the specific language governing +permissions and limitations under the Documentation License. \ No newline at end of file diff --git a/otf-robot-test-head/app/__init__.py b/otf-robot-test-head/app/__init__.py new file mode 100644 index 0000000..9fdd41a --- /dev/null +++ b/otf-robot-test-head/app/__init__.py @@ -0,0 +1,36 @@ +""" 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. # +#############################################################################""" + + +from flask import Flask +from app.routes import * +from app import database + + +def create_app(): + # create Flask application + app = Flask(__name__) + + # apply configuration + app.config.from_object(os.environ['APP_SETTINGS']) + app.config['g_database'] = None + app.config['g_base_folder'] = os.path.join(os.getcwd(), 'files') + app.config['g_data_folder'] = os.path.join(app.config['g_base_folder'], 'data') + app.config['g_working_folder'] = os.path.join(app.config['g_base_folder'], 'results') + + # register all routes on the APPLICATION_ROOT + app.register_blueprint(routes, url_prefix=app.config['APPLICATION_ROOT']) + + return app diff --git a/otf-robot-test-head/app/configuration.py b/otf-robot-test-head/app/configuration.py new file mode 100644 index 0000000..21cec1f --- /dev/null +++ b/otf-robot-test-head/app/configuration.py @@ -0,0 +1,41 @@ +""" 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. # +#############################################################################""" + + +import os + +basedir = os.path.abspath(os.path.dirname(__file__)) + + +class Configuration(object): + DEBUG = False + TESTING = False + CSRF_ENABLED = False + PREFERRED_URL_SCHEME = 'https' + APPLICATION_ROOT = '/otf/vth/robot' + + +class ProductionConfiguration(Configuration): + DEBUG = False + + +class DevelopmentConfiguration(Configuration): + DEVELOPMENT = True + DEBUG = True + + +class TestingConfiguration(Configuration): + DEVELOPMENT = True + DEBUG = True diff --git a/otf-robot-test-head/app/database.py b/otf-robot-test-head/app/database.py new file mode 100644 index 0000000..ea1b433 --- /dev/null +++ b/otf-robot-test-head/app/database.py @@ -0,0 +1,55 @@ +""" 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. # +#############################################################################""" + + +import os +from urllib import quote_plus + +from pymongo import MongoClient +from pymongo.errors import ConnectionFailure + + +class DatabaseConfiguration: + def __init__(self): + # read environment variables containing information for the MongoDB replica set. + MONGO_HOST = os.environ['OTF_MONGO_HOSTS'] + MONGO_USERNAME = os.environ['OTF_MONGO_USERNAME'] + MONGO_PASSWORD = os.environ['OTF_MONGO_PASSWORD'] + MONGO_REPLICA_SET = os.environ['OTF_MONGO_REPLICASET'] + MONGO_DATABASE = os.environ['OTF_MONGO_DATABASE'] + + # form the connection string for connection to a MongoDB replica set. + uri = "mongodb://%s:%s@%s?replicaSet=%s" % ( + quote_plus(MONGO_USERNAME), + quote_plus(MONGO_PASSWORD), + MONGO_HOST + MONGO_DATABASE, + MONGO_REPLICA_SET + ) + + client = MongoClient(uri) + + try: + # The ismaster command is cheap and does not require auth. + client.admin.command('ismaster') + print("Established connection to MongoDB.") + self.database = client[MONGO_DATABASE] + except ConnectionFailure: + print("Failed to initialize connection to MongoDB.") + + def set_database(self, database): + self.database = database + + def get_database(self): + return self.database diff --git a/otf-robot-test-head/app/routes/__init__.py b/otf-robot-test-head/app/routes/__init__.py new file mode 100644 index 0000000..67acc1f --- /dev/null +++ b/otf-robot-test-head/app/routes/__init__.py @@ -0,0 +1,22 @@ +""" 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. # +#############################################################################""" + + +from flask import Blueprint + +routes = Blueprint('routes', __name__) + +from .health import * +from .robot_processor import * diff --git a/otf-robot-test-head/app/routes/health.py b/otf-robot-test-head/app/routes/health.py new file mode 100644 index 0000000..53bdc22 --- /dev/null +++ b/otf-robot-test-head/app/routes/health.py @@ -0,0 +1,23 @@ +""" 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. # +#############################################################################""" + + +from flask import jsonify +from . import routes + + +@routes.route("v1/health", methods=['GET']) +def health(): + return jsonify({'status': 200, 'message': 'UP'}) \ No newline at end of file diff --git a/otf-robot-test-head/app/routes/robot_processor.py b/otf-robot-test-head/app/routes/robot_processor.py new file mode 100644 index 0000000..4444858 --- /dev/null +++ b/otf-robot-test-head/app/routes/robot_processor.py @@ -0,0 +1,164 @@ +""" 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. # +#############################################################################""" + + +import shutil +import uuid +import zipfile + +import gridfs +from bson import ObjectId +from flask import request, make_response, jsonify, current_app +from gridfs import NoFile +from robot import run + +from . import routes +from .. import database +from ..utils import * + +db_instance = database.DatabaseConfiguration() + + +def verify_directories(): + # retrieve the app object to retrieve the config we created + app = current_app._get_current_object() + + if not os.path.isdir(app.config['g_base_folder']): + os.makedirs(app.config['g_base_folder']) + if not os.path.isdir(app.config['g_data_folder']): + os.makedirs(app.config['g_data_folder']) + if not os.path.isdir(app.config['g_working_folder']): + os.makedirs(app.config['g_working_folder']) + + +@routes.route("/v1", methods=['POST']) +def robot(): + app = current_app._get_current_object() + + response_data = { + "vthResponse": { + "testDurationMS": "", + "dateTimeUTC": str(datetime.datetime.now()), + "abstractMessage": "", + "resultData": {} + } + } + + start_time = unix_time_millis(datetime.datetime.now()) + + try: + if not request.is_json: + raise ValueError('Invalid JSON object.') + + # get json data from the request + request_data = request.get_json() + + # get values for expected keys + vth_input = try_get_json_value('vthInput', request_data) + test_data = try_get_json_value('testData', vth_input) + robot_file_id = try_get_json_value('robotFileId', test_data) + + # set up a GridFS to access the database + db = db_instance.get_database() + fs = gridfs.GridFS(db) + + # try to find a file using the supplied robot_file_id + compressed_file = fs.get(ObjectId(robot_file_id)) + + # create the directories used during robot processing if they don't exist + verify_directories() + + # generate a folder named by a uuid to organize data for each request + random_uuid = uuid.uuid4().get_hex() + data_dir = os.path.join(app.config['g_data_folder'], random_uuid) + os.mkdir(data_dir) + + # + with open(os.path.join(data_dir, compressed_file.name), 'wb') as f: + f.write(compressed_file.read().__str__()) + f.close() + + with zipfile.ZipFile(os.path.join(data_dir, compressed_file.name)) as zip_ref: + # Create a temporary folder for storing extracted test file(s) + test_dir = os.path.join(app.config['g_working_folder'], random_uuid) + os.mkdir(test_dir) + + # Create a separate folder for the output files, so they can be compressed and sent back to the TCU + test_output_dir = os.path.join(test_dir, 'output') + os.mkdir(test_output_dir) + + # Extract the robot tests into the temporary directory + zip_ref.extractall(test_dir) + + # Run the robot tests with the outputdir pointed to the temporary directory + return_code = run(os.path.join(test_dir), outputdir=os.path.join(test_dir, 'output')) + + # this path is hardcoded so the entire system path isn't included in the zip + path = './files/results/{uuid}/output'.format(uuid=random_uuid) + zip_file = zipfile.ZipFile(path + '.zip', 'w', zipfile.ZIP_DEFLATED, allowZip64=True) + zip_dir(path, zip_file) + zip_file.close() + + # save the results to the database + zf = open(path + '.zip', 'rb') + result_id = fs.put(zf, filename='output.zip', contentType='application/zip') + zf.close() + + response_data['vthResponse']['resultData']['robotStatusCode'] = return_code + response_data['vthResponse']['resultData']['robotResultFileId'] = str(result_id) + response_data['vthResponse']['abstractMessage'] = resolve_robot_status_code(return_code) + + + + # delete data from the local disk + shutil.rmtree(path.replace('/output', '')) + shutil.rmtree(data_dir) + + # record the end time of the test + end_time = unix_time_millis(datetime.datetime.now()) + + # Calculate the total duration of the test + total_time = end_time - start_time + + # Set the test duration in the result + response_data['vthResponse']['testDurationMS'] = total_time + + return jsonify(response_data) + except NoFile as e: + # this exception can only occur if robot_file_id is set to something, so don't worry about reference precedence. + end_time = unix_time_millis(datetime.datetime.now()) + total_time = end_time - start_time + + response_data['vthResponse']['testDurationMS'] = '' + response_data['vthResponse']['abstractMessage'] = \ + 'An exception occurred after running for {totalTime} milliseconds. ' \ + 'A file with _id {id} was not found in the collection.'.format(id=robot_file_id, totalTime=total_time) + + response = make_response(json.dumps(response_data)) + return response + + except Exception as e: + app.logger.error(e) + end_time = unix_time_millis(datetime.datetime.now()) + total_time = end_time - start_time + + response_data['vthResponse']['testDurationMS'] = '' + response_data['vthResponse']['abstractMessage'] = \ + 'An exception occurred after running for {totalTime} milliseconds. ' \ + 'Exception: {exception}.'.format(exception=str(e), totalTime=total_time) + + response = make_response(json.dumps(response_data)) + + return response diff --git a/otf-robot-test-head/app/utils/__init__.py b/otf-robot-test-head/app/utils/__init__.py new file mode 100644 index 0000000..6608a64 --- /dev/null +++ b/otf-robot-test-head/app/utils/__init__.py @@ -0,0 +1,62 @@ +""" 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. # +#############################################################################""" + + +import os +import datetime +import json + + +def unix_time_millis(dt): + epoch = datetime.datetime.utcfromtimestamp(0) + return (dt - epoch).total_seconds() * 1000.0 + + +def zip_dir(path, zip_handle): + for root, dirs, files in os.walk(path): + for f in files: + zip_handle.write(os.path.join(root, f)) + + +def try_get_json_value(key, data): + if key not in data: + raise KeyError('The key {key} is not in {data}.' + .format(key=key, data=json.dumps(data))) + + return data[key] + + +def resolve_robot_status_code(code): + resolved_message = 'Invalid robot status code.' + + if code == 0: + resolved_message = 'All critical tests passed.' + elif 0 <= code <= 249: + resolved_message = '{numTestsFailed} test(s) failed.'.format(numTestsFailed=code) + elif code == 250: + resolved_message = '250 or more critical failures.' + elif code == 251: + resolved_message = 'Help or version information printed.' + elif code == 252: + resolved_message = 'Invalid test data or command line options.' + elif code == 253: + resolved_message = 'Test execution stopped by user.' + elif code == 255: + resolved_message = 'Unexpected internal error.' + + return resolved_message + + + diff --git a/otf-robot-test-head/helm/otf-robot-test-head/.helmignore b/otf-robot-test-head/helm/otf-robot-test-head/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/otf-robot-test-head/helm/otf-robot-test-head/.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/otf-robot-test-head/helm/otf-robot-test-head/Chart.yaml b/otf-robot-test-head/helm/otf-robot-test-head/Chart.yaml new file mode 100644 index 0000000..b9f835a --- /dev/null +++ b/otf-robot-test-head/helm/otf-robot-test-head/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the Robot Virtual Test Head +name: otf-robot-test-head +version: 0.0.5-SNAPSHOT diff --git a/otf-robot-test-head/helm/otf-robot-test-head/templates/deployment.yaml b/otf-robot-test-head/helm/otf-robot-test-head/templates/deployment.yaml new file mode 100644 index 0000000..70d6e4c --- /dev/null +++ b/otf-robot-test-head/helm/otf-robot-test-head/templates/deployment.yaml @@ -0,0 +1,151 @@ +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")}} + {{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: 5000 + nodePort: {{.Values.nodePort}} + protocol: TCP + env: + - name: NAMESPACE + value: {{.Values.namespace}} + - name: APP_NAME + value: {{ .Values.appName}} + - name: APP_VERSION + value: {{.Values.version}} + - name: OTF_MONGO_HOSTS + {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}} + value: {{ .Values.mongo.prod.OTF_MONGO_HOSTS | quote}} + {{ else if eq .Values.env "st" }} + value: {{ .Values.mongo.st.OTF_MONGO_HOSTS | quote}} + {{ else }} + value: {{ .Values.mongo.dev.OTF_MONGO_HOSTS | quote}} + {{ end }} + - name: OTF_MONGO_DATABASE + {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}} + value: {{ .Values.mongo.prod.OTF_MONGO_DBOTF | quote }} + {{ else if eq .Values.env "st" }} + value: {{ .Values.mongo.st.OTF_MONGO_DBOTF | quote }} + {{ else }} + value: {{ .Values.mongo.dev.OTF_MONGO_DBOTF | quote }} + {{ end }} + - name: OTF_MONGO_REPLICASET + {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}} + value: {{ .Values.mongo.prod.OTF_MONGO_REPLICASET | quote }} + {{ else if eq .Values.env "st" }} + value: {{ .Values.mongo.st.OTF_MONGO_REPLICASET | quote }} + {{ else }} + value: {{ .Values.mongo.dev.OTF_MONGO_REPLICASET | quote }} + {{ end }} + - name: OTF_MONGO_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: mongo_username + optional: true + - name: OTF_MONGO_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: mongo_password + optional: true + - name: APP_SETTINGS + value: {{ .Values.robot.APP_SETTINGS | quote }} + {{ if eq .Values.env "st"}} + resources: + limits: + memory: "2Gi" + cpu: "1" + requests: + memory: "1Gi" + cpu: "500m" + {{else}} + resources: + limits: + memory: "4Gi" + cpu: "2" + requests: + memory: "1Gi" + cpu: "1" + {{end}} + 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: HTTPS + httpHeaders: + - name: X-Custom-Header + value: Alive + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: {{.Values.health}} + port: http + scheme: HTTPS + httpHeaders: + - name: X-Custom-Header + value: Ready + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + restartPolicy: Always diff --git a/otf-robot-test-head/helm/otf-robot-test-head/templates/secret.yaml b/otf-robot-test-head/helm/otf-robot-test-head/templates/secret.yaml new file mode 100644 index 0000000..ac7c61b --- /dev/null +++ b/otf-robot-test-head/helm/otf-robot-test-head/templates/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + mongo_username: {{ .Values.mongo.username | b64enc}} + mongo_password: {{ .Values.mongo.password | b64enc}} diff --git a/otf-robot-test-head/helm/otf-robot-test-head/templates/service.yaml b/otf-robot-test-head/helm/otf-robot-test-head/templates/service.yaml new file mode 100644 index 0000000..f3bcfab --- /dev/null +++ b/otf-robot-test-head/helm/otf-robot-test-head/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: 5000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/otf-robot-test-head/helm/otf-robot-test-head/values.yaml b/otf-robot-test-head/helm/otf-robot-test-head/values.yaml new file mode 100644 index 0000000..111d059 --- /dev/null +++ b/otf-robot-test-head/helm/otf-robot-test-head/values.yaml @@ -0,0 +1,31 @@ +appName: otf-robot-test-head +env: dev +version: 0.0.1-SNAPSHOT +image: otf-robot-test-head:0.0.5-SNAPSHOT +namespace: org-oran-otf +nodePort: 32224 +replicas: 1 +health : /otf/vth/robot/v1/health +sharedCert: otf-cert-secret-builder +robot: + APP_SETTINGS: "app.configuration.DevelopmentConfiguration" +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv +mongo: + prod: + OTF_MONGO_HOSTS: "localhost:18720,localhost:18720,localhost:18720/" + OTF_MONGO_DBOTF: "otf" + OTF_MONGO_REPLICASET: "otf-rs-prod2" + dev: + OTF_MONGO_HOSTS: "localhost:27017,localhost:27017,localhost:27017/" + OTF_MONGO_DBOTF: "otf" + OTF_MONGO_REPLICASET: "mongoOTF" + st: + OTF_MONGO_HOSTS: "localhost:27017,localhost:27017,localhost:27017/" + OTF_MONGO_DBOTF: "otf_st" + OTF_MONGO_REPLICASET: "mongoOTF" + + username: "" + password: "" +sharedSecret: otf-aaf-credential-generator diff --git a/otf-robot-test-head/opt/cert/CERT_PLACEHOLDER b/otf-robot-test-head/opt/cert/CERT_PLACEHOLDER new file mode 100644 index 0000000..e69de29 diff --git a/otf-robot-test-head/pip-requirements.txt b/otf-robot-test-head/pip-requirements.txt new file mode 100644 index 0000000..354255b --- /dev/null +++ b/otf-robot-test-head/pip-requirements.txt @@ -0,0 +1,6 @@ +flask +FLASK +pymongo +robotframework +robotframework-selenium2library +selenium \ No newline at end of file diff --git a/otf-robot-test-head/run.py b/otf-robot-test-head/run.py new file mode 100644 index 0000000..c0c37dc --- /dev/null +++ b/otf-robot-test-head/run.py @@ -0,0 +1,14 @@ +from app import create_app + +if __name__ == '__main__': + app = create_app() + + # Set SSL context with the certificate chain and the private RSA key. + context = ('opt/cert/otf.pem', 'opt/cert/privateKey.pem') + app.run( + debug=app.config['DEBUG'], + host='0.0.0.0', port=5000, + use_reloader=True, + ssl_context=context) + # Run without ssl + # app.run(debug=app.config['DEBUG'], host='0.0.0.0', use_reloader=True, port=5000) \ No newline at end of file diff --git a/otf-ssh-test-head/.gitignore b/otf-ssh-test-head/.gitignore new file mode 100644 index 0000000..815f98d --- /dev/null +++ b/otf-ssh-test-head/.gitignore @@ -0,0 +1,5 @@ +otf.pem +privateKey.pem +*.log +*.pem +/otf diff --git a/otf-ssh-test-head/Dockerfile b/otf-ssh-test-head/Dockerfile new file mode 100644 index 0000000..84e31c1 --- /dev/null +++ b/otf-ssh-test-head/Dockerfile @@ -0,0 +1,17 @@ +FROM python:2.7 + +ARG HTTP_PROXY="localhost:8080" +ARG HTTPS_PROXY="localhost:8080" +ARG http_proxy="localhost:8080" +ARG https_proxy="localhost:8080" + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD ssh_test_head.py ssh_test_head.py + +RUN mkdir -p /otf/logs + +RUN python -m pip install -r pip-requirements.txt + +ENTRYPOINT ["python", "ssh_test_head.py"] diff --git a/otf-ssh-test-head/Jenkinsfile b/otf-ssh-test-head/Jenkinsfile new file mode 100644 index 0000000..c19179c --- /dev/null +++ b/otf-ssh-test-head/Jenkinsfile @@ -0,0 +1,157 @@ +#!/usr/bin/env groovy + + +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: "id_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-oran-otf"] +]]]) + + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="otf-ssh-test-head"; + VERSION="Blitzcrank.1.1"; + NAMESPACE="org-oran-otf" + DOCKER_REGISTRY="registry.hub.docker.io" + + if( ENV.equalsIgnoreCase("dev") ){ + IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + "/" + 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")){ + + 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.8.2-linux-amd64.tar.gz + tar -xf helm-v2.8.2-linux-amd64.tar.gz + rm helm-v2.8.2-linux-amd64.tar.gz + """ + } + + withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) { + + dir('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'), + file(credentialsId: 'id_PRIVATE_KEY', variable: 'VAR_id_PRIVATE_KEY'), + string(credentialsId: 'id_otf_key_passphrase', variable: 'VAR_id_otf_key_passphrase') + ]) { + + sh """ + + cp $VAR_id_PRIVATE_KEY $ARTIFACT_ID + FILE_id_PRIVATE_KEY=`basename $VAR_id_PRIVATE_KEY` + + 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 \ + --set Secret.id_private_key=\$FILE_id_PRIVATE_KEY \ + --set Secret.id_private_key_passphrase=$VAR_id_otf_key_passphrase \ + + + + 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 \ + --set Secret.id_private_key=\$FILE_id_PRIVATE_KEY \ + --set Secret.id_private_key_passphrase=$VAR_id_otf_key_passphrase \ + + + 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 \ + --set Secret.id_private_key=\$FILE_id_PRIVATE_KEY \ + --set Secret.id_private_key_passphrase=$VAR_id_otf_key_passphrase \ + + """ + } + } + + } + } + } + + } +} diff --git a/otf-ssh-test-head/LICENSE.txt b/otf-ssh-test-head/LICENSE.txt new file mode 100644 index 0000000..aa73a11 --- /dev/null +++ b/otf-ssh-test-head/LICENSE.txt @@ -0,0 +1,28 @@ +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the "Software License"); +you may not use this software except in compliance with the Software +License. You may obtain a copy of the Software License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the Software License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the Software License for the specific language governing permissions +and limitations under the Software License. + + + +Unless otherwise specified, all documentation contained herein is licensed +under the Creative Commons License, Attribution 4.0 Intl. (the +"Documentation License"); you may not use this documentation except in +compliance with the Documentation License. You may obtain a copy of the +Documentation License at + +https://creativecommons.org/licenses/by/4.0/ + +Unless required by applicable law or agreed to in writing, documentation +distributed under the Documentation License is distributed on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the Documentation License for the specific language governing +permissions and limitations under the Documentation License. \ No newline at end of file diff --git a/otf-ssh-test-head/helm/otf-ssh-test-head/.helmignore b/otf-ssh-test-head/helm/otf-ssh-test-head/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/otf-ssh-test-head/helm/otf-ssh-test-head/.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/otf-ssh-test-head/helm/otf-ssh-test-head/Chart.yaml b/otf-ssh-test-head/helm/otf-ssh-test-head/Chart.yaml new file mode 100644 index 0000000..17d1c7b --- /dev/null +++ b/otf-ssh-test-head/helm/otf-ssh-test-head/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the SSH Virtual Test Head +name: otf-ssh-test-head +version: 0.0.1 diff --git a/otf-ssh-test-head/helm/otf-ssh-test-head/templates/deployment.yaml b/otf-ssh-test-head/helm/otf-ssh-test-head/templates/deployment.yaml new file mode 100644 index 0000000..369c4f1 --- /dev/null +++ b/otf-ssh-test-head/helm/otf-ssh-test-head/templates/deployment.yaml @@ -0,0 +1,128 @@ +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 + - name: {{ .Values.appName}}-ssh-volume + secret: + secretName: {{.Values.appName}} + optional: true + items: + - key: id_private_key + path: id_otf.key + {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}} + {{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: 5000 + nodePort: {{.Values.nodePort}} + protocol: TCP + {{ if eq .Values.env "st"}} + resources: + limits: + memory: "2Gi" + cpu: "1" + requests: + memory: "1Gi" + cpu: "500m" + {{else}} + resources: + limits: + memory: "4Gi" + cpu: "2" + 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: id_private_key_passphrase + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: id_private_key_passphrase + optional: true + volumeMounts: + - name: {{.Values.appName}}-cert-volume + mountPath: /opt/cert + - name: {{.Values.appName}}-ssh-volume + mountPath: /ssh + {{ 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: HTTPS + httpHeaders: + - name: X-Custom-Header + value: Alive + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: {{.Values.health}} + port: http + scheme: HTTPS + httpHeaders: + - name: X-Custom-Header + value: Ready + initialDelaySeconds: 30 + timeoutSeconds: 30 + periodSeconds: 30 + restartPolicy: Always diff --git a/otf-ssh-test-head/helm/otf-ssh-test-head/templates/secret.yaml b/otf-ssh-test-head/helm/otf-ssh-test-head/templates/secret.yaml new file mode 100644 index 0000000..7a056a9 --- /dev/null +++ b/otf-ssh-test-head/helm/otf-ssh-test-head/templates/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + id_private_key: {{ .Files.Get .Values.Secret.id_private_key | b64enc }} + id_private_key_passphrase: {{ .Values.Secret.id_private_key_passphrase | b64enc }} \ No newline at end of file diff --git a/otf-ssh-test-head/helm/otf-ssh-test-head/templates/service.yaml b/otf-ssh-test-head/helm/otf-ssh-test-head/templates/service.yaml new file mode 100644 index 0000000..f3bcfab --- /dev/null +++ b/otf-ssh-test-head/helm/otf-ssh-test-head/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: 5000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/otf-ssh-test-head/helm/otf-ssh-test-head/values.yaml b/otf-ssh-test-head/helm/otf-ssh-test-head/values.yaml new file mode 100644 index 0000000..cae8899 --- /dev/null +++ b/otf-ssh-test-head/helm/otf-ssh-test-head/values.yaml @@ -0,0 +1,17 @@ +appName: otf-ssh-test-head +env: dev +version: 0.0.1-SNAPSHOT +image: otf-ssh-test-head:0.0.1-SNAPSHOT +namespace: org-oran-otf +nodePort: 32222 +replicas: 1 +health : /otf/vth/ssh/v1/health +sharedCert: otf-cert-secret-builder +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv + +Secret: + id_private_key: temp + id_private_key_passphrase: temp + diff --git a/otf-ssh-test-head/opt/cert/certInfo.txt b/otf-ssh-test-head/opt/cert/certInfo.txt new file mode 100644 index 0000000..0799b0b --- /dev/null +++ b/otf-ssh-test-head/opt/cert/certInfo.txt @@ -0,0 +1 @@ +add certs here. requires two pem files (Cert and key) diff --git a/otf-ssh-test-head/pip-requirements.txt b/otf-ssh-test-head/pip-requirements.txt new file mode 100644 index 0000000..4a744ee --- /dev/null +++ b/otf-ssh-test-head/pip-requirements.txt @@ -0,0 +1,6 @@ +flask +flask-cors +pyping +paramiko +FLASK +FLASK-CORS diff --git a/otf-ssh-test-head/ssh_test_head.py b/otf-ssh-test-head/ssh_test_head.py new file mode 100644 index 0000000..132bcfc --- /dev/null +++ b/otf-ssh-test-head/ssh_test_head.py @@ -0,0 +1,161 @@ +# 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. # +################################################################################ + + +from flask import Flask, request, make_response, jsonify, g +import json +import paramiko +import datetime +import logging +import os +from logging import FileHandler + +# redirect http to https +app = Flask(__name__) + + +# Prevents print statement every time an endpoint is triggered. +logging.getLogger("werkzeug").setLevel(logging.WARNING) + + +def unix_time_millis(dt): + epoch = datetime.datetime.utcfromtimestamp(0) + return (dt - epoch).total_seconds() * 1000.0 + + +@app.route("/otf/vth/ssh/v1/health", methods = ['GET']) +def getHealth(): + return "UP" + + +@app.route('/otf/vth/ssh/v1', methods = ['POST']) +def remoteSSH(): + responseData = { + "vthResponse": { + "testDurationMS": "", + "dateTimeUTC": "", + "abstractMessage": "", + "resultData": {} + } + } + + responseData['vthResponse']['dateTimeUTC'] = str(datetime.datetime.now()) + start_time = unix_time_millis(datetime.datetime.now()) + + try: + if not request.is_json: + raise ValueError('Request must be a valid JSON object.') + + request_data = request.get_json() + + if 'vthInput' in request_data: + vth_input = request_data['vthInput'] + expected_keys = ['vthName', 'testConfig', 'testData'] + received_keys = vth_input.keys(); + test_data = "" + test_config = "" + + if sorted(expected_keys) == sorted(received_keys): + test_data = vth_input['testData'] + + if 'command' not in test_data: + raise ValueError('Must supply value testData.command') + + else: + raise ValueError('Missing one or more expected keys: {expectedKeys}.'.format(expectedKeys=expected_keys)) + + test_config = vth_input['testConfig'] + + if 'jumpServer' not in test_config: + raise KeyError('Cannot use jump server when jumpServer key is missing.') + + jump_server = test_config['jumpServer'] + + if 'host' not in test_config['jumpServer']: + raise KeyError('Missing host value in jumpServer.') + + host = test_config['jumpServer']['host'] + + if 'credentials' not in jump_server: + raise KeyError('Missing credentials in jumpServer.') + + credentials = jump_server['credentials'] + + if 'username' not in credentials: + raise KeyError('Missing username in credentials.') + + username = credentials['username'] + + if 'password' not in credentials: + raise KeyError('Missing password in credentials.') + + password = credentials['password'] + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + if 'usePrivateKey' in test_config: + if test_config['usePrivateKey']: + key_passphrase = os.environ.get('id_otf_key_passphrase') + app.logger.info(key_passphrase) + ssh.connect(host, username=username, passphrase='passphrase', key_filename='./ssh/id_otf.key') + with open('./ssh/id_otf.key', 'r') as myfile: + data = myfile.read().replace('\n', '') + + app.logger.info(data) + else: + ssh.connect(host, username=username, password=password) + command = test_data['command'] + ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command) + + responseData['vthResponse']['resultData']['output'] = str(ssh_stdout.read()).replace('"', '\\"').replace('\n', '\\n') + responseData['vthResponse']['resultData']['error'] = str(ssh_stderr.read()).replace('"', '\\"').replace('\n', '\\n') + + else: + raise KeyError('Missing vthInput parameter(s)') + + # record the end time of the test + endTime = unix_time_millis(datetime.datetime.now()) + + # Calculate the total duration of the test + totalTime = endTime - start_time + + # Set the test duration in the result + responseData['vthResponse']['testDurationMS'] = totalTime + + responseData['vthResponse']['abstractMessage'] = 'done' + + app.logger.info(str(responseData)) + + return jsonify(responseData) + except Exception as e: + app.logger.info(e) + responseData['vthResponse']['abstractMessage'] = str(e) + resp = make_response(json.dumps(responseData)) + endTime = unix_time_millis(datetime.datetime.now()) + + totalTime = endTime - start_time + return resp + + +if __name__ == '__main__': + logHandler = FileHandler('otf/logs/sshVTH.log', mode='a') + # logHandler = FileHandler('sshVTH.log', mode='a') + logHandler.setLevel(logging.INFO) + app.logger.setLevel(logging.INFO) + app.logger.addHandler(logHandler) + 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=5000)