Skip to content

Commit 6ad7f27

Browse files
committed
NodeSet: do not require 'all' special group for file-based source
Fix 'all' fallback mechanism for file-based source. With this change, the all special group for file-based source is only used if defined, otherwise we just include all nodes from the source. Used by NodeSet.fromall() and @* group syntax. Change-Id: I3b3de11566421de94ff302698f3d2a8a0be18eb5
1 parent 6662fd9 commit 6ad7f27

File tree

4 files changed

+84
-30
lines changed

4 files changed

+84
-30
lines changed

conf/groups.d/cluster.yaml.example

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,32 @@
1717
#
1818
# Break and adapt to fit your own needs. Use nodeset CLI to test config.
1919

20-
# Group source cluster:
21-
# define groups @cluster:adm, @cluster:io, etc.
22-
cluster:
20+
# Group source roles:
21+
# define groups @roles:adm, @roles:io, etc.
22+
roles:
2323
adm: 'example0'
2424
io: '@racks:rack2,example2'
2525
compute: '@racks:rack[3-4]'
2626
gpu: '@racks:rack4'
27-
all: '@adm,@io,@compute'
27+
# the 'all' special group is only needed if we don't want all nodes from
28+
# this group source included, here we don't want example0 for clush -a
29+
all: '@io,@compute'
2830

2931
# Group source racks:
30-
# define groups @racks:rack[1-4] (ranges work for groups too!) and @racks:all
32+
# define groups @racks:rack[1-4], @racks:old and @racks:new
3133
racks:
3234
rack1: 'example[0,2]'
3335
rack2: 'example[4-5]'
3436
rack3: 'example[32-159]'
3537
rack4: 'example[156-159]'
36-
# other groups in same source may be referenced without the "source:" prefix
37-
all: '@rack[1-4]'
38+
# groups from same source may be referenced without the "source:" prefix
39+
# and yes, ranges work for groups too!
40+
old: '@rack[1,3]'
41+
new: '@rack[2,4]'
3842

3943
# Group source cpu:
4044
# define groups @cpu:ivy, @cpu:hsw and @cpu:all
4145
cpu:
4246
ivy: 'example[32-63]'
4347
# groups from other sources must be prefixed with "source:"
44-
hsw: '@cluster:compute!@ivy'
45-
all: '@ivy,@hsw'
48+
hsw: '@roles:compute!@ivy'

lib/ClusterShell/NodeSet.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -890,24 +890,24 @@ def all_nodes(self, namespace=None):
890890
"""Get all nodes from group resolver as a list of strings."""
891891
# namespace is the optional group source
892892
assert self.group_resolver is not None
893-
all = []
893+
alln = []
894894
try:
895895
# Ask resolver to provide all nodes.
896-
all = self.group_resolver.all_nodes(namespace)
896+
alln = self.group_resolver.all_nodes(namespace)
897897
except NodeUtils.GroupSourceNoUpcall:
898898
try:
899899
# As the resolver is not able to provide all nodes directly,
900900
# failback to list + map(s) method:
901901
for grp in self.grouplist(namespace):
902-
all += self.group_resolver.group_nodes(grp, namespace)
902+
alln += self.group_resolver.group_nodes(grp, namespace)
903903
except NodeUtils.GroupSourceNoUpcall:
904904
# We are not able to find "all" nodes, definitely.
905-
raise NodeSetExternalError("Not enough working external " \
906-
"calls (all, or map + list) defined to get all nodes")
905+
msg = "Not enough working methods (all or map + list) to " \
906+
"get all nodes"
907+
raise NodeSetExternalError(msg)
907908
except NodeUtils.GroupSourceQueryFailed, exc:
908-
raise NodeSetExternalError("Unable to get all nodes due to the " \
909-
"following external failure:\n\t%s" % exc)
910-
return all
909+
raise NodeSetExternalError("Failed to get all nodes: %s" % exc)
910+
return alln
911911

