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