diff --git a/docs/dashboard.rst b/docs/dashboard.rst
index 22bc50a2..89a3e138 100644
--- a/docs/dashboard.rst
+++ b/docs/dashboard.rst
@@ -273,7 +273,7 @@ The header of the Conciliation Page has exactly the same contents as the header
Conciliation Page Contents
~~~~~~~~~~~~~~~~~~~~~~~~~~
-On the right side of the page, the list of process conflicts is displayed into a table.
+On the left side of the page, the list of process conflicts is displayed into a table.
A process conflict is raised when the same program is running in multiple |Supvisors| instances.
So the table lists, for each conflict:
@@ -289,7 +289,7 @@ So the table lists, for each conflict:
* for each process, a list of automatic strategies (refer to :ref:`conciliation`) helping to the solving
of this conflict.
-The left side of the page contains a simple box that enables the user to perform a global conciliation on all conflicts,
+The right side of the page contains a simple box that enables the user to perform a global conciliation on all conflicts,
using one of the automatic strategies proposed by |Supvisors|.
diff --git a/supvisors/tests/test_viewconciliation.py b/supvisors/tests/test_viewconciliation.py
index e4e8cfc7..96fce587 100644
--- a/supvisors/tests/test_viewconciliation.py
+++ b/supvisors/tests/test_viewconciliation.py
@@ -58,34 +58,78 @@ def test_write_contents(mocker, supvisors, view):
def test_write_conciliation_strategies(view):
""" Test the ConciliationView.write_conciliation_strategies method. """
# patch context
- view.sup_ctx.master_address = {'10.0.0.1'}
- view.view_ctx = Mock(**{'format_url.return_value': 'an url'})
+ view.view_ctx = Mock(**{'format_url.side_effect': lambda x, y, namespec, action: f'{action} url'})
# build root structure with one single element
- global_strategy_a_mid = create_element()
- global_strategy_li_elt = create_element({'global_strategy_a_mid': global_strategy_a_mid})
- global_strategy_li_mid = create_element()
- global_strategy_li_mid .repeat.return_value = [(global_strategy_li_elt, 'infanticide')]
- contents_elt = create_element({'global_strategy_li_mid': global_strategy_li_mid})
+ infanticide_strategy_a_mid = create_element()
+ senicide_strategy_a_mid = create_element()
+ stop_strategy_a_mid = create_element()
+ restart_strategy_a_mid = create_element()
+ running_failure_strategy_a_mid = create_element()
+ contents_elt = create_element({'infanticide_strategy_a_mid': infanticide_strategy_a_mid,
+ 'senicide_strategy_a_mid': senicide_strategy_a_mid,
+ 'stop_strategy_a_mid': stop_strategy_a_mid,
+ 'restart_strategy_a_mid': restart_strategy_a_mid,
+ 'running_failure_strategy_a_mid': running_failure_strategy_a_mid})
# test call
view.write_conciliation_strategies(contents_elt)
- assert global_strategy_a_mid.attributes.call_args_list == [call(href='an url')]
- assert global_strategy_a_mid.content.call_args_list == [call('Infanticide')]
+ assert view.view_ctx.format_url.call_args_list == [call('', CONCILIATION_PAGE, namespec='', action='senicide'),
+ call('', CONCILIATION_PAGE, namespec='', action='infanticide'),
+ call('', CONCILIATION_PAGE, namespec='', action='stop'),
+ call('', CONCILIATION_PAGE, namespec='', action='restart'),
+ call('', CONCILIATION_PAGE, namespec='',
+ action='running_failure')]
+ assert infanticide_strategy_a_mid.attributes.call_args_list == [call(href='infanticide url')]
+ assert senicide_strategy_a_mid.attributes.call_args_list == [call(href='senicide url')]
+ assert stop_strategy_a_mid.attributes.call_args_list == [call(href='stop url')]
+ assert restart_strategy_a_mid.attributes.call_args_list == [call(href='restart url')]
+ assert running_failure_strategy_a_mid.attributes.call_args_list == [call(href='running_failure url')]
+
+
+def test_get_conciliation_data(mocker, view):
+ """ Test the get_conciliation_data method. """
+ # patch context
+ process_1 = Mock(namespec='proc_1', running_identifiers={'10.0.0.1', '10.0.0.2'},
+ info_map={'10.0.0.1': {'uptime': 12}, '10.0.0.2': {'uptime': 11}})
+ process_2 = Mock(namespec='proc_2', running_identifiers={'10.0.0.3', '10.0.0.2'},
+ info_map={'10.0.0.3': {'uptime': 10}, '10.0.0.2': {'uptime': 11}})
+ mocker.patch.object(view.sup_ctx, 'conflicts', return_value=[process_1, process_2])
+ # test call
+ expected = [{'row_type': ProcessRowTypes.APPLICATION_PROCESS, 'namespec': 'proc_1', 'nb_items': 2},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_1',
+ 'identifier': '10.0.0.1', 'uptime': 12},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_1',
+ 'identifier': '10.0.0.2', 'uptime': 11},
+ {'row_type': ProcessRowTypes.APPLICATION_PROCESS, 'namespec': 'proc_2', 'nb_items': 2},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_2',
+ 'identifier': '10.0.0.2', 'uptime': 11},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_2',
+ 'identifier': '10.0.0.3', 'uptime': 10}]
+ actual = view.get_conciliation_data()
+ print(actual)
+ # no direct method in pytest to compare 2 lists of dicts
+ for actual_single in actual:
+ assert any(actual_single == expected_single for expected_single in expected)
def test_write_conciliation_table(mocker, supvisors, view):
""" Test the write_conciliation_table method. """
- mocked_name = mocker.patch.object(view, '_write_conflict_name')
- mocked_node = mocker.patch.object(view, '_write_conflict_identifier')
- mocked_time = mocker.patch.object(view, '_write_conflict_uptime')
- mocked_actions = mocker.patch.object(view, '_write_conflict_process_actions')
- mocked_strategies = mocker.patch.object(view, '_write_conflict_strategies')
+ mocked_process = mocker.patch.object(view, '_write_conflict_process')
+ mocked_detail = mocker.patch.object(view, '_write_conflict_detail')
mocked_data = mocker.patch.object(view, 'get_conciliation_data')
# patch context
view.supvisors.fsm.state = SupvisorsStates.OPERATION
mocker.patch.object(view.sup_ctx, 'conflicts', return_value=True)
# sample data
- data = [{'namespec': 'proc_1', 'rowspan': 2}, {'namespec': 'proc_1', 'rowspan': 0},
- {'namespec': 'proc_2', 'rowspan': 2}, {'namespec': 'proc_2', 'rowspan': 0}]
+ data = [{'row_type': ProcessRowTypes.APPLICATION_PROCESS, 'namespec': 'proc_1', 'nb_items': 2},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_1',
+ 'identifier': '10.0.0.1', 'uptime': 12},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_1',
+ 'identifier': '10.0.0.2', 'uptime': 11},
+ {'row_type': ProcessRowTypes.APPLICATION_PROCESS, 'namespec': 'proc_2', 'nb_items': 2},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_2',
+ 'identifier': '10.0.0.2', 'uptime': 11},
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_2',
+ 'identifier': '10.0.0.3', 'uptime': 10}]
# build simple root structure
mocked_tr_elt = [create_element() for _ in data]
conflict_tr_mid = create_element()
@@ -97,10 +141,8 @@ def test_write_conciliation_table(mocker, supvisors, view):
assert not mocked_data.called
assert conflicts_div_mid.replace.call_args_list == [call('No conflict')]
assert all(tr_elt.attrib['class'] == '' for tr_elt in mocked_tr_elt)
- assert not mocked_name.called
- assert not mocked_node.called
- assert not mocked_actions.called
- assert not mocked_strategies.called
+ assert not mocked_process.called
+ assert not mocked_detail.called
conflicts_div_mid.reset_mock()
# test call in CONCILIATION state with conflicts
view.supvisors.fsm.state = SupvisorsStates.CONCILIATION
@@ -108,75 +150,92 @@ def test_write_conciliation_table(mocker, supvisors, view):
assert mocked_data.call_args_list == [call()]
# check elements background
assert mocked_tr_elt[0].attrib['class'] == 'brightened'
- assert mocked_tr_elt[1].attrib['class'] == 'brightened'
- assert mocked_tr_elt[2].attrib['class'] == 'shaded'
+ assert mocked_tr_elt[1].attrib['class'] == 'shaded'
+ assert mocked_tr_elt[2].attrib['class'] == 'brightened'
assert mocked_tr_elt[3].attrib['class'] == 'shaded'
- assert mocked_name.call_args_list == [call(mocked_tr_elt[0], data[0], False),
- call(mocked_tr_elt[1], data[1], False),
- call(mocked_tr_elt[2], data[2], True), call(mocked_tr_elt[3], data[3], True)]
- assert mocked_node.call_args_list == [call(mocked_tr_elt[0], data[0]), call(mocked_tr_elt[1], data[1]),
- call(mocked_tr_elt[2], data[2]), call(mocked_tr_elt[3], data[3])]
- assert mocked_time.call_args_list == [call(mocked_tr_elt[0], data[0]), call(mocked_tr_elt[1], data[1]),
- call(mocked_tr_elt[2], data[2]), call(mocked_tr_elt[3], data[3])]
- assert mocked_actions.call_args_list == [call(mocked_tr_elt[0], data[0]), call(mocked_tr_elt[1], data[1]),
- call(mocked_tr_elt[2], data[2]), call(mocked_tr_elt[3], data[3])]
- assert mocked_strategies.call_args_list == [call(mocked_tr_elt[0], data[0], False),
- call(mocked_tr_elt[1], data[1], False),
- call(mocked_tr_elt[2], data[2], True),
- call(mocked_tr_elt[3], data[3], True)]
- # test call in conciliation state
- view.supvisors.fsm.state = SupvisorsStates.CONCILIATION
+ assert mocked_tr_elt[4].attrib['class'] == 'brightened'
+ assert mocked_tr_elt[5].attrib['class'] == 'shaded'
+ assert mocked_process.call_args_list == [call(mocked_tr_elt[0],
+ {'row_type': ProcessRowTypes.APPLICATION_PROCESS,
+ 'namespec': 'proc_1', 'nb_items': 2}, False),
+ call(mocked_tr_elt[3],
+ {'row_type': ProcessRowTypes.APPLICATION_PROCESS,
+ 'namespec': 'proc_2', 'nb_items': 2}, True)]
+ assert mocked_detail.call_args_list == [call(mocked_tr_elt[1],
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS,
+ 'namespec': 'proc_1', 'identifier': '10.0.0.1', 'uptime': 12}),
+ call(mocked_tr_elt[2],
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS,
+ 'namespec': 'proc_1', 'identifier': '10.0.0.2', 'uptime': 11}),
+ call(mocked_tr_elt[4],
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS,
+ 'namespec': 'proc_2', 'identifier': '10.0.0.2', 'uptime': 11}),
+ call(mocked_tr_elt[5],
+ {'row_type': ProcessRowTypes.INSTANCE_PROCESS,
+ 'namespec': 'proc_2', 'identifier': '10.0.0.3', 'uptime': 10})]
+
+
+def test_write_conflict_process(mocker, view):
+ """ Test the _write_conflict_process method. """
+ mocked_node = mocker.patch.object(view, '_write_conflict_identifier')
+ mocked_time = mocker.patch.object(view, '_write_conflict_uptime')
+ mocked_actions = mocker.patch.object(view, '_write_conflict_process_actions')
+ mocked_strategies = mocker.patch.object(view, '_write_conflict_strategies')
+ # build xhtml structure
+ section_td_mid = create_element()
+ process_td_mid = create_element()
+ conflict_instance_td_mid = create_element()
+ pstop_td_mid = create_element()
+ pkeep_td_mid = create_element()
+ strategy_td_mid = create_element()
+ tr_elt = create_element({'section_td_mid': section_td_mid, 'process_td_mid': process_td_mid,
+ 'conflict_instance_td_mid': conflict_instance_td_mid,
+ 'pstop_td_mid': pstop_td_mid, 'pkeep_td_mid': pkeep_td_mid,
+ 'strategy_td_mid': strategy_td_mid})
+ # test call
+ info = {'row_type': ProcessRowTypes.APPLICATION_PROCESS, 'namespec': 'proc_1', 'nb_items': 2}
+ view._write_conflict_process(tr_elt, info, True)
+ assert section_td_mid.attrib == {'class': 'shaded', 'rowspan': '3'}
+ assert process_td_mid.content.call_args_list == [call('proc_1')]
+ assert not mocked_node.called
+ assert not mocked_time.called
+ assert not mocked_actions.called
+ assert mocked_strategies.call_args_list == [call(tr_elt, info, True)]
+ assert conflict_instance_td_mid.content.call_args_list == [call('')]
+ assert pstop_td_mid.content.call_args_list == [call('')]
+ assert pkeep_td_mid.content.call_args_list == [call('')]
-def test_write_conflict_name(view):
- """ Test the _write_conflict_name method. """
- # build root structure with one single element
- mocked_name_mid = Mock(attrib={})
- mocked_root = Mock(**{'findmeld.return_value': mocked_name_mid})
- # test call with different values of rowspan and shade
- # first line, shaded
- info = {'namespec': 'dummy_proc', 'rowspan': 2}
- view._write_conflict_name(mocked_root, info, True)
- assert mocked_root.findmeld.call_args_list == [call('name_td_mid')]
- assert mocked_name_mid.attrib['rowspan'] == '2'
- assert mocked_name_mid.attrib['class'] == 'shaded'
- assert mocked_name_mid.content.call_args_list == [call('dummy_proc')]
- # reset mocks
- mocked_root.findmeld.reset_mock()
- mocked_name_mid.content.reset_mock()
- mocked_name_mid.attrib = {}
- # first line, non shaded
- view._write_conflict_name(mocked_root, info, False)
- assert mocked_root.findmeld.call_args_list == [call('name_td_mid')]
- assert mocked_name_mid.attrib['rowspan'] == '2'
- assert mocked_name_mid.attrib['class'] == 'brightened'
- assert mocked_name_mid.content.call_args_list == [call('dummy_proc')]
- assert not mocked_name_mid.replace.called
- # reset mocks
- mocked_root.findmeld.reset_mock()
- mocked_name_mid.content.reset_mock()
- mocked_name_mid.attrib = {}
- # not first line, shaded
- info['rowspan'] = 0
- view._write_conflict_name(mocked_root, info, True)
- assert mocked_root.findmeld.call_args_list == [call('name_td_mid')]
- assert 'rowspan' not in mocked_name_mid.attrib
- assert 'class' not in mocked_name_mid.attrib
- assert not mocked_name_mid.content.called
- assert mocked_name_mid.replace.call_args_list == [call('')]
- # reset mocks
- mocked_root.findmeld.reset_mock()
- mocked_name_mid.replace.reset_mock()
- # not first line, non shaded
- view._write_conflict_name(mocked_root, info, False)
- assert mocked_root.findmeld.call_args_list == [call('name_td_mid')]
- assert 'rowspan' not in mocked_name_mid.attrib
- assert 'class' not in mocked_name_mid.attrib
- assert not mocked_name_mid.content.called
- assert mocked_name_mid.replace.call_args_list == [call('')]
-
-
-def test_write_conflict_node(view):
+def test_write_conflict_detail(mocker, view):
+ """ Test the _write_conflict_detail method. """
+ mocked_node = mocker.patch.object(view, '_write_conflict_identifier')
+ mocked_time = mocker.patch.object(view, '_write_conflict_uptime')
+ mocked_actions = mocker.patch.object(view, '_write_conflict_process_actions')
+ mocked_strategies = mocker.patch.object(view, '_write_conflict_strategies')
+ # build xhtml structure
+ section_td_mid = create_element()
+ process_td_mid = create_element()
+ conflict_instance_td_mid = create_element()
+ pstop_td_mid = create_element()
+ pkeep_td_mid = create_element()
+ strategy_td_mid = create_element()
+ tr_elt = create_element({'section_td_mid': section_td_mid, 'process_td_mid': process_td_mid,
+ 'conflict_instance_td_mid': conflict_instance_td_mid,
+ 'pstop_td_mid': pstop_td_mid, 'pkeep_td_mid': pkeep_td_mid,
+ 'strategy_td_mid': strategy_td_mid})
+ # test call
+ info = {'row_type': ProcessRowTypes.INSTANCE_PROCESS, 'namespec': 'proc_1', 'identifier': '10.0.0.2', 'uptime': 11}
+ view._write_conflict_detail(tr_elt, info)
+ assert section_td_mid.replace.call_args_list == [call('')]
+ assert process_td_mid.content.call_args_list == [call(SUB_SYMBOL)]
+ assert mocked_node.call_args_list == [call(tr_elt, info)]
+ assert mocked_time.call_args_list == [call(tr_elt, info)]
+ assert mocked_actions.call_args_list == [call(tr_elt, info)]
+ assert not mocked_strategies.called
+ assert strategy_td_mid.replace.call_args_list == [call('')]
+
+
+def test_write_conflict_identifier(view):
""" Test the _write_conflict_identifier method. """
# patch context
view.view_ctx = Mock(**{'format_url.return_value': 'an url'})
@@ -184,11 +243,11 @@ def test_write_conflict_node(view):
mocked_addr_mid = Mock()
mocked_root = Mock(**{'findmeld.return_value': mocked_addr_mid})
# test call
- view._write_conflict_identifier(mocked_root, {'identifier': '10.0.0.1'})
+ view._write_conflict_identifier(mocked_root, {'identifier': '10.0.0.1:25000'})
assert mocked_root.findmeld.call_args_list == [call('conflict_instance_a_mid')]
assert mocked_addr_mid.attributes.call_args_list == [call(href='an url')]
assert mocked_addr_mid.content.call_args_list == [call('10.0.0.1')]
- assert view.view_ctx.format_url.call_args_list == [call('10.0.0.1', PROC_INSTANCE_PAGE)]
+ assert view.view_ctx.format_url.call_args_list == [call('10.0.0.1:25000', PROC_INSTANCE_PAGE)]
def test_write_conflict_uptime(view):
@@ -228,81 +287,38 @@ def test_write_conflict_strategies(view):
""" Test the _write_conflict_strategies method. """
# patch context
view.sup_ctx.master_identifier = '10.0.0.1'
- view.view_ctx = Mock(**{'format_url.return_value': 'an url'})
+ view.view_ctx = Mock(**{'format_url.side_effect': lambda x, y, namespec, action: f'{action} url'})
# build root structure with one single element
- mocked_a_mid = Mock()
- mocked_li_mid = Mock(**{'findmeld.return_value': mocked_a_mid})
- mocked_li_template = Mock(**{'repeat.return_value': [(mocked_li_mid, 'senicide')]})
- mocked_td_mid = Mock(attrib={}, **{'findmeld.return_value': mocked_li_template})
- mocked_root = Mock(**{'findmeld.return_value': mocked_td_mid})
- # test call with different values of rowspan and shade
- # first line, shaded
- info = {'namespec': 'dummy_proc', 'rowspan': 2}
- view._write_conflict_strategies(mocked_root, info, True)
- assert mocked_td_mid.attrib['rowspan'] == '2'
- assert mocked_td_mid.attrib['class'] == 'shaded'
- assert mocked_a_mid.attributes.call_args_list == [call(href='an url')]
- assert mocked_a_mid.content.call_args_list == [call('Senicide')]
- assert view.view_ctx.format_url.call_args_list == [call('10.0.0.1', CONCILIATION_PAGE,
- action='senicide', namespec='dummy_proc')]
- # reset mocks
- view.view_ctx.format_url.reset_mock()
- mocked_a_mid.attributes.reset_mock()
- mocked_a_mid.content.reset_mock()
- mocked_td_mid.attrib = {}
- # first line, not shaded
- info = {'namespec': 'dummy_proc', 'rowspan': 2}
- view._write_conflict_strategies(mocked_root, info, False)
- assert mocked_td_mid.attrib['rowspan'] == '2'
- assert mocked_td_mid.attrib['class'] == 'brightened'
- assert mocked_a_mid.attributes.call_args_list == [call(href='an url')]
- assert mocked_a_mid.content.call_args_list == [call('Senicide')]
- assert view.view_ctx.format_url.call_args_list == [call('10.0.0.1', CONCILIATION_PAGE,
- action='senicide', namespec='dummy_proc')]
- # reset mocks
- view.view_ctx.format_url.reset_mock()
- mocked_a_mid.attributes.reset_mock()
- mocked_a_mid.content.reset_mock()
- mocked_td_mid.attrib = {}
- # not first line, shaded
- info = {'namespec': 'dummy_proc', 'rowspan': 0}
- view._write_conflict_strategies(mocked_root, info, True)
- assert 'rowspan' not in mocked_td_mid.attrib
- assert not mocked_a_mid.attributes.called
- assert not mocked_a_mid.content.called
- assert not mocked_a_mid.content.called
- assert not view.view_ctx.format_url.called
- assert mocked_td_mid.replace.call_args_list == [call('')]
- # reset mocks
- mocked_td_mid.replace.reset_mock()
- # not first line, not shaded
- info = {'namespec': 'dummy_proc', 'rowspan': 0}
- view._write_conflict_strategies(mocked_root, info, False)
- assert 'rowspan' not in mocked_td_mid.attrib
- assert not mocked_a_mid.attributes.called
- assert not mocked_a_mid.content.called
- assert not mocked_a_mid.content.called
- assert not view.view_ctx.format_url.called
- assert mocked_td_mid.replace.call_args_list == [call('')]
-
-
-def test_get_conciliation_data(mocker, view):
- """ Test the get_conciliation_data method. """
- # patch context
- process_1 = Mock(namespec='proc_1', running_identifiers={'10.0.0.1', '10.0.0.2'},
- info_map={'10.0.0.1': {'uptime': 12}, '10.0.0.2': {'uptime': 11}})
- process_2 = Mock(namespec='proc_2', running_identifiers={'10.0.0.3', '10.0.0.2'},
- info_map={'10.0.0.3': {'uptime': 10}, '10.0.0.2': {'uptime': 11}})
- mocker.patch.object(view.sup_ctx, 'conflicts', return_value=[process_1, process_2])
+ infanticide_strategy_a_mid = create_element()
+ senicide_strategy_a_mid = create_element()
+ stop_strategy_a_mid = create_element()
+ restart_strategy_a_mid = create_element()
+ running_failure_strategy_a_mid = create_element()
+ strategy_td_mid = create_element({'infanticide_local_strategy_a_mid': infanticide_strategy_a_mid,
+ 'senicide_local_strategy_a_mid': senicide_strategy_a_mid,
+ 'stop_local_strategy_a_mid': stop_strategy_a_mid,
+ 'restart_local_strategy_a_mid': restart_strategy_a_mid,
+ 'running_failure_local_strategy_a_mid': running_failure_strategy_a_mid})
+ tr_elt = create_element({'strategy_td_mid': strategy_td_mid})
# test call
- expected = [{'namespec': 'proc_1', 'rowspan': 2, 'identifier': '10.0.0.1', 'uptime': 12},
- {'namespec': 'proc_1', 'rowspan': 0, 'identifier': '10.0.0.2', 'uptime': 11},
- {'namespec': 'proc_2', 'rowspan': 2, 'identifier': '10.0.0.2', 'uptime': 11},
- {'namespec': 'proc_2', 'rowspan': 0, 'identifier': '10.0.0.3', 'uptime': 10}]
- actual = view.get_conciliation_data()
- # no direct method in pytest to compare 2 lists of dicts
- for actual_single in actual:
- assert any(actual_single == expected_single for expected_single in expected)
+ info = {'row_type': ProcessRowTypes.APPLICATION_PROCESS, 'namespec': 'proc_1', 'nb_items': 2}
+ view._write_conflict_strategies(tr_elt, info, False)
+ assert strategy_td_mid.attrib == {'class': 'brightened', 'rowspan': '3'}
+ assert view.view_ctx.format_url.call_args_list == [call('10.0.0.1', CONCILIATION_PAGE, namespec='proc_1',
+ action='senicide'),
+ call('10.0.0.1', CONCILIATION_PAGE, namespec='proc_1',
+ action='infanticide'),
+ call('10.0.0.1', CONCILIATION_PAGE, namespec='proc_1',
+ action='stop'),
+ call('10.0.0.1', CONCILIATION_PAGE, namespec='proc_1',
+ action='restart'),
+ call('10.0.0.1', CONCILIATION_PAGE, namespec='proc_1',
+ action='running_failure')]
+ assert infanticide_strategy_a_mid.attributes.call_args_list == [call(href='infanticide url')]
+ assert senicide_strategy_a_mid.attributes.call_args_list == [call(href='senicide url')]
+ assert stop_strategy_a_mid.attributes.call_args_list == [call(href='stop url')]
+ assert restart_strategy_a_mid.attributes.call_args_list == [call(href='restart url')]
+ assert running_failure_strategy_a_mid.attributes.call_args_list == [call(href='running_failure url')]
def test_stop_action(mocker, supvisors, view):
diff --git a/supvisors/ui/application.html b/supvisors/ui/application.html
index 7d465b49..038628c2 100644
--- a/supvisors/ui/application.html
+++ b/supvisors/ui/application.html
@@ -36,7 +36,7 @@
Supervisors
-
-
+
Supervisor
@@ -49,7 +49,7 @@
Supervisors
Applications
@@ -99,14 +99,14 @@
Starting strategies
@@ -116,7 +116,7 @@
@@ -128,14 +128,14 @@
@@ -144,12 +144,12 @@
-
+
diff --git a/supvisors/ui/conciliation.html b/supvisors/ui/conciliation.html
index 1f0c4272..a3b2dfa5 100644
--- a/supvisors/ui/conciliation.html
+++ b/supvisors/ui/conciliation.html
@@ -13,6 +13,7 @@
+
@@ -35,7 +36,7 @@ Supervisors
-
-
+
Supervisor
@@ -49,7 +50,7 @@
Applications
@@ -105,24 +106,24 @@
-
+
|
-
+
|
-
+
|
-
+
|
@@ -133,21 +134,11 @@
-
-
-
-
Conciliation Strategies
-
-
-
-
-
+
- Name |
+ Name |
Supervisor |
Uptime |
Actions |
@@ -156,29 +147,59 @@
- Name |
+ Name |
Supervisor |
Uptime |
Actions |
Strategy |
-
+
- -- |
- |
- -- |
- Stop |
- Keep |
- |
+ |
+ |
+ |
+ |
+ Stop |
+ Keep |
+
+
+ |
+
+
+
+
Global Conciliation Strategies
+
+
+
diff --git a/supvisors/ui/css/conciliation.css b/supvisors/ui/css/conciliation.css
index d30f78ed..8950a503 100644
--- a/supvisors/ui/css/conciliation.css
+++ b/supvisors/ui/css/conciliation.css
@@ -25,3 +25,21 @@
overflow: auto;
padding: 10px;
}
+
+/* update the background of the global strategies box */
+div.strategies {
+ background-image: linear-gradient(180deg, var(--light2-color), var(--light1-color));
+}
+
+div.strategies div.button_group {
+ background-image: none;
+}
+
+div.strategies div.button_group:not(:first-child) {
+ padding-top: 0;
+}
+
+/* update the area of the local strategies box */
+div.local_strategies div {
+ padding: 1px 3px;
+}
diff --git a/supvisors/ui/css/process_table.css b/supvisors/ui/css/process_table.css
index cec67d5d..5afe5834 100644
--- a/supvisors/ui/css/process_table.css
+++ b/supvisors/ui/css/process_table.css
@@ -28,15 +28,14 @@
}
/* process table */
-#process_left_side {
+.process_table {
font-family: Verdana, Arial, sans-serif;
text-align: justify;
margin-right: 5px;
overflow-y: auto;
}
-#process_left_side
-process_table thead, .process_table thead tr {
+.process_table thead, .process_table thead tr {
border: solid 1px var(--border-color);
}
@@ -110,7 +109,6 @@ tbody.hoverable tr:hover, tbody.hoverable tr:hover td:not(.state_cell) {
color: var(--selected-color);
}
-
.stats_contents {
flex: 0;
display: flex;
diff --git a/supvisors/web/viewconciliation.py b/supvisors/web/viewconciliation.py
index a8bdeea1..5e6dbde8 100644
--- a/supvisors/web/viewconciliation.py
+++ b/supvisors/web/viewconciliation.py
@@ -37,7 +37,7 @@ def __init__(self, context):
""" Call of the superclass constructors. """
MainView.__init__(self, context)
self.page_name: str = CONCILIATION_PAGE
- # get applicable conciliation strategies
+ # get applicable conciliation strategies (USER excluded)
self.strategies: List[str] = [x.name.lower() for x in ConciliationStrategies]
self.strategies.remove(ConciliationStrategies.USER.name.lower())
# process actions
@@ -53,24 +53,27 @@ def write_contents(self, contents_elt) -> None:
# Conciliation part
def write_conciliation_strategies(self, contents_elt):
""" Rendering of the global conciliation actions. """
- global_strategy_li_mid = contents_elt.findmeld('global_strategy_li_mid')
- for li_elt, item in global_strategy_li_mid.repeat(self.strategies):
- elt = li_elt.findmeld('global_strategy_a_mid')
+ for strategy in self.strategies:
+ elt = contents_elt.findmeld(f'{strategy}_strategy_a_mid')
# conciliation requests MUST be sent to MASTER and namespec MUST be reset
master = self.sup_ctx.master_identifier
- parameters = {NAMESPEC: '', ACTION: item}
+ parameters = {NAMESPEC: '', ACTION: strategy}
url = self.view_ctx.format_url(master, CONCILIATION_PAGE, **parameters)
elt.attributes(href=url)
- elt.content(item.title())
def get_conciliation_data(self):
""" Get information about all conflicting processes. """
- return [{'namespec': process.namespec,
- 'rowspan': len(process.running_identifiers) if idx == 0 else 0,
- 'identifier': identifier,
- 'uptime': process.info_map[identifier]['uptime']}
- for process in self.sup_ctx.conflicts()
- for idx, identifier in enumerate(sorted(process.running_identifiers))]
+ data = []
+ for process in self.sup_ctx.conflicts():
+ data.append({'row_type': ProcessRowTypes.APPLICATION_PROCESS,
+ 'namespec': process.namespec,
+ 'nb_items': len(process.running_identifiers)})
+ for identifier in sorted(process.running_identifiers):
+ data.append({'row_type': ProcessRowTypes.INSTANCE_PROCESS,
+ 'namespec': process.namespec,
+ 'identifier': identifier,
+ 'uptime': process.info_map[identifier]['uptime']})
+ return data
def write_conciliation_table(self, contents_elt):
""" Rendering of the conflicts table. """
@@ -79,45 +82,52 @@ def write_conciliation_table(self, contents_elt):
# get data for table
data = self.get_conciliation_data()
# create a row per conflict
- shaded_tr = True
- for tr_elt, item in conflicts_div_mid.findmeld('conflict_tr_mid').repeat(data):
- # first get the rowspan and change shade when rowspan is 0 (first line of conflict)
- rowspan = item['rowspan']
- if rowspan:
- shaded_tr = not shaded_tr
- # set row background
- apply_shade(tr_elt, shaded_tr)
- # write information and actions
- self._write_conflict_name(tr_elt, item, shaded_tr)
- self._write_conflict_identifier(tr_elt, item)
- self._write_conflict_uptime(tr_elt, item)
- self._write_conflict_process_actions(tr_elt, item)
- self._write_conflict_strategies(tr_elt, item, shaded_tr)
+ shaded_proc_tr, shaded_detail_tr = False, False # used to invert background style
+ for tr_elt, info in conflicts_div_mid.findmeld('conflict_tr_mid').repeat(data):
+ if info['row_type'] == ProcessRowTypes.APPLICATION_PROCESS:
+ self._write_conflict_process(tr_elt, info, shaded_proc_tr)
+ # set line background and invert
+ apply_shade(tr_elt, shaded_proc_tr)
+ shaded_proc_tr = not shaded_proc_tr
+ shaded_detail_tr = shaded_proc_tr
+ elif info['row_type'] == ProcessRowTypes.INSTANCE_PROCESS:
+ self._write_conflict_detail(tr_elt, info)
+ # set line background and invert
+ apply_shade(tr_elt, shaded_detail_tr)
+ shaded_detail_tr = not shaded_detail_tr
else:
# remove conflicts table
conflicts_div_mid.replace('No conflict')
- @staticmethod
- def _write_conflict_name(tr_elt, info, shaded_tr):
- """ In a conflicts table, write the process name in conflict. """
- elt = tr_elt.findmeld('name_td_mid')
- rowspan = info['rowspan']
- if rowspan > 0:
- namespec = info['namespec']
- elt.attrib['rowspan'] = str(rowspan)
- elt.content(namespec)
- # apply shade logic to td element too for background-image to work
- apply_shade(elt, shaded_tr)
- else:
- elt.replace('')
+ def _write_conflict_process(self, tr_elt, info, shaded_tr):
+ """ In a conflicts table, write the process entry in conflict. """
+ section_elt = tr_elt.findmeld('section_td_mid')
+ section_elt.attrib['rowspan'] = str(info['nb_items'] + 1)
+ apply_shade(section_elt, shaded_tr)
+ # write process row
+ tr_elt.findmeld('process_td_mid').content(info['namespec'])
+ tr_elt.findmeld('conflict_instance_td_mid').content('')
+ tr_elt.findmeld('pstop_td_mid').content('')
+ tr_elt.findmeld('pkeep_td_mid').content('')
+ self._write_conflict_strategies(tr_elt, info, shaded_tr)
+
+ def _write_conflict_detail(self, tr_elt, info):
+ """ In a conflicts table, write the process entry in conflict. """
+ tr_elt.findmeld('section_td_mid').replace('')
+ tr_elt.findmeld('process_td_mid').content(SUB_SYMBOL)
+ self._write_conflict_identifier(tr_elt, info)
+ self._write_conflict_uptime(tr_elt, info)
+ self._write_conflict_process_actions(tr_elt, info)
+ tr_elt.findmeld('strategy_td_mid').replace('')
def _write_conflict_identifier(self, tr_elt, info):
""" In a conflicts table, write the Supvisors instance identifier where runs the process in conflict. """
identifier = info['identifier']
+ nick_identifier = self.supvisors.mapper.get_nick_identifier(identifier)
elt = tr_elt.findmeld('conflict_instance_a_mid')
url = self.view_ctx.format_url(identifier, PROC_INSTANCE_PAGE)
elt.attributes(href=url)
- elt.content(identifier)
+ elt.content(nick_identifier)
@staticmethod
def _write_conflict_uptime(tr_elt, info):
@@ -128,7 +138,7 @@ def _write_conflict_uptime(tr_elt, info):
def _write_conflict_process_actions(self, tr_elt, info):
""" In a conflicts table, write the actions that can be requested on the process in conflict. """
namespec = info['namespec']
- identifier = info['identifier']
+ identifier = info.get('identifier', '')
for action in self.process_methods:
elt = tr_elt.findmeld(action + '_a_mid')
parameters = {NAMESPEC: namespec, IDENTIFIER: identifier, ACTION: action}
@@ -139,25 +149,19 @@ def _write_conflict_strategies(self, tr_elt, info, shaded_tr):
""" In a conflicts table, write the strategies that can be requested on the process in conflict. """
# extract info
namespec = info['namespec']
- rowspan = info['rowspan']
# update element structure
td_elt = tr_elt.findmeld('strategy_td_mid')
- if rowspan > 0:
- # apply shade logic to td element too for background-image to work
- apply_shade(td_elt, shaded_tr)
- # fill the strategies
- td_elt.attrib['rowspan'] = str(rowspan)
- strategy_iterator = td_elt.findmeld('local_strategy_li_mid').repeat(self.strategies)
- for li_elt, st_item in strategy_iterator:
- elt = li_elt.findmeld('local_strategy_a_mid')
- # conciliation requests MUST be sent to MASTER
- master = self.sup_ctx.master_identifier
- parameters = {NAMESPEC: namespec, ACTION: st_item}
- url = self.view_ctx.format_url(master, CONCILIATION_PAGE, **parameters)
- elt.attributes(href=url)
- elt.content(st_item.title())
- else:
- td_elt.replace('')
+ # apply shade logic to td element too for background-image to work
+ apply_shade(td_elt, shaded_tr)
+ # fill the strategies
+ td_elt.attrib['rowspan'] = str(info['nb_items'] + 1)
+ for strategy in self.strategies:
+ elt = td_elt.findmeld(f'{strategy}_local_strategy_a_mid')
+ # conciliation requests MUST be sent to the Supvisors Master
+ master = self.sup_ctx.master_identifier
+ parameters = {NAMESPEC: namespec, ACTION: strategy}
+ url = self.view_ctx.format_url(master, CONCILIATION_PAGE, **parameters)
+ elt.attributes(href=url)
def make_callback(self, namespec: str, action: str):
""" Triggers processing iaw action requested. """