Skip to content

Commit a5162d1

Browse files
committed
nD nodeset: added fold axis support and --axis option to nodeset
This adds support for constrainted nD folded nodeset to improve external tools support. NodeSet.py: NodeSetBase's member fold_axis can be set to an iterable over nD 0-indexed axis integers. If so, only rangesets along fold axis are effectively folded. However, we always use nD algorithms internally and only re-expand rangesets of unspecified axis at __str__() time. This may lead to suboptimal folding, mostly observed with tricky cases, like overlapping dimensions, etc. We cannot really talk about this as a 1.6 compat feature, as 1.6 was mostly broken on these aspects. However, for nodesets fed from unique nD nodes, like in clush when gathering results, setting fold_axis to [0] should lead to similar results than 1.6 (this patch doesn't include the feature for clush). CLI/Nodeset.py and CLI/Options.py: Added --axis=RANGESET option to nodeset, to use with --fold. Axis are 1-indexed with nodeset CLI. Also --axis=-INDEX is supported to count backward. Tests, docs and examples included. Closes #269. Change-Id: Ib1f7947f6ecf7b4acbdd1550800a9d89e32743c4
1 parent 6860608 commit a5162d1

File tree

10 files changed

+425
-45
lines changed

10 files changed

+425
-45
lines changed

