Use the RIC logging lib
[ric-plt/a1.git] / a1 / data.py
1 """
2 Represents A1s database and database access functions.
3 In the future, this may change to use a different backend, possibly dramatically.
4 Hopefully, the access functions are a good api so nothing else has to change when this happens
5
6 For now, the database is in memory.
7 We use dict data structures (KV) with the expectation of having to move this into Redis
8 """
9 # ==================================================================================
10 #       Copyright (c) 2019 Nokia
11 #       Copyright (c) 2018-2019 AT&T Intellectual Property.
12 #
13 #   Licensed under the Apache License, Version 2.0 (the "License");
14 #   you may not use this file except in compliance with the License.
15 #   You may obtain a copy of the License at
16 #
17 #          http://www.apache.org/licenses/LICENSE-2.0
18 #
19 #   Unless required by applicable law or agreed to in writing, software
20 #   distributed under the License is distributed on an "AS IS" BASIS,
21 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 #   See the License for the specific language governing permissions and
23 #   limitations under the License.
24 # ==================================================================================
25 import os
26 import time
27 from threading import Thread
28 import msgpack
29 from mdclogpy import Logger
30 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
31
32 mdc_logger = Logger(name=__name__)
33
34
35 INSTANCE_DELETE_NO_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5))
36 INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5))
37
38
39 class SDLWrapper:
40     """
41     This is a wrapper around the expected SDL Python interface.
42     The usage of POLICY_DATA will be replaced with  SDL when SDL for python is available.
43     The eventual SDL API is expected to be very close to what is here.
44
45     We use msgpack for binary (de)serialization: https://msgpack.org/index.html
46     """
47
48     def __init__(self):
49         self.POLICY_DATA = {}
50
51     def set(self, key, value):
52         """set a key"""
53         self.POLICY_DATA[key] = msgpack.packb(value, use_bin_type=True)
54
55     def get(self, key):
56         """get a key"""
57         if key in self.POLICY_DATA:
58             return msgpack.unpackb(self.POLICY_DATA[key], raw=False)
59         return None
60
61     def find_and_get(self, prefix):
62         """get all k v pairs that start with prefix"""
63         return {k: msgpack.unpackb(v, raw=False) for k, v in self.POLICY_DATA.items() if k.startswith(prefix)}
64
65     def delete(self, key):
66         """ delete a key"""
67         del self.POLICY_DATA[key]
68
69
70 SDL = SDLWrapper()
71
72 TYPE_PREFIX = "a1.policy_type."
73 INSTANCE_PREFIX = "a1.policy_instance."
74 METADATA_PREFIX = "a1.policy_inst_metadata."
75 HANDLER_PREFIX = "a1.policy_handler."
76
77
78 # Internal helpers
79
80
81 def _generate_type_key(policy_type_id):
82     """
83     generate a key for a policy type
84     """
85     return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
86
87
88 def _generate_instance_key(policy_type_id, policy_instance_id):
89     """
90     generate a key for a policy instance
91     """
92     return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
93
94
95 def _generate_instance_metadata_key(policy_type_id, policy_instance_id):
96     """
97     generate a key for a policy instance metadata
98     """
99     return "{0}{1}.{2}".format(METADATA_PREFIX, policy_type_id, policy_instance_id)
100
101
102 def _generate_handler_prefix(policy_type_id, policy_instance_id):
103     """
104     generate the prefix to a handler key
105     """
106     return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
107
108
109 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
110     """
111     generate a key for a policy handler
112     """
113     return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
114
115
116 def _type_is_valid(policy_type_id):
117     """
118     check that a type is valid
119     """
120     if SDL.get(_generate_type_key(policy_type_id)) is None:
121         raise PolicyTypeNotFound()
122
123
124 def _instance_is_valid(policy_type_id, policy_instance_id):
125     """
126     check that an instance is valid
127     """
128     _type_is_valid(policy_type_id)
129     if SDL.get(_generate_instance_key(policy_type_id, policy_instance_id)) is None:
130         raise PolicyInstanceNotFound
131
132
133 def _get_statuses(policy_type_id, policy_instance_id):
134     """
135     shared helper to get statuses for an instance
136     """
137     _instance_is_valid(policy_type_id, policy_instance_id)
138     prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
139     return list(SDL.find_and_get(prefixes_for_handler).values())
140
141
142 def _get_instance_list(policy_type_id):
143     """
144     shared helper to get instance list for a type
145     """
146     _type_is_valid(policy_type_id)
147     prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
148     instancekeys = SDL.find_and_get(prefixes_for_type).keys()
149     return [k.split(prefixes_for_type)[1] for k in instancekeys]
150
151
152 def _clear_handlers(policy_type_id, policy_instance_id):
153     """
154     delete all the handlers for a policy instance
155     """
156     all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
157     keys = SDL.find_and_get(all_handlers_pref)
158     for k in keys:
159         SDL.delete(k)
160
161
162 def _get_metadata(policy_type_id, policy_instance_id):
163     """
164     get instance metadata
165     """
166     _instance_is_valid(policy_type_id, policy_instance_id)
167     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
168     return SDL.get(metadata_key)
169
170
171 def _delete_after(policy_type_id, policy_instance_id, ttl):
172     """
173     this is a blocking function, must call this in a thread to not block!
174     waits ttl seconds, then deletes the instance
175     """
176     _instance_is_valid(policy_type_id, policy_instance_id)
177
178     time.sleep(ttl)
179
180     # ready to delete
181     _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
182     SDL.delete(_generate_instance_key(policy_type_id, policy_instance_id))  # delete instance
183     SDL.delete(_generate_instance_metadata_key(policy_type_id, policy_instance_id))  # delete instance metadata
184     mdc_logger.debug("type {0} instance {1} deleted".format(policy_type_id, policy_instance_id))
185
186
187 # Types
188
189
190 def get_type_list():
191     """
192     retrieve all type ids
193     """
194     typekeys = SDL.find_and_get(TYPE_PREFIX).keys()
195     # policy types are ints but they get butchered to strings in the KV
196     return [int(k.split(TYPE_PREFIX)[1]) for k in typekeys]
197
198
199 def store_policy_type(policy_type_id, body):
200     """
201     store a policy type if it doesn't already exist
202     """
203     key = _generate_type_key(policy_type_id)
204     if SDL.get(key) is not None:
205         raise PolicyTypeAlreadyExists()
206     SDL.set(key, body)
207
208
209 def delete_policy_type(policy_type_id):
210     """
211     delete a policy type; can only be done if there are no instances (business logic)
212     """
213     pil = get_instance_list(policy_type_id)
214     if pil == []:  # empty, can delete
215         SDL.delete(_generate_type_key(policy_type_id))
216     else:
217         raise CantDeleteNonEmptyType()
218
219
220 def get_policy_type(policy_type_id):
221     """
222     retrieve a type
223     """
224     _type_is_valid(policy_type_id)
225     return SDL.get(_generate_type_key(policy_type_id))
226
227
228 # Instances
229
230
231 def store_policy_instance(policy_type_id, policy_instance_id, instance):
232     """
233     Store a policy instance
234     """
235     _type_is_valid(policy_type_id)
236     creation_timestamp = time.time()
237
238     # store the instance
239     key = _generate_instance_key(policy_type_id, policy_instance_id)
240     if SDL.get(key) is not None:
241         # Reset the statuses because this is a new policy instance, even if it was overwritten
242         _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
243     SDL.set(key, instance)
244
245     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
246     SDL.set(metadata_key, {"created_at": creation_timestamp, "has_been_deleted": False})
247
248
249 def get_policy_instance(policy_type_id, policy_instance_id):
250     """
251     Retrieve a policy instance
252     """
253     _instance_is_valid(policy_type_id, policy_instance_id)
254     return SDL.get(_generate_instance_key(policy_type_id, policy_instance_id))
255
256
257 def get_instance_list(policy_type_id):
258     """
259     retrieve all instance ids for a type
260     """
261     return _get_instance_list(policy_type_id)
262
263
264 def delete_policy_instance(policy_type_id, policy_instance_id):
265     """
266     initially sets has_been_deleted
267     then launches a thread that waits until the relevent timer expires, and finally deletes the instance
268     """
269     _instance_is_valid(policy_type_id, policy_instance_id)
270
271     # set the metadata first
272     deleted_timestamp = time.time()
273     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
274     existing_metadata = _get_metadata(policy_type_id, policy_instance_id)
275     SDL.set(
276         metadata_key,
277         {"created_at": existing_metadata["created_at"], "has_been_deleted": True, "deleted_at": deleted_timestamp},
278     )
279
280     # wait, then delete
281     vector = _get_statuses(policy_type_id, policy_instance_id)
282     if vector == []:
283         # handler is empty; we wait for t1 to expire then goodnight
284         clos = lambda: _delete_after(policy_type_id, policy_instance_id, INSTANCE_DELETE_NO_RESP_TTL)
285     else:
286         # handler is not empty, we wait max t1,t2 to expire then goodnight
287         clos = lambda: _delete_after(
288             policy_type_id, policy_instance_id, max(INSTANCE_DELETE_RESP_TTL, INSTANCE_DELETE_NO_RESP_TTL)
289         )
290     Thread(target=clos).start()
291
292
293 # Statuses
294
295
296 def set_policy_instance_status(policy_type_id, policy_instance_id, handler_id, status):
297     """
298     update the database status for a handler
299     called from a1's rmr thread
300     """
301     _type_is_valid(policy_type_id)
302     _instance_is_valid(policy_type_id, policy_instance_id)
303     SDL.set(_generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)
304
305
306 def get_policy_instance_status(policy_type_id, policy_instance_id):
307     """
308     Gets the status of an instance
309     """
310     _instance_is_valid(policy_type_id, policy_instance_id)
311     metadata = _get_metadata(policy_type_id, policy_instance_id)
312     metadata["instance_status"] = "NOT IN EFFECT"
313     for i in _get_statuses(policy_type_id, policy_instance_id):
314         if i == "OK":
315             metadata["instance_status"] = "IN EFFECT"
316             break
317     return metadata