2 Represents A1s database and database access functions.
3 In the future, this may change to use a different backend, possibly dramatically.
4 Hopefully, the access functions are a good api so nothing else has to change when this happens
6 For now, the database is in memory.
7 We use dict data structures (KV) with the expectation of having to move this into Redis
9 # ==================================================================================
10 # Copyright (c) 2019 Nokia
11 # Copyright (c) 2018-2019 AT&T Intellectual Property.
13 # Licensed under the Apache License, Version 2.0 (the "License");
14 # you may not use this file except in compliance with the License.
15 # You may obtain a copy of the License at
17 # http://www.apache.org/licenses/LICENSE-2.0
19 # Unless required by applicable law or agreed to in writing, software
20 # distributed under the License is distributed on an "AS IS" BASIS,
21 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 # See the License for the specific language governing permissions and
23 # limitations under the License.
24 # ==================================================================================
27 from threading import Thread
29 from mdclogpy import Logger
30 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
32 mdc_logger = Logger(name=__name__)
35 INSTANCE_DELETE_NO_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5))
36 INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5))
41 This is a wrapper around the expected SDL Python interface.
42 The usage of POLICY_DATA will be replaced with SDL when SDL for python is available.
43 The eventual SDL API is expected to be very close to what is here.
45 We use msgpack for binary (de)serialization: https://msgpack.org/index.html
51 def set(self, key, value):
53 self.POLICY_DATA[key] = msgpack.packb(value, use_bin_type=True)
57 if key in self.POLICY_DATA:
58 return msgpack.unpackb(self.POLICY_DATA[key], raw=False)
61 def find_and_get(self, prefix):
62 """get all k v pairs that start with prefix"""
63 return {k: msgpack.unpackb(v, raw=False) for k, v in self.POLICY_DATA.items() if k.startswith(prefix)}
65 def delete(self, key):
67 del self.POLICY_DATA[key]
72 TYPE_PREFIX = "a1.policy_type."
73 INSTANCE_PREFIX = "a1.policy_instance."
74 METADATA_PREFIX = "a1.policy_inst_metadata."
75 HANDLER_PREFIX = "a1.policy_handler."
81 def _generate_type_key(policy_type_id):
83 generate a key for a policy type
85 return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
88 def _generate_instance_key(policy_type_id, policy_instance_id):
90 generate a key for a policy instance
92 return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
95 def _generate_instance_metadata_key(policy_type_id, policy_instance_id):
97 generate a key for a policy instance metadata
99 return "{0}{1}.{2}".format(METADATA_PREFIX, policy_type_id, policy_instance_id)
102 def _generate_handler_prefix(policy_type_id, policy_instance_id):
104 generate the prefix to a handler key
106 return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
109 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
111 generate a key for a policy handler
113 return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
116 def _type_is_valid(policy_type_id):
118 check that a type is valid
120 if SDL.get(_generate_type_key(policy_type_id)) is None:
121 raise PolicyTypeNotFound()
124 def _instance_is_valid(policy_type_id, policy_instance_id):
126 check that an instance is valid
128 _type_is_valid(policy_type_id)
129 if SDL.get(_generate_instance_key(policy_type_id, policy_instance_id)) is None:
130 raise PolicyInstanceNotFound
133 def _get_statuses(policy_type_id, policy_instance_id):
135 shared helper to get statuses for an instance
137 _instance_is_valid(policy_type_id, policy_instance_id)
138 prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
139 return list(SDL.find_and_get(prefixes_for_handler).values())
142 def _get_instance_list(policy_type_id):
144 shared helper to get instance list for a type
146 _type_is_valid(policy_type_id)
147 prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
148 instancekeys = SDL.find_and_get(prefixes_for_type).keys()
149 return [k.split(prefixes_for_type)[1] for k in instancekeys]
152 def _clear_handlers(policy_type_id, policy_instance_id):
154 delete all the handlers for a policy instance
156 all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
157 keys = SDL.find_and_get(all_handlers_pref)
162 def _get_metadata(policy_type_id, policy_instance_id):
164 get instance metadata
166 _instance_is_valid(policy_type_id, policy_instance_id)
167 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
168 return SDL.get(metadata_key)
171 def _delete_after(policy_type_id, policy_instance_id, ttl):
173 this is a blocking function, must call this in a thread to not block!
174 waits ttl seconds, then deletes the instance
176 _instance_is_valid(policy_type_id, policy_instance_id)
181 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
182 SDL.delete(_generate_instance_key(policy_type_id, policy_instance_id)) # delete instance
183 SDL.delete(_generate_instance_metadata_key(policy_type_id, policy_instance_id)) # delete instance metadata
184 mdc_logger.debug("type {0} instance {1} deleted".format(policy_type_id, policy_instance_id))
192 retrieve all type ids
194 typekeys = SDL.find_and_get(TYPE_PREFIX).keys()
195 # policy types are ints but they get butchered to strings in the KV
196 return [int(k.split(TYPE_PREFIX)[1]) for k in typekeys]
199 def store_policy_type(policy_type_id, body):
201 store a policy type if it doesn't already exist
203 key = _generate_type_key(policy_type_id)
204 if SDL.get(key) is not None:
205 raise PolicyTypeAlreadyExists()
209 def delete_policy_type(policy_type_id):
211 delete a policy type; can only be done if there are no instances (business logic)
213 pil = get_instance_list(policy_type_id)
214 if pil == []: # empty, can delete
215 SDL.delete(_generate_type_key(policy_type_id))
217 raise CantDeleteNonEmptyType()
220 def get_policy_type(policy_type_id):
224 _type_is_valid(policy_type_id)
225 return SDL.get(_generate_type_key(policy_type_id))
231 def store_policy_instance(policy_type_id, policy_instance_id, instance):
233 Store a policy instance
235 _type_is_valid(policy_type_id)
236 creation_timestamp = time.time()
239 key = _generate_instance_key(policy_type_id, policy_instance_id)
240 if SDL.get(key) is not None:
241 # Reset the statuses because this is a new policy instance, even if it was overwritten
242 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
243 SDL.set(key, instance)
245 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
246 SDL.set(metadata_key, {"created_at": creation_timestamp, "has_been_deleted": False})
249 def get_policy_instance(policy_type_id, policy_instance_id):
251 Retrieve a policy instance
253 _instance_is_valid(policy_type_id, policy_instance_id)
254 return SDL.get(_generate_instance_key(policy_type_id, policy_instance_id))
257 def get_instance_list(policy_type_id):
259 retrieve all instance ids for a type
261 return _get_instance_list(policy_type_id)
264 def delete_policy_instance(policy_type_id, policy_instance_id):
266 initially sets has_been_deleted
267 then launches a thread that waits until the relevent timer expires, and finally deletes the instance
269 _instance_is_valid(policy_type_id, policy_instance_id)
271 # set the metadata first
272 deleted_timestamp = time.time()
273 metadata_key = _generate_instance_metadata_key(policy_type_id, policy_instance_id)
274 existing_metadata = _get_metadata(policy_type_id, policy_instance_id)
277 {"created_at": existing_metadata["created_at"], "has_been_deleted": True, "deleted_at": deleted_timestamp},
281 vector = _get_statuses(policy_type_id, policy_instance_id)
283 # handler is empty; we wait for t1 to expire then goodnight
284 clos = lambda: _delete_after(policy_type_id, policy_instance_id, INSTANCE_DELETE_NO_RESP_TTL)
286 # handler is not empty, we wait max t1,t2 to expire then goodnight
287 clos = lambda: _delete_after(
288 policy_type_id, policy_instance_id, max(INSTANCE_DELETE_RESP_TTL, INSTANCE_DELETE_NO_RESP_TTL)
290 Thread(target=clos).start()
296 def set_policy_instance_status(policy_type_id, policy_instance_id, handler_id, status):
298 update the database status for a handler
299 called from a1's rmr thread
301 _type_is_valid(policy_type_id)
302 _instance_is_valid(policy_type_id, policy_instance_id)
303 SDL.set(_generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)
306 def get_policy_instance_status(policy_type_id, policy_instance_id):
308 Gets the status of an instance
310 _instance_is_valid(policy_type_id, policy_instance_id)
311 metadata = _get_metadata(policy_type_id, policy_instance_id)
312 metadata["instance_status"] = "NOT IN EFFECT"
313 for i in _get_statuses(policy_type_id, policy_instance_id):
315 metadata["instance_status"] = "IN EFFECT"