diff --git a/lib/utils/algo.py b/lib/utils/algo.py index 80edfa516c4e38ff255c5da3f899e6e3f1f754a6..b1ffaf4137a5f7e708f1d528298d2be7e8da6856 100644 --- a/lib/utils/algo.py +++ b/lib/utils/algo.py @@ -24,6 +24,7 @@ import re import time +import itertools from ganeti import compat from ganeti.utils import text @@ -193,6 +194,57 @@ def SequenceToDict(seq, key=compat.fst): return dict(zip(keys, seq)) +def _MakeFlatToDict(data): + """Helper function for C{FlatToDict}. + + This function is recursively called + + @param data: The input data as described in C{FlatToDict}, already splitted + @returns: The so far converted dict + + """ + if not compat.fst(compat.fst(data)): + assert len(data) == 1, \ + "not bottom most element, found %d elements, expected 1" % len(data) + return compat.snd(compat.fst(data)) + + keyfn = lambda e: compat.fst(e).pop(0) + return dict([(k, _MakeFlatToDict(list(g))) + for (k, g) in itertools.groupby(sorted(data), keyfn)]) + + +def FlatToDict(data, field_sep="/"): + """Converts a flat structure to a fully fledged dict. + + It accept a list of tuples in the form:: + + [ + ("foo/bar", {"key1": "data1", "key2": "data2"}), + ("foo/baz", {"key3" :"data3" }), + ] + + where the first element is the key separated by C{field_sep}. + + This would then return:: + + { + "foo": { + "bar": {"key1": "data1", "key2": "data2"}, + "baz": {"key3" :"data3" }, + }, + } + + @type data: list of tuple + @param data: Input list to convert + @type field_sep: str + @param field_sep: The separator for the first field of the tuple + @returns: A dict based on the input list + + """ + return _MakeFlatToDict([(keys.split(field_sep), value) + for (keys, value) in data]) + + class RunningTimeout(object): """Class to calculate remaining timeout when doing several operations. diff --git a/test/ganeti.utils.algo_unittest.py b/test/ganeti.utils.algo_unittest.py index b0b8e3661dcf6832505828a00ce49083c2c61cd9..5d08e2ea8ff1f59c80d4dffc6361423c8b218abb 100755 --- a/test/ganeti.utils.algo_unittest.py +++ b/test/ganeti.utils.algo_unittest.py @@ -339,5 +339,34 @@ class TestSequenceToDict(unittest.TestCase): [(i, ) for i in range(200)] + [(10, )]) +class TestFlatToDict(unittest.TestCase): + def testNormal(self): + data = [ + ("lv/xenvg", {"foo": "bar", "bar": "baz"}), + ("lv/xenfoo", {"foo": "bar", "baz": "blubb"}), + ("san/foo", {"ip": "127.0.0.1", "port": 1337}), + ("san/blubb/blibb", 54), + ] + reference = { + "lv": { + "xenvg": {"foo": "bar", "bar": "baz"}, + "xenfoo": {"foo": "bar", "baz": "blubb"}, + }, + "san": { + "foo": {"ip": "127.0.0.1", "port": 1337}, + "blubb": {"blibb": 54}, + }, + } + self.assertEqual(algo.FlatToDict(data), reference) + + def testUnlikeDepth(self): + data = [ + ("san/foo", {"ip": "127.0.0.1", "port": 1337}), + ("san/foo/blubb", 23), # Another foo entry under san + ("san/blubb/blibb", 54), + ] + self.assertRaises(AssertionError, algo.FlatToDict, data) + + if __name__ == "__main__": testutils.GanetiTestProgram()