Skip to content

Commit 6f27fca

Browse files
author
James E. Blair
committed
Zuul: support plugin dependencies
Change-Id: I81302e8988fe6498fea9f08ed66f5d0cc1fce161
1 parent b3517ca commit 6f27fca

File tree

5 files changed

+341
-55
lines changed

5 files changed

+341
-55
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*.log
44
*.log.[1-9]
55
*.pem
6+
*.pyc
67
.localrc.auto
78
.localrc.password
89
.prereqs

roles/write-devstack-local-conf/library/devstack_local_conf.py

Lines changed: 164 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,69 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17+
import os
1718
import re
1819

1920

20-
class VarGraph(object):
21+
class DependencyGraph(object):
2122
# This is based on the JobGraph from Zuul.
2223

24+
def __init__(self):
25+
self._names = set()
26+
self._dependencies = {} # dependent_name -> set(parent_names)
27+
28+
def add(self, name, dependencies):
29+
# Append the dependency information
30+
self._dependencies.setdefault(name, set())
31+
try:
32+
for dependency in dependencies:
33+
# Make sure a circular dependency is never created
34+
ancestors = self._getParentNamesRecursively(
35+
dependency, soft=True)
36+
ancestors.add(dependency)
37+
if name in ancestors:
38+
raise Exception("Dependency cycle detected in {}".
39+
format(name))
40+
self._dependencies[name].add(dependency)
41+
except Exception:
42+
del self._dependencies[name]
43+
raise
44+
45+
def getDependenciesRecursively(self, parent):
46+
dependencies = []
47+
48+
current_dependencies = self._dependencies[parent]
49+
for current in current_dependencies:
50+
if current not in dependencies:
51+
dependencies.append(current)
52+
for dep in self.getDependenciesRecursively(current):
53+
if dep not in dependencies:
54+
dependencies.append(dep)
55+
return dependencies
56+
57+
def _getParentNamesRecursively(self, dependent, soft=False):
58+
all_parent_items = set()
59+
items_to_iterate = set([dependent])
60+
while len(items_to_iterate) > 0:
61+
current_item = items_to_iterate.pop()
62+
current_parent_items = self._dependencies.get(current_item)
63+
if current_parent_items is None:
64+
if soft:
65+
current_parent_items = set()
66+
else:
67+
raise Exception("Dependent item {} not found: ".format(
68+
dependent))
69+
new_parent_items = current_parent_items - all_parent_items
70+
items_to_iterate |= new_parent_items
71+
all_parent_items |= new_parent_items
72+
return all_parent_items
73+
74+
75+
class VarGraph(DependencyGraph):
2376
def __init__(self, vars):
77+
super(VarGraph, self).__init__()
2478
self.vars = {}
2579
self._varnames = set()
26-
self._dependencies = {} # dependent_var_name -> set(parent_var_names)
2780
for k, v in vars.items():
2881
self._varnames.add(k)
2982
for k, v in vars.items():
@@ -38,77 +91,127 @@ def _addVar(self, key, value):
3891
raise Exception("Variable {} already added".format(key))
3992
self.vars[key] = value
4093
# Append the dependency information
41-
self._dependencies.setdefault(key, set())
94+
dependencies = set()
95+
for dependency in self.getDependencies(value):
96+
if dependency == key:
97+
# A variable is allowed to reference itself; no
98+
# dependency link needed in that case.
99+
continue
100+
if dependency not in self._varnames:
101+
# It's not necessary to create a link for an
102+
# external variable.
103+
continue
104+
dependencies.add(dependency)
42105
try:
43-
for dependency in self.getDependencies(value):
44-
if dependency == key:
45-
# A variable is allowed to reference itself; no
46-
# dependency link needed in that case.
47-
continue
48-
if dependency not in self._varnames:
49-
# It's not necessary to create a link for an
50-
# external variable.
51-
continue
52-
# Make sure a circular dependency is never created
53-
ancestor_vars = self._getParentVarNamesRecursively(
54-
dependency, soft=True)
55-
ancestor_vars.add(dependency)
56-
if any((key == anc_var) for anc_var in ancestor_vars):
57-
raise Exception("Dependency cycle detected in var {}".
58-
format(key))
59-
self._dependencies[key].add(dependency)
106+
self.add(key, dependencies)
60107
except Exception:
61108
del self.vars[key]
62-
del self._dependencies[key]
63109
raise
64110

65111
def getVars(self):
66112
ret = []
67113
keys = sorted(self.vars.keys())
68114
seen = set()
69115
for key in keys:
70-
dependencies = self.getDependentVarsRecursively(key)
116+
dependencies = self.getDependenciesRecursively(key)
71117
for var in dependencies + [key]:
72118
if var not in seen:
73119
ret.append((var, self.vars[var]))
74120
seen.add(var)
75121
return ret
76122

