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