ChangeLog

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2015-08-29 S. Thiell <sthiell@stanford.edu>
2+
3+
* CLI/Nodeset.py: added --axis option to choose nD fold axis (ticket
4+
#269).
5+
* NodeSet.py: added fold_axis public member to NodeSetBase along with
6+
expand algorithm when casting to string to choose nD fold axis (ticket
7+
#269).
8+
19
2015-08-28 S. Thiell <sthiell@stanford.edu>
210

311
* CLI/Config.py: better per-user clush.conf support. clush now also checks

doc/man/man1/nodeset.1

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.\" Man page generated from reStructuredText.
22
.
3-
.TH NODESET 1 "2015-07-10" "1.7" "ClusterShell User Manual"
3+
.TH NODESET 1 "2015-08-29" "1.7" "ClusterShell User Manual"
44
.SH NAME
55
nodeset \- compute advanced nodeset operations
66
.
@@ -131,6 +131,9 @@ split result into a number of subsets
131131
.TP
132132
.B \-\-contiguous
133133
split result into contiguous subsets (ie. for nodeset, subsets will contain nodes with same pattern name and a contiguous range of indexes, like foobar[1\-100]; for rangeset, subsets with consists in contiguous index ranges)"""
134+
.TP
135+
.BI \-\-axis\fB= RANGESET
136+
for nD nodesets, fold along provided axis only. Axis are indexed from 1 to n and can be specified here either using the rangeset syntax, eg. \(aq1\(aq, \(aq1\-2\(aq, \(aq1,3\(aq, or by a single negative number meaning that the indice is counted from the end. Because some nodesets may have several different dimensions, axis indices are silently truncated to fall in the allowed range.
134137
.UNINDENT
135138
.UNINDENT
136139
.UNINDENT
@@ -288,6 +291,14 @@ node[1\-3,5\-7]
288291
dc[1\-2]n[1\-2]
289292
.fi
290293
.sp
294+
.INDENT 7.0
295+
.TP
296+
.B $ nodeset \-\-axis=1 \-f dc1n2 dc2n2 dc1n1 dc2n1
297+
.UNINDENT
298+
.nf
299+
dc[1\-2]n1,dc[1\-2]n2
300+
.fi
301+
.sp
291302
.TP
292303
.B Expanding nodesets
293304
.INDENT 7.0

doc/sphinx/tools/nodeset.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,34 @@ contiguous node set in an expanded manner to separate lines::
483483
node11 node12 node13 node14 node15 node16 node17 node18 node19
484484

485485

486+
Choosing fold axis (nD)
487+
"""""""""""""""""""""""
488+
489+
The default folding behavior for multidimensional node sets is to fold along
490+
all *nD* axis. However, other cluster tools barely support nD nodeset syntax,
491+
so it may be useful to fold along one (or a few) axis only. The ``--axis``
492+
option allows you to specify indexes of dimensions to fold. Using this
493+
option, rangesets of unspecified axis there won't be folded. Please note
494+
however that the obtained result may be suboptimal, this is because
495+
:class:`.NodeSet` algorithms are optimized for folding along all axis.
496+
``--axis`` value is a set of 0-indexed integers, representing nD axis, in the
497+
form of a number or a rangeset. A common case is to restrict folding on a
498+
single axis, like in the following simple examples::
499+
500+
$ nodeset --axis=0 -f node1-ib0 node2-ib0 node1-ib1 node2-ib1
501+
node[1-2]-ib0,node[1-2]-ib1
502+
503+
$ nodeset --axis=1 -f node1-ib0 node2-ib0 node1-ib1 node2-ib1
504+
node1-ib[0-1],node2-ib[0-1]
505+
506+
Because a single nodeset may have several different dimensions, axis indices
507+
are silently truncated to fall in the allowed range. Negative indices are
508+
useful to fold along the last axis whatever number of dimensions used::
509+
510+
$ nodeset --axis=-1 -f comp-[1-2]-[1-36],login-[1-2]
511+
comp-1-[1-36],comp-2-[1-36],login-[1-2]
512+
513+
486514
.. _nodeset-groups:
487515

488516
Node groups

doc/txt/nodeset.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ compute advanced nodeset operations
77
-----------------------------------
88

99
:Author: Stephane Thiell <sthiell@stanford.edu>
10-
:Date: 2015-07-10
10+
:Date: 2015-08-29
1111
:Copyright: CeCILL-C V1
1212
:Version: 1.7
1313
:Manual section: 1
@@ -72,6 +72,7 @@ OPTIONS
7272
return sliced off result; examples of SLICE_RANGESET are "0" for simple index selection, or "1-9/2,16" for complex rangeset selection
7373
--split=MAXSPLIT split result into a number of subsets
7474
--contiguous split result into contiguous subsets (ie. for nodeset, subsets will contain nodes with same pattern name and a contiguous range of indexes, like foobar[1-100]; for rangeset, subsets with consists in contiguous index ranges)"""
75+
--axis=RANGESET for nD nodesets, fold along provided axis only. Axis are indexed from 1 to n and can be specified here either using the rangeset syntax, eg. '1', '1-2', '1,3', or by a single negative number meaning that the indice is counted from the end. Because some nodesets may have several different dimensions, axis indices are silently truncated to fall in the allowed range.
7576

7677

7778
For a short explanation of these options, see ``-h, --help``.
@@ -163,13 +164,18 @@ Folding nodesets
163164
| node[0-7,32-163]
164165

165166
:$ echo node3 node6 node1 node2 node7 node5 | nodeset -f:
166-
167+
167168
| node[1-3,5-7]
168169

169170
:$ nodeset -f dc1n2 dc2n2 dc1n1 dc2n1:
170171

171172
| dc[1-2]n[1-2]
172173

174+
:$ nodeset --axis=1 -f dc1n2 dc2n2 dc1n1 dc2n1:
175+
176+
| dc[1-2]n1,dc[1-2]n2
177+
178+
173179

174180
Expanding nodesets
175181
:$ nodeset -e node[160-163]:

lib/ClusterShell/CLI/Nodeset.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ def nodeset():
195195
if options.maxsplit is None:
196196
options.maxsplit = 1
197197

198+
if options.axis and (not options.fold or options.rangeset):
199+
parser.error("--axis option is only supported when folding nodeset")
200+
198201
if options.groupsource and not options.quiet and class_set == RangeSet:
199202
print >> sys.stderr, "WARNING: option group source \"%s\" ignored" \
200203
% options.groupsource
@@ -277,6 +280,15 @@ def nodeset():
277280
autofactor = float(options.autostep)
278281
xset.autostep = int(math.ceil(float(len(xset)) * autofactor))
279282

283+
# user-specified nD-nodeset fold axis
284+
if options.axis:
285+
if not options.axis.startswith('-'):
286+
# axis are 1-indexed in nodeset CLI (0 ignored)
287+
xset.fold_axis = tuple(x - 1 for x in RangeSet(options.axis) if x > 0)
288+
else:
289+
# negative axis index (only single number supported)
290+
xset.fold_axis = [int(options.axis)]
291+
280292
fmt = options.output_format # default to '%s'
281293

282294
# Display result according to command choice
@@ -304,12 +316,9 @@ def main():
304316
"""main script function"""
305317
try:
306318
nodeset()
307-
except AssertionError, ex:
319+
except (AssertionError, IndexError, ValueError), ex:
308320
print >> sys.stderr, "ERROR:", ex
309321
sys.exit(1)
310-
except IndexError:
311-
print >> sys.stderr, "ERROR: syntax error"
312-
sys.exit(1)
313322
except SyntaxError:
314323
print >> sys.stderr, "ERROR: invalid separator"
315324
sys.exit(1)

lib/ClusterShell/CLI/OptionParser.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,7 @@ def install_nodeset_options(self):
321321
optgrp.add_option("--contiguous", action="store_true",
322322
dest="contiguous", help="split result into "
323323
"contiguous subsets")
324+
optgrp.add_option("--axis", action="store", dest="axis",
325+
metavar="RANGESET", help="fold along these axis only "
326+
"(axis 1..n for nD nodeset)")
324327
self.add_option_group(optgrp)

lib/ClusterShell/NodeSet.py

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,12 @@ class NodeSetBase(object):
134134
'node[1-5,7]-ib[1-2]'
135135
"""
136136
def __init__(self, pattern=None, rangeset=None, copy_rangeset=True,
137-
autostep=None):
137+
autostep=None, fold_axis=None):
138138
"""New NodeSetBase object initializer"""
139139
self._autostep = autostep
140140
self._length = 0
141141
self._patterns = {}
142+
self.fold_axis = fold_axis
142143
if pattern:
143144
self._add(pattern, rangeset, copy_rangeset)
144145
elif rangeset:
@@ -252,6 +253,49 @@ def __len__(self):
252253
cnt += 1
253254
return cnt
254255

256+
def _iter_nd_pat(self, pat, rset):
257+
"""
258+
Take a pattern and a RangeSetND object and iterate over nD computed
259+
nodeset strings while following fold_axis constraints.
260+
"""
261+
try:
262+
dimcnt = rset.dim()
263+
if self.fold_axis is None:
264+
# fold along all axis (default)
265+
fold_axis = range(dimcnt)
266+
else:
267+
# set of user-provided fold axis (support negative numbers)
268+
fold_axis = [int(x) % dimcnt for x in self.fold_axis
269+
if -dimcnt <= int(x) < dimcnt]
270+
except (TypeError, ValueError), exc:
271+
raise NodeSetParseError("fold_axis=%s" % self.fold_axis, exc)
272+
273+
for rgvec in rset.vectors():
274+
rgnargs = [] # list of str rangeset args
275+
for axis, rangeset in enumerate(rgvec):
276+
# build an iterator over rangeset strings to add
277+
if len(rangeset) > 1:
278+
if axis not in fold_axis: # expand
279+
rgstrit = rangeset.striter()
280+
else:
281+
rgstrit = ["[%s]" % rangeset]
282+
else:
283+
rgstrit = [str(rangeset)]
284+
285+
# aggregate/expand along previous computed axis...
286+
t_rgnargs = []
287+
for rgstr in rgstrit: # 1-time when not expanding
288+
if not rgnargs:
289+
t_rgnargs.append([rgstr])
290+
else:
291+
for rga in rgnargs:
292+
t_rgnargs.append(rga + [rgstr])
293+
rgnargs = t_rgnargs
294+
295+
# get nodeset patterns formatted with range strings
296+
for rgargs in rgnargs:
297+
yield pat % tuple(rgargs)
298+
255299
def __str__(self):
256300
"""Get ranges-based pattern of node list."""
257301
results = []
@@ -260,29 +304,27 @@ def __str__(self):
260304
if not rset:
261305
results.append(pat % ())
262306
elif rset.dim() == 1:
263-
rgs = str(rset)
264-
cnt = len(rset)
265-
if cnt > 1:
266-
rgs = "[%s]" % rgs
267-
results.append(pat % rgs)
307+
# check if allowed to fold even for 1D pattern
308+
if self.fold_axis is None or \
309+
list(x for x in self.fold_axis if -1 <= int(x) < 1):
310+
rgs = str(rset)
311+
cnt = len(rset)
312+
if cnt > 1:
313+
rgs = "[%s]" % rgs
314+
results.append(pat % rgs)
315+
else:
316+
results.extend((pat % rgs for rgs in rset.striter()))
268317
elif rset.dim() > 1:
269-
for rgvec in rset.vectors():
270-
rgargs = []
271-
for rangeset in rgvec:
272-
rgs = str(rangeset)
273-
cnt = len(rangeset)
274-
if cnt > 1:
275-
rgs = "[%s]" % rgs
276-
rgargs.append(rgs)
277-
results.append(pat % tuple(rgargs))
318+
results.extend(self._iter_nd_pat(pat, rset))
278319
except TypeError:
279-
raise NodeSetParseError(pat, "Internal error: " \
280-
"node pattern and ranges mismatch")
320+
raise NodeSetParseError(pat, "Internal error: node pattern and "
321+
"ranges mismatch")
281322
return ",".join(results)
282323

283324
def copy(self):
284325
"""Return a shallow copy."""
285326
cpy = self.__class__()
327+
cpy.fold_axis = self.fold_axis
286328
cpy._autostep = self._autostep
287329
cpy._length = self._length
288330
dic = {}
@@ -1071,33 +1113,45 @@ class NodeSet(NodeSetBase):
10711113

10721114
_VERSION = 2
10731115

1074-
def __init__(self, nodes=None, autostep=None, resolver=None):
1116+
def __init__(self, nodes=None, autostep=None, resolver=None,
1117+
fold_axis=None):
10751118
"""Initialize a NodeSet object.
10761119
1077-
The `nodes' argument may be a valid nodeset string or a NodeSet
1120+
The `nodes` argument may be a valid nodeset string or a NodeSet
10781121
object. If no nodes are specified, an empty NodeSet is created.
10791122
1080-
The optional `autostep' argument is passed to underlying RangeSet
1081-
objects and aims to enable and make use of the range/step syntax
1082-
(eg. node[1-9/2]) when converting NodeSet to string (using folding).
1083-
To enable this feature, autostep must be set there to the min number of
1084-
indexes that are found at equal distance of each other inside a range
1085-
before NodeSet starts to use this syntax. For example, autostep=3 (or
1086-
less) will pack n[2,4,6] into n[2-6/2]. Default autostep value is None
1087-
which means "inherit whenever possible", ie. do not enable it unless
1088-
set in NodeSet objects passed as `nodes' here or during arithmetic
1089-
operations.
1090-
You may however use the special AUTOSTEP_DISABLED constant to force
1123+
The optional `autostep` argument is passed to underlying
1124+
:class:`.RangeSet.RangeSet` objects and aims to enable and make use of
1125+
the range/step syntax (eg. ``node[1-9/2]``) when converting NodeSet to
1126+
string (using folding). To enable this feature, autostep must be set
1127+
there to the min number of indexes that are found at equal distance of
1128+
each other inside a range before NodeSet starts to use this syntax. For
1129+
example, `autostep=3` (or less) will pack ``n[2,4,6]`` into
1130+
``n[2-6/2]``. Default autostep value is None which means "inherit
1131+
whenever possible", ie. do not enable it unless set in NodeSet objects
1132+
passed as `nodes` here or during arithmetic operations.
1133+
You may however use the special ``AUTOSTEP_DISABLED`` constant to force
10911134
turning off autostep feature.
10921135
1093-
The optional `resolver' argument may be used to override the group
1136+
The optional `resolver` argument may be used to override the group
10941137
resolving behavior for this NodeSet object. It can either be set to a
1095-
GroupResolver object, to the RESOLVER_NOGROUP constant to disable any
1096-
group resolution, or to None (default) to use standard NodeSet group
1097-
resolver (see set_std_group_resolver() at the module level to change
1098-
it if needed).
1099-
"""
1100-
NodeSetBase.__init__(self, autostep=autostep)
1138+
:class:`.NodeUtils.GroupResolver` object, to the ``RESOLVER_NOGROUP``
1139+
constant to disable any group resolution, or to None (default) to use
1140+
standard NodeSet group resolver (see :func:`.set_std_group_resolver()`
1141+
at the module level to change it if needed).
1142+
1143+
nD nodeset only: the optional `fold_axis` parameter, if specified, set
1144+
the public instance member `fold_axis` to an iterable over nD 0-indexed
1145+
axis integers. This parameter may be used to disengage some nD folding.
1146+
That may be useful as all cluster tools don't support folded-nD nodeset
1147+
syntax. Pass ``[0]``, for example, to only fold along first axis (that
1148+
is, to fold first dimension using ``[a-b]`` rangeset syntax whenever
1149+
possible). Using `fold_axis` ensures that rangeset won't be folded on
1150+
unspecified axis, but please note however, that using `fold_axis` may
1151+
lead to suboptimial folding, this is because NodeSet algorithms are
1152+
optimized for folding along all axis (default behavior).
1153+
"""
1154+
NodeSetBase.__init__(self, autostep=autostep, fold_axis=fold_axis)
11011155

11021156
# Set group resolver.
11031157
if resolver in (RESOLVER_NOGROUP, RESOLVER_NOINIT):
@@ -1155,6 +1209,7 @@ def __setstate__(self, dic):
11551209
self._resolver = None
11561210
self._parser = ParsingEngine(None)
11571211
if getattr(self, '_version', 1) <= 1:
1212+
self.fold_axis = None
11581213
# if setting state from first version, a conversion is needed to
11591214
# support native RangeSetND
11601215
old_patterns = self._patterns
@@ -1179,6 +1234,7 @@ def copy(self):
11791234
else:
11801235
dic[pat] = rangeset.copy()
11811236
cpy._patterns = dic
1237+
cpy.fold_axis = self.fold_axis
11821238
cpy._autostep = self._autostep
11831239
cpy._resolver = self._resolver
11841240
cpy._parser = self._parser

0 commit comments

Comments
 (0)