class CibPushProxy(TestCase): def setUp(self): self.env = LibraryEnvironment(mock.MagicMock(logging.Logger), MockLibraryReportProcessor()) get_cib_patcher = patch_env_object("get_cib", lambda self: "<cib />") self.addCleanup(get_cib_patcher.stop) get_cib_patcher.start() def test_push_loaded(self, mock_push_full, mock_push_diff): self.env.get_cib() self.env.push_cib() mock_push_full.assert_not_called() mock_push_diff.assert_called_once_with(False) def test_push_loaded_wait(self, mock_push_full, mock_push_diff): self.env.get_cib() self.env.push_cib(wait=10) mock_push_full.assert_not_called() mock_push_diff.assert_called_once_with(10) def test_push_custom(self, mock_push_full, mock_push_diff): self.env.get_cib() self.env.push_cib(custom_cib="<cib />") mock_push_full.assert_called_once_with("<cib />", False) mock_push_diff.assert_not_called() def test_push_custom_wait(self, mock_push_full, mock_push_diff): self.env.get_cib() self.env.push_cib(custom_cib="<cib />", wait=10) mock_push_full.assert_called_once_with("<cib />", 10) mock_push_diff.assert_not_called()
def remove_from_cluster( env: LibraryEnvironment, resource_remove, instance_name=None, allow_remove_multiple=False, ): """ Remove group with ip resource and booth resource env -- provides all for communication with externals function resource_remove -- provisional hack til resources are moved to lib string instance_name -- booth instance name bool allow_remove_multiple -- remove all resources if more than one found """ # TODO resource_remove is provisional hack til resources are moved to lib report_processor = env.report_processor booth_env = env.get_booth_env(instance_name) # This command does not work with booth config files at all, let's reject # them then. _ensure_live_booth_env(booth_env) resource.get_remover(resource_remove)( _find_resource_elements_for_operation( report_processor, get_resources(env.get_cib()), booth_env, allow_remove_multiple, ) )
def config( env: LibraryEnvironment, tag_filter: Sequence[str], ) -> Iterable[Dict[str, Iterable[str]]]: """ Get tags specified in tag_filter or if empty, then get all the tags configured. env -- provides all for communication with externals tag_filter -- list of tags we want to get """ tags_section: _Element = get_tags(env.get_cib(REQUIRED_CIB_VERSION)) if tag_filter: tag_element_list, report_list = tag.find_tag_elements_by_ids( tags_section, tag_filter, ) if env.report_processor.report_list(report_list).has_errors: raise LibraryError() else: tag_element_list = tag.get_list_of_tag_elements(tags_section) return [ tag.tag_element_to_dict(tag_element) for tag_element in tag_element_list ]
def restart( env: LibraryEnvironment, resource_restart, instance_name=None, allow_multiple=False, ): """ Restart group with ip resource and booth resource env -- provides all for communication with externals function resource_restart -- provisional hack til resources are moved to lib string instance_name -- booth instance name bool allow_remove_multiple -- remove all resources if more than one found """ # TODO resource_remove is provisional hack til resources are moved to lib report_processor = env.report_processor booth_env = env.get_booth_env(instance_name) _ensure_live_env(env, booth_env) for booth_element in _find_resource_elements_for_operation( report_processor, get_resources(env.get_cib()), booth_env, allow_multiple, ): resource_restart([booth_element.attrib["id"]])
def _ticket_operation( operation, env: LibraryEnvironment, ticket_name, site_ip, instance_name ): booth_env = env.get_booth_env(instance_name) _ensure_live_env(env, booth_env) if not site_ip: site_ip_list = resource.find_bound_ip( get_resources(env.get_cib()), booth_env.config_path ) if len(site_ip_list) != 1: raise LibraryError( ReportItem.error( reports.messages.BoothCannotDetermineLocalSiteIp() ) ) site_ip = site_ip_list[0] stdout, stderr, return_code = env.cmd_runner().run( [settings.booth_binary, operation, "-s", site_ip, ticket_name] ) if return_code != 0: raise LibraryError( ReportItem.error( reports.messages.BoothTicketOperationFailed( operation, join_multilines([stderr, stdout]), site_ip, ticket_name, ) ) )
def node_remove_guest( env: LibraryEnvironment, node_identifier, skip_offline_nodes=False, allow_remove_multiple_nodes=False, allow_pacemaker_remote_service_fail=False, wait: WaitType = False, ): """ remove a resource representing remote node and destroy remote node LibraryEnvironment env provides all for communication with externals string node_identifier -- node name, hostname or resource id bool skip_offline_nodes -- a flag for ignoring when some nodes are offline bool allow_remove_multiple_nodes -- is a flag for allowing remove unexpected multiple occurrence of remote node for node_identifier bool allow_pacemaker_remote_service_fail -- is a flag for allowing successfully finish this command even if stoping/disabling pacemaker_remote not succeeded """ wait_timeout = env.ensure_wait_satisfiable(wait) cib = env.get_cib() resource_element_list = _find_resources_to_remove( cib, env.report_processor, "guest", node_identifier, allow_remove_multiple_nodes, guest_node.find_node_resources, ) node_names_list = sorted( { guest_node.get_node_name_from_resource(node_element) for node_element in resource_element_list } ) if not env.is_cib_live: env.report_processor.report_list( _report_skip_live_parts_in_remove(node_names_list) ) else: _destroy_pcmk_remote_env( env, node_names_list, skip_offline_nodes, allow_pacemaker_remote_service_fail, ) for resource_element in resource_element_list: guest_node.unset_guest(resource_element) env.push_cib(wait_timeout=wait_timeout) # remove node from pcmk caches if env.is_cib_live: for node_name in node_names_list: remove_node(env.cmd_runner(), node_name)
def add_level(lib_env: LibraryEnvironment, level, target_type, target_value, devices, force_device=False, force_node=False): """ Validate and add a new fencing level LibraryEnvironment lib_env -- environment int|string level -- level (index) of the new fencing level constant target_type -- the new fencing level target value type mixed target_value -- the new fencing level target value Iterable devices -- list of stonith devices for the new fencing level bool force_device -- continue even if a stonith device does not exist bool force_node -- continue even if a node (target) does not exist """ version_check = None if target_type == TARGET_TYPE_REGEXP: version_check = Version(2, 3, 0) elif target_type == TARGET_TYPE_ATTRIBUTE: version_check = Version(2, 4, 0) cib = lib_env.get_cib(version_check) cib_fencing_topology.add_level( lib_env.report_processor, get_fencing_topology(cib), get_resources(cib), level, target_type, target_value, devices, ClusterState(get_cluster_status_xml( lib_env.cmd_runner())).node_section.nodes, force_device, force_node) if lib_env.report_processor.has_errors: raise LibraryError() lib_env.push_cib()
def create_alert( lib_env: LibraryEnvironment, alert_id, path, instance_attribute_dict, meta_attribute_dict, description=None, ): """ Create new alert. Raises LibraryError if path is not specified, or any other failure. lib_env -- LibraryEnvironment alert_id -- id of alert to be created, if None it will be generated path -- path to script for alert instance_attribute_dict -- dictionary of instance attributes meta_attribute_dict -- dictionary of meta attributes description -- alert description description """ if not path: raise LibraryError( ReportItem.error( reports.messages.RequiredOptionsAreMissing(["path"]))) cib = lib_env.get_cib(REQUIRED_CIB_VERSION) id_provider = IdProvider(cib) alert_el = alert.create_alert(cib, alert_id, path, description) arrange_first_instance_attributes(alert_el, instance_attribute_dict, id_provider) arrange_first_meta_attributes(alert_el, meta_attribute_dict, id_provider) lib_env.push_cib()
def update_alert( lib_env: LibraryEnvironment, alert_id, path, instance_attribute_dict, meta_attribute_dict, description=None, ): """ Update existing alert with specified id. lib_env -- LibraryEnvironment alert_id -- id of alert to be updated path -- new path, if None old value will stay unchanged instance_attribute_dict -- dictionary of instance attributes to update meta_attribute_dict -- dictionary of meta attributes to update description -- new description, if empty string, old description will be deleted, if None old value will stay unchanged """ cib = lib_env.get_cib(REQUIRED_CIB_VERSION) id_provider = IdProvider(cib) alert_el = alert.update_alert(cib, alert_id, path, description) arrange_first_instance_attributes(alert_el, instance_attribute_dict, id_provider) arrange_first_meta_attributes(alert_el, meta_attribute_dict, id_provider) lib_env.push_cib()
def _defaults_create( env: LibraryEnvironment, cib_section_name: str, validator_options: Mapping[str, Any], nvpairs: Mapping[str, str], nvset_options: Mapping[str, str], nvset_rule: Optional[str] = None, force_flags: Optional[Container] = None, ) -> None: if force_flags is None: force_flags = set() force = (reports.codes.FORCE in force_flags) or (reports.codes.FORCE_OPTIONS in force_flags) required_cib_version = None nice_to_have_cib_version = None if nvset_rule: # Parse the rule to see if we need to upgrade CIB schema. All errors # would be properly reported by a validator called bellow, so we can # safely ignore them here. try: rule_tree = parse_rule(nvset_rule) if has_rsc_or_op_expression(rule_tree): required_cib_version = Version(3, 4, 0) if has_node_attr_expr_with_type_integer(rule_tree): nice_to_have_cib_version = Version(3, 5, 0) except RuleParseError: pass cib = env.get_cib( minimal_version=required_cib_version, nice_to_have_version=nice_to_have_cib_version, ) id_provider = IdProvider(cib) validator = nvpair_multi.ValidateNvsetAppendNew( id_provider, nvpairs, nvset_options, nvset_rule=nvset_rule, **validator_options, ) if env.report_processor.report_list( validator.validate(force_options=force)).has_errors: raise LibraryError() nvpair_multi.nvset_append_new( sections.get(cib, cib_section_name), id_provider, get_pacemaker_version_by_which_cib_was_validated(cib), nvpair_multi.NVSET_META, nvpairs, nvset_options, nvset_rule=validator.get_parsed_rule(), ) env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) env.push_cib()
def remove_all_levels(lib_env: LibraryEnvironment): """ Remove all fencing levels LibraryEnvironment lib_env -- environment """ cib_fencing_topology.remove_all_levels( get_fencing_topology(lib_env.get_cib())) lib_env.push_cib()
def get_all_alerts(lib_env: LibraryEnvironment): """ Returns list of all alerts. See docs of pcs.lib.cib.alert.get_all_alerts for description of data format. lib_env -- LibraryEnvironment """ return alert.get_all_alerts(lib_env.get_cib())
def cib_runner_nodes(lib_env: LibraryEnvironment, wait: WaitType): wait_timeout = lib_env.ensure_wait_satisfiable(wait) yield ( lib_env.get_cib(), lib_env.cmd_runner(), ClusterState(lib_env.get_cluster_state()).node_section.nodes, ) lib_env.push_cib(wait_timeout=wait_timeout)
def test_get_cib_no_version_live(self, mock_get_cib_xml, mock_ensure_cib_version): mock_get_cib_xml.return_value = '<cib/>' env = LibraryEnvironment(self.mock_logger, self.mock_reporter) assert_xml_equal('<cib/>', etree.tostring(env.get_cib()).decode()) self.assertEqual(1, mock_get_cib_xml.call_count) self.assertEqual(0, mock_ensure_cib_version.call_count) self.assertFalse(env.cib_upgraded)
def _defaults_config( env: LibraryEnvironment, cib_section_name: str, ) -> List[CibNvsetDto]: return [ nvpair_multi.nvset_element_to_dto(nvset_el) for nvset_el in nvpair_multi.find_nvsets( sections.get(env.get_cib(), cib_section_name)) ]
def test_get_cib_no_version_live( self, mock_get_cib_xml, mock_ensure_cib_version ): mock_get_cib_xml.return_value = '<cib/>' env = LibraryEnvironment(self.mock_logger, self.mock_reporter) assert_xml_equal('<cib/>', etree.tostring(env.get_cib()).decode()) self.assertEqual(1, mock_get_cib_xml.call_count) self.assertEqual(0, mock_ensure_cib_version.call_count) self.assertFalse(env.cib_upgraded)
def test_get_cib_upgrade_live(self, mock_get_cib_xml, mock_ensure_cib_version): mock_get_cib_xml.return_value = '<cib/>' mock_ensure_cib_version.return_value = etree.XML('<new_cib/>') env = LibraryEnvironment(self.mock_logger, self.mock_reporter) assert_xml_equal('<new_cib/>', etree.tostring(env.get_cib((1, 2, 3))).decode()) self.assertEqual(1, mock_get_cib_xml.call_count) self.assertEqual(1, mock_ensure_cib_version.call_count) self.assertTrue(env.cib_upgraded)
def _defaults_remove(env: LibraryEnvironment, cib_section_name: str, nvset_id_list: Iterable[str]) -> None: if not nvset_id_list: return nvset_elements, report_list = nvpair_multi.find_nvsets_by_ids( sections.get(env.get_cib(), cib_section_name), nvset_id_list) if env.report_processor.report_list(report_list).has_errors: raise LibraryError() nvpair_multi.nvset_remove(nvset_elements) env.push_cib()
def get_config(lib_env: LibraryEnvironment): """ Get fencing levels configuration. Return a list of levels where each level is a dict with keys: target_type, target_value. level and devices. Devices is a list of stonith device ids. LibraryEnvironment lib_env -- environment """ cib = lib_env.get_cib() return cib_fencing_topology.export(get_fencing_topology(cib))
def test_get_cib_upgrade_live( self, mock_get_cib_xml, mock_ensure_cib_version ): mock_get_cib_xml.return_value = '<cib/>' mock_ensure_cib_version.return_value = etree.XML('<new_cib/>') env = LibraryEnvironment(self.mock_logger, self.mock_reporter) assert_xml_equal( '<new_cib/>', etree.tostring(env.get_cib((1, 2, 3))).decode() ) self.assertEqual(1, mock_get_cib_xml.call_count) self.assertEqual(1, mock_ensure_cib_version.call_count) self.assertTrue(env.cib_upgraded)
def test_get_cib_upgrade_live(self, mock_get_cib_xml, mock_ensure_cib_version): mock_get_cib_xml.return_value = '<cib/>' mock_ensure_cib_version.return_value = etree.XML('<new_cib/>') env = LibraryEnvironment(self.mock_logger, self.mock_reporter) assert_xml_equal('<new_cib/>', etree.tostring(env.get_cib((1, 2, 3))).decode()) self.assertEqual(1, mock_get_cib_xml.call_count) self.assertEqual(1, mock_ensure_cib_version.call_count) assert_report_item_list_equal( env.report_processor.report_item_list, [(severity.INFO, report_codes.CIB_UPGRADE_SUCCESSFUL, {})]) self.assertTrue(env.cib_upgraded)
def verify(lib_env: LibraryEnvironment): """ Check if all cluster nodes and stonith devices used in fencing levels exist LibraryEnvironment lib_env -- environment """ cib = lib_env.get_cib() lib_env.report_processor.report_list( cib_fencing_topology.verify( get_fencing_topology(cib), get_resources(cib), ClusterState(get_cluster_status_xml( lib_env.cmd_runner())).node_section.nodes)) if lib_env.report_processor.has_errors: raise LibraryError()
def update_scsi_devices_add_remove( env: LibraryEnvironment, stonith_id: str, add_device_list: Iterable[str], remove_device_list: Iterable[str], force_flags: Container[reports.types.ForceCode] = (), ) -> None: """ Update scsi fencing devices without restart and affecting other resources. env -- provides all for communication with externals stonith_id -- id of stonith resource add_device_list -- paths to the scsi devices that would be added to the stonith resource remove_device_list -- paths to the scsi devices that would be removed from the stonith resource force_flags -- list of flags codes """ runner = env.cmd_runner() ( stonith_el, current_device_list, ) = _update_scsi_devices_get_element_and_devices(runner, env.report_processor, env.get_cib(), stonith_id) if env.report_processor.report_list( validate_add_remove_items( add_device_list, remove_device_list, current_device_list, reports.const.ADD_REMOVE_CONTAINER_TYPE_STONITH_RESOURCE, reports.const.ADD_REMOVE_ITEM_TYPE_DEVICE, stonith_el.get("id", ""), )).has_errors: raise LibraryError() updated_device_set = (set(current_device_list).union( add_device_list).difference(remove_device_list)) resource.stonith.update_scsi_devices_without_restart( env.cmd_runner(), env.get_cluster_state(), stonith_el, IdProvider(stonith_el), updated_device_set, ) _unfencing_scsi_devices(env, stonith_el, current_device_list, updated_device_set, force_flags) env.push_cib()
def add_recipient( lib_env: LibraryEnvironment, alert_id, recipient_value, instance_attribute_dict, meta_attribute_dict, recipient_id=None, description=None, allow_same_value=False ): """ Add new recipient to alert witch id alert_id. lib_env -- LibraryEnvironment alert_id -- id of alert to which new recipient should be added recipient_value -- value of new recipient instance_attribute_dict -- dictionary of instance attributes to update meta_attribute_dict -- dictionary of meta attributes to update recipient_id -- id of new recipient, if None it will be generated description -- recipient description allow_same_value -- if True unique recipient value is not required """ if not recipient_value: raise LibraryError( reports.required_options_are_missing(["value"]) ) cib = lib_env.get_cib(REQUIRED_CIB_VERSION) id_provider = IdProvider(cib) recipient = alert.add_recipient( lib_env.report_processor, cib, alert_id, recipient_value, recipient_id=recipient_id, description=description, allow_same_value=allow_same_value ) arrange_first_instance_attributes( recipient, instance_attribute_dict, id_provider ) arrange_first_meta_attributes( recipient, meta_attribute_dict, id_provider ) lib_env.push_cib()
def remove_recipient(lib_env: LibraryEnvironment, recipient_id_list): """ Remove specified recipients. lib_env -- LibraryEnvironment recipient_id_list -- list of recipients ids to be removed """ cib = lib_env.get_cib(REQUIRED_CIB_VERSION) report_list: ReportItemList = [] for recipient_id in recipient_id_list: try: alert.remove_recipient(cib, recipient_id) except LibraryError as e: report_list += e.args if lib_env.report_processor.report_list(report_list).has_errors: raise LibraryError() lib_env.push_cib()
def remove_alert(lib_env: LibraryEnvironment, alert_id_list): """ Remove alerts with specified ids. lib_env -- LibraryEnvironment alert_id_list -- list of alerts ids which should be removed """ cib = lib_env.get_cib(REQUIRED_CIB_VERSION) report_list: ReportItemList = [] for alert_id in alert_id_list: try: alert.remove_alert(cib, alert_id) except LibraryError as e: report_list += e.args if lib_env.report_processor.report_list(report_list).has_errors: raise LibraryError() lib_env.push_cib()
def update_recipient( lib_env: LibraryEnvironment, recipient_id, instance_attribute_dict, meta_attribute_dict, recipient_value=None, description=None, allow_same_value=False ): """ Update existing recipient. lib_env -- LibraryEnvironment recipient_id -- id of recipient to be updated instance_attribute_dict -- dictionary of instance attributes to update meta_attribute_dict -- dictionary of meta attributes to update recipient_value -- new recipient value, if None old value will stay unchanged description -- new description, if empty string, old description will be deleted, if None old value will stay unchanged allow_same_value -- if True unique recipient value is not required """ if not recipient_value and recipient_value is not None: raise LibraryError( reports.cib_alert_recipient_invalid_value(recipient_value) ) cib = lib_env.get_cib(REQUIRED_CIB_VERSION) id_provider = IdProvider(cib) recipient = alert.update_recipient( lib_env.report_processor, cib, recipient_id, recipient_value=recipient_value, description=description, allow_same_value=allow_same_value ) arrange_first_instance_attributes( recipient, instance_attribute_dict, id_provider ) arrange_first_meta_attributes( recipient, meta_attribute_dict, id_provider ) lib_env.push_cib()
def _defaults_create( env: LibraryEnvironment, cib_section_name: str, validator_options: Mapping[str, Any], nvpairs: Mapping[str, str], nvset_options: Mapping[str, str], nvset_rule: Optional[str] = None, force_flags: Optional[Container] = None, ) -> None: if force_flags is None: force_flags = set() force = (reports.codes.FORCE in force_flags) or (reports.codes.FORCE_OPTIONS in force_flags) required_cib_version = None if nvset_rule: required_cib_version = Version(3, 4, 0) cib = env.get_cib(required_cib_version) id_provider = IdProvider(cib) validator = nvpair_multi.ValidateNvsetAppendNew( id_provider, nvpairs, nvset_options, nvset_rule=nvset_rule, **validator_options, ) if env.report_processor.report_list( validator.validate(force_options=force)).has_errors: raise LibraryError() nvpair_multi.nvset_append_new( sections.get(cib, cib_section_name), id_provider, nvpair_multi.NVSET_META, nvpairs, nvset_options, nvset_rule=validator.get_parsed_rule(), ) env.report_processor.report( ReportItem.warning(reports.messages.DefaultsCanBeOverriden())) env.push_cib()
def test_get_cib_upgrade_live( self, mock_get_cib_xml, mock_ensure_cib_version ): mock_get_cib_xml.return_value = '<cib/>' mock_ensure_cib_version.return_value = etree.XML('<new_cib/>') env = LibraryEnvironment(self.mock_logger, self.mock_reporter) assert_xml_equal( '<new_cib/>', etree.tostring(env.get_cib((1, 2, 3))).decode() ) self.assertEqual(1, mock_get_cib_xml.call_count) self.assertEqual(1, mock_ensure_cib_version.call_count) assert_report_item_list_equal( env.report_processor.report_item_list, [( severity.INFO, report_codes.CIB_UPGRADE_SUCCESSFUL, {} )] ) self.assertTrue(env.cib_upgraded)
def get_resource_relations_tree( env: LibraryEnvironment, resource_id: str, ) -> Mapping[str, Any]: """ Return a dict representing tree-like structure of resources and their relations. env -- library environment resource_id -- id of a resource which should be the root of the relation tree """ cib = env.get_cib() _find_resources_or_raise(get_resources(cib), [resource_id]) resources_dict, relations_dict = ( resource.relations.ResourceRelationsFetcher(cib).get_relations( resource_id)) return resource.relations.ResourceRelationTreeBuilder( resources_dict, relations_dict).get_tree(resource_id).to_dto().to_dict()
def update_scsi_devices( env: LibraryEnvironment, stonith_id: str, set_device_list: Iterable[str], force_flags: Container[reports.types.ForceCode] = (), ) -> None: """ Update scsi fencing devices without restart and affecting other resources. env -- provides all for communication with externals stonith_id -- id of stonith resource set_device_list -- paths to the scsi devices that would be set for stonith resource force_flags -- list of flags codes """ if not set_device_list: env.report_processor.report( ReportItem.error( reports.messages.InvalidOptionValue("devices", "", None, cannot_be_empty=True))) runner = env.cmd_runner() ( stonith_el, current_device_list, ) = _update_scsi_devices_get_element_and_devices(runner, env.report_processor, env.get_cib(), stonith_id) if env.report_processor.has_errors: raise LibraryError() resource.stonith.update_scsi_devices_without_restart( runner, env.get_cluster_state(), stonith_el, IdProvider(stonith_el), set_device_list, ) _unfencing_scsi_devices(env, stonith_el, current_device_list, set_device_list, force_flags) env.push_cib()
def operation_defaults_config(env: LibraryEnvironment, evaluate_expired: bool) -> CibDefaultsDto: """ List all operation defaults nvsets env -- evaluate_expired -- also evaluate whether rules are expired or in effect """ cib = env.get_cib() rule_evaluator = _get_rule_evaluator(cib, env.cmd_runner(), env.report_processor, evaluate_expired) get_config = lambda tag: _defaults_config( cib, tag, sections.OP_DEFAULTS, rule_evaluator, ) return CibDefaultsDto( instance_attributes=get_config(nvpair_multi.NVSET_INSTANCE), meta_attributes=get_config(nvpair_multi.NVSET_META), )
def _defaults_config(env: LibraryEnvironment, cib_section_name: str, evaluate_expired: bool) -> List[CibNvsetDto]: runner = env.cmd_runner() cib = env.get_cib() if evaluate_expired: if has_rule_in_effect_status_tool(): in_effect_eval: RuleInEffectEval = RuleInEffectEvalOneByOne( cib, runner) else: in_effect_eval = RuleInEffectEvalDummy() env.report_processor.report( ReportItem.warning(reports.messages. RuleInEffectStatusDetectionNotSupported())) else: in_effect_eval = RuleInEffectEvalDummy() return [ nvpair_multi.nvset_element_to_dto(nvset_el, in_effect_eval) for nvset_el in nvpair_multi.find_nvsets( sections.get(cib, cib_section_name)) ]