blob: 2f31e3edaefa0a46a2e04420cebe337c33a54497 [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
15
16import os
17import base64
18
19from xosconfig import Config
20from xossynchronizer.modelaccessor import *
21from xossynchronizer.ansible_helper import run_template
22
23# from tests.steps.mock_modelaccessor import model_accessor
24
25import json
26import time
27import pdb
28
29from xosconfig import Config
30from functools import reduce
31
32
33def f7(seq):
34 seen = set()
35 seen_add = seen.add
36 return [x for x in seq if not (x in seen or seen_add(x))]
37
38
39def elim_dups(backend_str):
40 strs = backend_str.split(" // ")
41 strs2 = f7(strs)
42 return " // ".join(strs2)
43
44
45def deepgetattr(obj, attr):
46 return reduce(getattr, attr.split("."), obj)
47
48
49def obj_class_name(obj):
50 return getattr(obj, "model_name", obj.__class__.__name__)
51
52
53class InnocuousException(Exception):
54 pass
55
56
57class DeferredException(Exception):
58 pass
59
60
61class FailedDependency(Exception):
62 pass
63
64
65class SyncStep(object):
66 """ An XOS Sync step.
67
68 Attributes:
69 psmodel Model name the step synchronizes
70 dependencies list of names of models that must be synchronized first if the current model depends on them
71 """
72
73 # map_sync_outputs can return this value to cause a step to be marked
74 # successful without running ansible. Used for sync_network_controllers
75 # on nat networks.
76 SYNC_WITHOUT_RUNNING = "sync_without_running"
77
78 slow = False
79
80 def get_prop(self, prop):
81 # NOTE config_dir is never define, is this used?
82 sync_config_dir = Config.get("config_dir")
83 prop_config_path = "/".join(sync_config_dir, self.name, prop)
84 return open(prop_config_path).read().rstrip()
85
86 def __init__(self, **args):
87 """Initialize a sync step
88 Keyword arguments:
89 name -- Name of the step
90 provides -- XOS models sync'd by this step
91 """
92 dependencies = []
93 self.driver = args.get("driver")
94 self.error_map = args.get("error_map")
95
96 try:
97 self.soft_deadline = int(self.get_prop("soft_deadline_seconds"))
98 except BaseException:
99 self.soft_deadline = 5 # 5 seconds
100
101 if "log" in args:
102 self.log = args.get("log")
103
104 return
105
106 def fetch_pending(self, deletion=False):
107 # This is the most common implementation of fetch_pending
108 # Steps should override it if they have their own logic
109 # for figuring out what objects are outstanding.
110
111 return model_accessor.fetch_pending(self.observes, deletion)
112
113 def sync_record(self, o):
114 self.log.debug("In default sync record", **o.tologdict())
115
116 tenant_fields = self.map_sync_inputs(o)
117 if tenant_fields == SyncStep.SYNC_WITHOUT_RUNNING:
118 return
119
120 main_objs = self.observes
121 if isinstance(main_objs, list):
122 main_objs = main_objs[0]
123
124 path = "".join(main_objs.__name__).lower()
125 res = run_template(self.playbook, tenant_fields, path=path, object=o)
126
127 if hasattr(self, "map_sync_outputs"):
128 self.map_sync_outputs(o, res)
129
130 self.log.debug("Finished default sync record", **o.tologdict())
131
132 def delete_record(self, o):
133 self.log.debug("In default delete record", **o.tologdict())
134
135 # If there is no map_delete_inputs, then assume deleting a record is a no-op.
136 if not hasattr(self, "map_delete_inputs"):
137 return
138
139 tenant_fields = self.map_delete_inputs(o)
140
141 main_objs = self.observes
142 if isinstance(main_objs, list):
143 main_objs = main_objs[0]
144
145 path = "".join(main_objs.__name__).lower()
146
147 tenant_fields["delete"] = True
148 res = run_template(self.playbook, tenant_fields, path=path)
149
150 if hasattr(self, "map_delete_outputs"):
151 self.map_delete_outputs(o, res)
152 else:
153 # "rc" is often only returned when something bad happens, so assume that no "rc" implies a successful rc
154 # of 0.
155 if res[0].get("rc", 0) != 0:
156 raise Exception("Nonzero rc from Ansible during delete_record")
157
158 self.log.debug("Finished default delete record", **o.tologdict())