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
27 from a1.exceptions import PolicyTypeNotFound, PolicyInstanceNotFound, PolicyTypeAlreadyExists, CantDeleteNonEmptyType
28 from a1 import get_module_logger
30 logger = get_module_logger(__name__)
35 This is a wrapper around the expected SDL Python interface.
36 The usage of POLICY_DATA will be replaced with SDL when SDL for python is available.
37 The eventual SDL API is expected to be very close to what is here.
39 We use msgpack for binary (de)serialization: https://msgpack.org/index.html
45 def set(self, key, value):
47 self.POLICY_DATA[key] = msgpack.packb(value, use_bin_type=True)
51 if key in self.POLICY_DATA:
52 return msgpack.unpackb(self.POLICY_DATA[key], raw=False)
55 def find_and_get(self, prefix):
56 """get all k v pairs that start with prefix"""
57 return {k: msgpack.unpackb(v, raw=False) for k, v in self.POLICY_DATA.items() if k.startswith(prefix)}
59 def delete(self, key):
61 del self.POLICY_DATA[key]
66 TYPE_PREFIX = "a1.policy_type."
67 INSTANCE_PREFIX = "a1.policy_instance."
68 HANDLER_PREFIX = "a1.policy_handler."
74 def _generate_type_key(policy_type_id):
76 generate a key for a policy type
78 return "{0}{1}".format(TYPE_PREFIX, policy_type_id)
81 def _generate_instance_key(policy_type_id, policy_instance_id):
83 generate a key for a policy instance
85 return "{0}{1}.{2}".format(INSTANCE_PREFIX, policy_type_id, policy_instance_id)
88 def _generate_handler_prefix(policy_type_id, policy_instance_id):
90 generate the prefix to a handler key
92 return "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
95 def _generate_handler_key(policy_type_id, policy_instance_id, handler_id):
97 generate a key for a policy handler
99 return "{0}{1}".format(_generate_handler_prefix(policy_type_id, policy_instance_id), handler_id)
102 def _get_statuses(policy_type_id, policy_instance_id):
104 shared helper to get statuses for an instance
106 instance_is_valid(policy_type_id, policy_instance_id)
107 prefixes_for_handler = "{0}{1}.{2}.".format(HANDLER_PREFIX, policy_type_id, policy_instance_id)
108 return list(SDL.find_and_get(prefixes_for_handler).values())
111 def _get_instance_list(policy_type_id):
113 shared helper to get instance list for a type
115 type_is_valid(policy_type_id)
116 prefixes_for_type = "{0}{1}.".format(INSTANCE_PREFIX, policy_type_id)
117 instancekeys = SDL.find_and_get(prefixes_for_type).keys()
118 return [k.split(prefixes_for_type)[1] for k in instancekeys]
121 def _clear_handlers(policy_type_id, policy_instance_id):
123 delete all the handlers for a policy instance
125 all_handlers_pref = _generate_handler_prefix(policy_type_id, policy_instance_id)
126 keys = SDL.find_and_get(all_handlers_pref)
131 def _clean_up_type(policy_type_id):
133 for all instances of type, see if it can be deleted
135 type_is_valid(policy_type_id)
136 for policy_instance_id in _get_instance_list(policy_type_id):
137 # see if we can delete
138 vector = _get_statuses(policy_type_id, policy_instance_id)
141 TODO: not being able to delete if the list is [] is prolematic.
142 There are cases, such as a bad routing file, where this type will never be able to be deleted because it never went to any xapps
143 However, A1 cannot distinguish between the case where [] was never going to work, and the case where it hasn't worked *yet*
145 However, removing this constraint also leads to problems.
146 Deleting the instance when the vector is empty, for example doing so “shortly after” the PUT, can lead to a worse race condition where the xapps get the policy after that, implement it, but because the DELETE triggered “too soon”, you can never get the status or do the delete on it again, so the xapps are all implementing the instance roguely.
148 This requires some thought to address.
149 For now we stick with the "less bad problem".
156 break # have at least one not DELETED, do nothing
158 # blow away from a1 db
160 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
161 SDL.delete(_generate_instance_key(policy_type_id, policy_instance_id)) # delete instance
169 retrieve all type ids
171 typekeys = SDL.find_and_get(TYPE_PREFIX).keys()
172 # policy types are ints but they get butchered to strings in the KV
173 return [int(k.split(TYPE_PREFIX)[1]) for k in typekeys]
176 def type_is_valid(policy_type_id):
178 check that a type is valid
180 if SDL.get(_generate_type_key(policy_type_id)) is None:
181 raise PolicyTypeNotFound()
184 def store_policy_type(policy_type_id, body):
186 store a policy type if it doesn't already exist
188 key = _generate_type_key(policy_type_id)
189 if SDL.get(key) is not None:
190 raise PolicyTypeAlreadyExists()
194 def delete_policy_type(policy_type_id):
196 delete a policy type; can only be done if there are no instances (business logic)
198 pil = get_instance_list(policy_type_id)
199 if pil == []: # empty, can delete
200 SDL.delete(_generate_type_key(policy_type_id))
202 raise CantDeleteNonEmptyType()
205 def get_policy_type(policy_type_id):
209 type_is_valid(policy_type_id)
210 return SDL.get(_generate_type_key(policy_type_id))
216 def instance_is_valid(policy_type_id, policy_instance_id):
218 check that an instance is valid
220 type_is_valid(policy_type_id)
221 if SDL.get(_generate_instance_key(policy_type_id, policy_instance_id)) is None:
222 raise PolicyInstanceNotFound
225 def store_policy_instance(policy_type_id, policy_instance_id, instance):
227 Store a policy instance
229 type_is_valid(policy_type_id)
230 key = _generate_instance_key(policy_type_id, policy_instance_id)
231 if SDL.get(key) is not None:
232 # Reset the statuses because this is a new policy instance, even if it was overwritten
233 _clear_handlers(policy_type_id, policy_instance_id) # delete all the handlers
234 SDL.set(key, instance)
237 def get_policy_instance(policy_type_id, policy_instance_id):
239 Retrieve a policy instance
241 _clean_up_type(policy_type_id)
242 instance_is_valid(policy_type_id, policy_instance_id)
243 return SDL.get(_generate_instance_key(policy_type_id, policy_instance_id))
246 def get_policy_instance_statuses(policy_type_id, policy_instance_id):
248 Retrieve the status vector for a policy instance
250 _clean_up_type(policy_type_id)
251 return _get_statuses(policy_type_id, policy_instance_id)
254 def get_instance_list(policy_type_id):
256 retrieve all instance ids for a type
258 _clean_up_type(policy_type_id)
259 return _get_instance_list(policy_type_id)
265 def set_status(policy_type_id, policy_instance_id, handler_id, status):
267 update the database status for a handler
268 called from a1's rmr thread
270 type_is_valid(policy_type_id)
271 instance_is_valid(policy_type_id, policy_instance_id)
272 SDL.set(_generate_handler_key(policy_type_id, policy_instance_id, handler_id), status)