def find_resources_to_delete(resource_el: _Element) -> List[_Element]: """ Get resources to delete, children and parents of the given resource if necessary. If element is a primitive which is in a clone and you specify one of them, you will get elements for both of them. If you specify group element which is in a clone then will you get clone, group, and all primitive elements in a group and etc. resource_el - resource element (bundle, clone, group, primitive) """ result = [resource_el] # childrens of bundle, clone, group, clone-with-group inner_resource_list = get_inner_resources(resource_el) if inner_resource_list: result.extend(inner_resource_list) inner_resource = inner_resource_list[0] if is_group(inner_resource): result.extend(get_inner_resources(inner_resource)) # parents of primitive if needed (group, clone) parent_el = get_parent_resource(resource_el) if parent_el is None or is_bundle(parent_el): return result if is_any_clone(parent_el): result.insert(0, parent_el) if is_group(parent_el): group_inner_resources = get_group_inner_resources(parent_el) if len(group_inner_resources) <= 1: result = [parent_el] + group_inner_resources clone_el = get_parent_resource(parent_el) if clone_el is not None: result.insert(0, clone_el) return result
def _validate_adjacent_resource_element(self): report_list = [] if self._adjacent_resource_element is not None: if self._group_element is not None: adjacent_parent = self._adjacent_resource_element.getparent() if not (adjacent_parent is not None and group.is_group(adjacent_parent) and (adjacent_parent.attrib.get("id") == self._group_element.attrib.get("id"))): # pylint: disable=line-too-long report_list.append( ReportItem.error( reports.messages. CannotGroupResourceAdjacentResourceNotInGroup( self._adjacent_resource_element.attrib.get( "id"), self._group_element.attrib.get("id"), ))) for resource in self._resource_element_list: if self._adjacent_resource_element.attrib.get( "id") == resource.attrib.get("id"): report_list.append( ReportItem.error( reports.messages.CannotGroupResourceNextToItself( self._adjacent_resource_element.attrib.get( "id")))) break return report_list
def is_wrapper_resource(resource_el: Element) -> bool: """ Return True for resource_el of types that can contain other resource(s) (these are: group, bundle, clone) and False otherwise. resource_el -- resource element to check """ return (is_group(resource_el) or is_bundle(resource_el) or is_any_clone(resource_el))
def move_resources_to_group( group_element, primitives_to_place, adjacent_resource=None, put_after_adjacent=True ): """ Put resources into a group or move them within their group etree.Element group_element -- the group to put resources into iterable primitives_to_place -- resource elements to put into the group etree.Element adjacent_resource -- put resources beside this one if set bool put_after_adjacent -- put resources after or before the adjacent one """ for resource in primitives_to_place: old_parent = resource.getparent() # Move a resource to the group. if ( adjacent_resource is not None and adjacent_resource.getnext() is not None and put_after_adjacent ): adjacent_resource.getnext().addprevious(resource) adjacent_resource = resource elif ( adjacent_resource is not None and not put_after_adjacent ): adjacent_resource.addprevious(resource) else: group_element.append(resource) adjacent_resource = resource # If the resource was the last resource in another group, that group is # now empty and must be deleted. If the group is in a clone element, # delete that as well. if ( old_parent is not None and group.is_group(old_parent) and not group.get_inner_resources(old_parent) and old_parent.getparent() is not None ): old_grandparent = old_parent.getparent() if ( clone.is_any_clone(old_grandparent) and old_grandparent.getparent() is not None ): old_grandparent.getparent().remove(old_grandparent) else: old_grandparent.remove(old_parent)
def move_resources_to_group( group_element: _Element, primitives_to_place: Iterable[_Element], adjacent_resource: Optional[_Element] = None, put_after_adjacent: bool = True, ) -> None: """ Put resources into a group or move them within their group There is a corner case which is not covered in this function. If the CIB contains references to a group or clone which this function deletes, they are not deleted and an invalid CIB is generated. These references can be constraints, fencing levels etc. - anything that contains group id of the deleted group. It is on the caller to detect this corner case and handle it appropriately (see group_add in lib/commands/resource.py). For future rewrites of this function, it would be better to ask for --force before deleting anything that user didn't explicitly ask for - like deleting the clone and its associated constraints. etree.Element group_element -- the group to put resources into iterable primitives_to_place -- resource elements to put into the group etree.Element adjacent_resource -- put resources beside this one if set bool put_after_adjacent -- put resources after or before the adjacent one """ for resource in primitives_to_place: old_parent = resource.getparent() # Move a resource to the group. if (adjacent_resource is not None and adjacent_resource.getnext() is not None and put_after_adjacent): adjacent_resource.getnext().addprevious(resource) # type: ignore adjacent_resource = resource elif adjacent_resource is not None and not put_after_adjacent: adjacent_resource.addprevious(resource) else: group_element.append(resource) adjacent_resource = resource # If the resource was the last resource in another group, that group is # now empty and must be deleted. If the group is in a clone element, # delete that as well. if (old_parent is not None and group.is_group( old_parent) # do not delete resources element and not group.get_inner_resources(old_parent)): old_grandparent = old_parent.getparent() if old_grandparent is not None: old_great_grandparent = old_grandparent.getparent() if (clone.is_any_clone(old_grandparent) and old_great_grandparent is not None): old_great_grandparent.remove(old_grandparent) else: old_grandparent.remove(old_parent)
def find_resources_to_unmanage(resource_el): """ Get resources to unmanage to unmanage the specified resource succesfully etree resource_el -- resource element """ # resource hierarchy - specified resource - what to return # a primitive - the primitive - the primitive # # a cloned primitive - the primitive - the primitive # a cloned primitive - the clone - the primitive # The resource will run on all nodes after unclone. However that doesn't # seem to be bad behavior. Moreover, if monitor operations were disabled, # they wouldn't enable on unclone, but the resource would become managed, # which is definitely bad. # # a primitive in a group - the primitive - the primitive # Otherwise all primitives in the group would become unmanaged. # a primitive in a group - the group - all primitives in the group # If only the group was set to unmanaged, setting any primitive in the # group to managed would set all the primitives in the group to managed. # If the group as well as all its primitives were set to unmanaged, any # primitive added to the group would become unmanaged. This new primitive # would become managed if any original group primitive becomes managed. # Therefore changing one primitive influences another one, which we do # not want to happen. # # a primitive in a cloned group - the primitive - the primitive # a primitive in a cloned group - the group - all primitives in the group # See group notes above # a primitive in a cloned group - the clone - all primitives in the group # See clone notes above # # a bundled primitive - the primitive - the primitive # a bundled primitive - the bundle - the bundle and the primitive # We need to unmanage implicit resources create by pacemaker and there is # no other way to do it than unmanage the bundle itself. # Since it is not possible to unbundle a resource, the concers described # at unclone don't apply here. However to prevent future bugs, in case # unbundling becomes possible, we unmanage the primitive as well. # an empty bundle - the bundle - the bundle # There is nothing else to unmanage. if is_bundle(resource_el): in_bundle = get_bundle_inner_resource(resource_el) return ( [resource_el, in_bundle] if in_bundle is not None else [resource_el] ) if is_any_clone(resource_el): resource_el = get_clone_inner_resource(resource_el) if is_group(resource_el): return get_group_inner_resources(resource_el) if is_primitive(resource_el): return [resource_el] return []
def find_primitives(resource_el): """ Get list of primitives contained in a given resource etree resource_el -- resource element """ if is_bundle(resource_el): in_bundle = get_bundle_inner_resource(resource_el) return [in_bundle] if in_bundle is not None else [] if is_any_clone(resource_el): resource_el = get_clone_inner_resource(resource_el) if is_group(resource_el): return get_group_inner_resources(resource_el) if is_primitive(resource_el): return [resource_el] return []
def is_resource_in_same_group(cib, resource_id_list): # We don't care about not found elements here, that is a job of another # validator. We do not care if the id doesn't belong to a resource either # for the same reason. element_list, _ = get_elements_by_ids(cib, set(resource_id_list)) parent_list = [] for element in element_list: parent = get_parent_resource(element) if parent is not None and group.is_group(parent): parent_list.append(parent) if len(set(parent_list)) != len(parent_list): raise LibraryError( ReportItem.error( reports.messages. CannotSetOrderConstraintsForResourcesInTheSameGroup()))
def get_inner_resources(resource_el: _Element) -> List[_Element]: """ Return list of inner resources (direct descendants) of a resource specified as resource_el. Example: for clone containing a group, this function will return only group and not resource inside the group resource_el -- resource element to get its inner resources """ if is_bundle(resource_el): in_bundle = get_bundle_inner_resource(resource_el) return [in_bundle] if in_bundle is not None else [] if is_any_clone(resource_el): return [get_clone_inner_resource(resource_el)] if is_group(resource_el): return get_group_inner_resources(resource_el) return []
def find_resources_to_unmanage(resource_el): """ Get resources to unmanage to unmanage the specified resource succesfully etree resource_el -- resource element """ # resource hierarchy - specified resource - what to return # a primitive - the primitive - the primitive # # a cloned primitive - the primitive - the primitive # a cloned primitive - the clone - the primitive # The resource will run on all nodes after unclone. However that doesn't # seem to be bad behavior. Moreover, if monitor operations were disabled, # they wouldn't enable on unclone, but the resource would become managed, # which is definitely bad. # # a primitive in a group - the primitive - the primitive # Otherwise all primitives in the group would become unmanaged. # a primitive in a group - the group - all primitives in the group # If only the group was set to unmanaged, setting any primitive in the # group to managed would set all the primitives in the group to managed. # If the group as well as all its primitives were set to unmanaged, any # primitive added to the group would become unmanaged. This new primitive # would become managed if any original group primitive becomes managed. # Therefore changing one primitive influences another one, which we do # not want to happen. # # a primitive in a cloned group - the primitive - the primitive # a primitive in a cloned group - the group - all primitives in the group # See group notes above # a primitive in a cloned group - the clone - all primitives in the group # See clone notes above # # a bundled primitive - the primitive - the primitive # a bundled primitive - the bundle - nothing # bundles currently cannot be set as unmanaged - pcmk does not support that # an empty bundle - the bundle - nothing # bundles currently cannot be set as unmanaged - pcmk does not support that if is_any_clone(resource_el): resource_el = get_clone_inner_resource(resource_el) if is_group(resource_el): return get_group_inner_resources(resource_el) if is_primitive(resource_el): return [resource_el] return []
def move_resources_to_group( group_element, primitives_to_place, adjacent_resource=None, put_after_adjacent=True, ): """ Put resources into a group or move them within their group etree.Element group_element -- the group to put resources into iterable primitives_to_place -- resource elements to put into the group etree.Element adjacent_resource -- put resources beside this one if set bool put_after_adjacent -- put resources after or before the adjacent one """ for resource in primitives_to_place: old_parent = resource.getparent() # Move a resource to the group. if (adjacent_resource is not None and adjacent_resource.getnext() is not None and put_after_adjacent): adjacent_resource.getnext().addprevious(resource) adjacent_resource = resource elif adjacent_resource is not None and not put_after_adjacent: adjacent_resource.addprevious(resource) else: group_element.append(resource) adjacent_resource = resource # If the resource was the last resource in another group, that group is # now empty and must be deleted. If the group is in a clone element, # delete that as well. if (old_parent is not None and group.is_group(old_parent) and not group.get_inner_resources(old_parent) and old_parent.getparent() is not None): old_grandparent = old_parent.getparent() if (clone.is_any_clone(old_grandparent) and old_grandparent.getparent() is not None): old_grandparent.getparent().remove(old_grandparent) else: old_grandparent.remove(old_parent)
def _validate_adjacent_resource_element(self): report_list = [] if self._adjacent_resource_element is not None: if self._group_element is not None: adjacent_parent = self._adjacent_resource_element.getparent() if not (adjacent_parent is not None and group.is_group(adjacent_parent) and (adjacent_parent.attrib.get("id") == self._group_element.attrib.get("id"))): report_list.append( reports. cannot_group_resource_adjacent_resource_not_in_group( self._adjacent_resource_element.attrib.get("id"), self._group_element.attrib.get("id"))) for resource in self._resource_element_list: if (self._adjacent_resource_element.attrib.get("id") == resource.attrib.get("id")): report_list.append( reports.cannot_group_resource_next_to_itself( self._adjacent_resource_element.attrib.get("id"))) break return report_list
def _validate_adjacent_resource_element(self): report_list = [] if self._adjacent_resource_element is not None: if self._group_element is not None: adjacent_parent = self._adjacent_resource_element.getparent() if not ( adjacent_parent is not None and group.is_group(adjacent_parent) and ( adjacent_parent.attrib.get("id") == self._group_element.attrib.get("id") ) ): report_list.append( reports .cannot_group_resource_adjacent_resource_not_in_group( self._adjacent_resource_element.attrib.get("id"), self._group_element.attrib.get("id") ) ) for resource in self._resource_element_list: if ( self._adjacent_resource_element.attrib.get("id") == resource.attrib.get("id") ): report_list.append( reports.cannot_group_resource_next_to_itself( self._adjacent_resource_element.attrib.get("id") ) ) break return report_list
def _validate_resource_elements( self, bad_or_missing_group_specified, bad_resources_specified, bad_adjacent_specified ): report_list = [] # Check if the group element is really a group unless it has been # checked before. if ( not bad_or_missing_group_specified and not group.is_group(self._group_element) ): bad_or_missing_group_specified = True report_list.append( reports.id_belongs_to_unexpected_type( self._group_element.attrib.get("id"), expected_types=[group.TAG], current_type=self._group_element.tag ) ) # Report an error if no resources were specified. If resource ids have # been specified but they are not valid resource ids, then some # resources were specified, even though not valid ones. if not bad_resources_specified and not self._resource_element_list: report_list.append(reports.cannot_group_resource_no_resources()) resources_already_in_the_group = set() resources_count = defaultdict(int) for resource in self._resource_element_list: resource_id = resource.attrib.get("id") if not primitive.is_primitive(resource): report_list.append( reports.cannot_group_resource_wrong_type( resource_id, resource.tag ) ) continue resources_count[resource_id] = resources_count[resource_id] + 1 parent = resource.getparent() if parent is not None: if group.is_group(parent): # If no adjacent resource has been specified (either valid # or invalid), the resources must not already be in the # group, if the group already exists. if ( not bad_or_missing_group_specified and not bad_adjacent_specified and self._adjacent_resource_element is None and ( parent.attrib.get("id") == self._group_element.attrib.get("id") ) ): resources_already_in_the_group.add(resource_id) elif parent.tag != "resources": # If the primitive is not in a 'group' or 'resources' tag, # it is either in a clone, master, bundle or similar tag # and cannot be put into a group. report_list.append( reports.cannot_group_resource_wrong_type( resource_id, parent.tag ) ) if resources_already_in_the_group: report_list.append( reports.cannot_group_resource_already_in_the_group( resources_already_in_the_group, self._group_element.attrib.get("id") ) ) more_than_once_resources = [ resource for resource, count in resources_count.items() if count > 1 ] if more_than_once_resources: report_list.append( reports.cannot_group_resource_more_than_once( more_than_once_resources ) ) return report_list
def test_is_group(self): self.assertTrue(group.is_group(etree.fromstring("<group/>"))) self.assertFalse(group.is_group(etree.fromstring("<clone/>"))) self.assertFalse(group.is_group(etree.fromstring("<master/>")))
def _validate_resource_elements( self, bad_or_missing_group_specified, bad_resources_specified, bad_adjacent_specified, ): report_list = [] # Check if the group element is really a group unless it has been # checked before. if not bad_or_missing_group_specified and not group.is_group( self._group_element): bad_or_missing_group_specified = True report_list.append( ReportItem.error( reports.messages.IdBelongsToUnexpectedType( self._group_element.attrib.get("id"), expected_types=[group.TAG], current_type=self._group_element.tag, ))) # Report an error if no resources were specified. If resource ids have # been specified but they are not valid resource ids, then some # resources were specified, even though not valid ones. if not bad_resources_specified and not self._resource_element_list: report_list.append( ReportItem.error( reports.messages.CannotGroupResourceNoResources())) resources_already_in_the_group = set() resources_count = defaultdict(int) for resource in self._resource_element_list: resource_id = resource.attrib.get("id") if not primitive.is_primitive(resource): report_list.append( ReportItem.error( reports.messages.CannotGroupResourceWrongType( resource_id, resource.tag))) continue resources_count[resource_id] = resources_count[resource_id] + 1 parent = resource.getparent() if parent is not None: if group.is_group(parent): # If no adjacent resource has been specified (either valid # or invalid), the resources must not already be in the # group, if the group already exists. if (not bad_or_missing_group_specified and not bad_adjacent_specified and self._adjacent_resource_element is None and (parent.attrib.get("id") == self._group_element.attrib.get("id"))): resources_already_in_the_group.add(resource_id) elif parent.tag != "resources": # If the primitive is not in a 'group' or 'resources' tag, # it is either in a clone, master, bundle or similar tag # and cannot be put into a group. report_list.append( ReportItem.error( reports.messages.CannotGroupResourceWrongType( resource_id, parent.tag))) if resources_already_in_the_group: report_list.append( ReportItem.error( reports.messages.CannotGroupResourceAlreadyInTheGroup( sorted(resources_already_in_the_group), self._group_element.attrib.get("id"), ))) more_than_once_resources = [ resource for resource, count in resources_count.items() if count > 1 ] if more_than_once_resources: report_list.append( ReportItem.error( reports.messages.CannotGroupResourceMoreThanOnce( sorted(more_than_once_resources)))) return report_list
def validate_move_resources_to_group( group_element: _Element, resource_element_list: List[_Element], adjacent_resource_element: Optional[_Element], ) -> ReportItemList: """ Validates that existing resources can be moved into a group, optionally beside an adjacent_resource_element group_element -- the group to put resources into resource_element_list -- resources that are being moved into the group adjacent_resource_element -- put resources beside this one """ report_list: ReportItemList = [] # Validate types of resources and their parents for resource_element in resource_element_list: # Only primitive resources can be moved if not is_resource(resource_element): report_list.append( ReportItem.error( reports.messages.IdBelongsToUnexpectedType( str(resource_element.attrib["id"]), ["primitive"], resource_element.tag, ))) elif is_wrapper_resource(resource_element): report_list.append( ReportItem.error( reports.messages.CannotGroupResourceWrongType( str(resource_element.attrib["id"]), resource_element.tag, parent_id=None, parent_type=None, ))) else: parent = get_parent_resource(resource_element) if parent is not None and not group.is_group(parent): # At the moment, moving resources out of bundles and clones # (or masters) is not possible report_list.append( ReportItem.error( reports.messages.CannotGroupResourceWrongType( str(resource_element.attrib["id"]), resource_element.tag, parent_id=str(parent.attrib["id"]), parent_type=parent.tag, ))) # Validate that elements can be added # Check if the group element is a group if group.is_group(group_element): report_list += validate_add_remove_items( [str(resource.attrib["id"]) for resource in resource_element_list], [], [ str(resource.attrib["id"]) for resource in get_inner_resources(group_element) ], ADD_REMOVE_CONTAINER_TYPE_GROUP, ADD_REMOVE_ITEM_TYPE_RESOURCE, str(group_element.attrib["id"]), str(adjacent_resource_element.attrib["id"]) if adjacent_resource_element is not None else None, True, ) else: report_list.append( ReportItem.error( reports.messages.IdBelongsToUnexpectedType( str(group_element.attrib["id"]), expected_types=[group.TAG], current_type=group_element.tag, ))) # Elements can always be removed from their old groups, except when the last # resource is removed but that is handled in resource.group_add for now, no # need to run the validation for removing elements return report_list
def test_is_group(self): self.assertTrue(group.is_group(etree.fromstring("<group/>"))) self.assertFalse(group.is_group(etree.fromstring("<clone/>"))) self.assertFalse(group.is_group(etree.fromstring("<master/>")))