def test_corosync_conf_not_set_need_offline_success( self, mock_get_corosync, mock_distribute, mock_is_running, mock_reload, mock_check_offline, mock_qdevice_reload): corosync_data = open(rc("corosync.conf")).read() new_corosync_data = corosync_data.replace("version: 2", "version: 3") mock_get_corosync.return_value = corosync_data mock_is_running.return_value = False env = LibraryEnvironment(self.mock_logger, self.mock_reporter) self.assertTrue(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(2, mock_get_corosync.call_count) conf_facade = CorosyncConfigFacade.from_string(new_corosync_data) conf_facade._need_stopped_cluster = True env.push_corosync_conf(conf_facade) mock_check_offline.assert_called_once_with("mock node communicator", self.mock_reporter, "mock node list", False) mock_distribute.assert_called_once_with("mock node communicator", self.mock_reporter, "mock node list", new_corosync_data, False) mock_reload.assert_not_called() mock_qdevice_reload.assert_not_called()
def set_options( lib_env: LibraryEnvironment, options, skip_offline_nodes=False, force=False, ): """ Set corosync quorum options, distribute and reload corosync.conf if live LibraryEnvironment lib_env dict options -- quorum options bool skip_offline_nodes -- continue even if not all nodes are accessible bool force -- force changes """ cfg = lib_env.get_corosync_conf() if lib_env.report_processor.report_list( corosync_conf_validators.update_quorum_options( options, cfg.has_quorum_device(), cfg.get_quorum_options())).has_errors: raise LibraryError() cfg.set_quorum_options(options) if lib_env.is_corosync_conf_live: _check_if_atb_can_be_disabled( lib_env.service_manager, lib_env.report_processor, cfg, cfg.is_enabled_auto_tie_breaker(), force, ) lib_env.push_corosync_conf(cfg, skip_offline_nodes)
def test_corosync_conf_not_set_need_offline_success( self, mock_get_corosync, mock_distribute, mock_is_running, mock_reload, mock_check_offline, mock_qdevice_reload ): corosync_data = open(rc("corosync.conf")).read() new_corosync_data = corosync_data.replace("version: 2", "version: 3") mock_get_corosync.return_value = corosync_data mock_is_running.return_value = False env = LibraryEnvironment(self.mock_logger, self.mock_reporter) self.assertTrue(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(2, mock_get_corosync.call_count) conf_facade = CorosyncConfigFacade.from_string(new_corosync_data) conf_facade._need_stopped_cluster = True env.push_corosync_conf(conf_facade) mock_check_offline.assert_called_once_with( "mock node communicator", self.mock_reporter, "mock node list", False ) mock_distribute.assert_called_once_with( "mock node communicator", self.mock_reporter, "mock node list", new_corosync_data, False ) mock_reload.assert_not_called() mock_qdevice_reload.assert_not_called()
def test_corosync_conf_set(self, mock_get_corosync, mock_distribute, mock_reload, mock_check_offline, mock_qdevice_reload): corosync_data = "totem {\n version: 2\n}\n" new_corosync_data = "totem {\n version: 3\n}\n" env = LibraryEnvironment(self.mock_logger, self.mock_reporter, corosync_conf_data=corosync_data) self.assertFalse(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(0, mock_get_corosync.call_count) env.push_corosync_conf( CorosyncConfigFacade.from_string(new_corosync_data)) self.assertEqual(0, mock_distribute.call_count) self.assertEqual(new_corosync_data, env.get_corosync_conf_data()) self.assertEqual(0, mock_get_corosync.call_count) mock_check_offline.assert_not_called() mock_reload.assert_not_called() mock_qdevice_reload.assert_not_called()
def test_corosync_conf_set( self, mock_get_corosync, mock_distribute, mock_reload, mock_check_offline, mock_qdevice_reload ): corosync_data = "totem {\n version: 2\n}\n" new_corosync_data = "totem {\n version: 3\n}\n" env = LibraryEnvironment( self.mock_logger, self.mock_reporter, corosync_conf_data=corosync_data ) self.assertFalse(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(0, mock_get_corosync.call_count) env.push_corosync_conf( CorosyncConfigFacade.from_string(new_corosync_data) ) self.assertEqual(0, mock_distribute.call_count) self.assertEqual(new_corosync_data, env.get_corosync_conf_data()) self.assertEqual(0, mock_get_corosync.call_count) mock_check_offline.assert_not_called() mock_reload.assert_not_called() mock_qdevice_reload.assert_not_called()
def test_corosync_conf_not_set( self, mock_get_corosync, mock_distribute, mock_reload ): corosync_data = open(rc("corosync.conf")).read() new_corosync_data = corosync_data.replace("version: 2", "version: 3") mock_get_corosync.return_value = corosync_data env = LibraryEnvironment(self.mock_logger, self.mock_reporter) self.assertTrue(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(2, mock_get_corosync.call_count) env.push_corosync_conf( CorosyncConfigFacade.from_string(new_corosync_data) ) mock_distribute.assert_called_once_with( "mock node communicator", self.mock_reporter, "mock node list", new_corosync_data, False ) mock_reload.assert_called_once_with("mock cmd runner")
def update_device( lib_env: LibraryEnvironment, model_options, generic_options, heuristics_options, force_options=False, skip_offline_nodes=False, ): """ Change quorum device settings, distribute and reload configs if live dict model_options -- model specific options dict generic_options -- generic quorum device options dict heuristics_options -- heuristics options bool force_options -- continue even if options are not valid bool skip_offline_nodes -- continue even if not all nodes are accessible """ cfg = lib_env.get_corosync_conf() if not cfg.has_quorum_device(): raise LibraryError(reports.qdevice_not_defined()) if lib_env.report_processor.report_list( corosync_conf_validators.update_quorum_device( cfg.get_quorum_device_model(), model_options, generic_options, heuristics_options, [node.nodeid for node in cfg.get_nodes()], force_options=force_options)).has_errors: raise LibraryError() cfg.update_quorum_device(model_options, generic_options, heuristics_options) if cfg.is_quorum_device_heuristics_enabled_with_no_exec(): lib_env.report_processor.report( reports.corosync_quorum_heuristics_enabled_with_no_exec()) lib_env.push_corosync_conf(cfg, skip_offline_nodes)
def remove_device(lib_env: LibraryEnvironment, skip_offline_nodes=False): """ Stop using quorum device, distribute and reload configs if live skip_offline_nodes continue even if not all nodes are accessible """ cfg = lib_env.get_corosync_conf() if not cfg.has_quorum_device(): raise LibraryError(reports.qdevice_not_defined()) model = cfg.get_quorum_device_model() cfg.remove_quorum_device() if lib_env.is_corosync_conf_live: report_processor = lib_env.report_processor # get nodes for communication cluster_nodes_names, report_list = get_existing_nodes_names( cfg, # Pcs is unable to communicate with nodes missing names. It cannot # send new corosync.conf to them. That might break the cluster. # Hence we error out. error_on_missing_name=True) if report_processor.report_list(report_list).has_errors: raise LibraryError() target_list = lib_env.get_node_target_factory().get_target_list( cluster_nodes_names, skip_non_existing=skip_offline_nodes, ) # fix quorum options for SBD to work properly if sbd.atb_has_to_be_enabled(lib_env.cmd_runner(), cfg): lib_env.report_processor.report( reports.corosync_quorum_atb_will_be_enabled_due_to_sbd()) cfg.set_quorum_options({"auto_tie_breaker": "1"}) # disable qdevice lib_env.report_processor.report( reports.service_disable_started("corosync-qdevice")) com_cmd_disable = qdevice_com.Disable(lib_env.report_processor, skip_offline_nodes) com_cmd_disable.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_disable) # stop qdevice lib_env.report_processor.report( reports.service_stop_started("corosync-qdevice")) com_cmd_stop = qdevice_com.Stop(lib_env.report_processor, skip_offline_nodes) com_cmd_stop.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_stop) # handle model specific configuration if model == "net": lib_env.report_processor.report( reports.qdevice_certificate_removal_started()) com_cmd_client_destroy = qdevice_net_com.ClientDestroy( lib_env.report_processor, skip_offline_nodes) com_cmd_client_destroy.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_client_destroy) lib_env.push_corosync_conf(cfg, skip_offline_nodes)
def test_corosync_conf_not_set_need_offline_fail( self, mock_get_corosync, mock_distribute, mock_reload, mock_check_offline, mock_qdevice_reload ): corosync_data = open(rc("corosync.conf")).read() new_corosync_data = corosync_data.replace("version: 2", "version: 3") mock_get_corosync.return_value = corosync_data def raiser(dummy_communicator, dummy_reporter, dummy_nodes, dummy_force): raise LibraryError( reports.corosync_not_running_check_node_error("test node") ) mock_check_offline.side_effect = raiser env = LibraryEnvironment(self.mock_logger, self.mock_reporter) self.assertTrue(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(2, mock_get_corosync.call_count) conf_facade = CorosyncConfigFacade.from_string(new_corosync_data) conf_facade._need_stopped_cluster = True assert_raise_library_error( lambda: env.push_corosync_conf(conf_facade), ( severity.ERROR, report_codes.COROSYNC_NOT_RUNNING_CHECK_NODE_ERROR, {"node": "test node"} ) ) mock_check_offline.assert_called_once_with( "mock node communicator", self.mock_reporter, "mock node list", False ) mock_distribute.assert_not_called() mock_reload.assert_not_called() mock_qdevice_reload.assert_not_called()
def test_corosync_conf_not_set_need_offline_fail(self, mock_get_corosync, mock_distribute, mock_reload, mock_check_offline, mock_qdevice_reload): corosync_data = open(rc("corosync.conf")).read() new_corosync_data = corosync_data.replace("version: 2", "version: 3") mock_get_corosync.return_value = corosync_data def raiser(dummy_communicator, dummy_reporter, dummy_nodes, dummy_force): raise LibraryError( reports.corosync_not_running_check_node_error("test node")) mock_check_offline.side_effect = raiser env = LibraryEnvironment(self.mock_logger, self.mock_reporter) self.assertTrue(env.is_corosync_conf_live) self.assertEqual(corosync_data, env.get_corosync_conf_data()) self.assertEqual(corosync_data, env.get_corosync_conf().config.export()) self.assertEqual(2, mock_get_corosync.call_count) conf_facade = CorosyncConfigFacade.from_string(new_corosync_data) conf_facade._need_stopped_cluster = True assert_raise_library_error( lambda: env.push_corosync_conf(conf_facade), (severity.ERROR, report_codes.COROSYNC_NOT_RUNNING_CHECK_NODE_ERROR, { "node": "test node" })) mock_check_offline.assert_called_once_with("mock node communicator", self.mock_reporter, "mock node list", False) mock_distribute.assert_not_called() mock_reload.assert_not_called() mock_qdevice_reload.assert_not_called()
def add_device( lib_env: LibraryEnvironment, model, model_options, generic_options, heuristics_options, force_model=False, force_options=False, skip_offline_nodes=False, ): # pylint: disable=too-many-locals """ Add a quorum device to a cluster, distribute and reload configs if live string model -- quorum device model dict model_options -- model specific options dict generic_options -- generic quorum device options dict heuristics_options -- heuristics options bool force_model -- continue even if the model is not valid bool force_options -- continue even if options are not valid bool skip_offline_nodes -- continue even if not all nodes are accessible """ cfg = lib_env.get_corosync_conf() if cfg.has_quorum_device(): raise LibraryError( ReportItem.error(reports.messages.QdeviceAlreadyDefined())) report_processor = lib_env.report_processor report_processor.report_list( corosync_conf_validators.add_quorum_device( model, model_options, generic_options, heuristics_options, [node.nodeid for node in cfg.get_nodes()], force_model=force_model, force_options=force_options, )) if lib_env.is_corosync_conf_live: cluster_nodes_names, report_list = get_existing_nodes_names( cfg, # Pcs is unable to communicate with nodes missing names. It cannot # send new corosync.conf to them. That might break the cluster. # Hence we error out. error_on_missing_name=True, ) report_processor.report_list(report_list) if report_processor.has_errors: raise LibraryError() cfg.add_quorum_device( model, model_options, generic_options, heuristics_options, ) if cfg.is_quorum_device_heuristics_enabled_with_no_exec(): lib_env.report_processor.report( ReportItem.warning( reports.messages.CorosyncQuorumHeuristicsEnabledWithNoExec())) # First setup certificates for qdevice, then send corosync.conf to nodes. # If anything fails, nodes will not have corosync.conf with qdevice in it, # so there is no effect on the cluster. if lib_env.is_corosync_conf_live: target_factory = lib_env.get_node_target_factory() target_list = target_factory.get_target_list( cluster_nodes_names, skip_non_existing=skip_offline_nodes, ) # Do model specific configuration. # If the model is not known to pcs and was forced, do not configure # anything else than corosync.conf, as we do not know what to do # anyway. if model == "net": qdevice_net.set_up_client_certificates( lib_env.cmd_runner(), lib_env.report_processor, lib_env.communicator_factory, # We are sure the "host" key is there, it has been validated # above. target_factory.get_target_from_hostname(model_options["host"]), cfg.get_cluster_name(), target_list, skip_offline_nodes, ) lib_env.report_processor.report( ReportItem.info( reports.messages.ServiceActionStarted( reports.const.SERVICE_ACTION_ENABLE, "corosync-qdevice"))) com_cmd = qdevice_com.Enable(lib_env.report_processor, skip_offline_nodes) com_cmd.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd) # everything set up, it's safe to tell the nodes to use qdevice lib_env.push_corosync_conf(cfg, skip_offline_nodes) # Now, when corosync.conf has been reloaded, we can start qdevice service. if lib_env.is_corosync_conf_live: lib_env.report_processor.report( ReportItem.info( reports.messages.ServiceActionStarted( reports.const.SERVICE_ACTION_START, "corosync-qdevice"))) com_cmd_start = qdevice_com.Start(lib_env.report_processor, skip_offline_nodes) com_cmd_start.set_targets(target_list) run_and_raise(lib_env.get_node_communicator(), com_cmd_start)