Documentation
¶
Overview ¶
Package nodeset parses, folds and expands ClusterShell style node sets, such as exe[0001-0010/2] or rack[1-2]node[01-04]!@drained.
A node set expression names a set of hosts. Numeric parts of a host name may be written as a bracketed range, and several expressions may be combined with set operators:
exe[0001-0010] a padded range exe[1-10/2] a range with a step rack[1-2]node[01-04] two independent numeric dimensions @compute a group, resolved by a Resolver exe[1-10],sub[1-2] union exe[1-10]!exe5 difference exe[1-10]&@idle intersection exe[1-10]^@drained symmetric difference
Operators have no precedence; an expression is evaluated strictly from left to right. Whitespace acts as a union operator, so the arguments of a command line may be joined with a space and parsed in one call. The operators !, & and ^ need an operand on each side: "exe[1-10]&" is an error, not exe[1-10]. A range needs both its bounds: "exe[1-]" is an error, not exe1.
Node names ¶
Every maximal run of digits in a name is a numeric dimension, whether or not it was written in brackets. "exe0001" and "exe[0001]" parse identically, and "10.0.1.7" has four dimensions. A dimension holding a single value is rendered without brackets, so folding is stable: parsing the output of String and folding it again yields the same string. A name may not begin with "-".
Zero padding is not part of a node's identity: "exe1" and "exe01" are the same host, so Parse("exe1,exe01") has Len 1 and Contains("exe01") is true for a set holding exe1. Each host keeps the spelling it was first given, and a set never shows a host under a name it was not given: "exe[01-02],exe3" prints as exe[01-02,3], and Canonical("exe1") on a set holding exe0001 answers "exe0001". In a range the padding of the first bound applies to the whole range, and a last bound padded to another width, such as exe[1-010], is an error.
A set does not report that it was given one host under two spellings; the second is simply the same member again. A program for which such a pair is an error, such as two machines in an inventory, checks its names itself: before it adds a name, Canonical on the set built so far answers the spelling already held for that host, if there is one.
These rules, and each place where this package differs from ClusterShell, are set out in the language reference.
Index ¶
- type Lister
- type MapResolver
- type NodeSet
- func (ns *NodeSet) Add(expr string) error
- func (ns *NodeSet) Canonical(name string) (string, bool)
- func (ns *NodeSet) Clone() *NodeSet
- func (ns *NodeSet) Contains(name string) bool
- func (ns *NodeSet) Difference(other *NodeSet) *NodeSet
- func (ns *NodeSet) Expand() []string
- func (ns *NodeSet) Hostlist() string
- func (ns *NodeSet) Intersection(other *NodeSet) *NodeSet
- func (ns *NodeSet) IsEmpty() bool
- func (ns *NodeSet) Len() int
- func (ns *NodeSet) Split(n int) []*NodeSet
- func (ns *NodeSet) String() string
- func (ns *NodeSet) SymmetricDifference(other *NodeSet) *NodeSet
- func (ns *NodeSet) Union(other *NodeSet) *NodeSet
- type Option
- type Resolver
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Lister ¶
type Lister interface {
// List returns the group names a source offers. An empty source means
// the default one.
List(source string) ([]string, error)
// DefaultSource names the source used when a reference names none.
DefaultSource() string
}
Lister is implemented by a Resolver that can also say which groups it offers, for a program that lists them or completes a group name. Parsing never needs it.
type MapResolver ¶
type MapResolver struct {
// Groups maps a source name to its groups.
Groups map[string]map[string]string
// Default names the source used when a reference names none.
Default string
}
MapResolver resolves groups from an in-memory table, such as groups a program reads from its configuration, or those of a test.
func NewMapResolver ¶
func NewMapResolver(source string, groups map[string]string) *MapResolver
NewMapResolver builds a resolver for a single source, which is also the default one.
func (*MapResolver) All ¶
func (m *MapResolver) All(source string) (string, error)
All implements Resolver by unioning every group of the source, each evaluated on its own. A group whose expression holds an operator other than the union is referred to as @source:group rather than written out: its operator would otherwise apply to every group before it. So is a group whose brackets do not balance, which would otherwise take in the group after it. A group referred to is evaluated one level of nesting deeper than one written out. A group that has to be referred to is an error when its name is empty or *, or holds whitespace, a comma, an operator or a bracket, or when the name of its source holds one of those or a colon, since such a reference might not read back as that group.
func (*MapResolver) DefaultSource ¶
func (m *MapResolver) DefaultSource() string
DefaultSource implements Lister.
func (*MapResolver) List ¶
func (m *MapResolver) List(source string) ([]string, error)
List implements Lister. An empty source means the default one.
func (*MapResolver) Resolve ¶
func (m *MapResolver) Resolve(source, group string) (string, error)
Resolve implements Resolver. An empty source means the default one. A group the source does not hold names no hosts, as it does in a static source of ClusterShell; a source the table does not hold is an error.
type NodeSet ¶
type NodeSet struct {
// contains filtered or unexported fields
}
NodeSet is an unordered set of host names that renders in folded form. The zero value is not usable; call New or Parse.
Padding is not part of a host's identity: exe1 and exe01 are one host. Each host keeps the spelling it was first given, and when sets are combined the spelling already held wins, so a set never shows a host under a name it was not given.
A set may be read from several goroutines at once. Add changes it, and needs the set to itself.
func MustParse ¶
MustParse is Parse for expressions fixed at compile time; it panics on a parse error.
func Parse ¶
Parse evaluates a node set expression without resolving groups. An expression containing a group reference is rejected.
Example ¶
package main
import (
"fmt"
"github.com/GSI-HPC/go-nodeset"
)
func main() {
ns, err := nodeset.Parse("exe[0001-0010]!exe0003")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ns, ns.Len())
// Operators have no precedence: the expression is read from left to
// right.
ns, err = nodeset.Parse("exe[1-10]!exe[1-5]&exe[1-7]")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ns)
_, err = nodeset.Parse("exe[1-10]&")
fmt.Println(err)
}
Output: exe[0001-0002,0004-0010] 9 exe[6-7] in "exe[1-10]&": the & operator has no right operand
func ParseWith ¶
ParseWith evaluates a node set expression, resolving group references through res. A nil resolver rejects every group reference.
Example ¶
package main
import (
"fmt"
"github.com/GSI-HPC/go-nodeset"
)
func main() {
// A group may refer to other groups; the resolver's answer is parsed in
// turn.
res := nodeset.NewMapResolver("site", map[string]string{
"compute": "exe[0001-0100]",
"gpu": "exe[0097-0100]",
"cpu": "@compute!@gpu",
})
ns, err := nodeset.ParseWith("@cpu&exe[0090-0200]", res)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ns)
// @* asks the resolver for every host of its default source.
ns, err = nodeset.ParseWith("@*", res)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ns.Len())
// A group the table does not hold names no hosts, as in ClusterShell; a
// source it does not hold is an error.
ns, err = nodeset.ParseWith("@login,exe0001", res)
fmt.Println(ns, err)
_, err = nodeset.ParseWith("@slurm:idle", res)
fmt.Println(err)
}
Output: exe[0090-0096] 100 exe0001 <nil> group @slurm:idle: unknown group source "slurm"
func (*NodeSet) Add ¶
Add parses an expression, without resolving groups, and adds the hosts it names to the set.
func (*NodeSet) Canonical ¶
Canonical returns the name this set holds for a host, which is always a name the set was given.
It differs from the name asked about when the two were written with different padding: a set holding exe0001 answers "exe0001" when asked about "exe1", because padding is not part of a host's identity and both name one host. A set holding exe0001 and exe11 answers "exe11" for exe11.
func (*NodeSet) Contains ¶
Contains reports whether name is a member. Padding is ignored, so "exe01" and "exe1" name the same host.
func (*NodeSet) Difference ¶
Difference returns the hosts of ns that are not in other.
func (*NodeSet) Expand ¶
Expand returns the host names in the order ClusterShell lists them: by pattern first; within a pattern with one number, in numeric order; within one with several, vector by vector as String folds them, each with its last number varying fastest.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/GSI-HPC/go-nodeset"
)
func main() {
// Hosts with one number come in order of their numbers, not of their
// names as strings.
ns := nodeset.MustParse("exe[9-11],rack[1-2]node[1-2]")
fmt.Println(strings.Join(ns.Expand(), " "))
}
Output: exe9 exe10 exe11 rack1node1 rack1node2 rack2node1 rack2node2
func (*NodeSet) Hostlist ¶
Hostlist renders the set in the syntax Slurm and FreeIPMI accept: one bracketed range per name at most, and no steps.
Each pattern is folded along the one dimension that gives the fewest names, so rack[1-2]node[001-100] becomes rack1node[001-100],rack2node[001-100] rather than two hundred names, and the argument stays short. Both parsers read several bracketed dimensions in one name as well, but this form is the one every version of them reads.
Example ¶
package main
import (
"fmt"
"github.com/GSI-HPC/go-nodeset"
)
func main() {
// For Slurm and FreeIPMI: one bracketed range per name at most, and no
// steps.
ns := nodeset.MustParse("rack[1-2]node[001-100],exe[1-9/2]")
fmt.Println(ns)
fmt.Println(ns.Hostlist())
}
Output: exe[1,3,5,7,9],rack[1-2]node[001-100] exe[1,3,5,7,9],rack1node[001-100],rack2node[001-100]
func (*NodeSet) Intersection ¶
Intersection returns the hosts in both sets.
func (*NodeSet) Split ¶
Split partitions the set into at most n chunks of near equal size, in expansion order. It returns nil for n below one.
func (*NodeSet) String ¶
String renders the set in folded form, which Parse reads back as the same set, every host spelled as before. An empty set renders as the empty string.
Example ¶
package main
import (
"fmt"
"github.com/GSI-HPC/go-nodeset"
)
func main() {
// Hosts written one by one fold into ranges. exe3 is exe03 again, since
// padding is not part of a host's identity, and a host keeps the spelling
// it was first given.
ns := nodeset.MustParse("exe01,exe02,exe03,exe3 exe05 login")
fmt.Println(ns)
// Names with several numbers fold as ClusterShell folds them.
fmt.Println(nodeset.MustParse("rack1node01,rack1node02,rack2node01,rack2node02"))
// Steps are written only when asked for.
fmt.Println(nodeset.MustParse("exe[1-9/2]"))
fmt.Println(nodeset.MustParse("exe[1-9/2]", nodeset.WithAutostep(3)))
}
Output: exe[01-03,05],login rack[1-2]node[01-02] exe[1,3,5,7,9] exe[1-9/2]
func (*NodeSet) SymmetricDifference ¶
SymmetricDifference returns the hosts in exactly one of the two sets.
type Option ¶
type Option func(*NodeSet)
Option configures a set.
func WithAutostep ¶
WithAutostep folds arithmetic progressions of at least n elements into "first-last/step" form, taking the values from left to right as ClusterShell's autostep does. ClusterShell disables this by default, and so does this package; n below 2 keeps it disabled.
type Resolver ¶
type Resolver interface {
// Resolve returns the expression a group names; an empty group returns
// an empty expression. What an unknown group means is the resolver's to
// decide: an error, for a program that must not select nothing by
// mistake, or no hosts, as ClusterShell's static sources and MapResolver
// answer. The name is empty for a reference written @ or @source:.
Resolve(source, group string) (string, error)
// All returns the expression naming every host a source knows. It
// answers the reference @source:*, or @* for an empty source.
//
// The expression is evaluated as one, left to right, so a resolver
// that joins the expressions of several groups into it has to keep the
// operators of each group to that group, as MapResolver does.
All(source string) (string, error)
}
Resolver turns a group reference into a node set expression.
A reference is written @group, or @source:group when several group sources are configured. The expression a resolver returns is parsed in turn, so a group may refer to other groups.
The source is passed on as written, and is empty for a bare @group: which source that means, the default one or a search of several, is the resolver's to decide. A bare @group inside a group of a named source is passed on with that source, as ClusterShell resolves it.