From cd4b9aaa9aa6f3f0bfef09c4808315f10a385310 Mon Sep 17 00:00:00 2001 From: Zhe Date: Mon, 13 Apr 2020 17:56:59 -0400 Subject: [PATCH] Implement the new xApp descriptor format This commit accepts the new xApp descriptor format and generate xApp helm charts accordingly. Please refer to https://wiki.o-ran-sc.org/display/RICA/xApp+descriptor and https://wiki.o-ran-sc.org/display/RICA/Schema+for+xApp+Descriptor for the latest xapp descriptor format Issue-ID: RIC-216 Signed-off-by: Zhe Change-Id: I5cd945814e131aef641d82ecfe33ad527bd19b31 --- xapp_onboarder/README.md | 20 - xapp_onboarder/tests/constants.py | 675 +++------------------ .../tests/mock_helm_repo/mock_helm_repo.py | 4 +- xapp_onboarder/tests/test_api.py | 24 +- xapp_onboarder/tests/test_helm_controller.py | 4 +- xapp_onboarder/tox.ini | 4 - .../xapp_onboarder/api/endpoints/onboard_ep.py | 8 +- .../xapp_onboarder/api/models/request_models.py | 4 +- xapp_onboarder/xapp_onboarder/api/onboard.py | 47 +- .../xapp_onboarder/helm_controller/xApp_builder.py | 36 -- .../xapp_onboarder/helm_controller/xapp_schema.py | 451 ++++++++++++++ .../resources/xapp-std/templates/deployment.yaml | 42 +- xapp_onboarder/xapp_onboarder/server/settings.py | 5 - 13 files changed, 619 insertions(+), 705 deletions(-) create mode 100644 xapp_onboarder/xapp_onboarder/helm_controller/xapp_schema.py diff --git a/xapp_onboarder/README.md b/xapp_onboarder/README.md index c6fefad..8ce97f7 100644 --- a/xapp_onboarder/README.md +++ b/xapp_onboarder/README.md @@ -17,10 +17,6 @@ A helm chart repo is needed to store the xApp helm charts. You can use [chartmus Environment variables: * **FLASK_SERVER_NAME**: Address that the xapp_onboarder is listening on. Default http://0.0.0.0:8888 * **CHART_REPO_URL**: helm chart repo URL. Default http://0.0.0.0:8080 -* **DBAAS_SERVICE_HOST**: DBAAS service host URL that will be injected into the xApp config -* **DBAAS_MASTER_NAME**: DBAAS_HA sentinel master URL that will be injected into the xApp config -* **DBAAS_SERVICE_SENTINEL_PORT**: DBAAS_HA sentinel port that will be injected into the xApp config -* **DBAAS_SERVICE_PORT**: DBAAS service port that will be injected into the xApp config ## Configurations Environment variables: @@ -32,25 +28,9 @@ Environment variables: ## Run the API server ```bash -python3 -m xapp_onboarder.server.server -``` -Or we recommend you can set up the symbolic link in your PATH -```bash -ln -s $(pip show xapp_onboarder | grep Location | awk '{printf $2 "/xapp_onboarder/xapp_onboarder"}') /usr/local/bin/xapp_onboarder -``` -Then you can run the server -```bash xapp_onboarder ``` ## Run the CLI tool ```bash -python3 -m xapp_onboarder.server.cli -``` -Or we recommend you can set up the symbolic link in your PATH -```bash -ln -s $(pip show xapp_onboarder | grep Location | awk '{printf $2 "/xapp_onboarder/cli"}') /usr/local/bin/cli -``` -Then you can run the server -```bash cli ``` diff --git a/xapp_onboarder/tests/constants.py b/xapp_onboarder/tests/constants.py index 0a52b6a..094595c 100644 --- a/xapp_onboarder/tests/constants.py +++ b/xapp_onboarder/tests/constants.py @@ -14,359 +14,23 @@ # limitations under the License. # ################################################################################ -schema_file = { +controls_schema_file = { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "http://example.com/root.json", + "$id": "#/controls", "type": "object", - "title": "The Root Schema", + "title": "Controls Section Schema", "required": [ - "local", - "logger", - "rmr", - "db", - "controls", - "metrics" + "test" ], "properties": { - "local": { - "$id": "#/properties/local", - "type": "object", - "title": "The Local Schema", - "required": [ - "host" - ], - "properties": { - "host": { - "$id": "#/properties/local/properties/host", - "type": "string", - "title": "The Host Schema", - "default": "", - "examples": [ - ":8080" - ], - "pattern": "^(.*)$" - } - } - }, - "logger": { - "$id": "#/properties/logger", - "type": "object", - "title": "The Logger Schema", - "required": [ - "level" - ], - "properties": { - "level": { - "$id": "#/properties/logger/properties/level", - "type": "integer", - "title": "The Level Schema", - "default": 0, - "examples": [ - 3 - ] - } - } - }, - "rmr": { - "$id": "#/properties/rmr", - "type": "object", - "title": "The Rmr Schema", - "required": [ - "protPort", - "maxSize", - "numWorkers", - "rxMessages", - "txMessages" - ], - "properties": { - "protPort": { - "$id": "#/properties/rmr/properties/protPort", - "type": "string", - "title": "The Protport Schema", - "default": "", - "examples": [ - "tcp:4560" - ], - "pattern": "^(.*)$" - }, - "maxSize": { - "$id": "#/properties/rmr/properties/maxSize", - "type": "integer", - "title": "The Maxsize Schema", - "default": 0, - "examples": [ - 2072 - ] - }, - "numWorkers": { - "$id": "#/properties/rmr/properties/numWorkers", - "type": "integer", - "title": "The Numworkers Schema", - "default": 0, - "examples": [ - 1 - ] - }, - "rxMessages": { - "$id": "#/properties/rmr/properties/rxMessages", - "type": "array", - "title": "The Rxmessages Schema", - "items": { - "$id": "#/properties/rmr/properties/rxMessages/items", - "type": "string", - "title": "The Items Schema", - "default": "", - "examples": [ - "RIC_SUB_RESP", - "RIC_SUB_FAILURE", - "RIC_SUB_DEL_RESP", - "RIC_SUB_DEL_FAILURE", - "RIC_INDICATION" - ], - "pattern": "^(.*)$" - } - }, - "txMessages": { - "$id": "#/properties/rmr/properties/txMessages", - "type": "array", - "title": "The Txmessages Schema", - "items": { - "$id": "#/properties/rmr/properties/txMessages/items", - "type": "string", - "title": "The Items Schema", - "default": "", - "examples": [ - "RIC_SUB_REQ", - "RIC_SUB_DEL_REQ", - "RIC_SGNB_ADDITION_REQ", - "RIC_SGNB_ADDITION_ACK", - "RIC_SGNB_ADDITION_REJECT", - "RIC_SGNB_MOD_REQUEST", - "RIC_SGNB_MOD_REQUEST_ACK", - "RIC_SGNB_MOD_REQUEST_REJECT", - "RIC_SGNB_MOD_REQUIRED", - "RIC_SGNB_MOD_CONFIRM", - "RIC_SGNB_MOD_REFUSE", - "RIC_SGNB_RECONF_COMPLETE", - "RIC_SGNB_RELEASE_REQUEST", - "RIC_SGNB_RELEASE_CONFIRM", - "RIC_SGNB_RELEASE_REQUIRED", - "RIC_SGNB_RELEASE_REQUEST_ACK", - "RIC_SECONDARY_RAT_DATA_USAGE_REPORT", - "RIC_SN_STATUS_TRANSFER", - "RIC_RRC_TRANSFER", - "RIC_UE_CONTEXT_RELEASE" - ], - "pattern": "^(.*)$" - } - } - } - }, - "db": { - "$id": "#/properties/db", - "type": "object", - "title": "The Db Schema", - "required": [ - "host", - "port", - "namespaces" - ], - "properties": { - "host": { - "$id": "#/properties/db/properties/host", - "type": "string", - "title": "The Host Schema", - "default": "", - "examples": [ - "localhost" - ], - "pattern": "^(.*)$" - }, - "port": { - "$id": "#/properties/db/properties/port", - "type": "integer", - "title": "The Port Schema", - "default": 0, - "examples": [ - 6379 - ] - }, - "namespaces": { - "$id": "#/properties/db/properties/namespaces", - "type": "array", - "title": "The Namespaces Schema", - "items": { - "$id": "#/properties/db/properties/namespaces/items", - "type": "string", - "title": "The Items Schema", - "default": "", - "examples": [ - "sdl", - "rnib" - ], - "pattern": "^(.*)$" - } - } - } - }, - "controls": { - "$id": "#/properties/controls", - "type": "object", - "title": "The Controls Schema", - "required": [ - "active", - "requestorId", - "ranFunctionId", - "ricActionId", - "interfaceId" - ], - "properties": { - "active": { - "$id": "#/properties/controls/properties/active", - "type": "boolean", - "title": "The Active Schema", - "default": False, - "examples": [ - True - ] - }, - "requestorId": { - "$id": "#/properties/controls/properties/requestorId", - "type": "integer", - "title": "The Requestorid Schema", - "default": 0, - "examples": [ - 66 - ] - }, - "ranFunctionId": { - "$id": "#/properties/controls/properties/ranFunctionId", - "type": "integer", - "title": "The Ranfunctionid Schema", - "default": 0, - "examples": [ - 1 - ] - }, - "ricActionId": { - "$id": "#/properties/controls/properties/ricActionId", - "type": "integer", - "title": "The Ricactionid Schema", - "default": 0, - "examples": [ - 0 - ] - }, - "interfaceId": { - "$id": "#/properties/controls/properties/interfaceId", - "type": "object", - "title": "The Interfaceid Schema", - "required": [ - "globalENBId" - ], - "properties": { - "globalENBId": { - "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId", - "type": "object", - "title": "The Globalenbid Schema", - "required": [ - "plmnId", - "eNBId" - ], - "properties": { - "plmnId": { - "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnId", - "type": "string", - "title": "The Plmnid Schema", - "default": "", - "examples": [ - "310150" - ], - "pattern": "^(.*)$" - }, - "eNBId": { - "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/eNBId", - "type": "integer", - "title": "The Enbid Schema", - "default": 0, - "examples": [ - 202251 - ] - } - } - } - } - } - } - }, - "metrics": { - "$id": "#/properties/metrics", - "type": "array", - "title": "The Metrics Schema", - "items": { - "$id": "#/properties/metrics/items", - "type": "object", - "title": "The Items Schema", - "required": [ - "objectName", - "objectInstance", - "name", - "type", - "description" - ], - "properties": { - "objectName": { - "$id": "#/properties/metrics/items/properties/objectName", - "type": "string", - "title": "The Objectname Schema", - "default": "", - "examples": [ - "UEEventStreamingCounters" - ], - "pattern": "^(.*)$" - }, - "objectInstance": { - "$id": "#/properties/metrics/items/properties/objectInstance", - "type": "string", - "title": "The Objectinstance Schema", - "default": "", - "examples": [ - "SgNBAdditionRequest" - ], - "pattern": "^(.*)$" - }, - "name": { - "$id": "#/properties/metrics/items/properties/name", - "type": "string", - "title": "The Name Schema", - "default": "", - "examples": [ - "SgNBAdditionRequest" - ], - "pattern": "^(.*)$" - }, - "type": { - "$id": "#/properties/metrics/items/properties/type", - "type": "string", - "title": "The Type Schema", - "default": "", - "examples": [ - "counter" - ], - "pattern": "^(.*)$" - }, - "description": { - "$id": "#/properties/metrics/items/properties/description", - "type": "string", - "title": "The Description Schema", - "default": "", - "examples": [ - "The total number of SG addition request events processed" - ], - "pattern": "^(.*)$" - } - } - } + "test": { + "$id": "#/controls/test", + "type": "string", + "title": "test", + "default": "test", + "examples": [ + "test" + ] } } } @@ -374,80 +38,69 @@ schema_file = { config_file = { "xapp_name": "test_xapp", "version": "1.0.0", - "containers": [{ - "name": "test1", - "image": { - "registry": "test_repo", - "name": "test_name", - "tag": "test_tag" - }, - "command": "test command" - }, + "containers": [ { - "name": "test2", + "name": "mcxapp", "image": { - "registry": "test2_repo", - "name": "test2_name", - "tag": "test2:_tag" + "registry": "nexus3.o-ran-sc.org:10002", + "name": "o-ran-sc/ric-app-mc", + "tag": "1.0.2" }, - "command": "test2 command" - }], - "local": { - "host": ":8080" - }, - "logger": { - "level": 3 + "command": "/playpen/bin/container_start.sh" + } + ], + "livenessProbe": { + "exec": { + "command": ["/usr/local/bin/health_ck"] + }, + "initialDelaySeconds": 5, + "periodSeconds": 15 }, - "db": { - "host": "localhost", - "port": 6379, - "namespaces": ["sdl", "rnib"] + "readinessProbe": { + "httpGet": { + "path": "ric/v1/health/alive", + "port": 8080 + }, + "initialDelaySeconds": 5, + "periodSeconds": 15 }, - "controls": { - "active": True, - "requestorId": 66, - "ranFunctionId": 1, - "ricActionId": 0, - "interfaceId": { - "globalENBId": { - "plmnId": "310150", - "eNBId": 202251 + "messaging": { + "ports": [ + { + "name": "http", + "container": "mcxapp", + "port": 8080, + "description": "http service" + }, + { + "name": "rmr_data", + "container": "mcxapp", + "port": 4560, + "description": "rmr data port for mcxapp" + }, + { + "name": "rmr_route", + "container": "mcxapp", + "port": 4561, + "description": "rmr route port for mcxapp" } - } - }, - "rmr": { - "protPort": "tcp:4560", - "maxSize": 10000, + ], + "maxSize": 2072, "numWorkers": 1, + "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" ], - "txMessages": [ - "RIC_SUB_REQ", - "RIC_SUB_DEL_REQ", - "RIC_SGNB_ADDITION_REQ", - "RIC_SGNB_ADDITION_ACK", - "RIC_SGNB_ADDITION_REJECT", - "RIC_SGNB_MOD_REQUEST", - "RIC_SGNB_MOD_REQUEST_ACK", - "RIC_SGNB_MOD_REQUEST_REJECT", - "RIC_SGNB_MOD_REQUIRED", - "RIC_SGNB_MOD_CONFIRM", - "RIC_SGNB_MOD_REFUSE", - "RIC_SGNB_RELEASE_REQUEST", - "RIC_SGNB_RELEASE_CONFIRM", - "RIC_SGNB_RELEASE_REQUIRED", - "RIC_SGNB_RELEASE_REQUEST_ACK", - "RIC_SGNB_RECONF_COMPLETE", - "RIC_UE_CONTEXT_RELEASE", - "RIC_RRC_TRANSFER", - "RIC_SECONDARY_RAT_DATA_USAGE_REPORT", - "RIC_SN_STATUS_TRANSFER" - ] + "policies": [1, 2] + }, + "controls": { + "test": "test" }, "metrics": [ { @@ -463,194 +116,40 @@ config_file = { "name": "SgNBAdditionRequestAcknowledge", "type": "counter", "description": "The total number of SG addition request acknowledge events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBAdditionRequestReject", - "name": "SgNBAdditionRequestReject", - "type": "counter", - "description": "The total number of SG addition request reject events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBModificationRequest", - "name": "SgNBModificationRequest", - "type": "counter", - "description": "The total number of SG modification request events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBModificationRequestAcknowledge", - "name": "SgNBModificationRequestAcknowledge", - "type": "counter", - "description": "The total number of SG modification request acknowledge events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBModificationRequestReject", - "name": "SgNBModificationRequestReject", - "type": "counter", - "description": "The total number of SG modification request reject events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBModificationRequired", - "name": "SgNBModificationRequired", - "type": "counter", - "description": "The total number of SG modification required events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBModificationConfirm", - "name": "SgNBModificationConfirm", - "type": "counter", - "description": "The total number of SG modification confirm events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBModificationRefuse", - "name": "SgNBModificationRefuse", - "type": "counter", - "description": "The total number of SG modification refuse events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBReleaseRequest", - "name": "SgNBReleaseRequest", - "type": "counter", - "description": "The total number of SG release request events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBReleaseRequestAcknowledge", - "name": "SgNBReleaseRequestAcknowledge", - "type": "counter", - "description": "The total number of SG release request acknowledge events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBReleaseRequestReject", - "name": "SgNBReleaseRequestReject", - "type": "counter", - "description": "The total number of SG release request reject events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBReleaseRequired", - "name": "SgNBReleaseRequired", - "type": "counter", - "description": "The total number of SG release required events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBReleasenConfirm", - "name": "SgNBReleasenConfirm", - "type": "counter", - "description": "The total number of SG release confirm events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SgNBReconfigurationComplete", - "name": "SgNBReconfigurationComplete", - "type": "counter", - "description": "The total number of SG reconfiguration complete events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "UEContextRelease", - "name": "UEContextRelease", - "type": "counter", - "description": "The total number of SG UE context release events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "RRCTransfer", - "name": "RRCTransfer", - "type": "counter", - "description": "The total number of SG RRC transfers events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SNStatusTransfer", - "name": "SNStatusTransfer", - "type": "counter", - "description": "The total number of SG SN status transfers events processed" - }, - { - "objectName": "UEEventStreamingCounters", - "objectInstance": "SecondaryRATDataUsageReport", - "name": "SecondaryRATDataUsageReport", - "type": "counter", - "description": "The total number of SG secondary RAT data usage reports events processed" - }, - { - "objectName": "RMRCounters", - "objectInstance": "Transmitted", - "name": "Transmitted", - "type": "counter", - "description": "The total number of RMR messages transmited" - }, - { - "objectName": "RMRCounters", - "objectInstance": "Received", - "name": "Received", - "type": "counter", - "description": "The total number of RMR messages received" - }, - { - "objectName": "RMRCounters", - "objectInstance": "TransmitError", - "name": "TransmitError", - "type": "counter", - "description": "The total number of RMR messages transmission errors" - }, - { - "objectName": "RMRCounters", - "objectInstance": "ReceiveError", - "name": "ReceiveError", - "type": "counter", - "description": "The total number of RMR messages receive errors" - }, - { - "objectName": "SDLounters", - "objectInstance": "Stored", - "name": "Stored", - "type": "counter", - "description": "The total number of stored SDL transactions" - }, - { - "objectName": "SDLounters", - "objectInstance": "StoreError", - "name": "StoreError", - "type": "counter", - "description": "The total number of SDL store errors" } ] } mock_json_body_url = { 'config-file.json_url': 'http://0.0.0.0:8080/config-file.json', - 'schema.json_url': 'http://0.0.0.0:8080/schema.json' + 'controls-schema.json_url': 'http://0.0.0.0:8080/schema.json' +} + +mock_json_body_url_without_controls = { + 'config-file.json_url': 'http://0.0.0.0:8080/config-file.json' } mock_json_body = { "config-file.json": config_file, - "schema.json": schema_file + "controls-schema.json": controls_schema_file } -helm_repo_index_response={'apiVersion': 'v1', - 'entries':{ - 'test_xapp':[{ - 'apiVersion': 'v1', - 'appVersion': '1.0', - 'created': '2020-03-12T19:10:17.178396719Z', - 'description': 'test xApp Helm Chart', - 'digest': 'd77dfb3f008e5174e90d79bfe982ef85b5dc5930141f6a1bd9995b2fa35', - 'name': 'test_xapp', - 'urls':['charts/test-1.0.0.tgz'], - 'version': '1.0.0' - }] - }, - 'generated': '2020-03-16T16:54:44Z', - 'serverInfo':{} - } +mock_json_body_without_controls = { + "config-file.json": config_file +} +helm_repo_index_response = {'apiVersion': 'v1', + 'entries': { + 'test_xapp': [{ + 'apiVersion': 'v1', + 'appVersion': '1.0', + 'created': '2020-03-12T19:10:17.178396719Z', + 'description': 'test xApp Helm Chart', + 'digest': 'd77dfb3f008e5174e90d79bfe982ef85b5dc5930141f6a1bd9995b2fa35', + 'name': 'test_xapp', + 'urls': ['charts/test-1.0.0.tgz'], + 'version': '1.0.0' + }] + }, + 'generated': '2020-03-16T16:54:44Z', + 'serverInfo': {} + } diff --git a/xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py b/xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py index 0e0e450..999a6b7 100755 --- a/xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py +++ b/xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py @@ -22,7 +22,7 @@ import json import os import tarfile from flask import Flask, jsonify, send_file -from tests.constants import config_file, schema_file, helm_repo_index_response +from tests.constants import config_file, controls_schema_file, helm_repo_index_response from xapp_onboarder.server import settings @@ -80,7 +80,7 @@ def download_xapp_helm_package(): def get_schema(): - return schema_file, 200 + return controls_schema_file, 200 @app.route('/config-file.json', methods=['GET']) def get_config_file(): diff --git a/xapp_onboarder/tests/test_api.py b/xapp_onboarder/tests/test_api.py index b50709b..23a50d6 100644 --- a/xapp_onboarder/tests/test_api.py +++ b/xapp_onboarder/tests/test_api.py @@ -15,7 +15,7 @@ ################################################################################ from http import HTTPStatus -from tests.constants import mock_json_body, mock_json_body_url, helm_repo_index_response +from tests.constants import mock_json_body, mock_json_body_url_without_controls, mock_json_body_url, mock_json_body_without_controls, helm_repo_index_response def test_health(client): @@ -52,9 +52,29 @@ def test_onboard_post(client): assert response.content_type == 'application/json', 'Content type error' assert response.json == {'status': 'Created'}, 'Onboard failed' +def test_onboard_without_controls_post(client): + url = '/api/v1/onboard' + response = client.post(url, json=mock_json_body_without_controls) + assert response.status_code == HTTPStatus.BAD_REQUEST, 'Wrong status code' + assert response.content_type == 'application/json', 'Content type error' + assert response.json == {'error_message': "'__empty_control_section__' is a required property", + 'error_source': 'config-file.json', + 'status': 'Input payload validation failed'}, 'Onboard failed' + + def test_onboard_download_post(client): url = '/api/v1/onboard/download' response = client.post(url, json=mock_json_body_url) assert response.status_code == HTTPStatus.CREATED, 'Wrong status code' assert response.content_type == 'application/json', 'Content type error' - assert response.json == {'status': 'Created'}, 'Onboard failed' \ No newline at end of file + assert response.json == {'status': 'Created'}, 'Onboard failed' + + +def test_onboard_download_without_controls_post(client): + url = '/api/v1/onboard/download' + response = client.post(url, json=mock_json_body_url_without_controls) + assert response.status_code == HTTPStatus.BAD_REQUEST, 'Wrong status code' + assert response.content_type == 'application/json', 'Content type error' + assert response.json == {'error_message': "'__empty_control_section__' is a required property", + 'error_source': 'config-file.json', + 'status': 'Input payload validation failed'}, 'Onboard failed' diff --git a/xapp_onboarder/tests/test_helm_controller.py b/xapp_onboarder/tests/test_helm_controller.py index 094cf9f..1298d1e 100644 --- a/xapp_onboarder/tests/test_helm_controller.py +++ b/xapp_onboarder/tests/test_helm_controller.py @@ -16,7 +16,7 @@ import os import shutil from xapp_onboarder.helm_controller.xApp_builder import xApp, xAppError -from tests.constants import config_file, schema_file +from tests.constants import config_file, controls_schema_file from xapp_onboarder.server import settings @@ -25,7 +25,7 @@ def test_packaging_xapp(client): if os.path.exists(chart_workspace_path): shutil.rmtree(chart_workspace_path) - xapp = xApp(config_file, schema_file) + xapp = xApp(config_file, controls_schema_file) xapp.package_chart() assert os.path.isfile(chart_workspace_path + '/test_xapp-1.0.0.tgz'), 'xApp packaging error' diff --git a/xapp_onboarder/tox.ini b/xapp_onboarder/tox.ini index 5621690..a66cd76 100644 --- a/xapp_onboarder/tox.ini +++ b/xapp_onboarder/tox.ini @@ -27,10 +27,6 @@ deps= setenv = PYTHONUNBUFFERED = 1 CHART_REPO_URL = http://0.0.0.0:8080 - DBAAS_SERVICE_HOST = test_dbaas_host - DBAAS_MASTER_NAME = test_master_name - DBAAS_SERVICE_SENTINEL_PORT = 29701 - DBAAS_SERVICE_PORT = 3710 MOCK_TEST_MODE = True lf: CHART_WORKSPACE_PATH = {toxworkdir}/.tmp/xapp_onboarder lf: MOCK_TEST_HELM_REPO_TEMP_DIR = {toxworkdir}/.tmp/helm_repo diff --git a/xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py b/xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py index d287f11..222307d 100644 --- a/xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py +++ b/xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py @@ -48,9 +48,9 @@ class OnboardxApps(Resource): Onboard xApp with the xApp descriptor and its scehma included in the Json body """ config_file = request.json.get('config-file.json') - schema_file = request.json.get('schema.json') + controls_schema_file = request.json.get('controls-schema.json') - return onboard(config_file, schema_file) + return onboard(config_file, controls_schema_file) @ns.route('/download') @@ -65,7 +65,7 @@ class OnboardxAppsDownload(Resource): Onboard xApp with xApp descriptor and schema downloading URLs """ config_file_url = request.json.get('config-file.json_url') - schema_url = request.json.get('schema.json_url') + controls_schema_url = request.json.get('controls-schema.json_url') - return download_config_and_schema_and_onboard(config_file_url, schema_url) + return download_config_and_schema_and_onboard(config_file_url, controls_schema_url) diff --git a/xapp_onboarder/xapp_onboarder/api/models/request_models.py b/xapp_onboarder/xapp_onboarder/api/models/request_models.py index d49a711..312ebaa 100644 --- a/xapp_onboarder/xapp_onboarder/api/models/request_models.py +++ b/xapp_onboarder/xapp_onboarder/api/models/request_models.py @@ -24,12 +24,12 @@ xapp_descriptor_post = api.model('descriptor', { 'version': fields.String(description='Version of the xApp chart', required=True, pattern='^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'), }), required=True), - 'schema.json': fields.Raw(description='Schema file body', required=True), + 'controls-schema.json': fields.Raw(description='Controls schema file body', required=False), }) xapp_descriptor_download_post = api.model('descriptor_remote', { 'config-file.json_url': fields.Url(description='URL to download the config-file.json file', absolute=True, required=True), - 'schema.json_url': fields.Url(description='URL to download the schema.json file', absolute=True, required=True), + 'controls-schema.json_url': fields.Url(description='URL to download the controls schema.json file', absolute=True, required=False), }) diff --git a/xapp_onboarder/xapp_onboarder/api/onboard.py b/xapp_onboarder/xapp_onboarder/api/onboard.py index bdc80eb..71b8f62 100644 --- a/xapp_onboarder/xapp_onboarder/api/onboard.py +++ b/xapp_onboarder/xapp_onboarder/api/onboard.py @@ -16,18 +16,19 @@ import logging import json +import copy from jsonschema import ValidationError, SchemaError from jsonschema import validate, Draft7Validator from xapp_onboarder.helm_controller.xApp_builder import xApp, xAppError from xapp_onboarder.server import settings from xapp_onboarder.repo_manager.repo_manager import requests_retry_session, repo_manager from xapp_onboarder.api.models.response_models import error_message_model, response, status_message_model - +from xapp_onboarder.helm_controller.xapp_schema import schema as xapp_schema log = logging.getLogger(__name__) -def onboard(config_file, schema_file): +def onboard(config_file, controls_schema_file): if not repo_manager.is_repo_ready(): response_message = response(model=error_message_model, status_code=500, error_source="xapp_onboarder", @@ -35,6 +36,11 @@ def onboard(config_file, schema_file): status="Service not ready.") return response_message.get_return() + schema_file = copy.deepcopy(xapp_schema) + + if controls_schema_file: + schema_file["properties"]["controls"] = controls_schema_file + try: Draft7Validator.check_schema(schema_file) validate(config_file, schema_file) @@ -67,7 +73,7 @@ def onboard(config_file, schema_file): return response(model=status_message_model, status_code=201, status="Created").get_return() -def download_config_and_schema_and_onboard(config_file_url, schema_url): +def download_config_and_schema_and_onboard(config_file_url, controls_schema_url): if not repo_manager.is_repo_ready(): response_message = response(model=error_message_model, status_code=500, error_source="xapp_onboarder", @@ -96,24 +102,27 @@ def download_config_and_schema_and_onboard(config_file_url, schema_url): return response_message.get_return() config_file = json.loads(response_content.content) - try: - response_content = session.get(schema_url, timeout=settings.HTTP_TIME_OUT) - except Exception as err: - log.error(err.message) - response_message = response(model=error_message_model, status_code=500, - error_source="schema.json", - error_message=err.message, - status="Downloading schema.json failed") - return response_message.get_return() - else: - if response_content.status_code != 200: - error_message = "Wrong response code. {}, {}".format(response_content.status_code, response_content.content.decode("utf-8")) - log.error(error_message) + controls_schema_file = None + if controls_schema_url: + try: + response_content = session.get(controls_schema_url, timeout=settings.HTTP_TIME_OUT) + except Exception as err: + log.error(err.message) response_message = response(model=error_message_model, status_code=500, error_source="schema.json", - error_message=error_message, + error_message=err.message, status="Downloading schema.json failed") return response_message.get_return() - schema_file = json.loads(response_content.content) + else: + if response_content.status_code != 200: + error_message = "Wrong response code. {}, {}".format(response_content.status_code, response_content.content.decode("utf-8")) + log.error(error_message) + response_message = response(model=error_message_model, status_code=500, + error_source="schema.json", + error_message=error_message, + status="Downloading schema.json failed") + return response_message.get_return() + controls_schema_file = json.loads(response_content.content) + - return onboard(config_file, schema_file) + return onboard(config_file, controls_schema_file) diff --git a/xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py b/xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py index 1018f40..0cd821b 100644 --- a/xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py +++ b/xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py @@ -159,46 +159,11 @@ class xApp(): outputfile.write(indented_probe_definition_yaml) - def append_config_to_values_yaml(self): with open(self.chart_workspace_path + '/' + self.chart_name + '/values.yaml', 'a') as outputfile: yaml.dump(self.config_file, outputfile, default_flow_style=False) - def append_env_to_config_map(self): - with open(self.chart_workspace_path + '/' + self.chart_name + '/templates/appenv.yaml', 'a') as outputfile: - append = {} - if settings.DBAAS_MASTER_NAME: - master_name = settings.DBAAS_MASTER_NAME - service_host = settings.DBAAS_SERVICE_HOST - sentinel_port = settings.DBAAS_SERVICE_SENTINEL_PORT - if not service_host: - raise xAppError( - "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_HOST'. (Caused by: Misconfiguration of temp deployment)", 500) - if not sentinel_port: - raise xAppError( - "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_SENTINEL_PORT'. (Caused by: Misconfiguration of temp deployment)", 500) - - append['DBAAS_MASTER_NAME'] = master_name - append['DBAAS_SERVICE_HOST'] = service_host - append['DBAAS_SERVICE_SENTINEL_PORT'] = sentinel_port - elif settings.DBAAS_SERVICE_HOST: - service_host = settings.DBAAS_SERVICE_HOST - service_port = settings.DBAAS_SERVICE_PORT - if not service_port: - raise xAppError( - "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_PORT'. (Caused by: Misconfiguration of temp deployment)", 500) - append['DBAAS_SERVICE_HOST'] = service_host - append['DBAAS_SERVICE_PORT'] = service_port - else: - raise xAppError( - "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_HOST' or 'DBAAS_MASTER_NAME'. (Caused by: Misconfiguration of temp deployment)", - 500) - output_yaml = yaml.dump(append) - indented_output_yaml = indent(output_yaml, 2) - outputfile.write(indented_output_yaml) - - def change_chart_name_version(self): with open(self.chart_workspace_path + '/' + self.chart_name + '/Chart.yaml', 'r') as inputfile: self.chart_yaml = yaml.load(inputfile, Loader=yaml.FullLoader) @@ -225,7 +190,6 @@ class xApp(): def package_chart(self): self.append_config_to_config_map() self.append_config_to_values_yaml() - self.append_env_to_config_map() self.add_probes_to_deployment() self.change_chart_name_version() self.helm_lint() diff --git a/xapp_onboarder/xapp_onboarder/helm_controller/xapp_schema.py b/xapp_onboarder/xapp_onboarder/helm_controller/xapp_schema.py new file mode 100644 index 0000000..e43f867 --- /dev/null +++ b/xapp_onboarder/xapp_onboarder/helm_controller/xapp_schema.py @@ -0,0 +1,451 @@ +################################################################################ +# Copyright (c) 2020 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. # +################################################################################ + +schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://o-ran-sc.org/xapp_root.json", + "type": "object", + "title": "The xApp Root Schema", + "required": [ + "xapp_name", + "version", + "containers" + ], + "properties": { + "xapp_name": { + "$id": "#/properties/xapp_name", + "type": "string", + "title": "The xApp Name", + "default": "xapp", + "examples": [ + "example_xapp" + ] + }, + "version": { + "$id": "#/properties/version", + "type": "string", + "title": "The xApp version", + "default": "1.0.0", + "examples": [ + "1.0.0" + ], + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "containers": { + "$id": "#/properties/containers", + "type": "array", + "title": "The Container Schema", + "items": { + "$id": "#/properties/containers/items", + "type": "object", + "title": "The Container Items Schema", + "required": [ + "name", + "image" + ], + "properties": { + "name": { + "$id": "#/properties/containers/items/properties/name", + "type": "string", + "title": "The xApp Container Name", + "default": "xapp", + "examples": [ + "xapp" + ] + }, + "image": { + "$id": "#/properties/containers/items/properties/image", + "type": "object", + "title": "The Container Image", + "required": [ + "registry", + "name", + "tag" + ], + "properties": { + "registry": { + "$id": "#/properties/containers/items/properties/image/properties/registry", + "type": "string", + "title": "The xApp Image Registry", + "default": "nexus3.o-ran-sc.org:10002", + "examples": [ + "nexus3.o-ran-sc.org:10002" + ], + "pattern": "^[A-Za-z0-9\\.-]{1,}\\.[A-Za-z]{1,}(?:\\:\\d+)?$" + }, + "name": { + "$id": "#/properties/containers/items/properties/image/properties/name", + "type": "string", + "title": "The xApp Image Name", + "default": "xapp", + "examples": [ + "xapp" + ] + }, + "tag": { + "$id": "#/properties/containers/items/properties/image/properties/tag", + "type": "string", + "title": "The xApp Image Tag", + "default": "latest", + "examples": [ + "latest" + ] + } + } + }, + "command": { + "$id": "#/properties/containers/items/properties/command", + "type": "string", + "title": "Command To Run The xApp Container", + "default": "command", + "examples": [ + "command" + ] + } + } + } + }, + "livenessProbe": { + "$id": "#/properties/livenessprobe", + "type": "object", + "title": "The Liveness Probe Definition", + "properties": { + "exec": { + "$id": "#/properties/livenessprobe/exec", + "type": "object", + "title": "Script of Liveness Probe", + "properties": { + "command": { + "$id": "#/properties/livenessprobe/exec/command", + "type": "array", + "items": [ + { + "$id": "#/properties/livenessprobe/exec/command/item", + "type": "string", + "title": "The Command Item", + "default": "/bin/sh", + "examples": [ + "/bin/sh" + ] + } + ] + } + }, + "required": [ + "command" + ] + }, + "httpGet": { + "$id": "#/properties/livenessprobe/httpget", + "type": "object", + "title": "Http of Liveness Probe", + "properties": { + "path": { + "$id": "#/properties/livenessprobe/httpget/path", + "type": "string", + "title": "The Path of Http Liveness Probe", + "default": "/health", + "examples": [ + "/health" + ] + }, + "port": { + "$id": "#/properties/livenessprobe/httpget/port", + "type": "integer", + "title": "The Port of Http Liveness Probe", + "default": 80, + "examples": [ + 80 + ] + } + }, + "required": [ + "path", + "port" + ] + }, + "initialDelaySeconds": { + "$id": "#/properties/livenessprobe/initialdelayseconds", + "type": "integer", + "title": "Initial Delay of Liveness Probe", + "default": 5, + "examples": [ + 5 + ] + }, + "periodSeconds": { + "$id": "#/properties/livenessprobe/periodseconds", + "type": "integer", + "title": "Period of Liveness Probe", + "default": 15, + "examples": [ + 15 + ] + } + }, + "oneOf": [ + { + "$id": "#/properties/livenessprobe/oneof/exec", + "required": ["exec", "initialDelaySeconds", "periodSeconds"] + }, + { + "$id": "#/properties/livenessprobe/oneof/httpget", + "required": ["httpGet", "initialDelaySeconds", "periodSeconds"] + } + ] + }, + "readinessProbe": { + "$id": "#/properties/readinessprobe", + "type": "object", + "title": "The Readiness Probe Definition", + "properties": { + "exec": { + "$id": "#/properties/readinessprobe/exec", + "type": "object", + "title": "Script of Readiness Probe", + "properties": { + "command": { + "$id": "#/properties/readinessprobe/exec/command", + "type": "array", + "items": [ + { + "type": "string" + } + ] + } + }, + "required": [ + "command" + ] + }, + "httpGet": { + "$id": "#/properties/readinessprobe/httpget", + "type": "object", + "title": "Http of Readiness Probe", + "properties": { + "path": { + "$id": "#/properties/readinessprobe/httpget/path", + "type": "string", + "title": "The Path of Http Readiness Probe", + "default": "/health", + "examples": [ + "/health" + ] + }, + "port": { + "$id": "#/properties/readinessprobe/httpget/port", + "type": "integer", + "title": "The Port of Http Readiness Probe", + "default": 80, + "examples": [ + 80 + ] + } + }, + "required": [ + "path", + "port" + ] + }, + "initialDelaySeconds": { + "$id": "#/properties/readinessprobe/initialdelayseconds", + "type": "integer", + "title": "Initial Delay of Readiness Probe", + "default": 5, + "examples": [ + 5 + ] + }, + "periodSeconds": { + "$id": "#/properties/readinessprobe/periodseconds", + "type": "integer", + "title": "Period of Readiness Probe", + "default": 15, + "examples": [ + 15 + ] + } + }, + "oneOf": [ + { + "$id": "#/properties/readinessprobe/oneof/exec", + "required": ["exec", "initialDelaySeconds", "periodSeconds"] + }, + { + "$id": "#/properties/readinessprobe/oneof/httpget", + "required": ["httpGet", "initialDelaySeconds", "periodSeconds"] + } + ] + }, + "messaging": { + "type": "object", + "$id": "#/properties/messaging", + "title": "The Messaging Schema", + "properties": { + "ports": { + "$id": "#/properties/messaging/ports", + "type": "array", + "title": "The Ports for Messaging", + "items": { + "$id": "#/properties/messaging/ports/items", + "type": "object", + "title": "The Item of Port", + "required": ["name", "container", "port"], + "dependencies": { + "txMessages": ["rxMessages", "policies"], + "rxMessages": ["txMessages", "policies"], + "policies": ["rxMessages", "txMessages"] + }, + "properties": { + "name": { + "$id": "#/properties/messaging/ports/items/name", + "type": "string", + "title": "The Name of the Port", + "default": "App", + "examples": [ + "App" + ] + }, + "container": { + "$id": "#/properties/messaging/ports/items/container", + "type": "string", + "title": "The Container of the Port", + "default": "xapp", + "examples": [ + "xapp" + ] + }, + "port": { + "$id": "#/properties/messaging/ports/items/port", + "type": "integer", + "title": "The Port Number", + "default": 8080, + "examples": [ + 8080 + ] + }, + "description": { + "$id": "#/properties/messaging/ports/items/description", + "type": "string", + "title": "The description for the port", + "default": "port description", + "examples": [ + "port description" + ] + }, + "txMessages": { + "$id": "#/properties/messaging/ports/items/txmessages", + "type": "array", + "title": "The txMessage Types", + "items": { + "$id": "#/properties/messaging/ports/items//txmessages/item", + "type": "string", + "title": "The txMessage Types Item", + "default": "RIC_SUB", + "examples": [ + "RIC_SUB" + ] + } + }, + "rxMessages": { + "$id": "#/properties/messaging/ports/items/rxmessages", + "type": "array", + "title": "The rxMessage Types", + "items": { + "$id": "#/properties/messaging/ports/items/rxmessages/item", + "type": "string", + "title": "The rxMessage Types Item", + "default": "RIC_SUB", + "examples": [ + "RIC_SUB" + ] + } + }, + "policies": { + "$id": "#/properties/messaging/ports/items/policies", + "type": "array", + "title": "The Policies Types", + "items": { + "$id": "#/properties/messaging/ports/items/policies/item", + "type": "integer", + "title": "The Policy Types Item", + "default": 1, + "examples": [ + 1 + ] + } + } + } + } + } + }, + "required": [ + "ports" + ] + + }, + "metrics": { + "type": "array", + "$id": "#/properties/metrics", + "title": "The Metrics Schema", + "items": { + "$id": "#/properties/metrics/items", + "type": "object", + "title": "The Metrics Items Schema", + "required": [ + "objectName", + "objectInstance", + "name", + "type", + "description" + ], + "properties": { + "objectName": { + "$id": "#/properties/metrics/items/objectname", + "type": "string", + "title": "The Object Name" + }, + "objectInstance": { + "$id": "#/properties/metrics/items/objectinstance", + "type": "string", + "title": "The Object Instance" + }, + "name": { + "$id": "#/properties/metrics/items/name", + "type": "string", + "title": "The Object Name" + }, + "type": { + "$id": "#/properties/metrics/items/type", + "type": "string", + "title": "The Object Type" + }, + "description": { + "$id": "#/properties/metrics/items/description", + "type": "string", + "title": "The Object Description" + } + } + } + }, + "controls": { + "required": [ + "__empty_control_section__" + ] + } + + } +} diff --git a/xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/deployment.yaml b/xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/deployment.yaml index 6b24b9a..defb6b4 100644 --- a/xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/deployment.yaml +++ b/xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/deployment.yaml @@ -44,31 +44,29 @@ spec: configMap: name: {{ include "ricxapp.configmapname" . }}-appconfig containers: -{{- range .Values.containers }} - - name: {{ .name }} - image: "{{ .image.registry }}/{{ .image.name }}:{{ .image.tag }}" - {{- if .command }} - command: [{{ .command }}] +{{- $containers := .Values.containers }} +{{- $ports := .Values.messaging.ports }} +{{- range $container := $containers }} + {{- $portlist := list }} + {{- range $port := $ports }} + {{- if eq $port.container $container.name }} + {{- $portlist = append $portlist $port }} + {{- end }} + {{- end }} + - name: {{ $container.name }} + image: "{{ $container.image.registry }}/{{ $container.image.name }}:{{ $container.image.tag }}" + {{- if $container.command }} + command: [{{ $container.command }}] {{- end}} imagePullPolicy: {{ $.Values.image_pull_policy }} - {{- if .ports }} + {{- if $portlist }} ports: - {{- if .ports.http }} - - name: http-{{ .name }} - containerPort: {{ .ports.http }} - protocol: TCP - {{- end }} - {{- if .ports.rmr_data }} - - name: rmrdata-{{ .name }} - containerPort: {{ .ports.rmr_data }} + {{- range $port := $portlist }} + - name: {{ $port.name }} + containerPort: {{ $port.port }} protocol: TCP - {{- end }} - {{- if .ports.rmr_route }} - - name: rmrroute-{{ .name }} - containerPort: {{ .ports.rmr_route }} - protocol: TCP - {{- end }} - {{- end }} + {{- end }} + {{- end }} {{- end }} volumeMounts: - name: config-volume @@ -76,4 +74,6 @@ spec: envFrom: - configMapRef: name: {{ include "ricxapp.configmapname" . }}-appenv + - configMapRef: + name: dbaas-appconfig restartPolicy: Always diff --git a/xapp_onboarder/xapp_onboarder/server/settings.py b/xapp_onboarder/xapp_onboarder/server/settings.py index 8bc1a3f..04100a5 100644 --- a/xapp_onboarder/xapp_onboarder/server/settings.py +++ b/xapp_onboarder/xapp_onboarder/server/settings.py @@ -38,8 +38,3 @@ MOCK_TEST_MODE = os.environ.get('MOCK_TEST_MODE') or False MOCK_TEST_HELM_REPO_TEMP_DIR = os.environ.get('MOCK_TEST_HELM_REPO_TEMP_DIR') or '/tmp/mock_helm_repo' -# Environment variables that will be passed into xApp -DBAAS_MASTER_NAME = os.environ.get('DBAAS_MASTER_NAME') -DBAAS_SERVICE_HOST = os.environ.get('DBAAS_SERVICE_HOST') -DBAAS_SERVICE_SENTINEL_PORT = os.environ.get('DBAAS_SERVICE_SENTINEL_PORT') -DBAAS_SERVICE_PORT = os.environ.get('DBAAS_SERVICE_PORT') -- 2.16.6