1 # ==================================================================================
2 # Copyright (c) 2019 Nokia
3 # Copyright (c) 2018-2019 AT&T Intellectual Property.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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 # ==================================================================================
19 Represents A1s database and database access functions.
20 In the future, this may change to use a different backend, possibly dramatically.
21 Hopefully, the access functions are a good api so nothing else has to change when this happens
23 For now, the database is in memory.
24 We use dict data structures (KV) with the expectation of having to move this into Redis
28 from threading import Thread
30 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
31 from a1 import get_module_logger
33 logger = get_module_logger(__name__)
36 INSTANCE_DELETE_NO_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5))
37 INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5))
42 This is a wrapper around the expected SDL Python interface.
43 The usage of POLICY_DATA will be replaced with SDL when SDL for python is available.
44 The eventual SDL API is expected to be very close to what is here.
46 We use msgpack for binary (de)serialization: https://msgpack.org/index.html
52 def set(self, key, value):
54 self.POLICY_DATA[key] = msgpack.packb(value, use_bin_type=True)
58 if key in self.POLICY_DATA:
59 return msgpack.unpackb(self.POLICY_DATA[key], raw=False)
62 def find_and_get(self, prefix):
63 """get all k v pairs that start with prefix"""
64 return {k: msgpack.unpackb(v, raw=False) for k, v in self.POLICY_DATA.items() if k.startswith(prefix)}
66 def delete(self, key):
68 del self.POLICY_DATA[key]
73 TYPE_PREFIX = "a1.policy_type."
74 INSTANCE_PREFIX = "a1.policy_instance."
75 METADATA_PREFIX = "a1.policy_inst_metadata."
76 HANDLER_PREFIX = "a1.policy_handler."
82 def _generate_type_key(policy_type_id):
84 generate a key for a policy type
86 return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
89 def _generate_instance_key(policy_type_id, policy_instance_id):
91 generate a key for a policy instance
93 return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
96 def _generate_instance_metadata_key(policy_type_id, policy_instance_id):
98 generate a key for a policy instance metadata
100 return "{0}{1}.{2}".format(METADATA_PREFIX, policy_type_id, policy_instance_id)
103 def _generate_handler_prefix(policy_type_id, policy_instance_id):
105 generate the prefix to a handler key
107 return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
110 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
112 generate a key for a policy handler
114 return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
117 def _type_is_valid(policy_type_id):
119 check that a type is valid
121 if SDL.get(_generate_type_key(policy_type_id)) is None:
122 raise PolicyTypeNotFound()
125 def _instance_is_valid(policy_type_id, policy_instance_id):
127 check that an instance is valid
129 _type_is_valid(policy_type_id)
130 if SDL.get(_generate_instance_key(policy_type_id, policy_instance_id)) is None:
131 raise PolicyInstanceNotFound
134 def _get_statuses(policy_type_id, policy_instance_id):
136 shared helper to get statuses for an instance
138 _instance_is_valid(policy_type_id, policy_instance_id)
139 prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
140 return list(SDL.find_and_get(prefixes_for_handler).values())
143 def _get_instance_list(policy_type_id):
145 shared helper to get instance list for a type
147 _type_is_valid(policy_type_id)
148 prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
149 instancekeys = SDL.find_and_get(prefixes_for_type).keys()
150 return [k.split(prefixes_for_type)[1] for k in instancekeys]
153 def _clear_handlers(policy_type_id, policy_instance_id):
155 delete all the handlers for a policy instance
157 all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
158 keys = SDL.find_and_get(all_handlers_pref)
163 def _get_metadata(policy_type_id, policy_instance_id):
165 get instance metadata
167 _instance_is_valid(policy_type_id, policy_instance_id)
168 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
169 return SDL.get(metadata_key)
172 def _delete_after(policy_type_id, policy_instance_id, ttl):
174 this is a blocking function, must call this in a thread to not block!
175 waits ttl seconds, then deletes the instance
177 _instance_is_valid(policy_type_id, policy_instance_id)
182 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
183 SDL.delete(_generate_instance_key(policy_type_id, policy_instance_id)) # delete instance
184 SDL.delete(_generate_instance_metadata_key(policy_type_id, policy_instance_id)) # delete instance metadata
185 logger.debug("type %s instance %s deleted", policy_type_id, policy_instance_id)
186 raise PolicyInstanceNotFound()
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
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"