2 Represents A1s database and database access functions.
4 # ==================================================================================
5 # Copyright (c) 2019-2020 Nokia
6 # Copyright (c) 2018-2020 AT&T Intellectual Property.
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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 # ==================================================================================
22 from threading import Thread
24 from mdclogpy import Logger
25 from ricsdl.syncstorage import SyncStorage
26 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
28 mdc_logger = Logger(name=__name__)
31 INSTANCE_DELETE_NO_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5))
32 INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5))
39 This is a wrapper around the expected SDL Python interface.
40 The usage of POLICY_DATA will be replaced with SDL when SDL for python is available.
41 The eventual SDL API is expected to be very close to what is here.
43 We use msgpack for binary (de)serialization: https://msgpack.org/index.html
47 self.sdl = SyncStorage()
49 def set(self, key, value):
51 self.sdl.set(A1NS, {key: msgpack.packb(value, use_bin_type=True)})
55 ret_dict = self.sdl.get(A1NS, {key})
57 return msgpack.unpackb(ret_dict[key], raw=False)
61 def find_and_get(self, prefix):
62 """get all k v pairs that start with prefix"""
63 # note: SDL "*" usage is inconsistent with real python regex, where it would be ".*"
64 ret_dict = self.sdl.find_and_get(A1NS, "{0}*".format(prefix))
65 return {k: msgpack.unpackb(v, raw=False) for k, v in ret_dict.items()}
67 def delete(self, key):
69 self.sdl.remove(A1NS, {key})
74 TYPE_PREFIX = "a1.policy_type."
75 INSTANCE_PREFIX = "a1.policy_instance."
76 METADATA_PREFIX = "a1.policy_inst_metadata."
77 HANDLER_PREFIX = "a1.policy_handler."
83 def _generate_type_key(policy_type_id):
85 generate a key for a policy type
87 return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
90 def _generate_instance_key(policy_type_id, policy_instance_id):
92 generate a key for a policy instance
94 return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
97 def _generate_instance_metadata_key(policy_type_id, policy_instance_id):
99 generate a key for a policy instance metadata
101 return "{0}{1}.{2}".format(METADATA_PREFIX, policy_type_id, policy_instance_id)
104 def _generate_handler_prefix(policy_type_id, policy_instance_id):
106 generate the prefix to a handler key
108 return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
111 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
113 generate a key for a policy handler
115 return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
118 def _type_is_valid(policy_type_id):
120 check that a type is valid
122 if SDL.get(_generate_type_key(policy_type_id)) is None:
123 raise PolicyTypeNotFound()
126 def _instance_is_valid(policy_type_id, policy_instance_id):
128 check that an instance is valid
130 _type_is_valid(policy_type_id)
131 if SDL.get(_generate_instance_key(policy_type_id, policy_instance_id)) is None:
132 raise PolicyInstanceNotFound
135 def _get_statuses(policy_type_id, policy_instance_id):
137 shared helper to get statuses for an instance
139 _instance_is_valid(policy_type_id, policy_instance_id)
140 prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
141 return list(SDL.find_and_get(prefixes_for_handler).values())
144 def _get_instance_list(policy_type_id):
146 shared helper to get instance list for a type
148 _type_is_valid(policy_type_id)
149 prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
150 instancekeys = SDL.find_and_get(prefixes_for_type).keys()
151 return [k.split(prefixes_for_type)[1] for k in instancekeys]
154 def _clear_handlers(policy_type_id, policy_instance_id):
156 delete all the handlers for a policy instance
158 all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
159 keys = SDL.find_and_get(all_handlers_pref)
164 def _get_metadata(policy_type_id, policy_instance_id):
166 get instance metadata
168 _instance_is_valid(policy_type_id, policy_instance_id)
169 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
170 return SDL.get(metadata_key)
173 def _delete_after(policy_type_id, policy_instance_id, ttl):
175 this is a blocking function, must call this in a thread to not block!
176 waits ttl seconds, then deletes the instance
178 _instance_is_valid(policy_type_id, policy_instance_id)
183 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
184 SDL.delete(_generate_instance_key(policy_type_id, policy_instance_id)) # delete instance
185 SDL.delete(_generate_instance_metadata_key(policy_type_id, policy_instance_id)) # delete instance metadata
186 mdc_logger.debug("type {0} instance {1} deleted".format(policy_type_id, policy_instance_id))
194 retrieve all type ids
196 typekeys = SDL.find_and_get(TYPE_PREFIX).keys()
197 # policy types are ints but they get butchered to strings in the KV
198 return [int(k.split(TYPE_PREFIX)[1]) for k in typekeys]
201 def store_policy_type(policy_type_id, body):
203 store a policy type if it doesn't already exist
205 key = _generate_type_key(policy_type_id)
206 if SDL.get(key) is not None:
207 raise PolicyTypeAlreadyExists()
211 def delete_policy_type(policy_type_id):
213 delete a policy type; can only be done if there are no instances (business logic)
215 pil = get_instance_list(policy_type_id)
216 if pil == []: # empty, can delete
217 SDL.delete(_generate_type_key(policy_type_id))
219 raise CantDeleteNonEmptyType()
222 def get_policy_type(policy_type_id):
226 _type_is_valid(policy_type_id)
227 return SDL.get(_generate_type_key(policy_type_id))
233 def store_policy_instance(policy_type_id, policy_instance_id, instance):
235 Store a policy instance
237 _type_is_valid(policy_type_id)
238 creation_timestamp = time.time()
241 key = _generate_instance_key(policy_type_id, policy_instance_id)
242 if SDL.get(key) is not None:
243 # Reset the statuses because this is a new policy instance, even if it was overwritten
244 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
245 SDL.set(key, instance)
247 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
248 SDL.set(metadata_key, {"created_at": creation_timestamp, "has_been_deleted": False})
251 def get_policy_instance(policy_type_id, policy_instance_id):
253 Retrieve a policy instance
255 _instance_is_valid(policy_type_id, policy_instance_id)
256 return SDL.get(_generate_instance_key(policy_type_id, policy_instance_id))
259 def get_instance_list(policy_type_id):
261 retrieve all instance ids for a type
263 return _get_instance_list(policy_type_id)
266 def delete_policy_instance(policy_type_id, policy_instance_id):
268 initially sets has_been_deleted in the status
269 then launches a thread that waits until the relevent timer expires, and finally deletes the instance
271 _instance_is_valid(policy_type_id, policy_instance_id)
273 # set the metadata first
274 deleted_timestamp = time.time()
275 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
276 existing_metadata = _get_metadata(policy_type_id, policy_instance_id)
279 {"created_at": existing_metadata["created_at"], "has_been_deleted": True, "deleted_at": deleted_timestamp},
283 vector = _get_statuses(policy_type_id, policy_instance_id)
285 # handler is empty; we wait for t1 to expire then goodnight
286 clos = lambda: _delete_after(policy_type_id, policy_instance_id, INSTANCE_DELETE_NO_RESP_TTL)
288 # handler is not empty, we wait max t1,t2 to expire then goodnight
289 clos = lambda: _delete_after(
290 policy_type_id, policy_instance_id, max(INSTANCE_DELETE_RESP_TTL, INSTANCE_DELETE_NO_RESP_TTL)
292 Thread(target=clos).start()
298 def set_policy_instance_status(policy_type_id, policy_instance_id, handler_id, status):
300 update the database status for a handler
301 called from a1's rmr thread
303 _type_is_valid(policy_type_id)
304 _instance_is_valid(policy_type_id, policy_instance_id)
305 SDL.set(_generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)
308 def get_policy_instance_status(policy_type_id, policy_instance_id):
310 Gets the status of an instance
312 _instance_is_valid(policy_type_id, policy_instance_id)
313 metadata = _get_metadata(policy_type_id, policy_instance_id)
314 metadata["instance_status"] = "NOT IN EFFECT"
315 for i in _get_statuses(policy_type_id, policy_instance_id):
317 metadata["instance_status"] = "IN EFFECT"