diff --git a/hooks/horizon_contexts.py b/hooks/horizon_contexts.py index f4267066..0d37bbb1 100644 --- a/hooks/horizon_contexts.py +++ b/hooks/horizon_contexts.py @@ -14,6 +14,7 @@ # vim: set ts=4:et +import functools import json from charmhelpers.core.hookenv import ( @@ -382,11 +383,79 @@ class LocalSettingsContext(OSContextGenerator): ctxt = { 'settings': [ '# {0}\n{1}'.format(u, rd['local-settings']) - for u, rd in sorted(relations, - key=lambda r: r[1]['priority'])] + for u, rd in sorted( + relations, + key=functools.cmp_to_key(self._priority_sort_cmp))] } return ctxt + @staticmethod + def _priority_sort_cmp(unit_rdata_l, unit_rdata_r): + """Sort two replates for __call__ . + + This is used from functools.cmp_to_key() function. + + It compares items in the (unit, rdata) as rdata['priority'] where + + * No 'priority' value > one that is present; technically this isn't + possible, but is defensive here. + * None > that something that is present. + * types are int-ed() and attempted to be compared + * Two identical types are cmp-ed + * If types won't compare or can't be converted to an int, then they + are assumed to be non comparable and return 'equal' (0) + + :param unit_rdata_l: the (unit, relation_data) on the left. + :type unit_data_l: Tuple[str, Any] + :param unit_rdata_r: the (unit, relation_data) on the right. + :type unit_data_r: Tuple[str, Any] + :returns: a cmp return value (-1 is lr, 0 is l==r) + :rtype: int + """ + try: + priority_l = unit_rdata_l[1].get('priority') + if priority_l is None: + # cmp(None, Anything) is 1 + return 1 + except IndexError: + # cmp( , Anything) is 1: i.e. missing to end of list + return 1 + try: + priority_r = unit_rdata_r[1]['priority'] + if priority_r is None: + # cmp(Anything, None) is -1 + return -1 + except IndexError: + # cmp( Anything, ) is -1: i.e. missing already at end + return -1 + + # define a _cmp function. + def _cmp(a, b): + if a < b: + return -1 + elif a > b: + return 1 + else: + return 0 + + # try converting to ints before doing strings, as the strings may be + # numbers. + try: + return _cmp(int(priority_l), int(priority_r)) + except ValueError: + pass + + # if they are the same type, try comparing them. + if type(priority_l) == type(priority_r): + try: + return _cmp(priority_l, priority_r) + except TypeError: + # types don't support comparisions + return 0 + # different types, that won't become ints, and also don't support + # so just say they are the same. + return 0 + class WebSSOFIDServiceProviderContext(OSContextGenerator): interfaces = ['websso-fid-service-provider'] diff --git a/unit_tests/test_horizon_contexts.py b/unit_tests/test_horizon_contexts.py index e8a79099..b9726b33 100644 --- a/unit_tests/test_horizon_contexts.py +++ b/unit_tests/test_horizon_contexts.py @@ -1363,6 +1363,115 @@ class TestHorizonContexts(CharmTestCase): '# horizon-plugin/0\n' 'FOO = True']}) + def test_LocalSettingsContext_unusual_priority(self): + # First, left priority missing. + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'local-settings': 'FOO = True'}, + {'priority': 60, + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': []}) + # First, right priority missing. + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': 99, + 'local-settings': 'FOO = True'}, + {'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': []}) + # Left priority is None. + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': None, + 'local-settings': 'FOO = True'}, + {'priority': 60, + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin-too/0\n' + 'BAR = False', + '# horizon-plugin/0\n' + 'FOO = True']}) + # Right priority is None. + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': 99, + 'local-settings': 'FOO = True'}, + {'priority': None, + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin/0\n' + 'FOO = True', + '# horizon-plugin-too/0\n' + 'BAR = False']}) + # Left priority is stringy number. + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': "99", + 'local-settings': 'FOO = True'}, + {'priority': 60, + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin-too/0\n' + 'BAR = False', + '# horizon-plugin/0\n' + 'FOO = True']}) + # Right priority is stringy number. + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': 99, + 'local-settings': 'FOO = True'}, + {'priority': "60", + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin-too/0\n' + 'BAR = False', + '# horizon-plugin/0\n' + 'FOO = True']}) + # Both priorities are strings + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': "99", + 'local-settings': 'FOO = True'}, + {'priority': "60", + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin-too/0\n' + 'BAR = False', + '# horizon-plugin/0\n' + 'FOO = True']}) + # Left priority is weired json object + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': "{'a': 1}", + 'local-settings': 'FOO = True'}, + {'priority': "60", + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin-too/0\n' + 'BAR = False', + '# horizon-plugin/0\n' + 'FOO = True']}) + # right priority is weired json object + self.relation_ids.return_value = ['plugin:0', 'plugin-too:0'] + self.related_units.side_effect = [['horizon-plugin/0'], + ['horizon-plugin-too/0']] + self.relation_get.side_effect = [{'priority': "99", + 'local-settings': 'FOO = True'}, + {'priority': "[1,2,3]", + 'local-settings': 'BAR = False'}] + self.assertEqual(horizon_contexts.LocalSettingsContext()(), + {'settings': ['# horizon-plugin/0\n' + 'FOO = True', + '# horizon-plugin-too/0\n' + 'BAR = False']}) + def test_WebSSOFIDServiceProviderContext(self): def relation_ids_side_effect(rname): return {