Initial commit of A1
[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 a1 import get_module_logger
21 from a1 import a1rmr, exceptions, utils
22
23
24 logger = get_module_logger(__name__)
25
26
27 def _get_needed_policy_info(policyname):
28     """
29     Get the needed info for a policy
30     """
31     # Currently we read the manifest on each call, which would seem to allow updating A1 in place. Revisit this?
32     manifest = utils.get_ric_manifest()
33     for m in manifest["controls"]:
34         if m["name"] == policyname:
35             schema = m["message_receives_payload_schema"] if "message_receives_payload_schema" in m else None
36             return (
37                 utils.rmr_string_to_int(m["message_receives_rmr_type"]),
38                 schema,
39                 utils.rmr_string_to_int(m["message_sends_rmr_type"]),
40             )
41     raise exceptions.PolicyNotFound()
42
43
44 def _try_func_return(func):
45     """
46     generic caller that returns the apporp http response if exceptions are raised
47     """
48     try:
49         return func()
50     except exceptions.PolicyNotFound as exc:
51         logger.exception(exc)
52         return "", 404
53     except exceptions.MissingManifest as exc:
54         logger.exception(exc)
55         return "A1 was unable to find the required RIC manifest. report this!", 500
56     except exceptions.MissingRmrString as exc:
57         logger.exception(exc)
58         return "A1 does not have a mapping for the desired rmr string. report this!", 500
59     except exceptions.MessageSendFailure as exc:
60         logger.exception(exc)
61         return "A1 was unable to send a needed message to a downstream subscriber", 504
62     except exceptions.ExpectedAckNotReceived as exc:
63         logger.exception(exc)
64         return "A1 was expecting an ACK back but it didn't receive one or didn't recieve the expected ACK", 504
65     except BaseException as exc:
66         # catch all, should never happen...
67         logger.exception(exc)
68         return Response(status=500)
69
70
71 def _put_handler(policyname, data):
72     """
73     Handles policy put
74     """
75
76     mtype_send, schema, mtype_return = _get_needed_policy_info(policyname)
77
78     # validate the PUT against the schema, or if there is no shema, make sure the pUT is empty
79     if schema:
80         utils.validate_json(data, schema)
81     elif data != {}:
82         return "BODY SUPPLIED BUT POLICY HAS NO EXPECTED BODY", 400
83
84     # send rmr, wait for ACK
85     return_payload = a1rmr.send_ack_retry(json.dumps(data), message_type=mtype_send, expected_ack_message_type=mtype_return)
86
87     # right now it is assumed that xapps respond with JSON payloads
88     # it is further assumed that they include a field "status" and that the value "SUCCESS" indicates a good policy change
89     try:
90         rpj = json.loads(return_payload)
91         return (rpj, 200) if rpj["status"] == "SUCCESS" else ({"reason": "BAD STATUS", "return_payload": rpj}, 502)
92     except json.decoder.JSONDecodeError:
93         return {"reason": "NOT JSON", "return_payload": return_payload}, 502
94     except KeyError:
95         return {"reason": "NO STATUS", "return_payload": rpj}, 502
96
97
98 # Public
99
100
101 def put_handler(policyname):
102     """
103     Handles policy replacement
104     """
105     data = connexion.request.json
106     return _try_func_return(lambda: _put_handler(policyname, data))
107
108
109 def get_handler(policyname):
110     """
111     Handles policy GET
112     """
113     return "", 501