X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=tests%2Ftest_controller.py;h=2aac5a98e164edee74c89ca654ce044b09b2ed4e;hb=2c1c4e9dd207289bbdc3453bfdb3e2dad68df8a8;hp=756a0d042abc3c7d9ad49ba6063a5d7eff62d393;hpb=0b42dfc507b22b49669f360883a1cecaa50cda7b;p=ric-plt%2Fa1.git diff --git a/tests/test_controller.py b/tests/test_controller.py index 756a0d0..2aac5a9 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,25 +17,30 @@ # See the License for the specific language governing permissions and # limitations under the License. # ================================================================================== - import time -from rmr.rmr_mocks import rmr_mocks -from a1 import a1rmr - - -ADM_CTRL = "admission_control_policy" -ADM_CTRL_POLICIES = "/a1-p/policytypes/20000/policies" -ADM_CTRL_INSTANCE = ADM_CTRL_POLICIES + "/" + ADM_CTRL +import json +from ricxappframe.rmr.rmr_mocks import rmr_mocks +from ricxappframe.xapp_sdl import SDLWrapper +from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError +from a1 import a1rmr, data + +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/20000" -TEST_TYPE = "/a1-p/policytypes/20001" +ADM_CTRL_TYPE = "/a1-p/policytypes/{0}".format(ADM_CRTL_TID) +ACK_MT = 20011 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] + 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(): @@ -43,23 +51,37 @@ def _fake_dequeue_none(): 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) - - 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) + 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 - fake_msg = {"payload": "asdf"} - new_msgs.append(fake_msg) + new_msgs.append(({"payload": "asdf", "message type": ACK_MT}, None)) - pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}' - fake_msg = {"payload": pay} - new_msgs.append(fake_msg) + # not even a json + new_msgs.append(("asdf", None)) + + # good + fake_msg = {"payload": good_pay, "message type": ACK_MT} + new_msgs.append((fake_msg, None)) return new_msgs @@ -67,48 +89,10 @@ def _fake_dequeue_deleted(): def _test_put_patch(monkeypatch): 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)) - - # we need this because free expects a real sbuf - # TODO: move this into rmr_mocks - def noop(_sbuf): - pass - - monkeypatch.setattr("rmr.rmr.rmr_free_msg", noop) + monkeypatch.setattr("ricxappframe.rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10)) - # we need to repatch alloc (already patched in patch_rmr) to fix the transactionid, alloc is called in send and recieve - def fake_alloc(_unused1, _unused2, _unused3, _unused4, _unused5): - sbuf = rmr_mocks.Rmr_mbuf_t() - sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002" - return sbuf - - # 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" - - # 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) - - -# Module level Hack - - -def setup_module(): - """module level setup""" - 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_nothing_there_yet(client): - """ test policy put good""" +def _no_ac(client): # no type there yet res = client.get(ADM_CTRL_TYPE) assert res.status_code == 404 @@ -123,31 +107,49 @@ def test_workflow_nothing_there_yet(client): assert res.status_code == 404 -def test_workflow(client, monkeypatch, adm_type_good, adm_instance_good): - """ - test a full A1 workflow - """ +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=adm_type_good) + 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] + 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 @@ -156,38 +158,20 @@ def test_workflow(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=adm_instance_good) + 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] + assert res.json == [ADM_CTRL_IID] - 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 we didn't get any ACKs yet to test NOT IN EFFECT - time.sleep(1) # wait for the rmr thread - get_instance_good("NOT IN EFFECT") - - # now pretend we did get a good ACK - a1rmr.replace_rcv_func(_fake_dequeue) - time.sleep(1) # wait for the rmr thread - 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 @@ -195,34 +179,145 @@ def test_workflow(client, monkeypatch, adm_type_good, adm_instance_good): # 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 - time.sleep(1) # wait for the rmr thread - get_instance_good("IN EFFECT") - # now pretend we deleted successfully - a1rmr.replace_rcv_func(_fake_dequeue_deleted) - time.sleep(1) # wait for the rmr thread +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 == [] - res = client.get(ADM_CTRL_INSTANCE_STATUS) # cant get status - assert res.status_code == 404 - res = client.get(ADM_CTRL_INSTANCE) # cant get instance + + # 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""" + + # swap sdl for the fake backend + data.SDL = SDLWrapper(use_fake_sdl=True) + + 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): + """ + test a full A1 workflow + """ + + # put type and instance + _put_ac_type(client, adm_type_good) + _put_ac_instance(client, monkeypatch, adm_instance_good) + + """ + 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) + + # 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 - res = client.delete(ADM_CTRL_TYPE) - assert res.status_code == 204 + _delete_ac_type(client) - # cant touch this - res = client.get(ADM_CTRL_TYPE) - assert res.status_code == 404 - res = client.delete(ADM_CTRL_TYPE) - assert res.status_code == 404 + +def test_cleanup_via_t1(client, monkeypatch, adm_type_good, adm_instance_good): + """ + create a type, create an instance, but no acks ever come in, delete instance + """ + _put_ac_type(client, adm_type_good) + + a1rmr.replace_rcv_func(_fake_dequeue_none) + + _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): @@ -248,7 +343,6 @@ def test_bad_instances(client, monkeypatch, adm_type_good): # get a non existent instance a1rmr.replace_rcv_func(_fake_dequeue) - time.sleep(1) res = client.get(ADM_CTRL_INSTANCE + "DARKNESS") assert res.status_code == 404 @@ -256,14 +350,34 @@ def test_bad_instances(client, monkeypatch, adm_type_good): res = client.delete(ADM_CTRL_TYPE) assert res.status_code == 204 + # test 503 handlers + + def monkey_set(ns, key, value): + # set a key override function that throws sdl errors on certain keys + if key == "a1.policy_type.111": + raise RejectedByBackend() + if key == "a1.policy_type.112": + raise NotConnected() + if key == "a1.policy_type.113": + raise BackendError() + + monkeypatch.setattr("a1.data.SDL.set", monkey_set) + + 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_illegal_types(client, adm_type_good): """ Test illegal types """ - res = client.put("/a1-p/policytypes/19999", json=adm_type_good) + res = client.put("/a1-p/policytypes/0", json=adm_type_good) assert res.status_code == 400 - res = client.put("/a1-p/policytypes/21024", json=adm_type_good) + res = client.put("/a1-p/policytypes/2147483648", json=adm_type_good) assert res.status_code == 400