1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
|
.. _guide-NodeSet:
Node sets handling
==================
.. highlight:: python
.. _class-NodeSet:
NodeSet class
-------------
:class:`.NodeSet` is a class to represent an ordered set of node names
(optionally indexed). It's a convenient way to deal with cluster nodes and
ease their administration. :class:`.NodeSet` is implemented with the help of
two other ClusterShell public classes, :class:`.RangeSet` and
:class:`.RangeSetND`, which implement methods to manage a set of numeric
ranges in one or multiple dimensions. :class:`.NodeSet`, :class:`.RangeSet`
and :class:`.RangeSetND` APIs match standard Python sets. A command-line
interface (:ref:`nodeset-tool`) which implements most of :class:`.NodeSet`
features, is also available.
Other classes of the ClusterShell library makes use of the :class:`.NodeSet`
class when they come to deal with distant nodes.
Using NodeSet
^^^^^^^^^^^^^
If you are used to `Python sets`_, :class:`.NodeSet` interface will be easy
for you to learn. The main conceptual difference is that :class:`.NodeSet`
iterators always provide ordered results (and also
:meth:`.NodeSet.__getitem__()` by index or slice is allowed). Furthermore,
:class:`.NodeSet` provides specific methods like
:meth:`.NodeSet.split()`, :meth:`.NodeSet.contiguous()` (see below), or
:meth:`.NodeSet.groups()`, :meth:`.NodeSet.regroup()` (these last two are
related to :ref:`class-NodeSet-groups`). The following code snippet shows you
a basic usage of the :class:`.NodeSet` class::
>>> from ClusterShell.NodeSet import NodeSet
>>> nodeset = NodeSet()
>>> nodeset.add("node7")
>>> nodeset.add("node6")
>>> print nodeset
node[6-7]
:class:`.NodeSet` class provides several object constructors::
>>> print NodeSet("node[1-5]")
node[1-5]
>>> print NodeSet.fromlist(["node1", "node2", "node3"])
node[1-3]
>>> print NodeSet.fromlist(["node[1-5]", "node[6-10]"])
node[1-10]
>>> print NodeSet.fromlist(["clu-1-[1-4]", "clu-2-[1-4]"])
clu-[1-2]-[1-4]
All corresponding Python sets operations are available, for example::
>>> from ClusterShell.NodeSet import NodeSet
>>> ns1 = NodeSet("node[10-42]")
>>> ns2 = NodeSet("node[11-16,18-39]")
>>> print ns1.difference(ns2)
node[10,17,40-42]
>>> print ns1 - ns2
node[10,17,40-42]
>>> ns3 = NodeSet("node[1-14,40-200]")
>>> print ns3.intersection(ns1)
node[10-14,40-42]
Unlike Python sets, it is important to notice that :class:`.NodeSet` is
somewhat not so strict about the type of element used for set operations. Thus
when a string object is encountered, it is automatically converted to a
NodeSet object for convenience. The following example shows an example of
this (set operation is working with either a native nodeset or a string)::
>>> nodeset = NodeSet("node[1-10]")
>>> nodeset2 = NodeSet("node7")
>>> nodeset.difference_update(nodeset2)
>>> print nodeset
node[1-6,8-10]
>>>
>>> nodeset.difference_update("node8")
>>> print nodeset
node[1-6,9-10]
NodeSet ordered content leads to the following being allowed::
>>> nodeset = NodeSet("node[10-49]")
>>> print nodeset[0]
node10
>>> print nodeset[-1]
node49
>>> print nodeset[10:]
node[20-49]
>>> print nodeset[:5]
node[10-14]
>>> print nodeset[::4]
node[10,14,18,22,26,30,34,38,42,46]
And it works for node names without index, for example::
>>> nodeset = NodeSet("lima,oscar,zulu,alpha,delta,foxtrot,tango,x-ray")
>>> print nodeset
alpha,delta,foxtrot,lima,oscar,tango,x-ray,zulu
>>> print nodeset[0]
alpha
>>> print nodeset[-2]
x-ray
And also for multidimensional node sets::
>>> nodeset = NodeSet("clu1-[1-10]-ib[0-1],clu2-[1-10]-ib[0-1]")
>>> print nodeset
clu[1-2]-[1-10]-ib[0-1]
>>> print nodeset[0]
clu1-1-ib0
>>> print nodeset[-1]
clu2-10-ib1
>>> print nodeset[::2]
clu[1-2]-[1-10]-ib0
.. _class-NodeSet-split:
To split a NodeSet object into *n* subsets, use the :meth:`.NodeSet.split()`
method, for example::
>>> for nodeset in NodeSet("node[10-49]").split(2):
... print nodeset
...
node[10-29]
node[30-49]
.. _class-NodeSet-contiguous:
To split a NodeSet object into contiguous subsets, use the
:meth:`.NodeSet.contiguous()` method, for example::
>>> for nodeset in NodeSet("node[10-49,51-53,60-64]").contiguous():
... print nodeset
...
node[10-49]
node[51-53]
node[60-64]
For further details, please use the following command to see full
:class:`.NodeSet` API documentation.
.. _class-NodeSet-nD:
Multidimensional considerations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Version 1.7 introduces full support of multidimensional NodeSet (eg.
*da[2-5]c[1-2]p[0-1]*). The :class:`.NodeSet` interface is the same,
multidimensional patterns are automatically detected by the parser and
processed internally. While expanding a multidimensional NodeSet is easily
solved by performing a cartesian product of all dimensions, folding nodes is
much more complex and time consuming. To reduce the performance impact of such
feature, the :class:`.NodeSet` class still relies on :class:`.RangeSet` when
only one dimension is varying (see :ref:`class-RangeSet`). Otherwise, it uses
a new class named :class:`.RangeSetND` for full multidimensional support (see
:ref:`class-RangeSetND`).
.. _class-NodeSet-extended-patterns:
Extended String Pattern
^^^^^^^^^^^^^^^^^^^^^^^
:class:`.NodeSet` class parsing engine recognizes an *extended string
pattern*, adding support for union (with special character *","*), difference
(with special character *"!"*), intersection (with special character *"&"*)
and symmetric difference (with special character *"^"*) operations. String
patterns are read from left to right, by proceeding any character operators
accordingly. The following example shows how you can use this feature::
>>> print NodeSet("node[10-42],node46!node10")
node[11-42,46]
.. _class-NodeSet-groups:
Node groups
-----------
Node groups are very useful and are needed to group similar cluster nodes in
terms of configuration, installed software, available resources, etc. A node
can be a member of more than one node group.
Using node groups
^^^^^^^^^^^^^^^^^
Node groups are prefixed with **@** character. Please see
:ref:`nodeset-groupsexpr` for more details about node group expression/syntax
rules.
Please also have a look at :ref:`Node groups configuration <groups-config>` to
learn how to configure external node group bingings (sources). Once setup
(please use the :ref:`nodeset-tool` command to check your configuration), the
NodeSet parsing engine automatically resolves node groups. For example::
>>> print NodeSet("@oss")
example[4-5]
>>> print NodeSet("@compute")
example[32-159]
>>> print NodeSet("@compute,@oss")
example[4-5,32-159]
That is, all NodeSet-based applications share the same system-wide node group
configuration (unless explicitly disabled --- see
:ref:`class-NodeSet-disable-group`).
When the **all** group upcall is configured (:ref:`node groups configuration
<groups-config>`), you can also use the following :class:`.NodeSet`
constructor::
>>> print NodeSet.fromall()
example[4-6,32-159]
When group upcalls are not properly configured, this constructor will raise a
*NodeSetExternalError* exception.
.. _class-NodeSet-groups-finding:
Finding node groups
^^^^^^^^^^^^^^^^^^^
In order to find node groups a specified node set belongs to, you can use the
:meth:`.NodeSet.groups()` method. This method is used by ``nodeset -l
<nodeset>`` command (see :ref:`nodeset-group-finding`). It returns a Python
dictionary where keys are groups found and values, provided for convenience,
are tuples of the form *(group_nodeset, contained_nodeset)*. For example::
>>> for group, (group_nodes, contained_nodes) in NodeSet("@oss").groups().iteritems():
... print group, group_nodes, contained_nodes
...
@all example[4-6,32-159] example[4-5]
@oss example[4-5] example[4-5]
More usage examples follow::
>>> print NodeSet("example4").groups().keys()
['@all', '@oss']
>>> print NodeSet("@mds").groups().keys()
['@all', '@mds']
>>> print NodeSet("dummy0").groups().keys()
[]
.. _class-NodeSet-regroup:
Regrouping node sets
^^^^^^^^^^^^^^^^^^^^
If needed group configuration conditions are met (cf. :ref:`node groups
configuration <groups-config>`), you can use the :meth:`.NodeSet.regroup()`
method to reduce node sets using matching groups, whenever possible::
>>> print NodeSet("example[4-6]").regroup()
@mds,@oss
The nodeset command makes use of the :meth:`.NodeSet.regroup()` method when
using the *-r* switch (see :ref:`nodeset-regroup`).
.. _class-NodeSet-groups-override:
Overriding default groups configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
It is possible to override the library default groups configuration by
changing the default :class:`.NodeSet` *resolver* object. Usually, this is
done for testing or special purposes. Here is an example of how to override
the *resolver* object using :func:`.NodeSet.set_std_group_resolver()` in order
to use another configuration file::
>>> from ClusterShell.NodeSet import NodeSet, set_std_group_resolver
>>> from ClusterShell.NodeUtils import GroupResolverConfig
>>> set_std_group_resolver(GroupResolverConfig("/other/groups.conf"))
>>> print NodeSet("@oss")
other[10-20]
It is possible to restore :class:`.NodeSet` *default group resolver* by
passing None to the :func:`.NodeSet.set_std_group_resolver()` module function,
for example::
>>> from ClusterShell.NodeSet import set_std_group_resolver
>>> set_std_group_resolver(None)
.. _class-NodeSet-disable-group:
Disabling node group resolution
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If for any reason, you want to disable host groups resolution, you can use the
special resolver value *RESOLVER_NOGROUP*. In that case, :class:`.NodeSet`
parsing engine will not recognize **@** group characters anymore, for
instance::
>>> from ClusterShell.NodeSet import NodeSet, RESOLVER_NOGROUP
>>> print NodeSet("@oss")
example[4-5]
>>> print NodeSet("@oss", resolver=RESOLVER_NOGROUP)
@oss
Any attempts to use a group-based method (like :meth:`.NodeSet.groups()` or
:meth:`.NodeSet.regroups()`) on such "no group" NodeSet will raise a
*NodeSetExternalError* exception.
NodeSet object serialization
----------------------------
The :class:`.NodeSet` class supports object serialization through the standard
*pickling*. Group resolution is done before *pickling*.
.. _Python sets: http://docs.python.org/library/sets.html
|