Skip to content

Commit bfa57ba

Browse files
authored
Merge pull request #5268 from grondo/issue#5266
flux-resource: fix `-i, --include=HOSTS` in `list` command when some hosts are excluded
2 parents e0ecaae + 9c1b48d commit bfa57ba

File tree

4 files changed

+177
-2
lines changed

4 files changed

+177
-2
lines changed

src/bindings/python/flux/resource/ResourceSet.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import json
1212
from collections.abc import Mapping
1313

14+
from flux.hostlist import Hostlist
1415
from flux.idset import IDset
1516
from flux.resource import Rlist
1617
from flux.resource.ResourceSetImplementation import ResourceSetImplementation
@@ -209,6 +210,25 @@ def copy_ranks(self, ranks):
209210
rset.state = self.state
210211
return rset
211212

213+
def host_ranks(self, hosts, ignore_nomatch=False):
214+
"""
215+
Translate a set of hostnames to broker ranks using the current
216+
ResourceSet.
217+
218+
Args:
219+
ignore_nomatch (bool): If True, then hosts that are not in
220+
the current ResourceSet are ignored, and only matching
221+
hosts result in a returned rank. O/w, FileNotFound error
222+
is raised.
223+
Returns:
224+
list of rank ids in order of provided hosts
225+
"""
226+
if not isinstance(hosts, Hostlist):
227+
hosts = Hostlist(hosts)
228+
ranks = list(self.ranks)
229+
index = self.nodelist.index(hosts, ignore_nomatch=ignore_nomatch)
230+
return [ranks[i] for i in index]
231+
212232
@property
213233
def nodelist(self):
214234
"""

src/bindings/python/flux/resource/list.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ def filter(self, include):
5858
try:
5959
include_ranks = IDset(include)
6060
except ValueError:
61-
nodelist = self["all"].nodelist
62-
include_ranks = nodelist.index(include, ignore_nomatch=True)
61+
include_ranks = IDset(self["all"].host_ranks(include, ignore_nomatch=True))
6362
for state in ["all", "down", "allocated"]:
6463
setattr(self, state, self[state].copy_ranks(include_ranks))
6564

t/python/t0022-resource-set.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ class TestRSet(unittest.TestCase):
4141
}
4242
}
4343
"""
44+
R2 = """
45+
{
46+
"version": 1,
47+
"execution": {
48+
"R_lite": [
49+
{
50+
"rank": "10-13",
51+
"children": {
52+
"core": "0-3",
53+
"gpu": "0"
54+
}
55+
}
56+
],
57+
"starttime": 0,
58+
"expiration": 0,
59+
"nodelist": [
60+
"fluke[10-13]"
61+
]
62+
}
63+
}
64+
"""
4465

4566
def test_init_string(self):
4667
# init by string
@@ -49,6 +70,11 @@ def test_init_string(self):
4970
self.assertEqual(rset.ncores, 16)
5071
self.assertEqual(rset.ngpus, 4)
5172

73+
rset = ResourceSet(self.R2)
74+
self.assertEqual(str(rset), "rank[10-13]/core[0-3],gpu0")
75+
self.assertEqual(rset.ncores, 16)
76+
self.assertEqual(rset.ngpus, 4)
77+
5278
def test_init_dict(self):
5379
# init by dict
5480
rdict = json.loads(self.R_input)
@@ -58,6 +84,13 @@ def test_init_dict(self):
5884
self.assertEqual(rset.ngpus, 4)
5985
self.assertEqual(rset.nnodes, 4)
6086

87+
rdict = json.loads(self.R2)
88+
rset = ResourceSet(rdict)
89+
self.assertEqual(str(rset), "rank[10-13]/core[0-3],gpu0")
90+
self.assertEqual(rset.ncores, 16)
91+
self.assertEqual(rset.ngpus, 4)
92+
self.assertEqual(rset.nnodes, 4)
93+
6194
def test_init_implementation(self):
6295
# init by resource set implementation
6396
rlist = Rlist().add_rank(0, cores="0-1").add_child(0, "gpu", "0")
@@ -126,6 +159,11 @@ def test_encode(self):
126159
rset2 = ResourceSet(rstring)
127160
self.assertEqual(str(rset), str(rset2))
128161

162+
rset = ResourceSet(self.R2)
163+
rstring = rset.encode()
164+
rset2 = ResourceSet(rstring)
165+
self.assertEqual(str(rset), str(rset2))
166+
129167
def test_append(self):
130168
rset = ResourceSet(self.R_input)
131169
rset2 = ResourceSet(Rlist().add_rank(4, cores="0-3").add_child(4, "gpu", "0"))
@@ -148,18 +186,57 @@ def test_nodelist(self):
148186
self.assertEqual(rset.nodelist.count(), 4)
149187
self.assertEqual(str(rset.nodelist), "fluke[0-3]")
150188

189+
rset = ResourceSet(self.R2)
190+
self.assertIsInstance(rset.nodelist, Hostlist)
191+
self.assertEqual(rset.nodelist.count(), 4)
192+
self.assertEqual(str(rset.nodelist), "fluke[10-13]")
193+
151194
def test_ranks(self):
152195
rset = ResourceSet(self.R_input)
153196
self.assertIsInstance(rset.ranks, IDset)
154197
self.assertEqual(rset.ranks.count(), 4)
155198
self.assertEqual(str(rset.ranks), "0-3")
156199

