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