912912
def _next_op(self, pat):
913913
"""Opcode parsing subroutine."""
@@ -1316,8 +1316,12 @@ def _find_groups(self, node, namespace, allgroups):
13161316
yield grp
13171317
else:
13181318
# find node groups using resolver
1319-
for group in self._resolver.node_groups(node, namespace):
1320-
yield group
1319+
try:
1320+
for group in self._resolver.node_groups(node, namespace):
1321+
yield group
1322+
except NodeUtils.GroupSourceQueryFailed, exc:
1323+
msg = "Group source query failed: %s" % exc
1324+
raise NodeSetExternalError(msg)
13211325

13221326
def _groups2(self, groupsource=None, autostep=None):
13231327
"""Find node groups this nodeset belongs to. [private]"""

lib/ClusterShell/NodeUtils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def __init__(self, message, group_source):
5959
self.group_source = group_source
6060

6161
class GroupSourceNoUpcall(GroupSourceError):
62-
"""Raised when upcall is not available"""
62+
"""Raised when upcall or method is not available"""
6363

6464
class GroupSourceQueryFailed(GroupSourceError):
6565
"""Raised when a query failed (eg. no group found)"""
@@ -111,14 +111,14 @@ def resolv_list(self):
111111
def resolv_all(self):
112112
"""Return the content of all groups as defined by this GroupSource"""
113113
if self.allgroups is None:
114-
raise GroupSourceQueryFailed("All groups info not available", self)
114+
raise GroupSourceNoUpcall("All groups info not available", self)
115115
return self.allgroups
116116

117117
def resolv_reverse(self, node):
118118
"""
119119
Return the group name matching the provided node.
120120
"""
121-
raise GroupSourceQueryFailed("Not implemented", self)
121+
raise GroupSourceNoUpcall("Not implemented", self)
122122

123123

124124
class FileGroupSource(GroupSource):

tests/NodeSetGroupTest.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def testAllNoResolver(self):
191191
"""test NodeSet.fromall() with no resolver"""
192192
self.assertRaises(NodeSetExternalError, NodeSet.fromall,
193193
resolver=RESOLVER_NOGROUP)
194-
194+
195195
def testGroupsNoResolver(self):
196196
"""test NodeSet.groups() with no resolver"""
197197
nodeset = NodeSet("foo", resolver=RESOLVER_NOGROUP)
@@ -214,7 +214,7 @@ def testGroupResolverAddSourceError(self):
214214

215215
def testGroupResolverMinimal(self):
216216
"""test NodeSet with minimal GroupResolver"""
217-
217+
218218
test_groups1 = makeTestG1()
219219

