Implement GET and /healthcheck
[ric-plt/a1.git] / a1 / controller.py
1 # ==================================================================================
2 #       Copyright (c) 2019 Nokia
3 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #          http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 # ==================================================================================
17 from flask import Response
18 import connexion
19 import json
20 from jsonschema.exceptions import ValidationError
21 from a1 import get_module_logger
22 from a1 import a1rmr, exceptions, utils
23
24
25 logger = get_module_logger(__name__)
26
27
28 def _get_policy_definition(policyname):
29     # Currently we read the manifest on each call, which would seem to allow updating A1 in place. Revisit this?
30     manifest = utils.get_ric_manifest()
31     for m in manifest["controls"]:
32         if m["name"] == policyname:
33             return m
34     raise exceptions.PolicyNotFound()
35
36
37 def _get_needed_policy_info(policyname):
38     """
39     Get the needed info for a policy
40     """
41     m = _get_policy_definition(policyname)
42     return (
43         utils.rmr_string_to_int(m["message_receives_rmr_type"]),
44         m["message_receives_payload_schema"] if "message_receives_payload_schema" in m else None,
45         utils.rmr_string_to_int(m["message_sends_rmr_type"]),
46     )
47
48
49 def _get_needed_policy_fetch_info(policyname):
50     """
51     Get the needed info for fetching a policy state
52     """
53     m = _get_policy_definition(policyname)
54     req_k = "control_state_request_rmr_type"
55     ack_k = "control_state_request_reply_rmr_type"
56     return (
57         utils.rmr_string_to_int(m[req_k]) if req_k in m else None,
58         utils.rmr_string_to_int(m[ack_k]) if ack_k in m else None,
59     )
60
61
62 def _try_func_return(func):
63     """
64     generic caller that returns the apporp http response if exceptions are raised
65     """
66     try:
67         return func()
68     except ValidationError as exc:
69         logger.exception(exc)
70         return "", 400
71     except exceptions.PolicyNotFound as exc:
72         logger.exception(exc)
73         return "", 404
74     except exceptions.MissingManifest as exc:
75         logger.exception(exc)
76         return "A1 was unable to find the required RIC manifest. report this!", 500
77     except exceptions.MissingRmrString as exc:
78         logger.exception(exc)
79         return "A1 does not have a mapping for the desired rmr string. report this!", 500
80     except exceptions.MessageSendFailure as exc:
81         logger.exception(exc)
82         return "A1 was unable to send a needed message to a downstream subscriber", 504
83     except exceptions.ExpectedAckNotReceived as exc:
84         logger.exception(exc)
85         return "A1 was expecting an ACK back but it didn't receive one or didn't recieve the expected ACK", 504
86     except BaseException as exc:
87         # catch all, should never happen...
88         logger.exception(exc)
89         return Response(status=500)
90
91
92 def _put_handler(policyname, data):
93     """
94     Handles policy put
95     """
96
97     mtype_send, schema, mtype_return = _get_needed_policy_info(policyname)
98
99     # validate the PUT against the schema, or if there is no shema, make sure the pUT is empty
100     if schema:
101         utils.validate_json(data, schema)
102     elif data != {}:
103         return "BODY SUPPLIED BUT POLICY HAS NO EXPECTED BODY", 400
104
105     # send rmr, wait for ACK
106     return_payload = a1rmr.send_ack_retry(json.dumps(data), message_type=mtype_send, expected_ack_message_type=mtype_return)
107
108     # right now it is assumed that xapps respond with JSON payloads
109     # it is further assumed that they include a field "status" and that the value "SUCCESS" indicates a good policy change
110     try:
111         rpj = json.loads(return_payload)
112         return (rpj, 200) if rpj["status"] == "SUCCESS" else ({"reason": "BAD STATUS", "return_payload": rpj}, 502)
113     except json.decoder.JSONDecodeError:
114         return {"reason": "NOT JSON", "return_payload": return_payload}, 502
115     except KeyError:
116         return {"reason": "NO STATUS", "return_payload": rpj}, 502
117
118
119 def _get_handler(policyname):
120     """
121     Handles policy GET
122     """
123     mtype_send, mtype_return = _get_needed_policy_fetch_info(policyname)
124
125     if not (mtype_send and mtype_return):
126         return "POLICY DOES NOT SUPPORT FETCHING", 400
127
128     # send rmr, wait for ACK
129     return_payload = a1rmr.send_ack_retry("", message_type=mtype_send, expected_ack_message_type=mtype_return)
130
131     # right now it is assumed that xapps respond with JSON payloads
132     try:
133         return (json.loads(return_payload), 200)
134     except json.decoder.JSONDecodeError:
135         return {"reason": "NOT JSON", "return_payload": return_payload}, 502
136
137
138 # Public
139
140
141 def put_handler(policyname):
142     """
143     Handles policy replacement
144     """
145     data = connexion.request.json
146     return _try_func_return(lambda: _put_handler(policyname, data))
147
148
149 def get_handler(policyname):
150     """
151     Handles policy GET
152     """
153     return _try_func_return(lambda: _get_handler(policyname))
154
155
156 def healthcheck_handler():
157     """
158     Handles healthcheck GET
159     Currently, this basically checks the server is alive.a1rmr
160     """
161     return "", 200