a9301d8cf6a72414e0c4817481ff67b7ebee68b0
[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     key = _generate_instance_key(policy_type_id, policy_instance_id)
208     if SDL.get(A1NS, key) is not None:
209         # Reset the statuses because this is a new policy instance, even if it was overwritten
210         _clear_handlers(policy_type_id, policy_instance_id)  # delete all the handlers
211     SDL.set(A1NS, key, instance)
212
213     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
214     SDL.set(A1NS, metadata_key, {"created_at": creation_timestamp, "has_been_deleted": False})
215
216
217 def get_policy_instance(policy_type_id, policy_instance_id):
218     """
219     Retrieve a policy instance
220     """
221     _instance_is_valid(policy_type_id, policy_instance_id)
222     return SDL.get(A1NS, _generate_instance_key(policy_type_id, policy_instance_id))
223
224
225 def get_instance_list(policy_type_id):
226     """
227     retrieve all instance ids for a type
228     """
229     return _get_instance_list(policy_type_id)
230
231
232 def delete_policy_instance(policy_type_id, policy_instance_id):
233     """
234     initially sets has_been_deleted in the status
235     then launches a thread that waits until the relevent timer expires, and finally deletes the instance
236     """
237     _instance_is_valid(policy_type_id, policy_instance_id)
238
239     # set the metadata first
240     deleted_timestamp = time.time()
241     metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
242     existing_metadata = _get_metadata(policy_type_id, policy_instance_id)
243     SDL.set(
244         A1NS,
245         metadata_key,
246         {"created_at": existing_metadata["created_at"], "has_been_deleted": True, "deleted_at": deleted_timestamp},
247     )
248
249     # wait, then delete
250     vector = _get_statuses(policy_type_id, policy_instance_id)
251     if vector == []:
252         # handler is empty; we wait for t1 to expire then goodnight
253         clos = lambda: _delete_after(policy_type_id, policy_instance_id, INSTANCE_DELETE_NO_RESP_TTL)
254     else:
255         # handler is not empty, we wait max t1,t2 to expire then goodnight
256         clos = lambda: _delete_after(
257             policy_type_id, policy_instance_id, max(INSTANCE_DELETE_RESP_TTL, INSTANCE_DELETE_NO_RESP_TTL)
258         )
259     Thread(target=clos).start()
260
261
262 # Statuses
263
264
265 def set_policy_instance_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(A1NS, _generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)
273
274
275 def get_policy_instance_status(policy_type_id, policy_instance_id):
276     """
277     Gets the status of an instance
278     """
279     _instance_is_valid(policy_type_id, policy_instance_id)
280     metadata = _get_metadata(policy_type_id, policy_instance_id)
281     metadata["instance_status"] = "NOT IN EFFECT"
282     for i in _get_statuses(policy_type_id, policy_instance_id):
283         if i == "OK":
284             metadata["instance_status"] = "IN EFFECT"
285             break
286     return metadata