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