def _validate_tag_create_idref_list_not_empty( idref_list: Iterable[str], ) -> ReportItemList: """ Validate that list of reference ids for tag create is not empty. idref_list -- reference ids to validate """ # list for emptiness check (issue with some iterables like iterator) idref_list = list(idref_list) if not idref_list: return [ ReportItem.error( reports.messages.TagCannotCreateEmptyTagNoIdsSpecified()) ] return []
def validate_stonith_restartless_update( cib: _Element, stonith_id: str, ) -> Tuple[Optional[_Element], ReportItemList]: """ Validate that stonith device exists and its type is supported for restartless update of scsi devices and has defined option 'devices'. cib -- cib element stonith_id -- id of a stonith resource """ stonith_el, report_list = resource.common.find_one_resource( cib, stonith_id, resource_tags=[TAG_PRIMITIVE]) if stonith_el is None: return stonith_el, report_list stonith_type = stonith_el.get("type", "") if (stonith_el.get("class", "") != "stonith" or stonith_el.get("provider", "") != "" or stonith_type not in SUPPORTED_RESOURCE_TYPES_FOR_RESTARLESS_UPDATE): report_list.append( ReportItem.error( reports.messages.StonithRestartlessUpdateUnsupportedAgent( stonith_id, stonith_type, SUPPORTED_RESOURCE_TYPES_FOR_RESTARLESS_UPDATE, ))) return stonith_el, report_list if not get_value(INSTANCE_ATTRIBUTES_TAG, stonith_el, "devices"): report_list.append( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( "no devices option configured for stonith device " f"'{stonith_id}'"))) return stonith_el, report_list
def _validate_ids_can_be_added_or_moved( self, resources_section: _Element, ) -> ReportItemList: """ Validate that ids can be added or moved: - there are no duplicate ids specified - ids belong to elements allowed to be put in a tag in case of adding - ids do not exist in the tag if no adjacent id was specified Save a list of created (for ids newly added to the tag) and found (for ids already existing in the tag) obj_ref elements keeping the order of given ids to add or move them in case of success validation. """ report_list: ReportItemList = [] unique_add_ids = list(OrderedDict.fromkeys(self._add_idref_list)) if self._add_idref_list: # report duplicate ids report_list.extend( _validate_add_remove_duplicate_reference_ids( self._add_idref_list, )) # report if references not found or belongs to unexpected types report_list.extend( _validate_reference_ids_are_resources( resources_section, unique_add_ids, )) if self._tag_element is not None and self._add_idref_list: existing_element_id_list = [] for id_ref in unique_add_ids: obj_ref = self._find_obj_ref_in_tag(id_ref) if obj_ref is None: obj_ref = etree.Element(TAG_OBJREF, id=id_ref) else: existing_element_id_list.append(id_ref) self._add_obj_ref_element_list.append(obj_ref) # report if a reference id exists in tag and no adjacent reference # id was specified if self._adjacent_idref is None and existing_element_id_list: report_list.append( ReportItem.error( # pylint: disable=line-too-long reports.messages. TagCannotAddReferenceIdsAlreadyInTheTag( self._tag_id, sorted(existing_element_id_list), ))) return report_list
def validate(self, option_dict): return [ ReportItem.error( reports.messages.PrerequisiteOptionIsMissing( option_name, self._prerequisite_name, self._option_type, self._prerequisite_type, ) ) for option_name in self._option_name_list if ( option_name in option_dict and self._prerequisite_name not in option_dict ) ]
def booth_ticket_operation_failed(operation, reason, site_ip, ticket_name): """ Pcs uses external booth tools for some ticket_name operations. For example grand and revoke. But the external command failed. string operatin determine what was intended perform with ticket_name string reason is taken from external booth command string site_ip specifiy what site had to run the command string ticket_name specify with which ticket had to run the command """ return ReportItem.error(report_codes.BOOTH_TICKET_OPERATION_FAILED, info={ "operation": operation, "reason": reason, "site_ip": site_ip, "ticket_name": ticket_name, })
def test_file_error(self, mock_config): node = "node" reason = "reason" mock_config.side_effect = LibraryError( ReportItem.error( reports.messages.UnableToGetSbdConfig(node, reason))) assert_raise_library_error( lambda: cmd_sbd.get_local_sbd_config(self.mock_env), ( Severities.ERROR, reports.codes.UNABLE_TO_GET_SBD_CONFIG, { "node": node, "reason": reason, }, ), )
def validate(self, option_dict: TypeOptionMap) -> ReportItemList: not_valid_options = [ name for name in option_dict if corosync_constants.OPTION_NAME_RE.fullmatch(name) is None ] if not_valid_options: # We must be strict and do not allow to override this validation, # otherwise setting a cratfed option name could be misused for # setting arbitrary corosync.conf settings. return [ ReportItem.error( reports.messages.InvalidUserdefinedOptions( sorted(not_valid_options), "a-z A-Z 0-9 /_-", self._option_type, )) ] return []
def test_file_error(self): node = "node" reason = "reason" self.config.fs.open( settings.sbd_config, side_effect=LibraryError( ReportItem.error( reports.messages.UnableToGetSbdConfig(node, reason))), ) assert_raise_library_error( lambda: cmd_sbd.get_local_sbd_config(self.env_assist.get_env()), ( Severities.ERROR, reports.codes.UNABLE_TO_GET_SBD_CONFIG, { "node": node, "reason": reason, }, ), )
def _validate_add_remove_duplicate_reference_ids( idref_list: Iterable[str], add_or_not_remove: bool = True, ) -> ReportItemList: """ Validate that idref_list does not contain duplicates. idref_list -- reference ids which we want to tag add_or_not_remove -- flag for add/remove action """ duplicate_ids_list = [ id for id, count in Counter(idref_list).items() if count > 1 ] if duplicate_ids_list: return [ ReportItem.error( reports.messages.TagAddRemoveIdsDuplication( sorted(duplicate_ids_list), add_or_not_remove, ) ) ] return []
def _find_resources_to_remove( cib, report_processor: ReportProcessor, node_type, node_identifier, allow_remove_multiple_nodes, find_resources, ): resource_element_list = find_resources(get_resources(cib), node_identifier) if not resource_element_list: raise LibraryError( ReportItem.error( reports.messages.NodeNotFound(node_identifier, [node_type]) ) ) if len(resource_element_list) > 1: if report_processor.report( ReportItem( severity=reports.item.get_severity( reports.codes.FORCE, allow_remove_multiple_nodes, ), message=reports.messages.MultipleResultsFound( "resource", [ resource.attrib["id"] for resource in resource_element_list ], node_identifier, ), ) ).has_errors: raise LibraryError() return resource_element_list
def _validate_value(self, value: ValuePair) -> ReportItemList: if not isinstance(value.normalized, str): return [] forbidden_characters = "{}\n\r" if set(value.normalized) & set(forbidden_characters): # We must be strict and do not allow to override this validation, # otherwise setting a cratfed option value could be misused for # setting arbitrary corosync.conf settings. return [ ReportItem.error( reports.messages.InvalidOptionValue( self._get_option_name_for_report(), value.original, None, # Make it actually print "\n" and "\r" strings instead # of going to the next line. # Let the user know all # forbidden characters right away. Do not let them try # them one by one by only reporting those actually used # in the value. forbidden_characters="{}\\n\\r", )) ] return []
def update_scsi_devices_without_restart( runner: CommandRunner, cluster_state: _Element, resource_el: _Element, id_provider: IdProvider, devices_list: Iterable[str], ) -> None: """ Update scsi devices without restart of stonith resource or other resources. runner -- command runner instance cluster_state -- status of the cluster resource_el -- resource element being updated id_provider -- elements' ids generator device_list -- list of updated scsi devices """ resource_id = resource_el.get("id", "") roles_with_nodes = get_resource_state(cluster_state, resource_id) if "Started" not in roles_with_nodes: raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( f"resource '{resource_id}' is not running on any node", reason_type=reports.const. STONITH_RESTARTLESS_UPDATE_UNABLE_TO_PERFORM_REASON_NOT_RUNNING, ))) if len(roles_with_nodes["Started"]) != 1: # TODO: do we want to be able update cloned fence_scsi? Or just case # when it's running on more than 1 node? It is possible but we need to # update more lrm_rsc_op elements raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( f"resource '{resource_id}' is running on more than 1 node") )) node_name = roles_with_nodes["Started"][0] new_instance_attrs = {"devices": ",".join(sorted(devices_list))} arrange_first_instance_attributes(resource_el, new_instance_attrs, id_provider) lrm_rsc_op_start_list = _get_lrm_rsc_op_elements(get_root(resource_el), resource_id, node_name, "start") if len(lrm_rsc_op_start_list) == 1: _update_digest_attrs_in_lrm_rsc_op( lrm_rsc_op_start_list[0], get_resource_digests( runner, resource_id, node_name, new_instance_attrs, ), ) else: raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( "lrm_rsc_op element for start operation was not found"))) monitor_attrs_list = _get_monitor_attrs(resource_el) lrm_rsc_op_monitor_list = _get_lrm_rsc_op_elements(get_root(resource_el), resource_id, node_name, "monitor") if len(lrm_rsc_op_monitor_list) != len(monitor_attrs_list): raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( ("number of lrm_rsc_op and op elements for monitor " "operation differs")))) for monitor_attrs in monitor_attrs_list: lrm_rsc_op_list = _get_lrm_rsc_op_elements( get_root(resource_el), resource_id, node_name, "monitor", monitor_attrs["interval"], ) if len(lrm_rsc_op_list) == 1: _update_digest_attrs_in_lrm_rsc_op( lrm_rsc_op_list[0], get_resource_digests( runner, resource_id, node_name, new_instance_attrs, crm_meta_attributes=monitor_attrs, ), ) else: raise LibraryError( ReportItem.error( reports.messages.StonithRestartlessUpdateUnableToPerform( ("monitor lrm_rsc_op element for resource " f"'{resource_id}', node '{node_name}' and interval " f"'{monitor_attrs['interval']}' not found"))))
def validate_add_remove_items( add_item_list: Iterable[str], remove_item_list: Iterable[str], current_item_list: Iterable[str], container_type: reports.types.AddRemoveContainerType, item_type: reports.types.AddRemoveItemType, container_id: str, adjacent_item_id: Optional[str] = None, container_can_be_empty: bool = False, ) -> ReportItemList: """ Validate if items can be added or removed to or from a container. add_item_list -- items to be added remove_item_list -- items to be removed current_item_list -- items currently in the container container_type -- container type item_type -- item type container_id -- id of the container adjacent_item_id -- an adjacent item in the container container_can_be_empty -- flag to decide if container can be left empty """ # pylint: disable=too-many-locals report_list: ReportItemList = [] if not add_item_list and not remove_item_list: report_list.append( ReportItem.error( reports.messages.AddRemoveItemsNotSpecified( container_type, item_type, container_id))) def _get_duplicate_items(item_list: Iterable[str]) -> Set[str]: return { item for item, count in Counter(item_list).items() if count > 1 } duplicate_items_list = _get_duplicate_items( add_item_list) | _get_duplicate_items(remove_item_list) if duplicate_items_list: report_list.append( ReportItem.error( reports.messages.AddRemoveItemsDuplication( container_type, item_type, container_id, sorted(duplicate_items_list), ))) already_present = set(add_item_list).intersection(current_item_list) # report only if an adjacent id is not defined, because we want to allow # to move items when adjacent_item_id is specified if adjacent_item_id is None and already_present: report_list.append( ReportItem.error( reports.messages.AddRemoveCannotAddItemsAlreadyInTheContainer( container_type, item_type, container_id, sorted(already_present), ))) missing_items = set(remove_item_list).difference(current_item_list) if missing_items: report_list.append( ReportItem.error( reports.messages.AddRemoveCannotRemoveItemsNotInTheContainer( container_type, item_type, container_id, sorted(missing_items), ))) common_items = set(add_item_list) & set(remove_item_list) if common_items: report_list.append( ReportItem.error( reports.messages.AddRemoveCannotAddAndRemoveItemsAtTheSameTime( container_type, item_type, container_id, sorted(common_items), ))) if not container_can_be_empty and not add_item_list: remaining_items = set(current_item_list).difference(remove_item_list) if not remaining_items: report_list.append( ReportItem.error( reports.messages. AddRemoveCannotRemoveAllItemsFromTheContainer( container_type, item_type, container_id, list(current_item_list), ))) if adjacent_item_id: if adjacent_item_id not in current_item_list: report_list.append( ReportItem.error( reports.messages.AddRemoveAdjacentItemNotInTheContainer( container_type, item_type, container_id, adjacent_item_id, ))) if adjacent_item_id in add_item_list: report_list.append( ReportItem.error( reports.messages.AddRemoveCannotPutItemNextToItself( container_type, item_type, container_id, adjacent_item_id, ))) if not add_item_list: report_list.append( ReportItem.error( reports.messages. AddRemoveCannotSpecifyAdjacentItemWithoutItemsToAdd( container_type, item_type, container_id, adjacent_item_id, ))) return report_list
def test_file_error(self, mock_config): mock_config.side_effect = LibraryError( ReportItem.error(report_codes.UNABLE_TO_GET_SBD_CONFIG, )) assert_raise_library_error( lambda: cmd_sbd.get_local_sbd_config(self.mock_env), (Severities.ERROR, report_codes.UNABLE_TO_GET_SBD_CONFIG, {}))