Threading pt3:
[ric-plt/a1.git] / tests / test_controller.py
1 # ==================================================================================
2 #       Copyright (c) 2019 Nokia
3 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #          http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 # ==================================================================================
17
18 import time
19 from rmr.rmr_mocks import rmr_mocks
20 from a1 import a1rmr
21
22
23 ADM_CTRL = "admission_control_policy"
24 ADM_CTRL_POLICIES = "/a1-p/policytypes/20000/policies"
25 ADM_CTRL_INSTANCE = ADM_CTRL_POLICIES + "/" + ADM_CTRL
26 ADM_CTRL_INSTANCE_STATUS = ADM_CTRL_INSTANCE + "/status"
27 ADM_CTRL_TYPE = "/a1-p/policytypes/20000"
28 TEST_TYPE = "/a1-p/policytypes/20001"
29
30
31 def _fake_dequeue():
32     """for monkeypatching with a good status"""
33     pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "OK"}'
34     fake_msg = {"payload": pay}
35     return [fake_msg]
36
37
38 def _fake_dequeue_none():
39     """for monkeypatching with no waiting messages"""
40     return []
41
42
43 def _fake_dequeue_deleted():
44     """for monkeypatching  with a DELETED status"""
45     new_msgs = []
46
47     # insert some that don't exist to make sure nothing blows up
48     pay = b'{"policy_type_id": 20666, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}'
49     fake_msg = {"payload": pay}
50     new_msgs.append(fake_msg)
51
52     pay = b'{"policy_type_id": 20000, "policy_instance_id": "darkness", "handler_id": "test_receiver", "status": "DELETED"}'
53     fake_msg = {"payload": pay}
54     new_msgs.append(fake_msg)
55
56     # insert a bad one with a malformed body to make sure we keep going
57     fake_msg = {"payload": "asdf"}
58     new_msgs.append(fake_msg)
59
60     pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}'
61     fake_msg = {"payload": pay}
62     new_msgs.append(fake_msg)
63
64     return new_msgs
65
66
67 def _test_put_patch(monkeypatch):
68     rmr_mocks.patch_rmr(monkeypatch)
69     # assert that rmr bad states don't cause problems
70     monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10))
71
72     # we need this because free expects a real sbuf
73     # TODO: move this into rmr_mocks
74     def noop(_sbuf):
75         pass
76
77     monkeypatch.setattr("rmr.rmr.rmr_free_msg", noop)
78
79     # we need to repatch alloc (already patched in patch_rmr) to fix the transactionid, alloc is called in send and recieve
80     def fake_alloc(_unused1, _unused2, _unused3, _unused4, _unused5):
81         sbuf = rmr_mocks.Rmr_mbuf_t()
82         sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002"
83         return sbuf
84
85     # we also need to repatch set, since in the send function, we alloc, then set a new transid
86     def fake_set_transactionid(sbuf):
87         sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002"
88
89     # Note, we could have just patched summary, but this patches at a "lower level" so is a better test
90     monkeypatch.setattr("rmr.rmr.rmr_alloc_msg", fake_alloc)
91     monkeypatch.setattr("rmr.rmr.generate_and_set_transaction_id", fake_set_transactionid)
92
93
94 # Module level Hack
95
96
97 def setup_module():
98     """module level setup"""
99
100     def noop():
101         pass
102
103     # launch the thread with a fake init func and a patched rcv func; we will "repatch" later
104     a1rmr.start_rmr_thread(init_func_override=noop, rcv_func_override=_fake_dequeue_none)
105
106
107 # Actual Tests
108
109
110 def test_workflow_nothing_there_yet(client):
111     """ test policy put good"""
112     # no type there yet
113     res = client.get(ADM_CTRL_TYPE)
114     assert res.status_code == 404
115
116     # no types at all
117     res = client.get("/a1-p/policytypes")
118     assert res.status_code == 200
119     assert res.json == []
120
121     # instance 404 because type not there yet
122     res = client.get(ADM_CTRL_POLICIES)
123     assert res.status_code == 404
124
125
126 def test_workflow(client, monkeypatch, adm_type_good, adm_instance_good):
127     """
128     test a full A1 workflow
129     """
130     # put the type
131     res = client.put(ADM_CTRL_TYPE, json=adm_type_good)
132     assert res.status_code == 201
133
134     # cant replace types
135     res = client.put(ADM_CTRL_TYPE, json=adm_type_good)
136     assert res.status_code == 400
137
138     # type there now
139     res = client.get(ADM_CTRL_TYPE)
140     assert res.status_code == 200
141     assert res.json == adm_type_good
142     res = client.get("/a1-p/policytypes")
143     assert res.status_code == 200
144     assert res.json == [20000]
145
146     # instance 200 but empty list
147     res = client.get(ADM_CTRL_POLICIES)
148     assert res.status_code == 200
149     assert res.json == []
150
151     # no instance there yet
152     res = client.get(ADM_CTRL_INSTANCE)
153     assert res.status_code == 404
154     res = client.get(ADM_CTRL_INSTANCE_STATUS)
155     assert res.status_code == 404
156
157     # create a good instance
158     _test_put_patch(monkeypatch)
159     res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good)
160     assert res.status_code == 202
161
162     # replace is allowed on instances
163     res = client.put(ADM_CTRL_INSTANCE, json=adm_instance_good)
164     assert res.status_code == 202
165
166     # instance 200 and in list
167     res = client.get(ADM_CTRL_POLICIES)
168     assert res.status_code == 200
169     assert res.json == [ADM_CTRL]
170
171     def get_instance_good(expected):
172         # get the instance
173         res = client.get(ADM_CTRL_INSTANCE)
174         assert res.status_code == 200
175         assert res.json == adm_instance_good
176
177         # get the instance status
178         res = client.get(ADM_CTRL_INSTANCE_STATUS)
179         assert res.status_code == 200
180         assert res.get_data(as_text=True) == expected
181
182     # try a status get but we didn't get any ACKs yet to test NOT IN EFFECT
183     time.sleep(1)  # wait for the rmr thread
184     get_instance_good("NOT IN EFFECT")
185
186     # now pretend we did get a good ACK
187     a1rmr.replace_rcv_func(_fake_dequeue)
188     time.sleep(1)  # wait for the rmr thread
189     get_instance_good("IN EFFECT")
190
191     # cant delete type until there are no instances
192     res = client.delete(ADM_CTRL_TYPE)
193     assert res.status_code == 400
194
195     # delete it
196     res = client.delete(ADM_CTRL_INSTANCE)
197     assert res.status_code == 202
198     res = client.delete(ADM_CTRL_INSTANCE)  # should be able to do multiple deletes
199     assert res.status_code == 202
200
201     # status after a delete, but there are no messages yet, should still return
202     time.sleep(1)  # wait for the rmr thread
203     get_instance_good("IN EFFECT")
204
205     # now pretend we deleted successfully
206     a1rmr.replace_rcv_func(_fake_dequeue_deleted)
207     time.sleep(1)  # wait for the rmr thread
208     # list still 200 but no instance
209     res = client.get(ADM_CTRL_POLICIES)
210     assert res.status_code == 200
211     assert res.json == []
212     res = client.get(ADM_CTRL_INSTANCE_STATUS)  # cant get status
213     assert res.status_code == 404
214     res = client.get(ADM_CTRL_INSTANCE)  # cant get instance
215     assert res.status_code == 404
216
217     # delete the type
218     res = client.delete(ADM_CTRL_TYPE)
219     assert res.status_code == 204
220
221     # cant touch this
222     res = client.get(ADM_CTRL_TYPE)
223     assert res.status_code == 404
224     res = client.delete(ADM_CTRL_TYPE)
225     assert res.status_code == 404
226
227
228 def test_bad_instances(client, monkeypatch, adm_type_good):
229     """
230     test various failure modes
231     """
232     # put the type (needed for some of the tests below)
233     rmr_mocks.patch_rmr(monkeypatch)
234     res = client.put(ADM_CTRL_TYPE, json=adm_type_good)
235     assert res.status_code == 201
236
237     # bad body
238     res = client.put(ADM_CTRL_INSTANCE, json={"not": "expected"})
239     assert res.status_code == 400
240
241     # bad media type
242     res = client.put(ADM_CTRL_INSTANCE, data="notajson")
243     assert res.status_code == 415
244
245     # delete a non existent instance
246     res = client.delete(ADM_CTRL_INSTANCE + "DARKNESS")
247     assert res.status_code == 404
248
249     # get a non existent instance
250     a1rmr.replace_rcv_func(_fake_dequeue)
251     time.sleep(1)
252     res = client.get(ADM_CTRL_INSTANCE + "DARKNESS")
253     assert res.status_code == 404
254
255     # delete the type (as cleanup)
256     res = client.delete(ADM_CTRL_TYPE)
257     assert res.status_code == 204
258
259
260 def test_illegal_types(client, adm_type_good):
261     """
262     Test illegal types
263     """
264     res = client.put("/a1-p/policytypes/19999", json=adm_type_good)
265     assert res.status_code == 400
266     res = client.put("/a1-p/policytypes/21024", json=adm_type_good)
267     assert res.status_code == 400
268
269
270 def test_healthcheck(client):
271     """
272     test healthcheck
273     """
274     res = client.get("/a1-p/healthcheck")
275     assert res.status_code == 200
276
277
278 def teardown_module():
279     """module teardown"""
280     a1rmr.stop_rmr_thread()