220220
source = UpcallGroupSource("minimal",
@@ -230,7 +230,6 @@ def testGroupResolverMinimal(self):
230230

231231
self.assertRaises(NodeSetExternalError, NodeSet.fromall, resolver=res)
232232

233-
234233
def testConfigEmpty(self):
235234
"""test groups with an empty configuration file"""
236235
f = make_temp_file("")
@@ -408,7 +407,7 @@ def testConfigQueryFailed(self):
408407
409408
[local]
410409
map: false
411-
#all:
410+
all: false
412411
list: echo foo
413412
#reverse:
414413
""")
@@ -417,6 +416,27 @@ def testConfigQueryFailed(self):
417416
self.assertEqual(str(nodeset), "example[1-100]")
418417
self.assertRaises(NodeSetExternalError, nodeset.regroup)
419418

419+
# all_nodes()
420+
self.assertRaises(NodeSetExternalError, NodeSet.fromall, resolver=res)
421+
422+
def testConfigQueryFailedReverse(self):
423+
"""test groups with config and failed query (reverse)"""
424+
f = make_temp_file("""
425+
# A comment
426+
427+
[Main]
428+
default: local
429+
430+
[local]
431+
map: echo example1
432+
list: echo foo
433+
reverse: false
434+
""")
435+
res = GroupResolverConfig(f.name)
436+
nodeset = NodeSet("@foo", resolver=res)
437+
self.assertEqual(str(nodeset), "example1")
438+
self.assertRaises(NodeSetExternalError, nodeset.regroup)
439+
420440
def testConfigRegroupWrongNamespace(self):
421441
"""test groups by calling regroup(wrong_namespace)"""
422442
f = make_temp_file("""
@@ -1236,8 +1256,8 @@ def test_base_class0(self):
12361256
self.assertEqual(gs.resolv_map('gr1'), '')
12371257
self.assertEqual(gs.resolv_map('gr2'), '')
12381258
self.assertEqual(gs.resolv_list(), [])
1239-
self.assertRaises(GroupSourceQueryFailed, gs.resolv_all)
1240-
self.assertRaises(GroupSourceQueryFailed, gs.resolv_reverse, 'n4')
1259+
self.assertRaises(GroupSourceNoUpcall, gs.resolv_all)
1260+
self.assertRaises(GroupSourceNoUpcall, gs.resolv_reverse, 'n4')
12411261

12421262
def test_base_class1(self):
12431263
"""test base GroupSource class (map and list)"""
@@ -1246,8 +1266,8 @@ def test_base_class1(self):
12461266
self.assertEqual(gs.resolv_map('gr1'), ['n1', 'n4', 'n3', 'n2'])
12471267
self.assertEqual(gs.resolv_map('gr2'), ['n9', 'n4'])
12481268
self.assertEqual(sorted(gs.resolv_list()), ['gr1', 'gr2'])
1249-
self.assertRaises(GroupSourceQueryFailed, gs.resolv_all)
1250-
self.assertRaises(GroupSourceQueryFailed, gs.resolv_reverse, 'n4')
1269+
self.assertRaises(GroupSourceNoUpcall, gs.resolv_all)
1270+
self.assertRaises(GroupSourceNoUpcall, gs.resolv_reverse, 'n4')
12511271

12521272
def test_base_class2(self):
12531273
"""test base GroupSource class (all)"""
@@ -1405,6 +1425,12 @@ def test_yaml_basic(self):
14051425

14061426
# No 'all' defined: all_nodes() should raise an error
14071427
self.assertRaises(GroupSourceError, res.all_nodes)
1428+
# but then NodeSet falls back to the union of all groups
1429+
nodeset = NodeSet.fromall(resolver=res)
1430+
self.assertEqual(str(nodeset), "example[1-100]")
1431+
# regroup doesn't use @all in that case
1432+
self.assertEqual(nodeset.regroup(), "@bar,@foo")
1433+
14081434
# No 'reverse' defined: node_groups() should raise an error
14091435
self.assertRaises(GroupSourceError, res.node_groups, "example1")
14101436

@@ -1420,6 +1446,27 @@ def test_yaml_basic(self):
14201446
nodeset = NodeSet("example[102-200]", resolver=res)
14211447
self.assertEqual(nodeset.regroup(), "example[102-200]")
14221448

1449+
def test_yaml_fromall(self):
1450+
"""test groups special all group"""
1451+
dname = make_temp_dir()
1452+
f = make_temp_file("""
1453+
[Main]
1454+
default: yaml
1455+
autodir: %s
1456+
""" % dname)
1457+
yamlfile = make_temp_file("""
1458+
yaml:
1459+
foo: example[1-4,91-100],example90
1460+
bar: example[5-89]
1461+
all: example[90-100]
1462+
""", suffix=".yaml", dir=dname)
1463+
1464+
res = GroupResolverConfig(f.name)
1465+
nodeset = NodeSet.fromall(resolver=res)
1466+
self.assertEqual(str(nodeset), "example[90-100]")
1467+
# regroup uses @all if it is defined
1468+
self.assertEqual(nodeset.regroup(), "@all")
1469+
14231470
def test_yaml_invalid_groups_not_dict(self):
14241471
"""test groups with an invalid YAML config file (1)"""
14251472
dname = make_temp_dir()

0 commit comments

Comments
 (0)