77-
def getDependentVarsRecursively(self, parent_var):
78-
dependent_vars = []
79-
80-
current_dependent_vars = self._dependencies[parent_var]
81-
for current_var in current_dependent_vars:
82-
if current_var not in dependent_vars:
83-
dependent_vars.append(current_var)
84-
for dep in self.getDependentVarsRecursively(current_var):
85-
if dep not in dependent_vars:
86-
dependent_vars.append(dep)
87-
return dependent_vars
88-
89-
def _getParentVarNamesRecursively(self, dependent_var, soft=False):
90-
all_parent_vars = set()
91-
vars_to_iterate = set([dependent_var])
92-
while len(vars_to_iterate) > 0:
93-
current_var = vars_to_iterate.pop()
94-
current_parent_vars = self._dependencies.get(current_var)
95-
if current_parent_vars is None:
96-
if soft:
97-
current_parent_vars = set()
98-
else:
99-
raise Exception("Dependent var {} not found: ".format(
100-
dependent_var))
101-
new_parent_vars = current_parent_vars - all_parent_vars
102-
vars_to_iterate |= new_parent_vars
103-
all_parent_vars |= new_parent_vars
104-
return all_parent_vars
123+
124+
class PluginGraph(DependencyGraph):
125+
def __init__(self, base_dir, plugins):
126+
super(PluginGraph, self).__init__()
127+
# The dependency trees expressed by all the plugins we found
128+
# (which may be more than those the job is using).
129+
self._plugin_dependencies = {}
130+
self.loadPluginNames(base_dir)
131+
132+
self.plugins = {}
133+
self._pluginnames = set()
134+
for k, v in plugins.items():
135+
self._pluginnames.add(k)
136+
for k, v in plugins.items():
137+
self._addPlugin(k, str(v))
138+
139+
def loadPluginNames(self, base_dir):
140+
if base_dir is None:
141+
return
142+
git_roots = []
143+
for root, dirs, files in os.walk(base_dir):
144+
if '.git' not in dirs:
145+
continue
146+
# Don't go deeper than git roots
147+
dirs[:] = []
148+
git_roots.append(root)
149+
for root in git_roots:
150+
devstack = os.path.join(root, 'devstack')
151+
if not (os.path.exists(devstack) and os.path.isdir(devstack)):
152+
continue
153+
settings = os.path.join(devstack, 'settings')
154+
if not (os.path.exists(settings) and os.path.isfile(settings)):
155+
continue
156+
self.loadDevstackPluginInfo(settings)
157+
158+
define_re = re.compile(r'^define_plugin\s+(\w+).*')
159+
require_re = re.compile(r'^plugin_requires\s+(\w+)\s+(\w+).*')
160+
def loadDevstackPluginInfo(self, fn):
161+
name = None
162+
reqs = set()
163+
with open(fn) as f:
164+
for line in f:
165+
m = self.define_re.match(line)
166+
if m:
167+
name = m.group(1)
168+
m = self.require_re.match(line)
169+
if m:
170+
if name == m.group(1):
171+
reqs.add(m.group(2))
172+
if name and reqs:
173+
self._plugin_dependencies[name] = reqs
174+
175+
def getDependencies(self, value):
176+
return self._plugin_dependencies.get(value, [])
177+
178+
def _addPlugin(self, key, value):
179+
if key in self.plugins:
180+
raise Exception("Plugin {} already added".format(key))
181+
self.plugins[key] = value
182+
# Append the dependency information
183+
dependencies = set()
184+
for dependency in self.getDependencies(key):
185+
if dependency == key:
186+
continue
187+
dependencies.add(dependency)
188+
try:
189+
self.add(key, dependencies)
190+
except Exception:
191+
del self.plugins[key]
192+
raise
193+
194+
def getPlugins(self):
195+
ret = []
196+
keys = sorted(self.plugins.keys())
197+
seen = set()
198+
for key in keys:
199+
dependencies = self.getDependenciesRecursively(key)
200+
for plugin in dependencies + [key]:
201+
if plugin not in seen:
202+
ret.append((plugin, self.plugins[plugin]))
203+
seen.add(plugin)
204+
return ret
105205

106206

107207
class LocalConf(object):
108208

109-
def __init__(self, localrc, localconf, base_services, services, plugins):
209+
def __init__(self, localrc, localconf, base_services, services, plugins,
210+
base_dir):
110211
self.localrc = []
111212
self.meta_sections = {}
213+
self.plugin_deps = {}
214+
self.base_dir = base_dir
112215
if plugins:
113216
self.handle_plugins(plugins)
114217
if services or base_services:
@@ -119,7 +222,8 @@ def __init__(self, localrc, localconf, base_services, services, plugins):
119222
self.handle_localconf(localconf)
120223

121224
def handle_plugins(self, plugins):
122-
for k, v in plugins.items():
225+
pg = PluginGraph(self.base_dir, plugins)
226+
for k, v in pg.getPlugins():
123227
if v:
124228
self.localrc.append('enable_plugin {} {}'.format(k, v))
125229

@@ -171,6 +275,7 @@ def main():
171275
services=dict(type='dict'),
172276
localrc=dict(type='dict'),
173277
local_conf=dict(type='dict'),
278+
base_dir=dict(type='path'),
174279
path=dict(type='str'),
175280
)
176281
)
@@ -180,14 +285,18 @@ def main():
180285
p.get('local_conf'),
181286
p.get('base_services'),
182287
p.get('services'),
183-
p.get('plugins'))
288+
p.get('plugins'),
289+
p.get('base_dir'))
184290
lc.write(p['path'])
185291

186292
module.exit_json()
187293

188294

189-
from ansible.module_utils.basic import * # noqa
190-
from ansible.module_utils.basic import AnsibleModule
295+
try:
296+
from ansible.module_utils.basic import * # noqa
297+
from ansible.module_utils.basic import AnsibleModule
298+
except ImportError:
299+
pass
191300

192301
if __name__ == '__main__':
193302
main()

0 commit comments

Comments
 (0)