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