Use the RIC logging lib
[ric-plt/a1.git] / tests / test_controller.py
index 88d6f70..0d31274 100644 (file)
@@ -1,3 +1,6 @@
+"""
+tests for controller
+"""
 # ==================================================================================
 #       Copyright (c) 2019 Nokia
 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
 #   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,54 +31,58 @@ 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
+    def noop(_sbuf):
+        pass
+
+    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
@@ -91,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
@@ -107,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]
@@ -128,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
@@ -136,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
@@ -144,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 test_xapp_put_good_bad_rmr(client, monkeypatch, adm_instance_good):
+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(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"})
@@ -224,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):
     """
@@ -231,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()