200+
rset = ResourceSet(self.R2)
201+
self.assertIsInstance(rset.ranks, IDset)
202+
self.assertEqual(rset.ranks.count(), 4)
203+
self.assertEqual(str(rset.ranks), "10-13")
204+
157205
def test_state(self):
158206
rset = ResourceSet(self.R_input)
159207
self.assertIsNone(rset.state)
160208
rset.state = "up"
161209
self.assertEqual(rset.state, "up")
162210

211+
def test_host_ranks(self):
212+
rset = ResourceSet(self.R_input)
213+
self.assertIsInstance(rset, ResourceSet)
214+
self.assertEqual(rset.host_ranks("fluke0"), [0])
215+
self.assertEqual(rset.host_ranks("fluke1"), [1])
216+
self.assertEqual(rset.host_ranks("fluke2"), [2])
217+
self.assertEqual(rset.host_ranks("fluke3"), [3])
218+
self.assertEqual(rset.host_ranks("fluke[0,3]"), [0, 3])
219+
self.assertEqual(rset.host_ranks("fluke[0-2]"), [0, 1, 2])
220+
self.assertEqual(
221+
rset.host_ranks("fluke[0-2,7]", ignore_nomatch=True), [0, 1, 2]
222+
)
223+
with self.assertRaises(FileNotFoundError):
224+
rset.host_ranks("fluke7")
225+
226+
rset = ResourceSet(self.R2)
227+
self.assertIsInstance(rset, ResourceSet)
228+
self.assertEqual(rset.host_ranks("fluke10"), [10])
229+
self.assertEqual(rset.host_ranks("fluke11"), [11])
230+
self.assertEqual(rset.host_ranks("fluke12"), [12])
231+
self.assertEqual(rset.host_ranks("fluke13"), [13])
232+
self.assertEqual(rset.host_ranks("fluke[10,13]"), [10, 13])
233+
self.assertEqual(rset.host_ranks("fluke[10-12]"), [10, 11, 12])
234+
self.assertEqual(
235+
rset.host_ranks("fluke[0-2,10-12]", ignore_nomatch=True), [10, 11, 12]
236+
)
237+
with self.assertRaises(FileNotFoundError):
238+
rset.host_ranks("fluke[0-3,10-12]")
239+
163240
def test_properties(self):
164241
rset = ResourceSet(self.R_input)
165242
self.assertIsInstance(rset, ResourceSet)

t/t2350-resource-list.t

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,85 @@ test_expect_success 'flux-resource list: --include works with hostnames' '
103103
test_debug "cat include-hosts.out" &&
104104
grep "^2 pi\[3,0\]" include-hosts.out
105105
'
106+
test_expect_success 'flux-resource list: -i works with excluded hosts #5266' '
107+
cat <<-'EOF' >corona.json &&
108+
{
109+
"all": {
110+
"execution": {
111+
"R_lite": [
112+
{
113+
"children": {
114+
"core": "0-47",
115+
"gpu": "0-7"
116+
},
117+
"rank": "4-124"
118+
}
119+
],
120+
"expiration": 0,
121+
"nodelist": [
122+
"corona[171-207,213-296]"
123+
],
124+
"properties": {
125+
"pbatch": "20-124",
126+
"pdebug": "4-19"
127+
},
128+
"starttime": 0
129+
},
130+
"version": 1
131+
},
132+
"allocated": {
133+
"execution": {
134+
"R_lite": [
135+
{
136+
"children": {
137+
"core": "0-47",
138+
"gpu": "0-7"
139+
},
140+
"rank": "20-27,29-34,36-75,78-84,86-87,90-97,99-111,113-124"
141+
}
142+
],
143+
"expiration": 0,
144+
"nodelist": [
145+
"corona[187-194,196-201,203-207,213-247,250-256,258-259,262-269,271-283,285-296]"
146+
],
147+
"properties": {
148+
"pbatch": "20-27,29-34,36-75,78-84,86-87,90-97,99-111,113-124"
149+
},
150+
"starttime": 0
151+
},
152+
"version": 1
153+
},
154+
"down": {
155+
"execution": {
156+
"R_lite": [
157+
{
158+
"children": {
159+
"core": "0-47",
160+
"gpu": "0-7"
161+
},
162+
"rank": "5,9,28,35,76-77,85,88-89,98,112"
163+
}
164+
],
165+
"expiration": 0,
166+
"nodelist": [
167+
"corona[172,176,195,202,248-249,257,260-261,270,284]"
168+
],
169+
"properties": {
170+
"pbatch": "28,35,76-77,85,88-89,98,112",
171+
"pdebug": "5,9"
172+
},
173+
"starttime": 0
174+
},
175+
"version": 1
176+
}
177+
}
178+
EOF
179+
NODELIST="corona[176,249,260-261,270,284]" &&
180+
flux resource list -s all -o "{nodelist}" -ni $NODELIST \
181+
--from-stdin < corona.json >corona.output &&
182+
test_debug "cat corona.output" &&
183+
test "$(cat corona.output)" = "$NODELIST"
184+
'
106185
test_expect_success 'flux-resource list: --include works with invalid host' '
107186
flux resource list -s all -o "{nnodes} {nodelist}" -ni pi7 \
108187
--from-stdin < $INPUT >include-invalid-hosts.out &&

0 commit comments

Comments
 (0)