Skip to content
Snippets Groups Projects

Convert llnl_hostlist_expand to Puppet 4.x API

Open Torbjörn Lönnemark requested to merge llnl_hostlist_expand-4.x-api into master
2 files
+ 216
218
Compare changes
  • Side-by-side
  • Inline
Files
2
+ 216
0
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2022
# Thomas Bellman, National Supercomputer Centre, Sweden
# Kent Engström, National Supercomputer Centre, Sweden
# Torbjörn Lönnemark, National Supercomputer Centre, Sweden
#
# Licensed under the GNU GPL v3+; see the README file for more information.
#
# This implementation is heavily based on the python-hostlist Python
# module by the above people (mostly Kent Engström). It can be found
# at https://www.nsc.liu.se/~kent/python-hostlist/.
# Expand an LLNL hostlist expression into a list of individual names.
#
# (Note: While this talks about 'hosts', there is no connection with
# e.g. DNS. These are just strings, and you can use them to name
# anything or nothing.)
#
# A couple of examples probably is the easiest way to explain:
#
# llnl_hostlist_expand('n[8-11]')
# ==> ['n8', 'n9', 'n10', 'n11']
#
# llnl_hostlist_expand('n[008-11]')
# ==> ['n008', 'n009', 'n010', 'n011']
#
# llnl_hostlist_expand('n[6-8,01-3]')
# ==> ['n01', 'n02', 'n03', 'n6', 'n7', 'n8']
#
# llnl_hostlist_expand('n[1-3]b,x[1-3].[07-08],a[30,20,10]')
# ==> ['a10', 'a20', 'a30', 'n1b', 'n2b', 'n3b',
# 'x1.07', 'x1.08', 'x2.07', 'x2.08', 'x3.07', 'x3.08']
#
# The resulting lists are always sorted in a \"natural\" order, and
# duplicates are removed.
#
# This syntax is used by several programs originating at the Lawrence
# Livermore National Laboratory, e.g. SLURM and pdsh.
Puppet::Functions.create_function(:llnl_hostlist_expand) do
def llnl_hostlist_expand(*args)
if args.length != 1
raise(Puppet::ParseError,
"llnl_hostlist_expand(): Wrong number of arguments")
end
hostlist = args[0]
NSC_Utils::llnl_hostlist_expand(hostlist)
end
# Helper functions, doing the actua work
module NSC_Utils
# Guard against ridiculously long expanded lists
LLNL_HOSTLIST_MAXSIZE = 100000
# Exception raised for bad hostlists
class BadLLNLHostlist < RuntimeError; end
# Expand a hostlist expression string to a Python list.
#
# Example: expand_hostlist("n[9-11],d[01-02]") ==>
# ['n9', 'n10', 'n11', 'd01', 'd02']
#
# Duplicates will be removed, and the results will be sorted.
#
def llnl_hostlist_expand(hostlist)
results = []
bracket_level = 0
part = ""
(hostlist+",").each_char do |c|
if c == "," && bracket_level == 0
# Comma at top level, split!
if part != ""
results += NSC_Utils::__hostlist_expand_part(part)
end
part = ""
else
part += c
end
if c == "["
bracket_level += 1
elsif c == "]"
bracket_level -= 1
end
if bracket_level > 1
raise(NSC_Utils::BadLLNLHostlist, "nested brackets")
elsif bracket_level < 0
raise(NSC_Utils::BadLLNLHostlist, "unbalanced brackets")
end
end
if bracket_level > 0
raise(NSC_Utils::BadLLNLHostlist, "unbalanced brackets")
end
results.uniq!
# Sort the results in a "natural" order, making sure that e.g.
# "n9" comes before "n10".
#
# Split names into a list of alternating numerical (decimal) and
# non-numerical parts, convert the numerical parts into Intgers,
# and compare the lists. This splitting will result in an empty
# string first if the name starts with a number, which means that
# the list comparison will always compare elements of equal types.
#
# Converting all elements once before sorting, and then back after,
# is significantly faster than calling sort with a comparison block
# which does the splitting "on demand" for each comparison.
#
results.collect! { |name|
name.split(/([0-9]+)/).collect { |part|
/^[0-9]+$/ =~ part ? part.to_i(10) : part
}
}
results.sort!
results.collect! { |partlist| partlist.join("") }
return results
end
module_function :llnl_hostlist_expand
# Expand a part (e.g. "x[1-2]y[1-3][1-3]") (no outer level commas).
#
def __hostlist_expand_part(s)
# Base case: the empty part expand to the singleton list of ""
return [""] if s == ""
# Split into:
# 1) prefix string (may be empty)
# 2) rangelist in brackets (may be missing)
# 3) the rest
/([^,\[]*)(\[[^\]]*\])?(.*)/ =~ s
prefix, rangelist, rest = $1, $2, $3
# Expand the rest first (here is where we recurse!)
rest_expanded = NSC_Utils::__hostlist_expand_part(rest)
# Expand our own part
if rangelist.nil?
# If there is no rangelist, our own contribution is the prefix only
us_expanded = [prefix]
else
# Otherwise expand the rangelist (adding the prefix before)
us_expanded = NSC_Utils::__hostlist_expand_rangelist(
prefix, rangelist[1..-2])
end
# Combine our list with the list from the expansion of the rest
# (but guard against too large results first)
if us_expanded.length * rest_expanded.length > NSC_Utils::LLNL_HOSTLIST_MAXSIZE
raise(NSC_Utils::BadLLNLHostlist, "results too large")
end
result = us_expanded.product(rest_expanded).collect {
|us_part, rest_part| us_part + rest_part
}
return result
end
module_function :__hostlist_expand_part
# Expand a rangelist (e.g. "1-10,14"), putting a prefix before.
#
def __hostlist_expand_rangelist(prefix, rangelist)
# Split at commas and expand each range separately
results = []
rangelist.split(",").each do |range_|
results += NSC_Utils::__hostlist_expand_range(prefix, range_)
end
return results
end
module_function :__hostlist_expand_rangelist
# Expand a range (e.g. 1-10 or 14), putting a prefix before.
#
def __hostlist_expand_range(prefix, range_)
# Check for a single number first
return [ prefix + range_ ] if /^[0-9]+$/ =~ range_
# Otherwise split low-high
if /^([0-9]+)-([0-9]+)$/ !~ range_
raise(NSC_Utils::BadLLNLHostlist, "bad range")
end
width = $1.length
low, high = $1.to_i, $2.to_i
if high <low
raise(NSC_Utils::BadLLNLHostlist, "start > stop")
elsif high - low > NSC_Utils::LLNL_HOSTLIST_MAXSIZE
raise(NSC_Utils::BadLLNLHostlist, "range too large")
end
results = (low .. high).collect { |i|
"%s%0*d" % [ prefix, width, i ]
}
return results
end
module_function :__hostlist_expand_range
end
end
Loading