X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=tests%2Ftest_controller.py;h=0d3127410a30f62c2f6fdefffbd408528a157bb4;hb=aa4ffa78f3e6a9430cc9ae9933165e58105c9d65;hp=2866dd8990e13b62563fb50a346d43c6c3333f01;hpb=a0876efd819b43b870ba2254b34676b1a03ad326;p=ric-plt%2Fa1.git diff --git a/tests/test_controller.py b/tests/test_controller.py index 2866dd8..0d31274 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -1,3 +1,6 @@ +""" +tests for controller +""" # ================================================================================== # Copyright (c) 2019 Nokia # Copyright (c) 2018-2019 AT&T Intellectual Property. @@ -14,12 +17,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # ================================================================================== -import tempfile -import os +import time from rmr.rmr_mocks import rmr_mocks -from a1 import app -import pytest +from a1 import a1rmr ADM_CTRL = "admission_control_policy" @@ -30,51 +31,48 @@ ADM_CTRL_TYPE = "/a1-p/policytypes/20000" TEST_TYPE = "/a1-p/policytypes/20001" -# 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() +def _fake_dequeue(): + """for monkeypatching with a good status""" + pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "OK"}' + fake_msg = {"payload": pay} + return [fake_msg] - yield cl - os.close(db_fd) - os.unlink(app.app.config["DATABASE"]) +def _fake_dequeue_none(): + """for monkeypatching with no waiting messages""" + return [] -def _fake_dequeue(_filter_type): - """ - for monkeypatching a1rmnr.dequeue_all_messages with a good status - """ - fake_msg = {} - pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "OK"}' - fake_msg["payload"] = pay - new_messages = [fake_msg] - return new_messages +def _fake_dequeue_deleted(): + """for monkeypatching with a DELETED status""" + new_msgs = [] + # insert some that don't exist to make sure nothing blows up + pay = b'{"policy_type_id": 20666, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}' + fake_msg = {"payload": pay} + new_msgs.append(fake_msg) -def _fake_dequeue_none(_filter_type): - """ - for monkeypatching a1rmnr.dequeue_all_messages with no waiting messages - """ - return [] + pay = b'{"policy_type_id": 20000, "policy_instance_id": "darkness", "handler_id": "test_receiver", "status": "DELETED"}' + fake_msg = {"payload": pay} + new_msgs.append(fake_msg) + # insert a bad one with a malformed body to make sure we keep going + new_msgs.append({"payload": "asdf"}) + + # not even a json + new_msgs.append("asdf") -def _fake_dequeue_deleted(_filter_type): - """ - for monkeypatching a1rmnr.dequeue_all_messages with a DELETED status - """ - fake_msg = {} pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}' - fake_msg["payload"] = pay - new_messages = [fake_msg] - return new_messages + fake_msg = {"payload": pay} + new_msgs.append(fake_msg) + + return new_msgs def _test_put_patch(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 + # assert that rmr bad states don't cause problems + monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10)) # we need this because free expects a real sbuf # TODO: move this into rmr_mocks @@ -84,7 +82,7 @@ def _test_put_patch(monkeypatch): monkeypatch.setattr("rmr.rmr.rmr_free_msg", noop) # 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): + def fake_alloc(_unused1, _unused2, _unused3, _unused4, _unused5): sbuf = rmr_mocks.Rmr_mbuf_t() sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002" return sbuf @@ -98,12 +96,7 @@ def _test_put_patch(monkeypatch): monkeypatch.setattr("rmr.rmr.generate_and_set_transaction_id", fake_set_transactionid) -# Actual Tests - - -def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good): - """ test policy put good""" - +def _no_ac(client): # no type there yet res = client.get(ADM_CTRL_TYPE) assert res.status_code == 404 @@ -114,18 +107,27 @@ def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good): assert res.json == [] # instance 404 because type not there yet - monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue_none) 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=adm_type_good) + 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 == adm_type_good + assert res.json == typedef + + # type in type list res = client.get("/a1-p/policytypes") assert res.status_code == 200 assert res.json == [20000] @@ -135,6 +137,23 @@ def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good): 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 @@ -143,7 +162,11 @@ def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good): # create a good instance _test_put_patch(monkeypatch) - res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good) + 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 @@ -151,77 +174,159 @@ def test_xapp_put_good(client, monkeypatch, adm_type_good, adm_instance_good): assert res.status_code == 200 assert res.json == [ADM_CTRL] - def get_instance_good(expected): - # get the instance - res = client.get(ADM_CTRL_INSTANCE) - assert res.status_code == 200 - assert res.json == adm_instance_good - - # get the instance status - res = client.get(ADM_CTRL_INSTANCE_STATUS) - assert res.status_code == 200 - assert res.get_data(as_text=True) == expected - - # try a status get but pretend we didn't get any ACKs yet to test NOT IN EFFECT - monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue_none) - get_instance_good("NOT IN EFFECT") - # now pretend we did get a good ACK - monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue) - get_instance_good("IN EFFECT") +def _delete_instance(client): + # cant delete type until there are no instances + res = client.delete(ADM_CTRL_TYPE) + assert res.status_code == 400 # delete it res = client.delete(ADM_CTRL_INSTANCE) assert res.status_code == 202 - res = client.delete(ADM_CTRL_INSTANCE) # should be able to do multiple deletes + + # should be able to do multiple deletes until it's actually gone + res = client.delete(ADM_CTRL_INSTANCE) assert res.status_code == 202 - # status after a delete, but there are no messages yet, should still return - monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue) - get_instance_good("IN EFFECT") - # now pretend we deleted successfully - monkeypatch.setattr("a1.a1rmr.dequeue_all_waiting_messages", _fake_dequeue_deleted) - res = client.get(ADM_CTRL_INSTANCE_STATUS) # cant get status - assert res.status_code == 404 - res = client.get(ADM_CTRL_INSTANCE) # cant get instance +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""" -def test_xapp_put_good_bad_rmr(client, monkeypatch, adm_instance_good): + 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_workflow(client, monkeypatch, adm_type_good, adm_instance_good): """ - assert that rmr bad states don't cause problems + test a full A1 workflow """ - _test_put_patch(monkeypatch) - monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10)) - res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good) - assert res.status_code == 202 + _put_ac_type(client, adm_type_good) + _put_ac_instance(client, monkeypatch, adm_instance_good) - monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(5)) - res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good) - assert res.status_code == 202 + """ + 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) + """ + # 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_bad_instances(client, monkeypatch, adm_type_good): + # 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) + + # 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 """ - rmr_mocks.patch_rmr(monkeypatch) + _put_ac_type(client, adm_type_good) - # TODO: reenable this after delete! - # put the type - # res = client.put(ADM_CTRL_TYPE, json=adm_type_good) - # assert res.status_code == 201 + a1rmr.replace_rcv_func(_fake_dequeue_none) - # 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 + _put_ac_instance(client, monkeypatch, adm_instance_good) + + """ + 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) + + # delete the instance + _delete_instance(client) + + _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_bad_instances(client, monkeypatch, adm_type_good): + """ + 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 # bad body res = client.put(ADM_CTRL_INSTANCE, json={"not": "expected"}) @@ -231,6 +336,29 @@ def test_bad_instances(client, monkeypatch, adm_type_good): 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 + + # 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 + + +def test_illegal_types(client, adm_type_good): + """ + Test illegal types + """ + 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 + def test_healthcheck(client): """ @@ -238,3 +366,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()