diff --git a/src/lib/Util.py b/src/lib/Util.py index cb55e807a0c0c081f17fb091e06df051837a719e..8819db38bc3fbbc342bd7e9de6d1f4abcb6c3607 100644 --- a/src/lib/Util.py +++ b/src/lib/Util.py @@ -272,6 +272,13 @@ def dgetopt(arglist, opt, vopt, msg): ret[vopt[option]] = garg return ret, list(args) +# regexes for merging nodelists: +cluster_node_reg = re.compile(r'(\D+)(\d+)') +device_reg = re.compile(r'(\D+\d+-\D+)(\d)') +cray_loc = re.compile(r'^(\d+)') +node_and_dev = re.compile(r'(\D+\d+)-(\D+[\[\]\-\d]+)') +first_number_reg = re.compile(r'(\d+)') + def merge_nodelist(locations): '''create a set of dashed-ranges from a node list @@ -283,9 +290,6 @@ def merge_nodelist(locations): list. ''' - reg = re.compile(r'(\D+)(\d+)') - device_reg = re.compile(r'(\D+\d+-\D+)(\d)') - cray_loc = re.compile(r'^(\d+)') # create a dictionary with a key for each node prefix, # with sorted lists of node numbers as the values noderanges = {} @@ -296,7 +300,7 @@ def merge_nodelist(locations): ret = [] if (len(locations) > 1 and - reg.match(locations[0]) is None and + cluster_node_reg.match(locations[0]) is None and device_reg.match(locations[0]) is None and cray_loc.match(locations[0]) is None): # We don't know how to merge this list of locations. @@ -310,12 +314,12 @@ def merge_nodelist(locations): for name in locations: try: - prefix = reg.match(name).group(1) - nodenum = reg.match(name).group(2) - if device_reg.match(name): + dev_match = device_reg.match(name) + if dev_match: has_device_naming = True - prefix = device_reg.match(name).group(1) - nodenum = device_reg.match(name).group(2) + prefix, nodenum = dev_match.groups() + else: + prefix, nodenum = cluster_node_reg.match(name).groups() num_digits = len(nodenum) newnum = int(nodenum) if not prefix in noderanges: @@ -345,7 +349,8 @@ def merge_nodelist(locations): prefix_format_str[prefix] = {'compact': "[%s%s-%s]", 'single': "%s%s"} - ret = _gen_compacted_list(noderanges, prefix_format_str) + # may have some node names from above + ret.extend(_gen_compacted_list(noderanges, prefix_format_str)) # Compact Nodes and Devices if has_device_naming: @@ -359,19 +364,20 @@ def _compact_device_list(locations): this is a second stage for the more complex device-split locations ''' ret = [] - node_and_dev = re.compile(r'(\D+\d+)-(\D+[\[\]\-\d]+)') nodes_by_dev = {} for node in sorted(locations): match = node_and_dev.match(node) - if match is None: - ret.append(node) - else: - node_name = match.group(1) - dev_set = match.group(2) - if nodes_by_dev.get(dev_set, None): - nodes_by_dev[dev_set].append(node_name) + try: + if match is None: + ret.append(node) else: - nodes_by_dev[dev_set] = [node_name] + node_name, dev_set = match.groups() + if nodes_by_dev.get(dev_set, None): + nodes_by_dev[dev_set].append(node_name) + else: + nodes_by_dev[dev_set] = [node_name] + except AttributeError: + ret.append(node) for dev_set, nodes in nodes_by_dev.items(): compacted_nodes = merge_nodelist(nodes).split(',') ret.extend(["%s-%s" % (node, dev_set) for node in compacted_nodes]) @@ -379,9 +385,16 @@ def _compact_device_list(locations): def _compare_on_low_range(left, right): '''comparison to sort a compressed list''' - first_number_reg = re.compile(r'(\d+)') - left_num = first_number_reg.search(left).group(1) - right_num = first_number_reg.search(right).group(1) + try: + left_num = first_number_reg.search(left).group(1) + right_num = first_number_reg.search(right).group(1) + except AttributeError: + # Didn't have a number with this particular node, fall back to lexical. + if left == right: + return 0 + elif left > right: + return 1 + return -1 if left_num == right_num: return 0 elif left_num > right_num: diff --git a/testsuite/TestCobalt/TestUtil.py b/testsuite/TestCobalt/TestUtil.py index 1735cf877398db0a5cd73fe92dcbcf5936196c75..791f89dd422b4ac2e244d3c85ec5da8d0f04fc8f 100644 --- a/testsuite/TestCobalt/TestUtil.py +++ b/testsuite/TestCobalt/TestUtil.py @@ -411,6 +411,13 @@ class TestMergeNodelist(object): "cc01.test", "cc44.test"]) assert_match(resp, correct, "Incorrect merged list") + def test_merge_nodelist_cluster_with_oneoff(self): + '''cluster system style full nodes with one-off nonconforming node name''' + correct = 'bombadil,cc01,cc[42-44],frodo' + resp = Cobalt.Util.merge_nodelist(["cc43.test", "cc42.test", + "cc01.test", "cc44.test", "frodo", "bombadil"]) + assert_match(resp, correct, "Incorrect merged list") + def test_merge_nodelist_cluster_gpu(self): '''cluster system style gpu-naming from single node''' correct = 'cc01-gpu[0-3]' @@ -460,3 +467,18 @@ class TestMergeNodelist(object): ] resp = Cobalt.Util.merge_nodelist(location) assert_match(resp, correct, "Incorrect merged list") + + def test_merge_nodelist_cluster_gpu_oneoff(self): + '''cluster system style gpu-naming mixed with standard and one-off nodes''' + correct = 'bombadil,cc01,cc[01-03]-gpu[0-3],cc04-gpu[4-7],cc05-gpu[0-3],cc06-gpu0,cc07,cc[08-09]-gpu5,cc[10-12],cc42' + location = ['cc01-gpu0', 'cc01-gpu1', 'cc01-gpu2', 'cc01-gpu3', + 'cc02-gpu0', 'cc02-gpu1', 'cc02-gpu2', 'cc02-gpu3', + 'cc10', 'cc11', 'cc12', 'cc42', + 'cc05-gpu0', 'cc05-gpu1', 'cc05-gpu2', 'cc05-gpu3', + 'cc03-gpu0', 'cc03-gpu1', 'cc03-gpu2', 'cc03-gpu3', + 'cc04-gpu4', 'cc04-gpu5', 'cc04-gpu6', 'cc04-gpu7', + 'cc06-gpu0', 'cc09-gpu5', 'cc08-gpu5', 'cc01', 'cc07', + 'bombadil', + ] + resp = Cobalt.Util.merge_nodelist(location) + assert_match(resp, correct, "Incorrect merged list")