Use the RIC logging lib
[ric-plt/a1.git] / tests / test_controller.py
1 """
2 tests for controller
3 """
4 # ==================================================================================
5 #       Copyright (c) 2019 Nokia
6 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
7 #
8 #   Licensed under the Apache License, Version 2.0 (the "License");
9 #   you may not use this file except in compliance with the License.
10 #   You may obtain a copy of the License at
11 #
12 #          http://www.apache.org/licenses/LICENSE-2.0
13 #
14 #   Unless required by applicable law or agreed to in writing, software
15 #   distributed under the License is distributed on an "AS IS" BASIS,
16 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 #   See the License for the specific language governing permissions and
18 #   limitations under the License.
19 # ==================================================================================
20
21 import time
22 from rmr.rmr_mocks import rmr_mocks
23 from a1 import a1rmr
24
25
26 ADM_CTRL = "admission_control_policy"
27 ADM_CTRL_POLICIES = "/a1-p/policytypes/20000/policies"
28 ADM_CTRL_INSTANCE = ADM_CTRL_POLICIES + "/" + ADM_CTRL
29 ADM_CTRL_INSTANCE_STATUS = ADM_CTRL_INSTANCE + "/status"
30 ADM_CTRL_TYPE = "/a1-p/policytypes/20000"
31 TEST_TYPE = "/a1-p/policytypes/20001"
32
33
34 def _fake_dequeue():
35     """for monkeypatching with a good status"""
36     pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "OK"}'
37     fake_msg = {"payload": pay}
38     return [fake_msg]
39
40
41 def _fake_dequeue_none():
42     """for monkeypatching with no waiting messages"""
43     return []
44
45
46 def _fake_dequeue_deleted():
47     """for monkeypatching  with a DELETED status"""
48     new_msgs = []
49
50     # insert some that don't exist to make sure nothing blows up
51     pay = b'{"policy_type_id": 20666, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}'
52     fake_msg = {"payload": pay}
53     new_msgs.append(fake_msg)
54
55     pay = b'{"policy_type_id": 20000, "policy_instance_id": "darkness", "handler_id": "test_receiver", "status": "DELETED"}'
56     fake_msg = {"payload": pay}
57     new_msgs.append(fake_msg)
58
59     # insert a bad one with a malformed body to make sure we keep going
60     new_msgs.append({"payload": "asdf"})
61
62     # not even a json
63     new_msgs.append("asdf")
64
65     pay = b'{"policy_type_id": 20000, "policy_instance_id": "admission_control_policy", "handler_id": "test_receiver", "status": "DELETED"}'
66     fake_msg = {"payload": pay}
67     new_msgs.append(fake_msg)
68
69     return new_msgs
70
71
72 def _test_put_patch(monkeypatch):
73     rmr_mocks.patch_rmr(monkeypatch)
74     # assert that rmr bad states don't cause problems
75     monkeypatch.setattr("rmr.rmr.rmr_send_msg", rmr_mocks.send_mock_generator(10))
76
77     # we need this because free expects a real sbuf
78     # TODO: move this into rmr_mocks
79     def noop(_sbuf):
80         pass
81
82     monkeypatch.setattr("rmr.rmr.rmr_free_msg", noop)
83
84     # we need to repatch alloc (already patched in patch_rmr) to fix the transactionid, alloc is called in send and recieve
85     def fake_alloc(_unused1, _unused2, _unused3, _unused4, _unused5):
86         sbuf = rmr_mocks.Rmr_mbuf_t()
87         sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002"
88         return sbuf
89
90     # we also need to repatch set, since in the send function, we alloc, then set a new transid
91     def fake_set_transactionid(sbuf):
92         sbuf.contents.xaction = b"d49b53e478b711e9a1130242ac110002"
93
94     # Note, we could have just patched summary, but this patches at a "lower level" so is a better test
95     monkeypatch.setattr("rmr.rmr.rmr_alloc_msg", fake_alloc)
96     monkeypatch.setattr("rmr.rmr.generate_and_set_transaction_id", fake_set_transactionid)
97
98
99 def _no_ac(client):
100     # no type there yet
101     res = client.get(ADM_CTRL_TYPE)
102     assert res.status_code == 404
103
104     # no types at all
105     res = client.get("/a1-p/policytypes")
106     assert res.status_code == 200
107     assert res.json == []
108
109     # instance 404 because type not there yet
110     res = client.get(ADM_CTRL_POLICIES)
111     assert res.status_code == 404
112
113
114 def _put_ac_type(client, typedef):
115     _no_ac(client)
116
117     # put the type
118     res = client.put(ADM_CTRL_TYPE, json=typedef)
119     assert res.status_code == 201
120
121     # cant replace types
122     res = client.put(ADM_CTRL_TYPE, json=typedef)
123     assert res.status_code == 400
124
125     # type there now
126     res = client.get(ADM_CTRL_TYPE)
127     assert res.status_code == 200
128     assert res.json == typedef
129
130     # type in type list
131     res = client.get("/a1-p/policytypes")
132     assert res.status_code == 200
133     assert res.json == [20000]
134
135     # instance 200 but empty list
136     res = client.get(ADM_CTRL_POLICIES)
137     assert res.status_code == 200
138     assert res.json == []
139
140
141 def _delete_ac_type(client):
142     res = client.delete(ADM_CTRL_TYPE)
143     assert res.status_code == 204
144
145     # cant get
146     res = client.get(ADM_CTRL_TYPE)
147     assert res.status_code == 404
148
149     # cant invoke delete on it again
150     res = client.delete(ADM_CTRL_TYPE)
151     assert res.status_code == 404
152
153     _no_ac(client)
154
155
156 def _put_ac_instance(client, monkeypatch, instancedef):
157     # no instance there yet
158     res = client.get(ADM_CTRL_INSTANCE)
159     assert res.status_code == 404
160     res = client.get(ADM_CTRL_INSTANCE_STATUS)
161     assert res.status_code == 404
162
163     # create a good instance
164     _test_put_patch(monkeypatch)
165     res = client.put(ADM_CTRL_INSTANCE, json=instancedef)
166     assert res.status_code == 202
167
168     # replace is allowed on instances
169     res = client.put(ADM_CTRL_INSTANCE, json=instancedef)
170     assert res.status_code == 202
171
172     # instance 200 and in list
173     res = client.get(ADM_CTRL_POLICIES)
174     assert res.status_code == 200
175     assert res.json == [ADM_CTRL]
176
177
178 def _delete_instance(client):
179     # cant delete type until there are no instances
180     res = client.delete(ADM_CTRL_TYPE)
181     assert res.status_code == 400
182
183     # delete it
184     res = client.delete(ADM_CTRL_INSTANCE)
185     assert res.status_code == 202
186
187     # should be able to do multiple deletes until it's actually gone
188     res = client.delete(ADM_CTRL_INSTANCE)
189     assert res.status_code == 202
190
191
192 def _instance_is_gone(client, seconds_to_try=10):
193     for _ in range(seconds_to_try):
194         # idea here is that we have to wait for the seperate thread to process the event
195         try:
196             res = client.get(ADM_CTRL_INSTANCE_STATUS)
197             assert res.status_code == 404
198         except AssertionError:
199             time.sleep(1)
200
201     res = client.get(ADM_CTRL_INSTANCE_STATUS)
202     assert res.status_code == 404
203
204     # list still 200 but no instance
205     res = client.get(ADM_CTRL_POLICIES)
206     assert res.status_code == 200
207     assert res.json == []
208
209     # cant get instance
210     res = client.get(ADM_CTRL_INSTANCE)
211     assert res.status_code == 404
212
213
214 def _verify_instance_and_status(client, expected_instance, expected_status, expected_deleted, seconds_to_try=5):
215     # get the instance
216     res = client.get(ADM_CTRL_INSTANCE)
217     assert res.status_code == 200
218     assert res.json == expected_instance
219
220     for _ in range(seconds_to_try):
221         # idea here is that we have to wait for the seperate thread to process the event
222         res = client.get(ADM_CTRL_INSTANCE_STATUS)
223         assert res.status_code == 200
224         assert res.json["has_been_deleted"] == expected_deleted
225         try:
226             assert res.json["instance_status"] == expected_status
227             return
228         except AssertionError:
229             time.sleep(1)
230     assert res.json["instance_status"] == expected_status
231
232
233 # Module level Hack
234
235
236 def setup_module():
237     """module level setup"""
238
239     def noop():
240         pass
241
242     # launch the thread with a fake init func and a patched rcv func; we will "repatch" later
243     a1rmr.start_rmr_thread(init_func_override=noop, rcv_func_override=_fake_dequeue_none)
244
245
246 # Actual Tests
247
248
249 def test_workflow(client, monkeypatch, adm_type_good, adm_instance_good):
250     """
251     test a full A1 workflow
252     """
253     _put_ac_type(client, adm_type_good)
254     _put_ac_instance(client, monkeypatch, adm_instance_good)
255
256     """
257     we test the state transition diagram of all 5 states here;
258     1. not in effect, not deleted
259     2. in effect, not deleted
260     3. in effect, deleted
261     4. not in effect, deleted
262     5. gone (timeout expires)
263     """
264
265     # try a status get but we didn't get any ACKs yet to test NOT IN EFFECT
266     _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", False)
267
268     # now pretend we did get a good ACK
269     a1rmr.replace_rcv_func(_fake_dequeue)
270     _verify_instance_and_status(client, adm_instance_good, "IN EFFECT", False)
271
272     # delete the instance
273     _delete_instance(client)
274
275     # status after a delete, but there are no messages yet, should still return
276     _verify_instance_and_status(client, adm_instance_good, "IN EFFECT", True)
277
278     # now pretend we deleted successfully
279     a1rmr.replace_rcv_func(_fake_dequeue_deleted)
280
281     # status should be reflected first (before delete triggers)
282     _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", True)
283
284     # instance should be totally gone after a few seconds
285     _instance_is_gone(client)
286
287     # delete the type
288     _delete_ac_type(client)
289
290
291 def test_cleanup_via_t1(client, monkeypatch, adm_type_good, adm_instance_good):
292     """
293     create a type, create an instance, but no acks ever come in, delete instance
294     """
295     _put_ac_type(client, adm_type_good)
296
297     a1rmr.replace_rcv_func(_fake_dequeue_none)
298
299     _put_ac_instance(client, monkeypatch, adm_instance_good)
300
301     """
302     here we test the state transition diagram when it never goes into effect:
303     1. not in effect, not deleted
304     2. not in effect, deleted
305     3. gone (timeout expires)
306     """
307
308     _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", False)
309
310     # delete the instance
311     _delete_instance(client)
312
313     _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", True)
314
315     # instance should be totally gone after a few seconds
316     _instance_is_gone(client)
317
318     # delete the type
319     _delete_ac_type(client)
320
321
322 def test_bad_instances(client, monkeypatch, adm_type_good):
323     """
324     test various failure modes
325     """
326     # put the type (needed for some of the tests below)
327     rmr_mocks.patch_rmr(monkeypatch)
328     res = client.put(ADM_CTRL_TYPE, json=adm_type_good)
329     assert res.status_code == 201
330
331     # bad body
332     res = client.put(ADM_CTRL_INSTANCE, json={"not": "expected"})
333     assert res.status_code == 400
334
335     # bad media type
336     res = client.put(ADM_CTRL_INSTANCE, data="notajson")
337     assert res.status_code == 415
338
339     # delete a non existent instance
340     res = client.delete(ADM_CTRL_INSTANCE + "DARKNESS")
341     assert res.status_code == 404
342
343     # get a non existent instance
344     a1rmr.replace_rcv_func(_fake_dequeue)
345     res = client.get(ADM_CTRL_INSTANCE + "DARKNESS")
346     assert res.status_code == 404
347
348     # delete the type (as cleanup)
349     res = client.delete(ADM_CTRL_TYPE)
350     assert res.status_code == 204
351
352
353 def test_illegal_types(client, adm_type_good):
354     """
355     Test illegal types
356     """
357     res = client.put("/a1-p/policytypes/19999", json=adm_type_good)
358     assert res.status_code == 400
359     res = client.put("/a1-p/policytypes/21024", json=adm_type_good)
360     assert res.status_code == 400
361
362
363 def test_healthcheck(client):
364     """
365     test healthcheck
366     """
367     res = client.get("/a1-p/healthcheck")
368     assert res.status_code == 200
369
370
371 def teardown_module():
372     """module teardown"""
373     a1rmr.stop_rmr_thread()