blob: 5259778b31de9ed33c0783678c3d912342c7c6f3 [file] [log] [blame]
Scott Bakerbba67b62019-01-28 17:38:21 -08001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Zack Williams5c2ea232019-01-30 15:23:01 -070015from __future__ import absolute_import, print_function
Scott Bakerbba67b62019-01-28 17:38:21 -080016
Zack Williams5c2ea232019-01-30 15:23:01 -070017import os
Scott Bakerbba67b62019-01-28 17:38:21 -080018import imp
Scott Bakerc2fddaa2019-01-30 15:45:03 -080019import inspect
Scott Bakerbba67b62019-01-28 17:38:21 -080020import time
21import traceback
22
23
24class XOSPolicyEngine(object):
Scott Bakerc2fddaa2019-01-30 15:45:03 -080025 def __init__(self, policies_dir, model_accessor, log):
Zack Williams5c2ea232019-01-30 15:23:01 -070026
27 self.log = log # has to come before self.load_model_policies(), which logs
28
Scott Bakerc2fddaa2019-01-30 15:45:03 -080029 self.model_accessor = model_accessor
Scott Bakerbba67b62019-01-28 17:38:21 -080030 self.model_policies = self.load_model_policies(policies_dir)
31 self.policies_by_name = {}
32 self.policies_by_class = {}
Scott Bakerbba67b62019-01-28 17:38:21 -080033
34 for policy in self.model_policies:
35 if policy.model_name not in self.policies_by_name:
36 self.policies_by_name[policy.model_name] = []
37 self.policies_by_name[policy.model_name].append(policy)
38
39 if policy.model not in self.policies_by_class:
40 self.policies_by_class[policy.model] = []
41 self.policies_by_class[policy.model].append(policy)
42
43 def update_wp(self, d, o):
44 try:
45 save_fields = []
46 if d.write_protect != o.write_protect:
47 d.write_protect = o.write_protect
48 save_fields.append("write_protect")
49 if save_fields:
50 d.save(update_fields=save_fields)
51 except AttributeError as e:
52 raise e
53
54 def update_dep(self, d, o):
55 try:
Zack Williams5c2ea232019-01-30 15:23:01 -070056 self.log.info("Trying to update %s", d)
Scott Bakerbba67b62019-01-28 17:38:21 -080057 save_fields = []
58 if d.updated < o.updated:
59 save_fields = ["updated"]
60
61 if save_fields:
62 d.save(update_fields=save_fields)
63 except AttributeError as e:
Zack Williams5c2ea232019-01-30 15:23:01 -070064 self.log.exception("AttributeError in update_dep", e=e)
Scott Bakerbba67b62019-01-28 17:38:21 -080065 raise e
66 except Exception as e:
Zack Williams5c2ea232019-01-30 15:23:01 -070067 self.log.exception("Exception in update_dep", e=e)
Scott Bakerbba67b62019-01-28 17:38:21 -080068
69 def delete_if_inactive(self, d, o):
70 try:
71 d.delete()
Zack Williams5c2ea232019-01-30 15:23:01 -070072 self.log.info("Deleted %s (%s)" % (d, d.__class__.__name__))
Scott Bakerbba67b62019-01-28 17:38:21 -080073 except BaseException:
74 pass
75 return
76
77 def load_model_policies(self, policies_dir):
78 policies = []
79 for fn in os.listdir(policies_dir):
80 if fn.startswith("test"):
81 # don't try to import unit tests!
82 continue
83 pathname = os.path.join(policies_dir, fn)
84 if (
85 os.path.isfile(pathname)
86 and fn.endswith(".py")
87 and (fn != "__init__.py")
88 ):
89 module = imp.load_source(fn[:-3], pathname)
90 for classname in dir(module):
91 c = getattr(module, classname, None)
92
93 # make sure 'c' is a descendent of Policy and has a
94 # provides field (this eliminates the abstract base classes
95 # since they don't have a provides)
96
Scott Bakerc2fddaa2019-01-30 15:45:03 -080097 if inspect.isclass(c):
98 base_names = [b.__name__ for b in c.__bases__]
99
100 if (
101 "Policy" in base_names
102 and hasattr(c, "model_name")
103 and (c not in policies)
104 ):
105 if not c.model_name:
Zack Williams5c2ea232019-01-30 15:23:01 -0700106 self.log.info(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800107 "load_model_policies: skipping model policy",
108 classname=classname,
109 )
110 continue
111 if not self.model_accessor.has_model_class(c.model_name):
Zack Williams5c2ea232019-01-30 15:23:01 -0700112 self.log.error(
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800113 "load_model_policies: unable to find model policy",
114 classname=classname,
115 model=c.model_name,
116 )
117 c.model = self.model_accessor.get_model_class(c.model_name)
118 policies.append(c)
Scott Bakerbba67b62019-01-28 17:38:21 -0800119
Zack Williams5c2ea232019-01-30 15:23:01 -0700120 self.log.info("Loaded model policies", policies=policies)
Scott Bakerbba67b62019-01-28 17:38:21 -0800121 return policies
122
123 def execute_model_policy(self, instance, action):
124 # These are the models whose children get deleted when they are
Zack Williams5c2ea232019-01-30 15:23:01 -0700125 # delete_policy_models = ["Slice", "Instance", "Network"]
Scott Bakerbba67b62019-01-28 17:38:21 -0800126 sender_name = getattr(instance, "model_name", instance.__class__.__name__)
127
128 # if (action != "deleted"):
129 # walk_inv_deps(self.update_dep, instance)
130 # walk_deps(self.update_wp, instance)
131 # elif (sender_name in delete_policy_models):
132 # walk_inv_deps(self.delete_if_inactive, instance)
133
134 policies_failed = False
135 for policy in self.policies_by_name.get(sender_name, None):
136 method_name = "handle_%s" % action
137 if hasattr(policy, method_name):
138 try:
Zack Williams5c2ea232019-01-30 15:23:01 -0700139 self.log.debug(
Scott Bakerbba67b62019-01-28 17:38:21 -0800140 "MODEL POLICY: calling handler",
141 sender_name=sender_name,
142 instance=instance,
143 policy=policy.__name__,
144 method=method_name,
145 )
Zack Williams5c2ea232019-01-30 15:23:01 -0700146 getattr(policy(model_accessor=self.model_accessor), method_name)(
147 instance
148 )
149 self.log.debug(
Scott Bakerbba67b62019-01-28 17:38:21 -0800150 "MODEL POLICY: completed handler",
151 sender_name=sender_name,
152 instance=instance,
153 policy_name=policy.__name__,
154 method=method_name,
155 )
156 except Exception as e:
Zack Williams5c2ea232019-01-30 15:23:01 -0700157 self.log.exception("MODEL POLICY: Exception when running handler", e=e)
Scott Bakerbba67b62019-01-28 17:38:21 -0800158 policies_failed = True
159
160 try:
161 instance.policy_status = "%s" % traceback.format_exc(limit=1)
162 instance.policy_code = 2
163 instance.save(update_fields=["policy_status", "policy_code"])
164 except Exception as e:
Zack Williams5c2ea232019-01-30 15:23:01 -0700165 self.log.exception(
Scott Bakerbba67b62019-01-28 17:38:21 -0800166 "MODEL_POLICY: Exception when storing policy_status", e=e
167 )
168
169 if not policies_failed:
170 try:
171 instance.policed = max(instance.updated, instance.changed_by_step)
172 instance.policy_status = "done"
173 instance.policy_code = 1
174
175 instance.save(update_fields=["policed", "policy_status", "policy_code"])
176
177 if hasattr(policy, "after_policy_save"):
Zack Williams5c2ea232019-01-30 15:23:01 -0700178 policy(model_accessor=self.model_accessor).after_policy_save(
179 instance
180 )
Scott Bakerbba67b62019-01-28 17:38:21 -0800181
Zack Williams5c2ea232019-01-30 15:23:01 -0700182 self.log.info("MODEL_POLICY: Saved", o=instance)
Scott Bakerbba67b62019-01-28 17:38:21 -0800183 except BaseException:
Zack Williams5c2ea232019-01-30 15:23:01 -0700184 self.log.exception(
Scott Bakerbba67b62019-01-28 17:38:21 -0800185 "MODEL POLICY: Object failed to update policed timestamp",
186 instance=instance,
187 )
188
189 def noop(self, o, p):
190 pass
191
192 def run(self):
193 while True:
194 start = time.time()
195 try:
196 self.run_policy_once()
197 except Exception as e:
Zack Williams5c2ea232019-01-30 15:23:01 -0700198 self.log.exception("MODEL_POLICY: Exception in run()", e=e)
Scott Bakerbba67b62019-01-28 17:38:21 -0800199 if time.time() - start < 5:
200 time.sleep(5)
201
202 # TODO: This loop is different from the synchronizer event_loop, but they both do mostly the same thing. Look for
203 # ways to combine them.
204
205 def run_policy_once(self):
Zack Williams5c2ea232019-01-30 15:23:01 -0700206 models = list(self.policies_by_class.keys())
Scott Bakerbba67b62019-01-28 17:38:21 -0800207
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800208 self.model_accessor.check_db_connection_okay()
Scott Bakerbba67b62019-01-28 17:38:21 -0800209
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800210 objects = self.model_accessor.fetch_policies(models, False)
211 deleted_objects = self.model_accessor.fetch_policies(models, True)
Scott Bakerbba67b62019-01-28 17:38:21 -0800212
213 for o in objects:
214 if o.deleted:
215 # This shouldn't happen, but previous code was examining o.deleted. Verify.
216 continue
217 if not o.policed:
218 self.execute_model_policy(o, "create")
219 else:
220 self.execute_model_policy(o, "update")
221
222 for o in deleted_objects:
223 self.execute_model_policy(o, "delete")
224
225 try:
Scott Bakerc2fddaa2019-01-30 15:45:03 -0800226 self.model_accessor.reset_queries()
Scott Bakerbba67b62019-01-28 17:38:21 -0800227 except Exception as e:
228 # this shouldn't happen, but in case it does, catch it...
Zack Williams5c2ea232019-01-30 15:23:01 -0700229 self.log.exception("MODEL POLICY: exception in reset_queries", e)