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