Threading pt 1
[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 msgpack
27 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
28 from a1 import get_module_logger
29
30 logger = get_module_logger(__name__)
31
32
33 class SDLWrapper:
34     """
35     This is a wrapper around the expected SDL Python interface.
36     The usage of POLICY_DATA will be replaced with  SDL when SDL for python is available.
37     The eventual SDL API is expected to be very close to what is here.
38
39     We use msgpack for binary (de)serialization: https://msgpack.org/index.html
40     """
41
42     def __init__(self):
43         self.POLICY_DATA = {}
44
45     def set(self, key, value):
46         """set a key"""
47         self.POLICY_DATA[key] = msgpack.packb(value, use_bin_type=True)
48
49     def get(self, key):
50         """get a key"""
51         if key in self.POLICY_DATA:
52             return msgpack.unpackb(self.POLICY_DATA[key], raw=False)
53         return None
54
55     def find_and_get(self, prefix):
56         """get all k v pairs that start with prefix"""
57         return {k: msgpack.unpackb(v, raw=False) for k, v in self.POLICY_DATA.items() if k.startswith(prefix)}
58
59     def delete(self, key):
60         """ delete a key"""
61         del self.POLICY_DATA[key]
62
63
64 SDL = SDLWrapper()
65
66 TYPE_PREFIX = "a1.policy_type."
67 INSTANCE_PREFIX = "a1.policy_instance."
68 HANDLER_PREFIX = "a1.policy_handler."
69
70
71 # Internal helpers
72
73
74 def _generate_type_key(policy_type_id):
75     """
76     generate a key for a policy type
77     """
78     return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
79
80
81 def _generate_instance_key(policy_type_id, policy_instance_id):
82     """
83     generate a key for a policy instance
84     """
85     return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
86
87
88 def _generate_handler_prefix(policy_type_id, policy_instance_id):
89     """
90     generate the prefix to a handler key
91     """
92     return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
93
94
95 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
96     """
97     generate a key for a policy handler
98     """
99     return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
100
101
102 def _get_statuses(policy_type_id, policy_instance_id):
103     """
104     shared helper to get statuses for an instance
105     """
106     instance_is_valid(policy_type_id, policy_instance_id)
107     prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
108     return list(SDL.find_and_get(prefixes_for_handler).values())
109
110
111 def _get_instance_list(policy_type_id):
112     """
113     shared helper to get instance list for a type
114     """
115     type_is_valid(policy_type_id)
116     prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
117     instancekeys = SDL.find_and_get(prefixes_for_type).keys()
118     return [k.split(prefixes_for_type)[1] for k in instancekeys]
119
120
121 def _clear_handlers(policy_type_id, policy_instance_id):
122     """
123     delete all the handlers for a policy instance
124     """
125     all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
126     keys = SDL.find_and_get(all_handlers_pref)
127     for k in keys:
128         SDL.delete(k)
129
130
131 def _clean_up_type(policy_type_id):
132     """
133     for all instances of type, see if it can be deleted
134     """
135     type_is_valid(policy_type_id)
136     for policy_instance_id in _get_instance_list(policy_type_id):
137         # see if we can delete
138         vector = _get_statuses(policy_type_id, policy_instance_id)
139
140         """
141         TODO: not being able to delete if the list is [] is prolematic.
142         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
143         However, A1 cannot distinguish between the case where [] was never going to work, and the case where it hasn't worked *yet*
144
145         However, removing this constraint also leads to problems.
146         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.
147
148         This requires some thought to address.
149         For now we stick with the "less bad problem".
150         """
151         if vector != []:
152             all_deleted = True
153             for i in vector:
154                 if i != "DELETED":
155                     all_deleted = False
156                     break  # have at least one not DELETED, do nothing
157
158             # blow away from a1 db
159             if all_deleted:
160                 _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
161                 SDL.delete(_generate_instance_key(policy_type_id, policy_instance_id))  # delete instance
162
163
164 # Types
165
166
167 def get_type_list():
168     """
169     retrieve all type ids
170     """
171     typekeys = SDL.find_and_get(TYPE_PREFIX).keys()
172     # policy types are ints but they get butchered to strings in the KV
173     return [int(k.split(TYPE_PREFIX)[1]) for k in typekeys]
174
175
176 def type_is_valid(policy_type_id):
177     """
178     check that a type is valid
179     """
180     if SDL.get(_generate_type_key(policy_type_id)) is None:
181         raise PolicyTypeNotFound()
182
183
184 def store_policy_type(policy_type_id, body):
185     """
186     store a policy type if it doesn't already exist
187     """
188     key = _generate_type_key(policy_type_id)
189     if SDL.get(key) is not None:
190         raise PolicyTypeAlreadyExists()
191     SDL.set(key, body)
192
193
194 def delete_policy_type(policy_type_id):
195     """
196     delete a policy type; can only be done if there are no instances (business logic)
197     """
198     pil = get_instance_list(policy_type_id)
199     if pil == []:  # empty, can delete
200         SDL.delete(_generate_type_key(policy_type_id))
201     else:
202         raise CantDeleteNonEmptyType()
203
204
205 def get_policy_type(policy_type_id):
206     """
207     retrieve a type
208     """
209     type_is_valid(policy_type_id)
210     return SDL.get(_generate_type_key(policy_type_id))
211
212
213 # Instances
214
215
216 def instance_is_valid(policy_type_id, policy_instance_id):
217     """
218     check that an instance is valid
219     """
220     type_is_valid(policy_type_id)
221     if SDL.get(_generate_instance_key(policy_type_id, policy_instance_id)) is None:
222         raise PolicyInstanceNotFound
223
224
225 def store_policy_instance(policy_type_id, policy_instance_id, instance):
226     """
227     Store a policy instance
228     """
229     type_is_valid(policy_type_id)
230     key = _generate_instance_key(policy_type_id, policy_instance_id)
231     if SDL.get(key) is not None:
232         # Reset the statuses because this is a new policy instance, even if it was overwritten
233         _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
234     SDL.set(key, instance)
235
236
237 def get_policy_instance(policy_type_id, policy_instance_id):
238     """
239     Retrieve a policy instance
240     """
241     _clean_up_type(policy_type_id)
242     instance_is_valid(policy_type_id, policy_instance_id)
243     return SDL.get(_generate_instance_key(policy_type_id, policy_instance_id))
244
245
246 def get_policy_instance_statuses(policy_type_id, policy_instance_id):
247     """
248     Retrieve the status vector for a policy instance
249     """
250     _clean_up_type(policy_type_id)
251     return _get_statuses(policy_type_id, policy_instance_id)
252
253
254 def get_instance_list(policy_type_id):
255     """
256     retrieve all instance ids for a type
257     """
258     _clean_up_type(policy_type_id)
259     return _get_instance_list(policy_type_id)
260
261
262 # Statuses
263
264
265 def set_status(policy_type_id, policy_instance_id, handler_id, status):
266     """
267     update the database status for a handler
268     called from a1's rmr thread
269     """
270     type_is_valid(policy_type_id)
271     instance_is_valid(policy_type_id, policy_instance_id)
272     SDL.set(_generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)