4 # ==================================================================================
5 # Copyright (c) 2019 Nokia
6 # Copyright (c) 2018-2019 AT&T Intellectual Property.
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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 # ==================================================================================
22 from rmr.rmr_mocks import rmr_mocks
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"
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}
41 def _fake_dequeue_none():
42 """for monkeypatching with no waiting messages"""
46 def _fake_dequeue_deleted():
47 """for monkeypatching with a DELETED status"""
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)
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)
59 # insert a bad one with a malformed body to make sure we keep going
60 new_msgs.append({"payload": "asdf"})
63 new_msgs.append("asdf")
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)
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))
77 # we need this because free expects a real sbuf
78 # TODO: move this into rmr_mocks
82 monkeypatch.setattr("rmr.rmr.rmr_free_msg", noop)
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"
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"
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)
101 res = client.get(ADM_CTRL_TYPE)
102 assert res.status_code == 404
105 res = client.get("/a1-p/policytypes")
106 assert res.status_code == 200
107 assert res.json == []
109 # instance 404 because type not there yet
110 res = client.get(ADM_CTRL_POLICIES)
111 assert res.status_code == 404
114 def _put_ac_type(client, typedef):
118 res = client.put(ADM_CTRL_TYPE, json=typedef)
119 assert res.status_code == 201
122 res = client.put(ADM_CTRL_TYPE, json=typedef)
123 assert res.status_code == 400
126 res = client.get(ADM_CTRL_TYPE)
127 assert res.status_code == 200
128 assert res.json == typedef
131 res = client.get("/a1-p/policytypes")
132 assert res.status_code == 200
133 assert res.json == [20000]
135 # instance 200 but empty list
136 res = client.get(ADM_CTRL_POLICIES)
137 assert res.status_code == 200
138 assert res.json == []
141 def _delete_ac_type(client):
142 res = client.delete(ADM_CTRL_TYPE)
143 assert res.status_code == 204
146 res = client.get(ADM_CTRL_TYPE)
147 assert res.status_code == 404
149 # cant invoke delete on it again
150 res = client.delete(ADM_CTRL_TYPE)
151 assert res.status_code == 404
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
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
168 # replace is allowed on instances
169 res = client.put(ADM_CTRL_INSTANCE, json=instancedef)
170 assert res.status_code == 202
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]
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
184 res = client.delete(ADM_CTRL_INSTANCE)
185 assert res.status_code == 202
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
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
196 res = client.get(ADM_CTRL_INSTANCE_STATUS)
197 assert res.status_code == 404
198 except AssertionError:
201 res = client.get(ADM_CTRL_INSTANCE_STATUS)
202 assert res.status_code == 404
204 # list still 200 but no instance
205 res = client.get(ADM_CTRL_POLICIES)
206 assert res.status_code == 200
207 assert res.json == []
210 res = client.get(ADM_CTRL_INSTANCE)
211 assert res.status_code == 404
214 def _verify_instance_and_status(client, expected_instance, expected_status, expected_deleted, seconds_to_try=5):
216 res = client.get(ADM_CTRL_INSTANCE)
217 assert res.status_code == 200
218 assert res.json == expected_instance
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
226 assert res.json["instance_status"] == expected_status
228 except AssertionError:
230 assert res.json["instance_status"] == expected_status
237 """module level setup"""
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)
249 def test_workflow(client, monkeypatch, adm_type_good, adm_instance_good):
251 test a full A1 workflow
253 _put_ac_type(client, adm_type_good)
254 _put_ac_instance(client, monkeypatch, adm_instance_good)
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)
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)
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)
272 # delete the instance
273 _delete_instance(client)
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)
278 # now pretend we deleted successfully
279 a1rmr.replace_rcv_func(_fake_dequeue_deleted)
281 # status should be reflected first (before delete triggers)
282 _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", True)
284 # instance should be totally gone after a few seconds
285 _instance_is_gone(client)
288 _delete_ac_type(client)
291 def test_cleanup_via_t1(client, monkeypatch, adm_type_good, adm_instance_good):
293 create a type, create an instance, but no acks ever come in, delete instance
295 _put_ac_type(client, adm_type_good)
297 a1rmr.replace_rcv_func(_fake_dequeue_none)
299 _put_ac_instance(client, monkeypatch, adm_instance_good)
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)
308 _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", False)
310 # delete the instance
311 _delete_instance(client)
313 _verify_instance_and_status(client, adm_instance_good, "NOT IN EFFECT", True)
315 # instance should be totally gone after a few seconds
316 _instance_is_gone(client)
319 _delete_ac_type(client)
322 def test_bad_instances(client, monkeypatch, adm_type_good):
324 test various failure modes
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
332 res = client.put(ADM_CTRL_INSTANCE, json={"not": "expected"})
333 assert res.status_code == 400
336 res = client.put(ADM_CTRL_INSTANCE, data="notajson")
337 assert res.status_code == 415
339 # delete a non existent instance
340 res = client.delete(ADM_CTRL_INSTANCE + "DARKNESS")
341 assert res.status_code == 404
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
348 # delete the type (as cleanup)
349 res = client.delete(ADM_CTRL_TYPE)
350 assert res.status_code == 204
353 def test_illegal_types(client, adm_type_good):
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
363 def test_healthcheck(client):
367 res = client.get("/a1-p/healthcheck")
368 assert res.status_code == 200
371 def teardown_module():
372 """module teardown"""
373 a1rmr.stop_rmr_thread()