Changing log severity level in case of liveness/readiness issues
[ric-plt/a1.git] / a1 / data.py
1 # ==================================================================================
2 #       Copyright (c) 2019-2020 Nokia
3 #       Copyright (c) 2018-2020 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 Represents A1s database and database access functions.
19 """
20 import distutils.util
21 import os
22 import time
23 from threading import Thread
24 from mdclogpy import Logger
25 from ricxappframe.xapp_sdl import SDLWrapper
26 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, PolicyTypeIdMismatch, CantDeleteNonEmptyType
27
28 # constants
29 INSTANCE_DELETE_NO_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5))
30 INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5))
31 USE_FAKE_SDL = bool(distutils.util.strtobool(os.environ.get("USE_FAKE_SDL", "False")))
32 A1NS = "A1m_ns"
33 TYPE_PREFIX = "a1.policy_type."
34 INSTANCE_PREFIX = "a1.policy_instance."
35 METADATA_PREFIX = "a1.policy_inst_metadata."
36 HANDLER_PREFIX = "a1.policy_handler."
37
38
39 mdc_logger = Logger(name=__name__)
40 mdc_logger.mdclog_format_init(configmap_monitor=True)
41 if USE_FAKE_SDL:
42     mdc_logger.debug("Using fake SDL")
43 SDL = SDLWrapper(use_fake_sdl=USE_FAKE_SDL)
44
45 # Internal helpers
46
47
48 def _generate_type_key(policy_type_id):
49     """
50     generate a key for a policy type
51     """
52     return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
53
54
55 def _generate_instance_key(policy_type_id, policy_instance_id):
56     """
57     generate a key for a policy instance
58     """
59     return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
60
61
62 def _generate_instance_metadata_key(policy_type_id, policy_instance_id):
63     """
64     generate a key for a policy instance metadata
65     """
66     return "{0}{1}.{2}".format(METADATA_PREFIX, policy_type_id, policy_instance_id)
67
68
69 def _generate_handler_prefix(policy_type_id, policy_instance_id):
70     """
71     generate the prefix to a handler key
72     """
73     return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
74
75
76 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
77     """
78     generate a key for a policy handler
79     """
80     return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
81
82
83 def _type_is_valid(policy_type_id):
84     """
85     check that a type is valid
86     """
87     if SDL.get(A1NS, _generate_type_key(policy_type_id)) is None:
88         raise PolicyTypeNotFound(policy_type_id)
89
90
91 def _instance_is_valid(policy_type_id, policy_instance_id):
92     """
93     check that an instance is valid
94     """
95     _type_is_valid(policy_type_id)
96     if SDL.get(A1NS, _generate_instance_key(policy_type_id, policy_instance_id)) is None:
97         raise PolicyInstanceNotFound(policy_type_id)
98
99
100 def _get_statuses(policy_type_id, policy_instance_id):
101     """
102     shared helper to get statuses for an instance
103     """
104     _instance_is_valid(policy_type_id, policy_instance_id)
105     prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
106     return list(SDL.find_and_get(A1NS, prefixes_for_handler).values())
107
108
109 def _get_instance_list(policy_type_id):
110     """
111     shared helper to get instance list for a type
112     """
113     _type_is_valid(policy_type_id)
114     prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
115     instancekeys = SDL.find_and_get(A1NS, prefixes_for_type).keys()
116     return [k.split(prefixes_for_type)[1] for k in instancekeys]
117
118
119 def _clear_handlers(policy_type_id, policy_instance_id):
120     """
121     delete all the handlers for a policy instance
122     """
123     all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
124     keys = SDL.find_and_get(A1NS, all_handlers_pref)
125     for k in keys:
126         SDL.delete(A1NS, k)
127
128
129 def _get_metadata(policy_type_id, policy_instance_id):
130     """
131     get instance metadata
132     """
133     _instance_is_valid(policy_type_id, policy_instance_id)
134     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
135     return SDL.get(A1NS, metadata_key)
136
137
138 def _delete_after(policy_type_id, policy_instance_id, ttl):
139     """
140     this is a blocking function, must call this in a thread to not block!
141     waits ttl seconds, then deletes the instance
142     """
143     _instance_is_valid(policy_type_id, policy_instance_id)
144
145     time.sleep(ttl)
146
147     # ready to delete
148     _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
149     SDL.delete(A1NS, _generate_instance_key(policy_type_id, policy_instance_id))  # delete instance
150     SDL.delete(A1NS, _generate_instance_metadata_key(policy_type_id, policy_instance_id))  # delete instance metadata
151     mdc_logger.debug("type {0} instance {1} deleted".format(policy_type_id, policy_instance_id))
152
153
154 # Types
155
156
157 def get_type_list():
158     """
159     retrieve all type ids
160     """
161     typekeys = SDL.find_and_get(A1NS, TYPE_PREFIX).keys()
162     # policy types are ints but they get butchered to strings in the KV
163     return [int(k.split(TYPE_PREFIX)[1]) for k in typekeys]
164
165
166 def store_policy_type(policy_type_id, body):
167     """
168     store a policy type if it doesn't already exist
169     """
170     if policy_type_id != body['policy_type_id']:
171         raise PolicyTypeIdMismatch("{0} vs. {1}".format(policy_type_id, body['policy_type_id']))
172     key = _generate_type_key(policy_type_id)
173     if SDL.get(A1NS, key) is not None:
174         raise PolicyTypeAlreadyExists(policy_type_id)
175     SDL.set(A1NS, key, body)
176
177
178 def delete_policy_type(policy_type_id):
179     """
180     delete a policy type; can only be done if there are no instances (business logic)
181     """
182     pil = get_instance_list(policy_type_id)
183     if pil == []:  # empty, can delete
184         SDL.delete(A1NS, _generate_type_key(policy_type_id))
185     else:
186         raise CantDeleteNonEmptyType(policy_type_id)
187
188
189 def get_policy_type(policy_type_id):
190     """
191     retrieve a type
192     """
193     _type_is_valid(policy_type_id)
194     return SDL.get(A1NS, _generate_type_key(policy_type_id))
195
196
197 # Instances
198
199
200 def store_policy_instance(policy_type_id, policy_instance_id, instance):
201     """
202     Store a policy instance
203     """
204     _type_is_valid(policy_type_id)
205     creation_timestamp = time.time()
206
207     # store the instance
208     operation = "CREATE"
209     key = _generate_instance_key(policy_type_id, policy_instance_id)
210     if SDL.get(A1NS, key) is not None:
211         operation = "UPDATE"
212         # Reset the statuses because this is a new policy instance, even if it was overwritten
213         _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
214     SDL.set(A1NS, key, instance)
215
216     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
217     SDL.set(A1NS, metadata_key, {"created_at": creation_timestamp, "has_been_deleted": False})
218
219     return operation
220
221
222 def get_policy_instance(policy_type_id, policy_instance_id):
223     """
224     Retrieve a policy instance
225     """
226     _instance_is_valid(policy_type_id, policy_instance_id)
227     return SDL.get(A1NS, _generate_instance_key(policy_type_id, policy_instance_id))
228
229
230 def get_instance_list(policy_type_id):
231     """
232     retrieve all instance ids for a type
233     """
234     return _get_instance_list(policy_type_id)
235
236
237 def delete_policy_instance(policy_type_id, policy_instance_id):
238     """
239     initially sets has_been_deleted in the status
240     then launches a thread that waits until the relevent timer expires, and finally deletes the instance
241     """
242     _instance_is_valid(policy_type_id, policy_instance_id)
243
244     # set the metadata first
245     deleted_timestamp = time.time()
246     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
247     existing_metadata = _get_metadata(policy_type_id, policy_instance_id)
248     SDL.set(
249         A1NS,
250         metadata_key,
251         {"created_at": existing_metadata["created_at"], "has_been_deleted": True, "deleted_at": deleted_timestamp},
252     )
253
254     # wait, then delete
255     vector = _get_statuses(policy_type_id, policy_instance_id)
256     if vector == []:
257         # handler is empty; we wait for t1 to expire then goodnight
258         clos = lambda: _delete_after(policy_type_id, policy_instance_id, INSTANCE_DELETE_NO_RESP_TTL)
259     else:
260         # handler is not empty, we wait max t1,t2 to expire then goodnight
261         clos = lambda: _delete_after(
262             policy_type_id, policy_instance_id, max(INSTANCE_DELETE_RESP_TTL, INSTANCE_DELETE_NO_RESP_TTL)
263         )
264     Thread(target=clos).start()
265
266
267 # Statuses
268
269
270 def set_policy_instance_status(policy_type_id, policy_instance_id, handler_id, status):
271     """
272     update the database status for a handler
273     called from a1's rmr thread
274     """
275     _type_is_valid(policy_type_id)
276     _instance_is_valid(policy_type_id, policy_instance_id)
277     SDL.set(A1NS, _generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)
278
279
280 def get_policy_instance_status(policy_type_id, policy_instance_id):
281     """
282     Gets the status of an instance
283     """
284     _instance_is_valid(policy_type_id, policy_instance_id)
285     metadata = _get_metadata(policy_type_id, policy_instance_id)
286     metadata["instance_status"] = "NOT IN EFFECT"
287     for i in _get_statuses(policy_type_id, policy_instance_id):
288         if i == "OK":
289             metadata["instance_status"] = "IN EFFECT"
290             break
291     return metadata