From: jv246a.oran Date: Tue, 5 May 2020 19:37:02 +0000 (-0400) Subject: added new vths(smo,a1-mediator,dmaap) X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?p=it%2Fotf.git;a=commitdiff_plain;h=e3ba96506390ab3ad630d2dfc4967fd49527615d added new vths(smo,a1-mediator,dmaap) updated smo_vth name to o1_vth and updated it's files to reflect the change Change-Id: Iaa130354b6149d830897ae56de7c34eef67e09da Signed-off-by: Chen, Jackie --- diff --git a/a1-mediator-vth/Dockerfile b/a1-mediator-vth/Dockerfile new file mode 100644 index 0000000..e708483 --- /dev/null +++ b/a1-mediator-vth/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7.4 + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD a1-mediator-vth.py a1-mediator-vth.py +ADD config.json config.json + +RUN mkdir -p /otf/logs + +RUN python -m pip install --proxy -r pip-requirements.txt + +ENTRYPOINT ["python", "a1-mediator-vth.py"] diff --git a/a1-mediator-vth/Jenkinsfile b/a1-mediator-vth/Jenkinsfile new file mode 100644 index 0000000..a58148c --- /dev/null +++ b/a1-mediator-vth/Jenkinsfile @@ -0,0 +1,156 @@ +#!/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: "id_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "registry.hub.docker.io"] +]]]) + + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="a1-mediator-vth"; + VERSION="0.0.1-SNAPSHOT"; + NAMESPACE="org-oran-otf" + DOCKER_REGISTRY="registry.hub.docker.io" + + 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")){ + + stage 'Publish Artifact' + + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + dir("smo-vth") { + 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-mediator-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 """ + helm version + 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-mediator-vth/a1-mediator-vth.py b/a1-mediator-vth/a1-mediator-vth.py new file mode 100644 index 0000000..e0269b1 --- /dev/null +++ b/a1-mediator-vth/a1-mediator-vth.py @@ -0,0 +1,198 @@ +# 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-mediator-vth.py # +# Description: vth for a1-mediator-vth service # +# Date created: 08/22/2019 # +# Last modified: 04/02/2020 # +# Python Version: 3.7.4 # +# Author: Jackie Chen (jv246a) # +# Email: jv246a@att.com # +################################################################################ +import datetime +import json +import logging +from logging import FileHandler +import os + +import requests +from flask import Flask, request, jsonify + +# redirect http to https +app = Flask(__name__) + +# Prevents print statement every time an endpoint is triggered. +logging.getLogger("werkzeug").setLevel(logging.WARNING) + + +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 + + +@app.route("/otf/vth/oran/a1/v1/health", methods=['GET']) +def getHealth(): + return "UP" + + +@app.route("/otf/vth/oran/a1/v1", 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') + + +if __name__ == '__main__': + logHandler = FileHandler('a1-mediator-vth.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/a1-mediator-vth/a1_mock_server/index.js b/a1-mediator-vth/a1_mock_server/index.js new file mode 100644 index 0000000..5830d11 --- /dev/null +++ b/a1-mediator-vth/a1_mock_server/index.js @@ -0,0 +1,80 @@ +const express = require('express') +const app = express() +const port = 3000 + +app.use(express.json()) + +app.get('/', (req, res) => res.send('Hello World!')) + +app.get('/a1-p/healthcheck',function(req,res){ + console.log("health checking") + res.sendStatus(200) +}) + +app.get('/a1-p/policytypes',function(req,res){ + res.status=(200) + res.send([20000, 20020]) +}) + +app.get('/a1-p/policytypes/:policy_type_id',function(req,res){ + res.status=(200) + policy_type_id = req.params['policy_type_id'] + res.send({"name": "example policy instance","description":"fake description","policy_type_id": policy_type_id,"create_schema":"{name:sample object}"}) +}) +app.delete('/a1-p/policytypes/:policy_type_id',function(req,res){ + res.sendStatus(204) +}) + +app.put('/a1-p/policytypes/:policy_type_id',function(req,res){ + console.log(req.body) + res.sendStatus(201) +}) + +app.get('/a1-p/policytypes/:policy_type_id/policies',function(req,res){ + console.log('listing policies') + res.status=(200) + res.send(["3d2157af-6a8f-4a7c-810f-38c2f824bf12", "06911bfc-c127-444a-8eb1-1bffad27cc3d"]) +}) + +app.get('/a1-p/policytypes/:policy_type_id/policies/:policy_instance_id',function(req,res){ + res.status=(200) + policy_type_id = req.params['policy_type_id'] + policy_instance_id = req.params['policy_instance_id'] + res.send({"name": "example policy instance","description":"fake description","policy_type_id": policy_type_id,"create_schema":"{name:sample object}"}) +}) +app.delete('/a1-p/policytypes/:policy_type_id/policies/:policy_instance_id',function(req,res){ + res.sendStatus(202) +}) + +app.put('/a1-p/policytypes/:policy_type_id/policies/:policy_instance_id',function(req,res){ + console.log(req.body) + res.sendStatus(202) +}) + +app.get('/a1-p/policytypes/:policy_type_id/policies/:policy_instance_id/status',function(req,res){ + res.status(200) + res.send({"properties":{"instance_status": "fake status","enum": "IN EFFECT"}}) +}) + + + + + + +app.get('/appmgr/ric/v1/xapps',function(req,res){ + res.status(200) + res.send([{"name":"admin-xapp","status":"deployed","version":"1.0","instances":null},{"name":"mcxapp","status":"deployed","version":"1.0","instances":[{"name":"mcxapp-649d7494-h5tjb","status":"running","ip":"service-ricxapp-mcxapp-rmr.ricxapp","port":4560,"txMessages":null,"rxMessages":["RIC_SUB_RESP","RIC_SUB_FAILURE","RIC_SUB_DEL_RESP","RIC_SUB_DEL_FAILURE","RIC_INDICATION"]}]},{"name":"ueec","status":"deployed","version":"1.0","instances":[{"name":"ueec-6675694b75-jtnz6","status":"running","ip":"service-ricxapp-ueec-rmr.ricxapp","port":4560,"txMessages":["RIC_SUB_REQ","RIC_SUB_DEL_REQ"],"rxMessages":["RIC_SUB_RESP","RIC_SUB_FAILURE","RIC_SUB_DEL_RESP","RIC_SUB_DEL_FAILURE","RIC_INDICATION"]}]}]) +}) + +app.post('/appmgr/ric/v1/xapps', function(req,res){ + res.statusMessage = 'Created' + res.status(201) + res.send({"result_output":{"name":"anr","status":"deployed","version":"1.0","instances":[{"name":"anr-7d4c47b4bb-jlslm","status":"running","ip":"service-ricxapp-anr-rmr.ricxapp","port":4560,"txMessages":null,"rxMessages":["RIC_SGNB_ADDITION_REQ","RIC_RRC_TRANSFER"]}]}}) +}) + +app.delete('/appmgr/ric/v1/xapps/:name',function(req,res){ + res.sendStatus(204) +}) + +app.listen(port, () => console.log(`Example app listening on port ${port}!`)) + diff --git a/a1-mediator-vth/a1_mock_server/package-lock.json b/a1-mediator-vth/a1_mock_server/package-lock.json new file mode 100644 index 0000000..3877b38 --- /dev/null +++ b/a1-mediator-vth/a1_mock_server/package-lock.json @@ -0,0 +1,374 @@ +{ + "name": "a1_mock_server", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/a1-mediator-vth/a1_mock_server/package.json b/a1-mediator-vth/a1_mock_server/package.json new file mode 100644 index 0000000..8d910e3 --- /dev/null +++ b/a1-mediator-vth/a1_mock_server/package.json @@ -0,0 +1,14 @@ +{ + "name": "a1_mock_server", + "version": "1.0.0", + "description": "mock for a1 ", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "jackie chen (jv246a)", + "license": "ISC", + "dependencies": { + "express": "^4.17.1" + } +} diff --git a/a1-mediator-vth/config.json b/a1-mediator-vth/config.json new file mode 100644 index 0000000..79d9855 --- /dev/null +++ b/a1-mediator-vth/config.json @@ -0,0 +1,11 @@ +{ + "base_address": "127.0.0.1:3000", + "actions_path": { + "health_check": "/a1-p/healthcheck", + "list_policy": "/a1-p/policytypes", + "edit_policy": "/a1-p/policytypes/{policy_type_id}", + "list_policy_instance": "/a1-p/policytypes/{policy_type_id}/policies", + "edit_policy_instance": "/a1-p/policytypes/{policy_type_id}/policies/{policy_instance_id}", + "get_policy_instance_status": "/a1-p/policytypes/{policy_type_id}/policies/{policy_instance_id}/status" + } +} \ No newline at end of file diff --git a/a1-mediator-vth/doc/a1-documentation.docx b/a1-mediator-vth/doc/a1-documentation.docx new file mode 100644 index 0000000..820dbab Binary files /dev/null and b/a1-mediator-vth/doc/a1-documentation.docx differ diff --git a/a1-mediator-vth/helm/a1-mediator-vth/.helmignore b/a1-mediator-vth/helm/a1-mediator-vth/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/a1-mediator-vth/helm/a1-mediator-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-mediator-vth/helm/a1-mediator-vth/Chart.yaml b/a1-mediator-vth/helm/a1-mediator-vth/Chart.yaml new file mode 100644 index 0000000..a16372a --- /dev/null +++ b/a1-mediator-vth/helm/a1-mediator-vth/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the a1 mediator Virtual Test Head +name: a1-mediator-vth +version: 0.0.1 diff --git a/a1-mediator-vth/helm/a1-mediator-vth/templates/deployment.yaml b/a1-mediator-vth/helm/a1-mediator-vth/templates/deployment.yaml new file mode 100644 index 0000000..afb1dec --- /dev/null +++ b/a1-mediator-vth/helm/a1-mediator-vth/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")}} 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: 5000 + 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}} + 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-mediator-vth/helm/a1-mediator-vth/templates/service.yaml b/a1-mediator-vth/helm/a1-mediator-vth/templates/service.yaml new file mode 100644 index 0000000..f3bcfab --- /dev/null +++ b/a1-mediator-vth/helm/a1-mediator-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: 5000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/a1-mediator-vth/helm/a1-mediator-vth/values.yaml b/a1-mediator-vth/helm/a1-mediator-vth/values.yaml new file mode 100644 index 0000000..81d2226 --- /dev/null +++ b/a1-mediator-vth/helm/a1-mediator-vth/values.yaml @@ -0,0 +1,12 @@ +appName: a1-mediator-vth +env: dev +version: 0.0.1-SNAPSHOT +image: a1-mediator-vth:0.0.1-SNAPSHOT +namespace: org-oran-otf +nodePort: 32324 +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 diff --git a/a1-mediator-vth/pip-requirements.txt b/a1-mediator-vth/pip-requirements.txt new file mode 100644 index 0000000..863a045 --- /dev/null +++ b/a1-mediator-vth/pip-requirements.txt @@ -0,0 +1,5 @@ +flask +flask-cors +FLASK +FLASK-CORS +requests \ No newline at end of file diff --git a/dmaap-vth/Dockerfile b/dmaap-vth/Dockerfile new file mode 100644 index 0000000..bc5b296 --- /dev/null +++ b/dmaap-vth/Dockerfile @@ -0,0 +1,27 @@ +# 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 python:3.7.4 + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD dmaap_vth.py dmaap_vth.py +ADD config.ini config.ini + +RUN mkdir -p /otf/logs + +RUN python -m pip install -r pip-requirements.txt +ENTRYPOINT ["python", "dmaap_vth.py"] diff --git a/dmaap-vth/Jenkinsfile b/dmaap-vth/Jenkinsfile new file mode 100644 index 0000000..dfb3067 --- /dev/null +++ b/dmaap-vth/Jenkinsfile @@ -0,0 +1,162 @@ +#!/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: "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="dmaap-vth"; + VERSION="0.0.1-SNAPSHOT"; + NAMESPACE="org-oran-otf" + DOCKER_REGISTRY="registry.hub.docker.io" + + 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")){ + + stage 'Publish Artifact' + + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + dir("dmaap-vth") + 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('dmaap-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 """ + helm version + 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 credentials.username= $USERNAME \ + --set credentials.password= $PASSWORD + + 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 credentials.username=$USERNAME \ + --set credentials.password=$PASSWORD + + + 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 credentials.username=$USERNAME \ + --set credentials.password=$PASSWORD + + """ + } + } + + } + } + } + + } +} diff --git a/dmaap-vth/config.ini b/dmaap-vth/config.ini new file mode 100644 index 0000000..9c5f7d4 --- /dev/null +++ b/dmaap-vth/config.ini @@ -0,0 +1,11 @@ +[auth] +auth_enabled= true +username = %(USER)s +password = %(PW)s +[resource] +proxy_enabled = true +https_proxy= %(HTTPS)s +http_proxy= %(HTTP)s +base_address = %(BASE_URL)s +publish = /{topic_name} +subscribe = /{topic_name}/{consumer_group}/{consumer_id} \ No newline at end of file diff --git a/dmaap-vth/dmaap_vth.py b/dmaap-vth/dmaap_vth.py new file mode 100644 index 0000000..fce93fd --- /dev/null +++ b/dmaap-vth/dmaap_vth.py @@ -0,0 +1,238 @@ +# 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: dmaap-vth.py # +# Description: vth that utilize dmaap to subscribe and publish to topics # +# Date created: 02/21/2020 # +# Last modified: 04/02/2020 # +# Python Version: 3.7 # +# Author: Jackie Chen (jv246a) # +# Email: jv246a@att.com # +################################################################################ + +import datetime +from configparser import ConfigParser +import os +import logging +from logging import FileHandler +import requests +from flask import Flask, request, jsonify + +# redirect http to https +app = Flask(__name__) + +# Prevents print statement every time an endpoint is triggered. +logging.getLogger("werkzeug").setLevel(logging.WARNING) + + +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 _get_request_data(): + if not request.is_json: + raise ValueError("request must be json") + requestData = request.get_json() + return requestData + + +def _get_config(config_file_name): + config = ConfigParser(os.environ) + config.read(config_file_name) + return config + +def _validate_request(request_data, isPublish=True): + 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) + +@app.route("/otf/vth/oran/dmaap/v1/health", methods=['GET']) +def getHealth(): + return 'UP' + +@app.route("/otf/vth/oran/dmaap/v1/subscribe", methods=["POST"]) +def subscribeRequest(): + response_data = { + 'vthResponse': { + 'testDuration': '', + 'dateTimeUTC': str(datetime.datetime.now()), + 'abstractMessage': '', + 'resultData': {} + } + } + ret_url = request.args.get('retURL') + startTime = unix_time_millis(datetime.datetime.now()) + try: + # validate request + request_data = _get_request_data() + _validate_request(request_data, isPublish=False) + app.logger.info("incoming subscribe request w/ the following payload:" + str(request_data)) + + # setup phase + config = _get_config('config.ini') + subscribe_address = _build_url(config, request_data, is_publish=False) + + # build response + app.logger.info('Sending GET to subscribe') + res = _send_request(subscribe_address, config, is_subscribe_request=True) + app.logger.info('Response received from subscribe: {}'.format(res.json())) + response_data['vthResponse']['abstractMessage'] = 'Result from subscribe request' + response_data['vthResponse']['resultData']['status_code'] = res.status_code + response_data['vthResponse']['resultData']['result_output'] = res.json() + except Exception as ex: + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + response_data['vthResponse']['testDuration'] = totalTime + response_data['vthResponse']['abstractMessage'] = 'error: ' + str(ex) + app.logger.error('ERROR:{}'.format(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 + + +@app.route("/otf/vth/oran/dmaap/v1/publish", methods=['POST']) +def publishRequest(): + 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: + # validate request + request_data = _get_request_data() + _validate_request(request_data) + app.logger.info("incoming publish request w/ the following payload:" + str(request_data)) + + # setup phase + config = _get_config('config.ini') + payload = request_data['data'] + publish_address = _build_url(config, request_data) + + # build response + app.logger.info("Sending POST to publish") + res = _send_request(url=publish_address, config=config, payload=payload) + app.logger.info("Response received from publish: {}".format(res.json())) + response_data['vthResponse']['abstractMessage'] = 'Result from publish request' + response_data['vthResponse']['resultData']['status_code'] = res.status_code + response_data['vthResponse']['resultData']['result_output'] = res.json() + except Exception as ex: + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + response_data['vthResponse']['testDuration'] = totalTime + response_data['vthResponse']['abstractMessage'] = 'error: ' + str(ex) + app.logger.error('ERROR:{}'.format(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 + +if __name__ == '__main__': + logHandler = FileHandler('dmaap-vth.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/dmaap-vth/doc/dmaap-documentation.docx b/dmaap-vth/doc/dmaap-documentation.docx new file mode 100644 index 0000000..7b72aba Binary files /dev/null and b/dmaap-vth/doc/dmaap-documentation.docx differ diff --git a/dmaap-vth/helm/dmaap-vth/.helmignore b/dmaap-vth/helm/dmaap-vth/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/dmaap-vth/helm/dmaap-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/dmaap-vth/helm/dmaap-vth/Chart.yaml b/dmaap-vth/helm/dmaap-vth/Chart.yaml new file mode 100644 index 0000000..f219144 --- /dev/null +++ b/dmaap-vth/helm/dmaap-vth/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the dmaap Virtual Test Head +name: dmaap-vth +version: 0.0.1 diff --git a/dmaap-vth/helm/dmaap-vth/templates/deployment.yaml b/dmaap-vth/helm/dmaap-vth/templates/deployment.yaml new file mode 100644 index 0000000..f78b5c8 --- /dev/null +++ b/dmaap-vth/helm/dmaap-vth/templates/deployment.yaml @@ -0,0 +1,97 @@ +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 + 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: HTTP + value: {{ .Values.HTTP}} + - name: HTTPS + value: {{ .Values.HTTPS}} + - name: BASE_URL + value: {{ .Values.BASE_URL}} + - name: USER + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: username + - name: PW + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: password + volumeMounts: + - name: {{.Values.appName}}-cert-volume + mountPath: /opt/cert + 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/dmaap-vth/helm/dmaap-vth/templates/secret.yaml b/dmaap-vth/helm/dmaap-vth/templates/secret.yaml new file mode 100644 index 0000000..b5c04dd --- /dev/null +++ b/dmaap-vth/helm/dmaap-vth/templates/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + username: {{ .Values.credentials.username | b64enc}} + password: {{ .Values.credentials.password | b64enc}} \ No newline at end of file diff --git a/dmaap-vth/helm/dmaap-vth/templates/service.yaml b/dmaap-vth/helm/dmaap-vth/templates/service.yaml new file mode 100644 index 0000000..f3bcfab --- /dev/null +++ b/dmaap-vth/helm/dmaap-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: 5000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/dmaap-vth/helm/dmaap-vth/values.yaml b/dmaap-vth/helm/dmaap-vth/values.yaml new file mode 100644 index 0000000..73c63aa --- /dev/null +++ b/dmaap-vth/helm/dmaap-vth/values.yaml @@ -0,0 +1,18 @@ +appName: dmaap-vth +env: dev +version: 0.0.1-SNAPSHOT +image: dmaap-vth:0.0.1-SNAPSHOT +namespace: org-oran-otf +nodePort: 32324 +replicas: 1 +health : /otf/vth/oran/dmaap/v1/health +sharedCert: otf-cert-secret-builder +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv +HTTP: "proxy address here if there is" +HTTPS: "proxy address here if there is" +BASE_URL: "base dmaap address here" +credentials: + username: "!" + password: "!" diff --git a/dmaap-vth/pip-requirements.txt b/dmaap-vth/pip-requirements.txt new file mode 100644 index 0000000..1a9cd87 --- /dev/null +++ b/dmaap-vth/pip-requirements.txt @@ -0,0 +1,6 @@ +flask +flask-cors +FLASK +FLASK-CORS +requests +configparser \ No newline at end of file diff --git a/o1-vth/Doc/o1-documentation.docx b/o1-vth/Doc/o1-documentation.docx new file mode 100644 index 0000000..003e35c Binary files /dev/null and b/o1-vth/Doc/o1-documentation.docx differ diff --git a/o1-vth/Dockerfile b/o1-vth/Dockerfile new file mode 100644 index 0000000..01ff38f --- /dev/null +++ b/o1-vth/Dockerfile @@ -0,0 +1,27 @@ +# 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 python:3.7.4 + +RUN python --version + +ADD pip-requirements.txt pip-requirements.txt +ADD o1_vth.py o1_vth.py +ADD config.ini config.ini + +RUN mkdir -p /otf/logs + +RUN python -m pip install -r pip-requirements.txt +ENTRYPOINT ["python", "o1_vth.py"] diff --git a/o1-vth/Jenkinsfile b/o1-vth/Jenkinsfile new file mode 100644 index 0000000..8da4b07 --- /dev/null +++ b/o1-vth/Jenkinsfile @@ -0,0 +1,164 @@ +#!/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: "id_otf_dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"], + [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "registry.hub.docker.io"] +]]]) + + +echo "Build branch: ${env.BRANCH_NAME}" + +node("docker"){ + stage 'Checkout' + checkout scm + PHASES=PHASE.tokenize( '_' ); + echo "PHASES : " + PHASES + + + ARTIFACT_ID="o1-vth"; + VERSION="0.0.1-SNAPSHOT"; + NAMESPACE="org-oran-otf" + DOCKER_REGISTRY="registry.hub.docker.io" + + 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")){ + + stage 'Publish Artifact' + + withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) { + + dir("o1-vth"){ + 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('o1-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 """ + helm version + 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 credentials.username= $USERNAME \ + --set credentials.password= $PASSWORD + + 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 credentials.username=$USERNAME \ + --set credentials.password=$PASSWORD + + + 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 credentials.username=$USERNAME \ + --set credentials.password=$PASSWORD + + """ + } + } + + } + } + } + + } +} diff --git a/o1-vth/config.ini b/o1-vth/config.ini new file mode 100644 index 0000000..16ec93c --- /dev/null +++ b/o1-vth/config.ini @@ -0,0 +1,10 @@ +[auth] +auth_enabled = true +username = %(USER)s +password = %(PW)s +[resource] +proxy_enabled = true +https_proxy = %(HTTPS)s +http_proxy = %(HTTP)s +base_address = %(BASE_URL)s +get_config_alarms_list = /restconf/config/network-topology:network-topology/topology/topology-netconf/node/o-ran-ru-1/yang-ext:mount/ietf-alarms:alarms/alarm-list \ No newline at end of file diff --git a/o1-vth/helm/o1-vth/.helmignore b/o1-vth/helm/o1-vth/.helmignore new file mode 100644 index 0000000..daebc7d --- /dev/null +++ b/o1-vth/helm/o1-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/o1-vth/helm/o1-vth/Chart.yaml b/o1-vth/helm/o1-vth/Chart.yaml new file mode 100644 index 0000000..35abef6 --- /dev/null +++ b/o1-vth/helm/o1-vth/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the o1 Virtual Test Head +name: o1-vth +version: 0.0.1 diff --git a/o1-vth/helm/o1-vth/templates/deployment.yaml b/o1-vth/helm/o1-vth/templates/deployment.yaml new file mode 100644 index 0000000..f78b5c8 --- /dev/null +++ b/o1-vth/helm/o1-vth/templates/deployment.yaml @@ -0,0 +1,97 @@ +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 + 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: HTTP + value: {{ .Values.HTTP}} + - name: HTTPS + value: {{ .Values.HTTPS}} + - name: BASE_URL + value: {{ .Values.BASE_URL}} + - name: USER + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: username + - name: PW + valueFrom: + secretKeyRef: + name: {{ .Values.appName}} + key: password + volumeMounts: + - name: {{.Values.appName}}-cert-volume + mountPath: /opt/cert + 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/o1-vth/helm/o1-vth/templates/secret.yaml b/o1-vth/helm/o1-vth/templates/secret.yaml new file mode 100644 index 0000000..b5c04dd --- /dev/null +++ b/o1-vth/helm/o1-vth/templates/secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.appName}} +type: Opaque +data: + username: {{ .Values.credentials.username | b64enc}} + password: {{ .Values.credentials.password | b64enc}} \ No newline at end of file diff --git a/o1-vth/helm/o1-vth/templates/service.yaml b/o1-vth/helm/o1-vth/templates/service.yaml new file mode 100644 index 0000000..f3bcfab --- /dev/null +++ b/o1-vth/helm/o1-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: 5000 + protocol: TCP + nodePort: {{ .Values.nodePort}} + selector: + app: {{ .Values.appName }} + version: {{ .Values.version}} diff --git a/o1-vth/helm/o1-vth/values.yaml b/o1-vth/helm/o1-vth/values.yaml new file mode 100644 index 0000000..66a2c9c --- /dev/null +++ b/o1-vth/helm/o1-vth/values.yaml @@ -0,0 +1,18 @@ +appName: o1-vth +env: dev +version: 0.0.1-SNAPSHOT +image: o1-vth:0.0.1-SNAPSHOT +namespace: org-oran-otf +nodePort: 32130 +replicas: 1 +health : /otf/vth/oran/smo/v1/health +sharedCert: otf-cert-secret-builder +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv +HTTP: "[Your HTTP PROXY HERE]" +HTTPS: "[Your HTTPS PROXY HERE]" +BASE_URL: http://sdn-r-dev.open5g-test.com +credentials: + username: "!" + password: "!" diff --git a/o1-vth/o1_vth.py b/o1-vth/o1_vth.py new file mode 100644 index 0000000..3732d75 --- /dev/null +++ b/o1-vth/o1_vth.py @@ -0,0 +1,145 @@ +# 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: o1-vth.py # +# Description: Mainly used to get alarm list # +# Date created: 04/14/2020 # +# Python Version: 3.7 # +# Author: Jackie Chen (jv246a) # +# Email: jv246a@att.com # +################################################################################ + +import datetime +from configparser import ConfigParser +import os +import logging +from logging import FileHandler +import requests +from flask import Flask, request, jsonify + +# redirect http to https +app = Flask(__name__) + +# Prevents print statement every time an endpoint is triggered. +logging.getLogger("werkzeug").setLevel(logging.WARNING) + + +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 _get_config(config_file_name): + config = ConfigParser(os.environ) + config.read(config_file_name) + return config + + +def _build_url(config): + return config['resource']['base_address'] + config['resource']['get_config_alarms_list'] + + +def _send_request(url, config): + # setup default values + proxy_enabled = config.getboolean('resource', 'proxy_enabled') + auth_enabled = config.getboolean('auth', 'auth_enabled') + username = '' + password = '' + + req_proxies = { + 'http': None, + 'https': None + } + # place proxy information + if proxy_enabled: + req_proxies['http'] = config['resource']['http_proxy'] + req_proxies['https'] = config['resource']['https_proxy'] + if auth_enabled: + username = config['auth']['username'] + password = config['auth']['password'] + # get call for alarm list + return requests.get(url, + auth=(username, password) if auth_enabled else None, + proxies=req_proxies if proxy_enabled else None) + +def _parse_resposne(response): + try: + return response.json() + except ValueError: + return response.text + +@app.route('/otf/vth/oran/smo/v1/alarm-list' , methods=['POST']) +def get_alarm_list(): + response_data = { + 'vthResponse': { + 'testDuration': '', + 'dateTimeUTC': str(datetime.datetime.now()), + 'abstractMessage': '', + 'resultData': {} + } + } + ret_url = request.args.get('retURL') + startTime = unix_time_millis(datetime.datetime.now()) + try: + # setup phase + config = _get_config('config.ini') + alarm_list_url = _build_url(config) + + # build initial response + app.logger.info('Sending GET for alarm list') + res = _send_request(alarm_list_url, config) + app.logger.info('Status code from GET: {}'.format(res.status_code)) + app.logger.info('Response received from GET alarm-list: {}'.format(res.content)) + response_data['vthResponse']['abstractMessage'] = 'Result from GET alarm list request' + response_data['vthResponse']['resultData']['status_code'] = res.status_code + response_data['vthResponse']['resultData']['result_output'] = _parse_resposne(res) + except Exception as ex: + endTime = unix_time_millis(datetime.datetime.now()) + totalTime = endTime - startTime + response_data['vthResponse']['testDuration'] = totalTime + response_data['vthResponse']['abstractMessage'] = 'error: ' + str(ex) + app.logger.error('ERROR:{}'.format(str(ex))) + return jsonify(response_data) + + #finish up building response + 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 + +@app.route("/otf/vth/oran/smo/v1/health", methods=['GET']) +def getHealth(): + return 'UP' + +if __name__ == '__main__': + logHandler = FileHandler('o1-vth.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/o1-vth/pip-requirements.txt b/o1-vth/pip-requirements.txt new file mode 100644 index 0000000..1a9cd87 --- /dev/null +++ b/o1-vth/pip-requirements.txt @@ -0,0 +1,6 @@ +flask +flask-cors +FLASK +FLASK-CORS +requests +configparser \ No newline at end of file