Final A1 V1.0.0 Release (but 1.1.0 to come)
[ric-plt/a1.git] / a1 / data.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
18 """
19 Represents A1s database and database access functions.
20 In the future, this may change to use a different backend, possibly dramatically.
21 Hopefully, the access functions are a good api so nothing else has to change when this happens
22
23 For now, the database is in memory.
24 We use dict data structures (KV) with the expectation of having to move this into Redis
25 """
26 import json
27 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
28 from a1 import get_module_logger
29 from a1 import a1rmr
30
31 logger = get_module_logger(__name__)
32
33 # This is essentially mockouts for future KV
34 # Note that the D subkey won't be needed when in redis, since you can store data at x anx x_y
35 POLICY_DATA = {}
36 I = "instances"
37 H = "handlers"
38 D = "data"
39
40
41 # Internal helpers
42
43
44 def _get_statuses(policy_type_id, policy_instance_id):
45     """
46     shared helper to get statuses for an instance
47     """
48     instance_is_valid(policy_type_id, policy_instance_id)
49     return [v for _, v in POLICY_DATA[policy_type_id][I][policy_instance_id][H].items()]
50
51
52 def _get_instance_list(policy_type_id):
53     """
54     shared helper to get instance list for a type
55     """
56     type_is_valid(policy_type_id)
57     return list(POLICY_DATA[policy_type_id][I].keys())
58
59
60 def _clean_up_type(policy_type_id):
61     """
62     pop through a1s mailbox, updating a1s db of all policy statuses
63     for all instances of type, see if it can be deleted
64     """
65     type_is_valid(policy_type_id)
66     for msg in a1rmr.dequeue_all_waiting_messages([21024]):
67         # try to parse the messages as responses. Drop those that are malformed
68         pay = json.loads(msg["payload"])
69         if "policy_type_id" in pay and "policy_instance_id" in pay and "handler_id" in pay and "status" in pay:
70             """
71             NOTE: can't raise an exception here e.g.:
72                 instance_is_valid(pti, pii)
73             because this is called on many functions; just drop bad status messages.
74             We def don't want bad messages that happen to hit a1s mailbox to blow up anything
75
76             NOTE2: we don't use the parameters "policy_type_id, policy_instance" from above here,
77             # because we are popping the whole mailbox, which might include other statuses
78             """
79             pti = pay["policy_type_id"]
80             pii = pay["policy_instance_id"]
81             if pti in POLICY_DATA and pii in POLICY_DATA[pti][I]:  # manual check per comment above
82                 POLICY_DATA[pti][I][pii][H][pay["handler_id"]] = pay["status"]
83         else:
84             logger.debug("Dropping message")
85             logger.debug(pay)
86
87     for policy_instance_id in _get_instance_list(policy_type_id):
88         # see if we can delete
89         vector = _get_statuses(policy_type_id, policy_instance_id)
90
91         """
92         TODO: not being able to delete if the list is [] is prolematic.
93         There are cases, such as a bad routing file, where this type will never be able to be deleted because it never went to any xapps
94         However, A1 cannot distinguish between the case where [] was never going to work, and the case where it hasn't worked *yet*
95
96         However, removing this constraint also leads to problems.
97         Deleting the instance when the vector is empty, for example doing so “shortly after” the PUT, can lead to a worse race condition where the xapps get the policy after that, implement it, but because the DELETE triggered “too soon”, you can never get the status or do the delete on it again, so the xapps are all implementing the instance roguely.
98
99         This requires some thought to address.
100         For now we stick with the "less bad problem".
101         """
102         if vector != []:
103             all_deleted = True
104             for i in vector:
105                 if i != "DELETED":
106                     all_deleted = False
107                     break  # have at least one not DELETED, do nothing
108
109             # blow away from a1 db
110             if all_deleted:
111                 del POLICY_DATA[policy_type_id][I][policy_instance_id]
112
113
114 # Types
115
116
117 def get_type_list():
118     """
119     retrieve all type ids
120     """
121     return list(POLICY_DATA.keys())
122
123
124 def type_is_valid(policy_type_id):
125     """
126     check that a type is valid
127     """
128     if policy_type_id not in POLICY_DATA:
129         logger.error("%s not found", policy_type_id)
130         raise PolicyTypeNotFound()
131
132
133 def store_policy_type(policy_type_id, body):
134     """
135     store a policy type if it doesn't already exist
136     """
137     if policy_type_id in POLICY_DATA:
138         raise PolicyTypeAlreadyExists()
139
140     POLICY_DATA[policy_type_id] = {}
141     POLICY_DATA[policy_type_id][D] = body
142     POLICY_DATA[policy_type_id][I] = {}
143
144
145 def delete_policy_type(policy_type_id):
146     """
147     delete a policy type; can only be done if there are no instances (business logic)
148     """
149     pil = get_instance_list(policy_type_id)
150     if pil == []:  # empty, can delete
151         del POLICY_DATA[policy_type_id]
152     else:
153         raise CantDeleteNonEmptyType()
154
155
156 def get_policy_type(policy_type_id):
157     """
158     retrieve a type
159     """
160     type_is_valid(policy_type_id)
161     return POLICY_DATA[policy_type_id][D]
162
163
164 # Instances
165
166
167 def instance_is_valid(policy_type_id, policy_instance_id):
168     """
169     check that an instance is valid
170     """
171     type_is_valid(policy_type_id)
172     if policy_instance_id not in POLICY_DATA[policy_type_id][I]:
173         raise PolicyInstanceNotFound
174
175
176 def store_policy_instance(policy_type_id, policy_instance_id, instance):
177     """
178     Store a policy instance
179     """
180     type_is_valid(policy_type_id)
181
182     # store the instance
183     # Reset the statuses because this is a new policy instance, even if it was overwritten
184     POLICY_DATA[policy_type_id][I][policy_instance_id] = {}
185     POLICY_DATA[policy_type_id][I][policy_instance_id][D] = instance
186     POLICY_DATA[policy_type_id][I][policy_instance_id][H] = {}
187
188
189 def get_policy_instance(policy_type_id, policy_instance_id):
190     """
191     Retrieve a policy instance
192     """
193     _clean_up_type(policy_type_id)
194     instance_is_valid(policy_type_id, policy_instance_id)
195     return POLICY_DATA[policy_type_id][I][policy_instance_id][D]
196
197
198 def get_policy_instance_statuses(policy_type_id, policy_instance_id):
199     """
200     Retrieve the status vector for a policy instance
201     """
202     _clean_up_type(policy_type_id)
203     return _get_statuses(policy_type_id, policy_instance_id)
204
205
206 def get_instance_list(policy_type_id):
207     """
208     retrieve all instance ids for a type
209     """
210     _clean_up_type(policy_type_id)
211     return _get_instance_list(policy_type_id)