From: Tommy Carpenter Date: Wed, 18 Sep 2019 14:45:50 +0000 (-0400) Subject: Towards A1 v1.0.0 X-Git-Tag: 1.0.0~6 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=91ae88989c82b08b9fb69a28f838d6b80681d953;p=ric-plt%2Fa1.git Towards A1 v1.0.0 - Implement type PUT - Implement type GET - Remove RIC manifest - Read type GET to get schema for instance PUT - Remove Utils (no longer needed) - lots more tests Change-Id: I8facd9fd9d388a9087ba856bf781397397ac2ad3 Signed-off-by: Tommy Carpenter --- diff --git a/a1/controller.py b/a1/controller.py index 92ff278..9fd36d5 100644 --- a/a1/controller.py +++ b/a1/controller.py @@ -14,35 +14,18 @@ # See the License for the specific language governing permissions and # limitations under the License. # ================================================================================== +import json from flask import Response +from jsonschema import validate import connexion -import json from jsonschema.exceptions import ValidationError from a1 import get_module_logger -from a1 import a1rmr, exceptions, utils, data +from a1 import a1rmr, exceptions, data logger = get_module_logger(__name__) -def _get_policy_definition(policy_type_id): - # Currently we read the manifest on each call, which would seem to allow updating A1 in place. Revisit this? - manifest = utils.get_ric_manifest() - for m in manifest["controls"]: - if m["name"] == policy_type_id: - return m - - raise exceptions.PolicyTypeNotFound() - - -def _get_policy_schema(policy_type_id): - """ - Get the needed info for a policy - """ - m = _get_policy_definition(policy_type_id) - return m["message_receives_payload_schema"] if "message_receives_payload_schema" in m else None - - def _try_func_return(func): """ generic caller that returns the apporp http response if exceptions are raised @@ -52,6 +35,9 @@ def _try_func_return(func): except ValidationError as exc: logger.exception(exc) return "", 400 + except exceptions.PolicyTypeAlreadyExists as exc: + logger.exception(exc) + return "", 400 except exceptions.PolicyTypeNotFound as exc: logger.exception(exc) return "", 404 @@ -70,70 +56,6 @@ def _try_func_return(func): return Response(status=500) -def _put_handler(policy_type_id, policy_instance_id, instance): - """ - Handles policy put - - For now, policy_type_id is used as the message type - """ - # check for 404 - data.type_is_valid(policy_type_id) - - # validate the PUT against the schema, or if there is no shema, make sure the pUT is empty - schema = _get_policy_schema(policy_type_id) - if schema: - utils.validate_json(instance, schema) - elif instance != {}: - return "BODY SUPPLIED BUT POLICY HAS NO EXPECTED BODY", 400 - - # store the instance - data.store_policy_instance(policy_type_id, policy_instance_id, instance) - - body = { - "operation": "CREATE", - "policy_type_id": policy_type_id, - "policy_instance_id": policy_instance_id, - "payload": instance, - } - - # send rmr (best effort) - a1rmr.send(json.dumps(body), message_type=policy_type_id) - - return "", 201 - - -def _get_status_handler(policy_type_id, policy_instance_id): - """ - Pop trough A1s mailbox, insert the latest status updates into the database, and then return the status vector - - NOTE: this is done lazily. Meaning, when someone performs a GET on this API, we pop through a1s mailbox. - THis may not work in the future if there are "thousands" of policy acknowledgements that hit a1 before this is called, - because the rmr mailbox may fill. However, in the near term, we do not expect this to happen. - """ - # check validity to 404 first: - data.type_is_valid(policy_type_id) - data.instance_is_valid(policy_type_id, policy_instance_id) - - # pop a1s mailbox, looking for policy notifications - new_messages = a1rmr.dequeue_all_waiting_messages(21024) - - # try to parse the messages as responses. Drop those that are malformed - for msg in new_messages: - # note, we don't use the parameters "policy_type_id, policy_instance" from above here, - # because we are popping the whole mailbox, which might include other statuses - pay = json.loads(msg["payload"]) - if "policy_type_id" in pay and "policy_instance_id" in pay and "handler_id" in pay and "status" in pay: - data.set_policy_instance_status( - pay["policy_type_id"], pay["policy_instance_id"], pay["handler_id"], pay["status"] - ) - else: - logger.debug("Dropping message") - logger.debug(pay) - - # return the status vector - return data.get_policy_instance_statuses(policy_type_id, policy_instance_id) - - # Healthcheck @@ -159,14 +81,20 @@ def create_policy_type(policy_type_id): """ Handles PUT /a1-p/policytypes/policy_type_id """ - return "", 501 + + def _put_type_handler(policy_type_id, body): + data.store_policy_type(policy_type_id, body) + return "", 201 + + body = connexion.request.json + return _try_func_return(lambda: _put_type_handler(policy_type_id, body)) def get_policy_type(policy_type_id): """ Handles GET /a1-p/policytypes/policy_type_id """ - return "", 501 + return _try_func_return(lambda: data.get_policy_type(policy_type_id)) def delete_policy_type(policy_type_id): @@ -190,6 +118,7 @@ def get_policy_instance(policy_type_id, policy_instance_id): """ Handles GET /a1-p/policytypes/polidyid/policies/policy_instance_id """ + # 200 is automatic here return _try_func_return(lambda: data.get_policy_instance(policy_type_id, policy_instance_id)) @@ -197,6 +126,38 @@ def get_policy_instance_status(policy_type_id, policy_instance_id): """ Handles GET /a1-p/policytypes/polidyid/policies/policy_instance_id/status """ + + def _get_status_handler(policy_type_id, policy_instance_id): + """ + Pop trough A1s mailbox, insert the latest status updates into the database, and then return the status vector + + NOTE: this is done lazily. Meaning, when someone performs a GET on this API, we pop through a1s mailbox. + THis may not work in the future if there are "thousands" of policy acknowledgements that hit a1 before this is called, + because the rmr mailbox may fill. However, in the near term, we do not expect this to happen. + """ + # check validity to 404 first: + data.type_is_valid(policy_type_id) + data.instance_is_valid(policy_type_id, policy_instance_id) + + # pop a1s mailbox, looking for policy notifications + new_messages = a1rmr.dequeue_all_waiting_messages(21024) + + # try to parse the messages as responses. Drop those that are malformed + for msg in new_messages: + # note, we don't use the parameters "policy_type_id, policy_instance" from above here, + # because we are popping the whole mailbox, which might include other statuses + pay = json.loads(msg["payload"]) + if "policy_type_id" in pay and "policy_instance_id" in pay and "handler_id" in pay and "status" in pay: + data.set_policy_instance_status( + pay["policy_type_id"], pay["policy_instance_id"], pay["handler_id"], pay["status"] + ) + else: + logger.debug("Dropping message") + logger.debug(pay) + + # return the status vector + return data.get_policy_instance_statuses(policy_type_id, policy_instance_id) + return _try_func_return(lambda: _get_status_handler(policy_type_id, policy_instance_id)) @@ -204,8 +165,34 @@ def create_or_replace_policy_instance(policy_type_id, policy_instance_id): """ Handles PUT /a1-p/policytypes/polidyid/policies/policy_instance_id """ + + def _put_instance_handler(policy_type_id, policy_instance_id, instance): + """ + Handles policy instance put + + For now, policy_type_id is used as the message type + """ + # validate the PUT against the schema + schema = data.get_policy_type(policy_type_id)["create_schema"] + validate(instance=instance, schema=schema) + + # store the instance + data.store_policy_instance(policy_type_id, policy_instance_id, instance) + + body = { + "operation": "CREATE", + "policy_type_id": policy_type_id, + "policy_instance_id": policy_instance_id, + "payload": instance, + } + + # send rmr (best effort) + a1rmr.send(json.dumps(body), message_type=policy_type_id) + + return "", 201 + instance = connexion.request.json - return _try_func_return(lambda: _put_handler(policy_type_id, policy_instance_id, instance)) + return _try_func_return(lambda: _put_instance_handler(policy_type_id, policy_instance_id, instance)) def delete_policy_instance(policy_type_id, policy_instance_id): diff --git a/a1/data.py b/a1/data.py index 17ccf4a..6fa8b3f 100644 --- a/a1/data.py +++ b/a1/data.py @@ -23,7 +23,7 @@ Hopefully, the access functions are a good api so nothing else has to change whe For now, the database is in memory. We use dict data structures (KV) with the expectation of having to move this into Redis """ -from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound +from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists from a1 import get_module_logger logger = get_module_logger(__name__) @@ -35,14 +35,7 @@ I = "instances" H = "handlers" D = "data" - -# TODO: REMOVE THIS!! (should be done via PUT) -POLICY_DATA[20000] = {} -POLICY_DATA[20000][D] = {"foo": "bar"} -POLICY_DATA[20000][I] = {} -POLICY_DATA[20001] = {} -POLICY_DATA[20001][D] = {"foo": "bar"} -POLICY_DATA[20001][I] = {} +# Types def type_is_valid(policy_type_id): @@ -54,6 +47,29 @@ def type_is_valid(policy_type_id): raise PolicyTypeNotFound() +def store_policy_type(policy_type_id, body): + """ + store a policy type if it doesn't already exist + """ + if policy_type_id in POLICY_DATA: + raise PolicyTypeAlreadyExists() + + POLICY_DATA[policy_type_id] = {} + POLICY_DATA[policy_type_id][D] = body + POLICY_DATA[policy_type_id][I] = {} + + +def get_policy_type(policy_type_id): + """ + retrieve a type + """ + type_is_valid(policy_type_id) + return POLICY_DATA[policy_type_id][D] + + +# Instances + + def instance_is_valid(policy_type_id, policy_instance_id): """ check that an instance is valid diff --git a/a1/exceptions.py b/a1/exceptions.py index b924292..1362b2a 100644 --- a/a1/exceptions.py +++ b/a1/exceptions.py @@ -27,6 +27,10 @@ class PolicyTypeNotFound(BaseException): """a policy type instance cannot be found""" +class PolicyTypeAlreadyExists(BaseException): + """a policy type already exists and replace not supported at this time""" + + class MissingRmrString(BaseException): pass diff --git a/a1/openapi.yaml b/a1/openapi.yaml index 5a380bc..9dbe02b 100644 --- a/a1/openapi.yaml +++ b/a1/openapi.yaml @@ -318,7 +318,7 @@ components: represents a policy type identifier. Currently this is restricted to an integer range. type: integer minimum: 20000 - maximum: 21024 + maximum: 21023 policy_instance_id: description: > diff --git a/a1/run.py b/a1/run.py index 903fe85..9eb59f5 100644 --- a/a1/run.py +++ b/a1/run.py @@ -16,9 +16,7 @@ # ================================================================================== from gevent.pywsgi import WSGIServer from a1 import get_module_logger, app -from a1 import utils, exceptions from a1.a1rmr import init_rmr -import sys logger = get_module_logger(__name__) @@ -26,15 +24,8 @@ logger = get_module_logger(__name__) def main(): """Entrypoint""" - # Fail fast if we don't have a manifest - try: - utils.get_ric_manifest() - except exceptions.MissingManifest: - logger.error("Failing fast: no A1 manifest found!") - sys.exit(1) - logger.debug("Initializing rmr") init_rmr() logger.debug("Starting gevent server") - http_server = WSGIServer(('', 10000), app) + http_server = WSGIServer(("", 10000), app) http_server.serve_forever() diff --git a/a1/utils.py b/a1/utils.py deleted file mode 100644 index 0193b1d..0000000 --- a/a1/utils.py +++ /dev/null @@ -1,47 +0,0 @@ -# ================================================================================== -# Copyright (c) 2019 Nokia -# Copyright (c) 2018-2019 AT&T Intellectual Property. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ================================================================================== -import json -from jsonschema import validate -from a1 import get_module_logger -from a1 import exceptions - - -logger = get_module_logger(__name__) - - -# Public - - -def validate_json(instance, schema): - """ - validate incoming policy payloads - """ - validate(instance=instance, schema=schema) - - -def get_ric_manifest(): - """ - Get the ric level manifest - """ - try: - with open("/opt/ricmanifest.json", "r") as f: - content = f.read() - manifest = json.loads(content) - return manifest - except FileNotFoundError: - logger.error("Missing A1 Manifest!") - raise exceptions.MissingManifest diff --git a/container-tag.yaml b/container-tag.yaml index fdbd454..bdcf339 100644 --- a/container-tag.yaml +++ b/container-tag.yaml @@ -1,4 +1,4 @@ # The Jenkins job uses this string for the tag in the image name # for example nexus3.o-ran-sc.org:10004/my-image-name:my-tag --- -tag: 0.11.0-NOT_FOR_USE_YET +tag: 0.12.0-NOT_FOR_USE_YET diff --git a/docs/release-notes.rst b/docs/release-notes.rst index d087051..7a1894f 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -30,6 +30,16 @@ and this project adheres to `Semantic Versioning `__. * Release 1.0.0 will be the Release A version of A1 +[0.12.0] - 9/19/2019 +:: + + * Implement type PUT + * Implement type GET + * Remove RIC manifest + * Read type GET to get schema for instance PUT + * Remove Utils (no longer needed) + * lots more tests (unit and integration) + [0.11.0] - 9/17/2019 :: diff --git a/integration_tests/a1mediator/Chart.yaml b/integration_tests/a1mediator/Chart.yaml index 916aadc..b4f509e 100644 --- a/integration_tests/a1mediator/Chart.yaml +++ b/integration_tests/a1mediator/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 description: A1 Helm chart for Kubernetes name: a1mediator -version: 0.11.0 +version: 0.12.0 diff --git a/integration_tests/a1mediator/templates/config.yaml b/integration_tests/a1mediator/templates/config.yaml index bf4134c..bd0dae3 100644 --- a/integration_tests/a1mediator/templates/config.yaml +++ b/integration_tests/a1mediator/templates/config.yaml @@ -9,4 +9,3 @@ data: rte|20001|delayreceiverrmrservice:4563 rte|21024|{{ .Values.rmrservice.name }}:{{ .Values.rmrservice.port }} newrt|end - ricmanifest.json: {{tpl (.Files.Get "files/ricmanifest.json") . | quote}} diff --git a/integration_tests/a1mediator/templates/deployment.yaml b/integration_tests/a1mediator/templates/deployment.yaml index a2963fb..a30c21c 100644 --- a/integration_tests/a1mediator/templates/deployment.yaml +++ b/integration_tests/a1mediator/templates/deployment.yaml @@ -21,9 +21,6 @@ spec: containers: - name: {{ .Chart.Name }} volumeMounts: - - name: a1conf - mountPath: /opt/ricmanifest.json - subPath: ricmanifest.json - name: a1conf mountPath: /opt/route/local.rt subPath: local.rt diff --git a/integration_tests/test_a1.tavern.yaml b/integration_tests/test_a1.tavern.yaml index d9de88e..25d06be 100644 --- a/integration_tests/test_a1.tavern.yaml +++ b/integration_tests/test_a1.tavern.yaml @@ -15,6 +15,59 @@ stages: test_name: test admission control stages: + - name: type not there yet + request: + url: http://localhost:10000/a1-p/policytypes/20000 + method: GET + response: + status_code: 404 + + - name: put the type + request: + url: http://localhost:10000/a1-p/policytypes/20000 + method: PUT + json: + name: Admission Control + description: various parameters to control admission of dual connection + policy_type_id: 20000 + create_schema: + "$schema": http://json-schema.org/draft-07/schema# + type: object + properties: + enforce: + type: boolean + default: true + window_length: + type: integer + default: 1 + minimum: 1 + maximum: 60 + description: Sliding window length (in minutes) + blocking_rate: + type: number + default: 10 + minimum: 1 + maximum: 100 + description: "% Connections to block" + trigger_threshold: + type: integer + default: 10 + minimum: 1 + description: Minimum number of events in window to trigger blocking + required: + - enforce + - blocking_rate + - trigger_threshold + - window_length + additionalProperties: false + + - name: type there now + request: + url: http://localhost:10000/a1-p/policytypes/20000 + method: GET + response: + status_code: 200 + - name: test the admission control policy get not there yet request: url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy @@ -29,7 +82,7 @@ stages: response: status_code: 404 - - name: test the admission control policy + - name: put the admission control policy request: url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy method: PUT @@ -71,7 +124,53 @@ stages: test_name: test the delay receiver stages: - - name: test the delay policy get not there yet + + - name: test the delay policy type not there yet + request: + url: http://localhost:10000/a1-p/policytypes/20001 + method: GET + response: + status_code: 404 + + - name: put the type + request: + url: http://localhost:10000/a1-p/policytypes/20001 + method: PUT + json: + name: test policy + description: just for testing + policy_type_id: 20001 + create_schema: + "$schema": http://json-schema.org/draft-07/schema# + type: object + properties: + test: + type: string + required: + - test + additionalProperties: false + + - name: type there now + request: + url: http://localhost:10000/a1-p/policytypes/20001 + method: GET + response: + status_code: 200 + body: + name: test policy + description: just for testing + policy_type_id: 20001 + create_schema: + "$schema": http://json-schema.org/draft-07/schema# + type: object + properties: + test: + type: string + required: + - test + additionalProperties: false + + - name: test the delay policy instance get not there yet request: url: http://localhost:10000/a1-p/policytypes/20001/policies/delaytest method: GET @@ -89,7 +188,8 @@ stages: request: url: http://localhost:10000/a1-p/policytypes/20001/policies/delaytest method: PUT - json: {} + json: + test: foo headers: content-type: application/json response: @@ -101,7 +201,8 @@ stages: method: GET response: status_code: 200 - body: {} + body: + test: foo - name: test the admission control policy status get delay_before: 8 # give it a few seconds for rmr ; delay reciever sleeps for 5 seconds by default @@ -115,14 +216,58 @@ stages: status: OK - - --- test_name: bad_requests stages: + - name: bad type get + request: + url: http://localhost:10000/a1-p/policytypes/20002 + method: GET + response: + status_code: 404 + + + - name: bad instance get + request: + url: http://localhost:10000/a1-p/policytypes/20000/policies/darkness + method: GET + response: + status_code: 404 + + - name: bad int range 1 + request: + url: http://localhost:10000/a1-p/policytypes/19999 + method: PUT + json: + name: test policy + description: just for testing + policy_type_id: 19999 + create_schema: + "$schema": http://json-schema.org/draft-07/schema# + type: object + response: + status_code: 400 + + - name: bad int range 2 + request: + url: http://localhost:10000/a1-p/policytypes/21024 + method: PUT + json: + name: test policy + description: just for testing + policy_type_id: 21024 + create_schema: + "$schema": http://json-schema.org/draft-07/schema# + type: object + response: + status_code: 400 + + + + - name: bad body for admission control policy request: url: http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy @@ -142,9 +287,9 @@ stages: response: status_code: 415 - - name: bad body for test policy + - name: bad body for delaytest request: - url: http://localhost:10000/a1-p/policytypes/20001/policies/test_policy + url: http://localhost:10000/a1-p/policytypes/20001/policies/delaytest method: PUT json: not: "welcome" diff --git a/setup.py b/setup.py index e885438..441c054 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ from setuptools import setup, find_packages setup( name="a1", - version="0.11.0", + version="0.12.0", packages=find_packages(exclude=["tests.*", "tests"]), author="Tommy Carpenter", description="RIC A1 Mediator for policy/intent changes", diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c39b962 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,44 @@ +import pytest + + +@pytest.fixture +def adm_type_good(): + return { + "name": "Admission Control", + "description": "various parameters to control admission of dual connection", + "policy_type_id": 20000, + "create_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "enforce": {"type": "boolean", "default": True}, + "window_length": { + "type": "integer", + "default": 1, + "minimum": 1, + "maximum": 60, + "description": "Sliding window length (in minutes)", + }, + "blocking_rate": { + "type": "number", + "default": 10, + "minimum": 1, + "maximum": 100, + "description": "% Connections to block", + }, + "trigger_threshold": { + "type": "integer", + "default": 10, + "minimum": 1, + "description": "Minimum number of events in window to trigger blocking", + }, + }, + "required": ["enforce", "blocking_rate", "trigger_threshold", "window_length"], + "additionalProperties": False, + }, + } + + +@pytest.fixture +def adm_instance_good(): + return {"enforce": True, "window_length": 10, "blocking_rate": 20, "trigger_threshold": 10} diff --git a/tests/fixtures/ricmanifest.json b/tests/fixtures/ricmanifest.json deleted file mode 100644 index 7883c8a..0000000 --- a/tests/fixtures/ricmanifest.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "controls":[ - { - "name":20000, - "description":"various parameters to control admission of dual connection", - "control_state_request_rmr_type":"DC_ADM_GET_POLICY", - "control_state_request_reply_rmr_type":"DC_ADM_GET_POLICY_ACK", - "message_receives_rmr_type":"DC_ADM_INT_CONTROL", - "message_receives_payload_schema":{ - "$schema":"http://json-schema.org/draft-07/schema#", - "type":"object", - "properties":{ - "enforce":{ - "type":"boolean", - "default":true - }, - "window_length":{ - "type":"integer", - "default":1, - "minimum":1, - "maximum":60, - "description":"Sliding window length (in minutes)" - }, - "blocking_rate":{ - "type":"number", - "default":10, - "minimum":1, - "maximum":100, - "description":"% Connections to block" - }, - "trigger_threshold":{ - "type":"integer", - "default":10, - "minimum":1, - "description":"Minimum number of events in window to trigger blocking" - } - }, - "required":[ - "enforce", - "blocking_rate", - "trigger_threshold", - "window_length" - ], - "additionalProperties":false - }, - "message_sends_rmr_type":"DC_ADM_INT_CONTROL_ACK", - "message_sends_payload_schema":{ - "$schema":"http://json-schema.org/draft-07/schema#", - "type":"object", - "properties":{ - "status":{ - "type":"string", - "enum":[ - "SUCCESS", - "FAIL" - ] - }, - "message":{ - "type":"string" - } - }, - "required":[ - "status" - ], - "additionalProperties":false - } - }, - { - "name":20001, - "description":"for the purposes of testing", - "message_receives_rmr_type":"TEST_REQ", - "message_sends_rmr_type":"TEST_ACK", - "message_sends_payload_schema":{ - "$schema":"http://json-schema.org/draft-07/schema#", - "type":"object", - "properties":{ - "status":{ - "type":"string", - "enum":[ - "SUCCESS", - "FAIL" - ] - } - } - } - } - ] -} diff --git a/tests/test_controller.py b/tests/test_controller.py index 32f6d87..5bd3a08 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -19,13 +19,14 @@ import os from rmr.rmr_mocks import rmr_mocks from a1 import app -import testing_helpers import pytest ADM_CTRL = "admission_control_policy" ADM_CTRL_INSTANCE = "/a1-p/policytypes/20000/policies/" + ADM_CTRL ADM_CTRL_INSTANCE_STATUS = ADM_CTRL_INSTANCE + "/status" +ADM_CTRL_TYPE = "/a1-p/policytypes/20000" +TEST_TYPE = "/a1-p/policytypes/20001" # http://flask.pocoo.org/docs/1.0/testing/ @@ -53,7 +54,7 @@ def _fake_dequeue(_filter_type): def _test_put_patch(monkeypatch): - testing_helpers.patch_all(monkeypatch) + rmr_mocks.patch_rmr(monkeypatch) monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(0)) # good sends for this whole batch # we need to repatch alloc (already patched in patch_rmr) to fix the transactionid, alloc is called in send and recieve @@ -74,34 +75,23 @@ def _test_put_patch(monkeypatch): # Actual Tests -# def test_policy_get(client, monkeypatch): -# """ -# test policy GET -# """ -# _test_put_patch(monkeypatch) -# monkeypatch.setattr( -# "a1.a1rmr.dequeue_all_waiting_messages", -# _fake_dequeue(monkeypatch, msg_payload={"GET ack": "pretend policy is here"}, msg_type=20003), -# ) -# res = client.get("/a1-p/policies/admission_control_policy") -# assert res.status_code == 200 -# assert res.json == {"GET ack": "pretend policy is here"} -# -# -# def test_policy_get_unsupported(client, monkeypatch): -# """ -# test policy GET -# """ -# testing_helpers.patch_all(monkeypatch, nofetch=True) -# res = client.get("/a1-p/policies/admission_control_policy") -# assert res.status_code == 400 -# assert res.data == b'"POLICY DOES NOT SUPPORT FETCHING"\n' -# -# -def test_xapp_put_good(client, monkeypatch): +def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good): """ test policy put good""" - # nothing there yet + # no type there yet + res = client.get(ADM_CTRL_TYPE) + assert res.status_code == 404 + + # put the type + res = client.put(ADM_CTRL_TYPE, json=adm_type_good) + assert res.status_code == 201 + + # there now + res = client.get(ADM_CTRL_TYPE) + assert res.status_code == 200 + assert res.json == adm_type_good + + # no instance there yet res = client.get(ADM_CTRL_INSTANCE) assert res.status_code == 404 res = client.get(ADM_CTRL_INSTANCE_STATUS) @@ -109,13 +99,13 @@ def test_xapp_put_good(client, monkeypatch): # create a good instance _test_put_patch(monkeypatch) - res = client.put(ADM_CTRL_INSTANCE, json=testing_helpers.good_payload()) + res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good) assert res.status_code == 201 # get the instance res = client.get(ADM_CTRL_INSTANCE) assert res.status_code == 200 - assert res.json == testing_helpers.good_payload() + assert res.json == adm_instance_good # get the instance status monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue) @@ -125,16 +115,14 @@ def test_xapp_put_good(client, monkeypatch): # assert that rmr bad states don't cause problems monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10)) - res = client.put(ADM_CTRL_INSTANCE, json=testing_helpers.good_payload()) + res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good) assert res.status_code == 201 monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(5)) - res = client.put(ADM_CTRL_INSTANCE, json=testing_helpers.good_payload()) + res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good) assert res.status_code == 201 -# -# # def test_xapp_put_bad(client, monkeypatch): # """Test policy put fails""" # _test_put_patch(monkeypatch) @@ -174,13 +162,26 @@ def test_xapp_put_good(client, monkeypatch): # res = client.put("/a1-p/policies/admission_control_policy", json=testing_helpers.good_payload()) # assert res.status_code == 504 # assert res.data == b"\"A1 was expecting an ACK back but it didn't receive one or didn't recieve the expected ACK\"\n" -# -# -def test_bad_requests(client, monkeypatch): + + +def test_bad_instances(client, monkeypatch, adm_type_good): """ Test bad send failures """ - testing_helpers.patch_all(monkeypatch) + rmr_mocks.patch_rmr(monkeypatch) + + # TODO: reenable this after delete! + # put the type + # res = client.put(ADM_CTRL_TYPE, json=adm_type_good) + # assert res.status_code == 201 + + # illegal type range + res = client.put("/a1-p/policytypes/19999", json=adm_type_good) + assert res.status_code == 400 + res = client.put("/a1-p/policytypes/21024", json=adm_type_good) + assert res.status_code == 400 + + # bad body res = client.put(ADM_CTRL_INSTANCE, json={"not": "expected"}) assert res.status_code == 400 @@ -188,37 +189,6 @@ def test_bad_requests(client, monkeypatch): res = client.put(ADM_CTRL_INSTANCE, data="notajson") assert res.status_code == 415 - # test a PUT body against a poliucy not expecting one - res = client.put("/a1-p/policytypes/20001/policies/test_policy", json=testing_helpers.good_payload()) - assert res.status_code == 400 - assert res.data == b'"BODY SUPPLIED BUT POLICY HAS NO EXPECTED BODY"\n' - - -# def test_bad_requests(client, monkeypatch): -# """Test bad requests""" -# testing_helpers.patch_all(monkeypatch) -# -# # test a 404 -# res = client.put("/a1-p/policies/noexist", json=testing_helpers.good_payload()) -# assert res.status_code == 404 - - -# def test_missing_manifest(client, monkeypatch): -# """ -# test that we get a 500 with an approrpiate message on a missing manifest -# """ -# -# def f(): -# raise exceptions.MissingManifest() -# -# monkeypatch.setattr("a1.utils.get_ric_manifest", f) -# -# res = client.put("/a1-p/policies/admission_control_policy", json=testing_helpers.good_payload()) -# assert res.status_code == 500 -# assert res.data == b'"A1 was unable to find the required RIC manifest. report this!"\n' -# -# - def test_healthcheck(client): """ diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index 2895632..0000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,53 +0,0 @@ -# ================================================================================== -# Copyright (c) 2019 Nokia -# Copyright (c) 2018-2019 AT&T Intellectual Property. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ================================================================================== -import pytest -import jsonschema -from a1 import utils, exceptions -import testing_helpers - - -def test_bad_get_ric_manifest(monkeypatch): - """ - testing missing manifest - """ - - def badopen(filename, mode): - raise FileNotFoundError() - - monkeypatch.setattr("builtins.open", badopen) - with pytest.raises(exceptions.MissingManifest): - utils.get_ric_manifest() - - -def test_good_get_ric_manifest(monkeypatch): - """ - test get_ric_manifest - """ - testing_helpers.patch_all(monkeypatch) - utils.get_ric_manifest() - - -def test_validate(monkeypatch): - """ - test json validation wrapper - """ - testing_helpers.patch_all(monkeypatch) - ricmanifest = utils.get_ric_manifest() - schema = ricmanifest["controls"][0]["message_receives_payload_schema"] - utils.validate_json(testing_helpers.good_payload(), schema) - with pytest.raises(jsonschema.exceptions.ValidationError): - utils.validate_json({"dc_admission_start_time": "10:00:00", "dc_admission_end_time": "nevergonnagiveyouup"}, schema) diff --git a/tests/testing_helpers.py b/tests/testing_helpers.py deleted file mode 100644 index c59426b..0000000 --- a/tests/testing_helpers.py +++ /dev/null @@ -1,42 +0,0 @@ -# ================================================================================== -# Copyright (c) 2019 Nokia -# Copyright (c) 2018-2019 AT&T Intellectual Property. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ================================================================================== -import json -import os -from rmr.rmr_mocks import rmr_mocks - - -def _get_fixture_path(name): - cur_dir = os.path.dirname(os.path.realpath(__file__)) - return "{0}/fixtures/{1}".format(cur_dir, name) - - -def patch_all(monkeypatch, nonexisting_rmr=False, nofetch=False): - rmr_mocks.patch_rmr(monkeypatch) - - # patch manifest - man = json.loads(open(_get_fixture_path("ricmanifest.json"), "r").read()) - if nonexisting_rmr: - man["controls"][0]["message_receives_rmr_type"] = "DARKNESS" - - if nofetch: - del man["controls"][0]["control_state_request_rmr_type"] - - monkeypatch.setattr("a1.utils.get_ric_manifest", lambda: man) - - -def good_payload(): - return {"enforce": True, "window_length": 10, "blocking_rate": 20, "trigger_threshold": 10} diff --git a/tox-integration.ini b/tox-integration.ini index 08f5482..f6191e0 100644 --- a/tox-integration.ini +++ b/tox-integration.ini @@ -24,22 +24,30 @@ whitelist_externals= ab echo pkill + kubectl passenv = * deps = tavern changedir=integration_tests commands_pre= + echo "WARNING: make sure you're running with latest docker builds!" + sleep 5 helm install --devel testreceiver -n testreceiver - helm install --devel a1mediator/ -n a1 + helm install --devel a1mediator -n a1 +# wait for helm charts sleep 20 + kubectl get pods --all-namespaces ./portforward.sh sleep 2 commands= echo "linting" - helm lint a1mediator/ + helm lint a1mediator + helm lint testreceiver echo "running tavern" - pytest +# run tavern + pytest --tavern-beta-new-traceback echo "running ab" +# run apache bench ab -n 100 -c 10 -u putdata -T application/json http://localhost:10000/a1-p/policytypes/20000/policies/admission_control_policy commands_post= helm delete testreceiver