X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=tests%2Ftest_controller.py;h=833fc6ef64e60ff74c2aefcf12cc86994b26d165;hb=ccb4a69e473cab6db7e8d52a04f9b4922528e24f;hp=989390b2cfcf9c5116588da234275f1357ef57bc;hpb=fdf050451414e1a816e343bcd56f33186a742e49;p=ric-plt%2Fa1.git diff --git a/tests/test_controller.py b/tests/test_controller.py index 989390b..833fc6e 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -1,6 +1,9 @@ +""" +tests for controller +""" # ================================================================================== -# Copyright (c) 2019 Nokia -# Copyright (c) 2018-2019 AT&T Intellectual Property. +# Copyright (c) 2019-2020 Nokia +# Copyright (c) 2018-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. @@ -14,218 +17,354 @@ # See the License for the specific language governing permissions and # limitations under the License. # ================================================================================== -import tempfile -import os +import time +import json from rmr.rmr_mocks import rmr_mocks -from a1 import app -from a1 import exceptions -from rmr import rmr -import testing_helpers -import pytest - -# http://flask.pocoo.org/docs/1.0/testing/ -@pytest.fixture -def client(): - db_fd, app.app.config["DATABASE"] = tempfile.mkstemp() - app.app.config["TESTING"] = True - cl = app.app.test_client() - - yield cl - - os.close(db_fd) - os.unlink(app.app.config["DATABASE"]) - - -def _fake_dequeue( - monkeypatch, - msg_payload={"status": "SUCCESS", "foo": "bar"}, - msg_type=20001, - msg_state=0, - jsonb=True, - unexpected_first=True, -): - """ - generates a mock rmr message response (returns a function that does; uses closures to set params) - """ - new_messages = [] - # stick a message we don't want at the front of the queue, then stick the message we want - if unexpected_first: - monkeypatch.setattr("rmr.rmr.rmr_torcv_msg", rmr_mocks.rcv_mock_generator(msg_payload, -1, msg_state, jsonb)) - sbuf = rmr.rmr_alloc_msg(None, None) - sbuf = rmr.rmr_torcv_msg(None, sbuf, None) - summary = rmr.message_summary(sbuf) - new_messages.append(summary) +from a1 import a1rmr, data +from .a1test_helpers import MockSDLWrapper + +RCV_ID = "test_receiver" +ADM_CRTL_TID = 6660666 +ADM_CTRL_IID = "admission_control_policy" +ADM_CTRL_POLICIES = "/a1-p/policytypes/{0}/policies".format(ADM_CRTL_TID) +ADM_CTRL_INSTANCE = ADM_CTRL_POLICIES + "/" + ADM_CTRL_IID +ADM_CTRL_INSTANCE_STATUS = ADM_CTRL_INSTANCE + "/status" +ADM_CTRL_TYPE = "/a1-p/policytypes/{0}".format(ADM_CRTL_TID) +ACK_MT = 20011 + + +def _fake_dequeue(): + """for monkeypatching with a good status""" + pay = json.dumps( + {"policy_type_id": ADM_CRTL_TID, "policy_instance_id": ADM_CTRL_IID, "handler_id": RCV_ID, "status": "OK"} + ).encode() + fake_msg = {"payload": pay, "message type": ACK_MT} + return [(fake_msg, None)] + + +def _fake_dequeue_none(): + """for monkeypatching with no waiting messages""" + return [] + + +def _fake_dequeue_deleted(): + """for monkeypatching with a DELETED status""" + new_msgs = [] + good_pay = json.dumps( + {"policy_type_id": ADM_CRTL_TID, "policy_instance_id": ADM_CTRL_IID, "handler_id": RCV_ID, "status": "DELETED"} + ).encode() + + # non existent type id + pay = json.dumps( + {"policy_type_id": 911, "policy_instance_id": ADM_CTRL_IID, "handler_id": RCV_ID, "status": "DELETED"} + ).encode() + fake_msg = {"payload": pay, "message type": ACK_MT} + new_msgs.append((fake_msg, None)) + + # bad instance id + pay = json.dumps( + {"policy_type_id": ADM_CRTL_TID, "policy_instance_id": "darkness", "handler_id": RCV_ID, "status": "DELETED"} + ).encode() + fake_msg = {"payload": pay, "message type": ACK_MT} + new_msgs.append((fake_msg, None)) + + # good body but bad message type + fake_msg = {"payload": good_pay, "message type": ACK_MT * 3} + new_msgs.append((fake_msg, None)) + + # insert a bad one with a malformed body to make sure we keep going + new_msgs.append(({"payload": "asdf", "message type": ACK_MT}, None)) - monkeypatch.setattr("rmr.rmr.rmr_torcv_msg", rmr_mocks.rcv_mock_generator(msg_payload, msg_type, msg_state, jsonb)) - sbuf = rmr.rmr_alloc_msg(None, None) - sbuf = rmr.rmr_torcv_msg(None, sbuf, None) - summary = rmr.message_summary(sbuf) - new_messages.append(summary) + # not even a json + new_msgs.append(("asdf", None)) - def f(): - return new_messages + # good + fake_msg = {"payload": good_pay, "message type": ACK_MT} + new_msgs.append((fake_msg, None)) - return f + return new_msgs def _test_put_patch(monkeypatch): - testing_helpers.patch_all(monkeypatch) - monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(0)) # good sends for this whole batch + rmr_mocks.patch_rmr(monkeypatch) + # assert that rmr bad states don't cause problems + monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10)) + + +def _no_ac(client): + # no type there yet + res = client.get(ADM_CTRL_TYPE) + assert res.status_code == 404 + + # no types at all + res = client.get("/a1-p/policytypes") + assert res.status_code == 200 + assert res.json == [] + + # instance 404 because type not there yet + res = client.get(ADM_CTRL_POLICIES) + assert res.status_code == 404 + + +def _put_ac_type(client, typedef): + _no_ac(client) + + # put the type + res = client.put(ADM_CTRL_TYPE, json=typedef) + assert res.status_code == 201 + + # cant replace types + res = client.put(ADM_CTRL_TYPE, json=typedef) + assert res.status_code == 400 + + # type there now + res = client.get(ADM_CTRL_TYPE) + assert res.status_code == 200 + assert res.json == typedef + + # type in type list + res = client.get("/a1-p/policytypes") + assert res.status_code == 200 + assert res.json == [ADM_CRTL_TID] + + # instance 200 but empty list + res = client.get(ADM_CTRL_POLICIES) + assert res.status_code == 200 + assert res.json == [] + + +def _delete_ac_type(client): + res = client.delete(ADM_CTRL_TYPE) + assert res.status_code == 204 + + # cant get + res = client.get(ADM_CTRL_TYPE) + assert res.status_code == 404 + + # cant invoke delete on it again + res = client.delete(ADM_CTRL_TYPE) + assert res.status_code == 404 + + _no_ac(client) + + +def _put_ac_instance(client, monkeypatch, instancedef): + # no instance there yet + res = client.get(ADM_CTRL_INSTANCE) + assert res.status_code == 404 + res = client.get(ADM_CTRL_INSTANCE_STATUS) + assert res.status_code == 404 + + # create a good instance + _test_put_patch(monkeypatch) + res = client.put(ADM_CTRL_INSTANCE, json=instancedef) + assert res.status_code == 202 + + # replace is allowed on instances + res = client.put(ADM_CTRL_INSTANCE, json=instancedef) + assert res.status_code == 202 + + # instance 200 and in list + res = client.get(ADM_CTRL_POLICIES) + assert res.status_code == 200 + assert res.json == [ADM_CTRL_IID] + + +def _delete_instance(client): + # cant delete type until there are no instances + res = client.delete(ADM_CTRL_TYPE) + assert res.status_code == 400 - # we need to repatch alloc (already patched in patch_rmr) to fix the transactionid, alloc is called in send and recieve - def fake_alloc(_unused, _alsounused): - sbuf = rmr_mocks.Rmr_mbuf_t() - sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002" - return sbuf + # delete it + res = client.delete(ADM_CTRL_INSTANCE) + assert res.status_code == 202 - # we also need to repatch set, since in the send function, we alloc, then set a new transid - def fake_set_transactionid(sbuf): - sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002" + # should be able to do multiple deletes until it's actually gone + res = client.delete(ADM_CTRL_INSTANCE) + assert res.status_code == 202 - # Note, we could have just patched summary, but this patches at a "lower level" so is a better test - monkeypatch.setattr("rmr.rmr.rmr_alloc_msg", fake_alloc) - monkeypatch.setattr("rmr.rmr.generate_and_set_transaction_id", fake_set_transactionid) + +def _instance_is_gone(client, seconds_to_try=10): + for _ in range(seconds_to_try): + # idea here is that we have to wait for the seperate thread to process the event + try: + res = client.get(ADM_CTRL_INSTANCE_STATUS) + assert res.status_code == 404 + except AssertionError: + time.sleep(1) + + res = client.get(ADM_CTRL_INSTANCE_STATUS) + assert res.status_code == 404 + + # list still 200 but no instance + res = client.get(ADM_CTRL_POLICIES) + assert res.status_code == 200 + assert res.json == [] + + # cant get instance + res = client.get(ADM_CTRL_INSTANCE) + assert res.status_code == 404 + + +def _verify_instance_and_status(client, expected_instance, expected_status, expected_deleted, seconds_to_try=5): + # get the instance + res = client.get(ADM_CTRL_INSTANCE) + assert res.status_code == 200 + assert res.json == expected_instance + + for _ in range(seconds_to_try): + # idea here is that we have to wait for the seperate thread to process the event + res = client.get(ADM_CTRL_INSTANCE_STATUS) + assert res.status_code == 200 + assert res.json["has_been_deleted"] == expected_deleted + try: + assert res.json["instance_status"] == expected_status + return + except AssertionError: + time.sleep(1) + assert res.json["instance_status"] == expected_status + + +# Module level Hack + + +def setup_module(): + """module level setup""" + + data.SDL = MockSDLWrapper() # patch SDL + + def noop(): + pass + + # launch the thread with a fake init func and a patched rcv func; we will "repatch" later + a1rmr.start_rmr_thread(init_func_override=noop, rcv_func_override=_fake_dequeue_none) # Actual Tests -def test_policy_get(client, monkeypatch): +def test_workflow(client, monkeypatch, adm_type_good, adm_instance_good): """ - test policy GET + test a full A1 workflow """ - _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("/ric/policies/admission_control_policy") - assert res.status_code == 200 - assert res.json == {"GET ack": "pretend policy is here"} + # put type and instance + _put_ac_type(client, adm_type_good) + _put_ac_instance(client, monkeypatch, adm_instance_good) -def test_policy_get_unsupported(client, monkeypatch): """ - test policy GET + we test the state transition diagram of all 5 states here; + 1. not in effect, not deleted + 2. in effect, not deleted + 3. in effect, deleted + 4. not in effect, deleted + 5. gone (timeout expires) """ - testing_helpers.patch_all(monkeypatch, nofetch=True) - res = client.get("/ric/policies/admission_control_policy") - assert res.status_code == 400 - assert res.data == b'"POLICY DOES NOT SUPPORT FETCHING"\n' + # try a status get but we didn't get any ACKs yet to test NOT IN EFFECT + _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", False) -def test_xapp_put_good(client, monkeypatch): - """ test policy put good""" - _test_put_patch(monkeypatch) - monkeypatch.setattr("a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch)) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 200 - assert res.json == {"status": "SUCCESS", "foo": "bar"} + # now pretend we did get a good ACK + a1rmr.replace_rcv_func(_fake_dequeue) + _verify_instance_and_status(client, adm_instance_good, "IN EFFECT", False) + # delete the instance + _delete_instance(client) -def test_xapp_put_bad(client, monkeypatch): - """Test policy put fails""" - _test_put_patch(monkeypatch) - # return from policy handler has a status indicating FAIL - monkeypatch.setattr( - "a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch, msg_payload={"status": "FAIL", "foo": "bar"}) - ) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 502 - assert res.json["reason"] == "BAD STATUS" - assert res.json["return_payload"] == {"status": "FAIL", "foo": "bar"} - - # return from policy handler has no status field - monkeypatch.setattr("a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch, msg_payload={"foo": "bar"})) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 502 - assert res.json["reason"] == "NO STATUS" - assert res.json["return_payload"] == {"foo": "bar"} - - # return from policy handler not a json - monkeypatch.setattr( - "a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch, msg_payload="booger", jsonb=False) - ) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 502 - assert res.json["reason"] == "NOT JSON" - assert res.json["return_payload"] == "booger" - - # bad type - monkeypatch.setattr("a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch, msg_type=666)) - res = client.put("/ric/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" - - # bad state - monkeypatch.setattr("a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch, msg_state=666)) - res = client.put("/ric/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_xapp_put_bad_send(client, monkeypatch): + # status after a delete, but there are no messages yet, should still return + _verify_instance_and_status(client, adm_instance_good, "IN EFFECT", True) + + # now pretend we deleted successfully + a1rmr.replace_rcv_func(_fake_dequeue_deleted) + + # status should be reflected first (before delete triggers) + _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", True) + + # instance should be totally gone after a few seconds + _instance_is_gone(client) + + # delete the type + _delete_ac_type(client) + + +def test_cleanup_via_t1(client, monkeypatch, adm_type_good, adm_instance_good): """ - Test bad send failures + create a type, create an instance, but no acks ever come in, delete instance """ - testing_helpers.patch_all(monkeypatch) + _put_ac_type(client, adm_type_good) - monkeypatch.setattr("a1.a1rmr._dequeue_all_waiting_messages", _fake_dequeue(monkeypatch)) - res = client.put("/ric/policies/admission_control_policy", json={"not": "expected"}) - assert res.status_code == 400 + a1rmr.replace_rcv_func(_fake_dequeue_none) - monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10)) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 504 - assert res.data == b'"A1 was unable to send a needed message to a downstream subscriber"\n' + _put_ac_instance(client, monkeypatch, adm_instance_good) - monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(5)) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 504 - assert res.data == b'"A1 was unable to send a needed message to a downstream subscriber"\n' + """ + here we test the state transition diagram when it never goes into effect: + 1. not in effect, not deleted + 2. not in effect, deleted + 3. gone (timeout expires) + """ + _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", False) -def test_bad_requests(client, monkeypatch): - """Test bad requests""" - testing_helpers.patch_all(monkeypatch) + # delete the instance + _delete_instance(client) - # test a 404 - res = client.put("/ric/policies/noexist", json=testing_helpers.good_payload()) - assert res.status_code == 404 + _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", True) - # bad media type - res = client.put("/ric/policies/admission_control_policy", data="notajson") - assert res.status_code == 415 + # instance should be totally gone after a few seconds + _instance_is_gone(client) - # test a PUT body against a poliucy not expecting one - res = client.put("/ric/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' + # delete the type + _delete_ac_type(client) -def test_missing_manifest(client, monkeypatch): +def test_bad_instances(client, monkeypatch, adm_type_good): """ - test that we get a 500 with an approrpiate message on a missing manifest + test various failure modes """ + # put the type (needed for some of the tests below) + rmr_mocks.patch_rmr(monkeypatch) + res = client.put(ADM_CTRL_TYPE, json=adm_type_good) + assert res.status_code == 201 - def f(): - raise exceptions.MissingManifest() + # bad body + res = client.put(ADM_CTRL_INSTANCE, json={"not": "expected"}) + assert res.status_code == 400 + + # bad media type + res = client.put(ADM_CTRL_INSTANCE, data="notajson") + assert res.status_code == 415 + + # delete a non existent instance + res = client.delete(ADM_CTRL_INSTANCE + "DARKNESS") + assert res.status_code == 404 - monkeypatch.setattr("a1.utils.get_ric_manifest", f) + # get a non existent instance + a1rmr.replace_rcv_func(_fake_dequeue) + res = client.get(ADM_CTRL_INSTANCE + "DARKNESS") + assert res.status_code == 404 + + # delete the type (as cleanup) + res = client.delete(ADM_CTRL_TYPE) + assert res.status_code == 204 - res = client.put("/ric/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' + # test 503 handlers + res = client.put("/a1-p/policytypes/111", json=adm_type_good) + assert res.status_code == 503 + res = client.put("/a1-p/policytypes/112", json=adm_type_good) + assert res.status_code == 503 + res = client.put("/a1-p/policytypes/113", json=adm_type_good) + assert res.status_code == 503 -def test_missing_rmr(client, monkeypatch): +def test_illegal_types(client, adm_type_good): """ - test that we get a 500 with an approrpiate message on a missing rmr rmr_string + Test illegal types """ - testing_helpers.patch_all(monkeypatch, nonexisting_rmr=True) - res = client.put("/ric/policies/admission_control_policy", json=testing_helpers.good_payload()) - assert res.status_code == 500 - assert res.data == b'"A1 does not have a mapping for the desired rmr string. report this!"\n' + res = client.put("/a1-p/policytypes/0", json=adm_type_good) + assert res.status_code == 400 + res = client.put("/a1-p/policytypes/2147483648", json=adm_type_good) + assert res.status_code == 400 def test_healthcheck(client): @@ -234,3 +373,8 @@ def test_healthcheck(client): """ res = client.get("/a1-p/healthcheck") assert res.status_code == 200 + + +def teardown_module(): + """module teardown""" + a1rmr.stop_rmr_thread()