def test_unfence_failure_unknown_command(self): self.config_cib() self.config.corosync_conf.load_content( corosync_conf_fixture(self.existing_corosync_nodes)) self.config.http.corosync.get_corosync_online_targets( node_labels=self.existing_nodes) communication_list = [ dict( label=node, raw_data=json.dumps(self.get_raw_data(node)), ) for node in self.existing_nodes ] communication_list[2]["response_code"] = 404 communication_list[2]["output"] = json.dumps( dto.to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_UNKNOWN_CMD, status_msg=("Unknown command '/api/v1/unknown/v2'"), report_list=[], data=None, ))) self.unfence_config(communication_list=communication_list) self.env_assist.assert_raise_library_error(self.command()) self.env_assist.assert_reports([ fixture.error( reports.codes.PCSD_VERSION_TOO_OLD, node=self.existing_nodes[2], ), ])
def get_config(env: LibraryEnvironment) -> Mapping[str, Any]: """ Return local disaster recovery config env -- LibraryEnvironment """ report_processor = env.report_processor report_list, dr_config = _load_dr_config(env.get_dr_env().config) report_processor.report_list(report_list) if report_processor.has_errors: raise LibraryError() return dto.to_dict( DrConfigDto( local_site=DrConfigSiteDto( site_role=dr_config.local_role, node_list=[], ), remote_site_list=[ DrConfigSiteDto( site_role=site.role, node_list=[ DrConfigNodeDto(name=name) for name in site.node_name_list ], ) for site in dr_config.get_remote_site_list() ], ))
def test_all(self): stdout, retval = self.pcs_runner.run( ["resource", "config", "--output-format=json"] ) self.assertEqual(retval, 0) expected = ListCibResourcesDto( primitives=[ resources_dto.PRIMITIVE_R1, resources_dto.PRIMITIVE_R7, resources_dto.PRIMITIVE_R5, resources_dto.STONITH_S1, resources_dto.PRIMITIVE_R2, resources_dto.PRIMITIVE_R3, resources_dto.PRIMITIVE_R4, resources_dto.PRIMITIVE_R6, ], clones=[ resources_dto.CLONE_G1, resources_dto.CLONE_R6, ], groups=[ resources_dto.GROUP_G2, resources_dto.GROUP_G1, ], bundles=[ resources_dto.BUNDLE_B1, resources_dto.BUNDLE_B2, ], ) self.assertEqual(json.loads(stdout), to_dict(expected))
def test_unfence_failure_agent_script_failed(self): self.config_cib() self.config.corosync_conf.load_content( corosync_conf_fixture(self.existing_corosync_nodes)) self.config.http.corosync.get_corosync_online_targets( node_labels=self.existing_nodes) communication_list = [ dict( label=node, raw_data=json.dumps(self.get_raw_data(node)), ) for node in self.existing_nodes ] communication_list[1]["output"] = json.dumps( dto.to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_ERROR, status_msg="error", report_list=[ reports.ReportItem.error( reports.messages.StonithUnfencingFailed( "errB")).to_dto() ], data=None, ))) self.unfence_config(communication_list=communication_list) self.env_assist.assert_raise_library_error(self.command()) self.env_assist.assert_reports([ fixture.error( reports.codes.STONITH_UNFENCING_FAILED, reason="errB", context=reports.dto.ReportItemContextDto( node=self.existing_nodes[1], ), ), ])
def test_simple_order(self): resources_members = ["order-d1-d2-mandatory"] resources = { "d1": self.primitive_fixture("d1", resources_members), "d2": self.primitive_fixture("d2", resources_members), } relations = { "order-d1-d2-mandatory": RelationEntityDto( "order-d1-d2-mandatory", ResourceRelationType.ORDER, members=["d1", "d2"], metadata={ "id": "order-d1-d2-mandatory", "first": "d1", "first-action": "start", "then": "d2", "then-action": "start", "kind": "Mandatory", }, ), } expected = dict( relation_entity=dto.to_dict(resources["d2"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict( relations["order-d1-d2-mandatory"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict(resources["d1"]), is_leaf=False, members=[], ) ], ) ], ) self.assertEqual( expected, dto.to_dict( lib.ResourceRelationTreeBuilder( resources, relations).get_tree("d2").to_dto()), )
def main() -> None: # pylint: disable=broad-except argv = sys.argv[1:] if argv: _exit( communication.const.COM_STATUS_INPUT_ERROR, status_msg="No arguments allowed", ) utils.subprocess_setup() logging.basicConfig() try: input_dto = dto.from_dict( communication.dto.InternalCommunicationRequestDto, json.load(sys.stdin), ) cli_env = get_cli_env(input_dto.options) lib = Library(cli_env, utils.get_middleware_factory()) if input_dto.cmd not in SUPPORTED_COMMANDS: _exit( communication.const.COM_STATUS_UNKNOWN_CMD, status_msg=f"Unknown command '{input_dto.cmd}'", ) for sub_cmd in input_dto.cmd.split("."): lib = getattr(lib, sub_cmd) output_data = lib(**input_dto.cmd_data) # type: ignore _exit( communication.const.COM_STATUS_SUCCESS, report_list=cli_env.report_processor.processed_items, data=( dto.to_dict(output_data) if isinstance(output_data, dto.DataTransferObject) else output_data ), ) except LibraryError as e: _exit( communication.const.COM_STATUS_ERROR, report_list=( cli_env.report_processor.processed_items + list(e.args) ), data=e.output, ) except json.JSONDecodeError as e: _exit( communication.const.COM_STATUS_INPUT_ERROR, status_msg=f"Unable to parse input data: {e.msg}", ) except DaciteError as e: _exit( communication.const.COM_STATUS_INPUT_ERROR, status_msg=str(e), ) except Exception as e: # TODO: maybe add traceback? _exit(communication.const.COM_STATUS_EXCEPTION, status_msg=str(e))
def test_unfence_failure_unable_to_connect(self): self._unfence_failure_common_calls() self.config.http.corosync.get_corosync_online_targets( node_labels=self.existing_nodes) self.config.http.scsi.unfence_node( DEVICES_2, communication_list=[ dict( label=self.existing_nodes[0], raw_data=json.dumps( dict(devices=DEVICES_2, node=self.existing_nodes[0])), was_connected=False, error_msg="errA", ), dict( label=self.existing_nodes[1], raw_data=json.dumps( dict(devices=DEVICES_2, node=self.existing_nodes[1])), output=json.dumps( dto.to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_ERROR, status_msg="error", report_list=[ reports.ReportItem.error( reports.messages. StonithUnfencingFailed( "errB")).to_dto() ], data=None, ))), ), dict( label=self.existing_nodes[2], raw_data=json.dumps( dict(devices=DEVICES_2, node=self.existing_nodes[2])), ), ], ) self.env_assist.assert_raise_library_error( lambda: stonith.update_scsi_devices(self.env_assist.get_env(), SCSI_STONITH_ID, DEVICES_2), ) self.env_assist.assert_reports([ fixture.error( reports.codes.NODE_COMMUNICATION_ERROR_UNABLE_TO_CONNECT, node=self.existing_nodes[0], command="api/v1/scsi-unfence-node/v1", reason="errA", ), fixture.error( reports.codes.STONITH_UNFENCING_FAILED, reason="errB", context=reports.dto.ReportItemContextDto( node=self.existing_nodes[1], ), ), ])
def test_unfence_failure_unknown_command(self): self._unfence_failure_common_calls() self.config.http.corosync.get_corosync_online_targets( node_labels=self.existing_nodes ) communication_list = [ dict( label=node, raw_data=json.dumps( dict( node=node, original_devices=DEVICES_1, updated_devices=DEVICES_2, ) ), ) for node in self.existing_nodes[0:2] ] communication_list.append( dict( label=self.existing_nodes[2], response_code=404, raw_data=json.dumps( dict( node=self.existing_nodes[2], original_devices=DEVICES_1, updated_devices=DEVICES_2, ) ), output=json.dumps( dto.to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_UNKNOWN_CMD, status_msg=( "Unknown command '/api/v1/scsi-unfence-node/v2'" ), report_list=[], data=None, ) ) ), ), ) self.config.http.scsi.unfence_node( communication_list=communication_list ) self.env_assist.assert_raise_library_error(self.command()) self.env_assist.assert_reports( [ fixture.error( reports.codes.PCSD_VERSION_TOO_OLD, node=self.existing_nodes[2], ), ] )
def test_simple_in_group(self): resources_members = ["outer:g1"] resources = { "d1": self.primitive_fixture("d1", resources_members), "d2": self.primitive_fixture("d2", resources_members), "g1": RelationEntityDto("g1", "group", ["inner:g1"], {"id": "g1"}), } relations = { "inner:g1": RelationEntityDto( "inner:g1", ResourceRelationType.INNER_RESOURCES, ["d1", "d2"], {"id": "g1"}, ), "outer:g1": RelationEntityDto( "outer:g1", ResourceRelationType.OUTER_RESOURCE, ["g1"], {"id": "g1"}, ), } expected = dict( relation_entity=dto.to_dict(resources["d1"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict(relations["outer:g1"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict(resources["g1"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict( relations["inner:g1"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict( resources["d2"]), is_leaf=False, members=[], ), ], ), ], ), ], ), ], ) self.assertEqual( expected, dto.to_dict( lib.ResourceRelationTreeBuilder( resources, relations).get_tree("d1").to_dto()), )
def unfence_node_mpath( self, node_key_map, original_devices=(), updated_devices=(), node_labels=None, communication_list=None, name="http.scsi.unfence_node", ): """ Create a calls for node unfencing dict node_key_map -- map of node name to its registration key list original_devices -- list of scsi devices before an update list updated_devices -- list of scsi devices after an update list node_labels -- create success responses from these nodes list communication_list -- use these custom responses string name -- the key of this call """ if (node_labels is None and communication_list is None) or (node_labels and communication_list): raise AssertionError( "Exactly one of 'node_labels', 'communication_list' " "must be specified") if node_labels: communication_list = [ dict( label=node, raw_data=json.dumps( dict( key=node_key_map[node], original_devices=original_devices, updated_devices=updated_devices, )), ) for node in node_labels ] place_communication( self.__calls, name, communication_list, action="api/v1/scsi-unfence-node-mpath/v1", output=json.dumps( to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_SUCCESS, status_msg=None, report_list=[], data=None, ))), )
def _action_dto_to_dict( dto: AgentActionDto, new_role_names_supported: bool, ) -> Dict[str, str]: result = dict( filter( lambda item: item[0] != "deph" and item[1] not in (None, ""), to_dict(dto).items(), )) if "role" in result: result["role"] = pacemaker.role.get_value_for_cib( result["role"], new_role_names_supported) return result
def describe_agent( lib_env: LibraryEnvironment, agent_name: str ) -> Dict[str, Any]: """ Get agent's description (metadata) in a structure agent_name -- name of the agent (not containing "stonith:" prefix) """ agent = resource_agent.find_valid_stonith_agent_by_name( lib_env.report_processor, lib_env.cmd_runner(), agent_name, absent_agent_supported=False, ) return to_dict(agent.get_full_info())
def test_get_specified(self): stdout, retval = self.pcs_runner.run( ["stonith", "config", "--output-format=json", "S1"], ignore_stderr=True, ) self.assertEqual(retval, 0) expected = ListCibResourcesDto( primitives=[ resources_dto.STONITH_S1, ], clones=[], groups=[], bundles=[], ) self.assertEqual(json.loads(stdout), to_dict(expected))
def _exit( status: communication.types.CommunicationResultStatus, status_msg: Optional[str] = None, report_list: Optional[ReportItemList] = None, data: Any = None, ) -> None: json.dump( dto.to_dict( communication.dto.InternalCommunicationResultDto( status, status_msg, [report.to_dto() for report in (report_list or [])], data, )), sys.stdout, ) sys.exit(0)
def test_get_multiple(self): stdout, retval = self.pcs_runner.run( ["resource", "config", "--output-format=json", "G1-clone", "R1"] ) self.assertEqual(retval, 0) expected = ListCibResourcesDto( primitives=[ resources_dto.PRIMITIVE_R1, resources_dto.PRIMITIVE_R2, resources_dto.PRIMITIVE_R3, resources_dto.PRIMITIVE_R4, ], clones=[resources_dto.CLONE_G1], groups=[resources_dto.GROUP_G1], bundles=[], ) self.assertEqual(json.loads(stdout), to_dict(expected))
def operation_dto_to_legacy_dict( operation: CibResourceOperationDto, defaults: Mapping[str, Any], ) -> ResourceOperationOut: operation_dict = dict(defaults) operation_dict.update({ key: value for key, value in to_dict(operation).items() if key in ("name", "timeout", "interval", "role") }) operation_dict["start-delay"] = operation.start_delay operation_dict["OCF_CHECK_LEVEL"] = None for nvset in operation.instance_attributes: for nvpair in nvset.nvpairs: if nvpair.name == "OCF_CHECK_LEVEL": operation_dict["OCF_CHECK_LEVEL"] = nvpair.value return operation_dict
def _agent_metadata_to_dict(agent: ResourceAgentMetadata, describe: bool = False) -> Dict[str, str]: agent_dto = agent.to_dto() agent_dict = to_dict(agent_dto) del agent_dict["name"] agent_dict["name"] = agent.name.full_name agent_dict["standard"] = agent.name.standard agent_dict["provider"] = agent.name.provider agent_dict["type"] = agent.name.type agent_dict["actions"] = [ action_to_operation(action, keep_extra_keys=True) for action in agent_dto.actions ] agent_dict["default_actions"] = (complete_operations_options( get_default_operations(agent, keep_extra_keys=True)) if describe else []) return agent_dict
def unfence_node( self, devices, node_labels=None, communication_list=None, name="http.scsi.unfence_node", ): """ Create a calls for node unfencing list devices -- list of scsi devices list node_labels -- create success responses from these nodes list communication_list -- use these custom responses string name -- the key of this call """ if (node_labels is None and communication_list is None) or (node_labels and communication_list): raise AssertionError( "Exactly one of 'node_labels', 'communication_list' " "must be specified") if node_labels: communication_list = [ dict( label=node, raw_data=json.dumps(dict(devices=devices, node=node)), ) for node in node_labels ] place_communication( self.__calls, name, communication_list, action="api/v1/scsi-unfence-node/v1", output=json.dumps( to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_SUCCESS, status_msg=None, report_list=[], data=None, ))), )
def _agent_metadata_to_dict(agent: ResourceAgentMetadata, describe: bool = False) -> Dict[str, str]: agent_dto = agent.to_dto() agent_dict = to_dict(agent_dto) del agent_dict["name"] agent_dict["name"] = agent.name.full_name agent_dict["standard"] = agent.name.standard agent_dict["provider"] = agent.name.provider agent_dict["type"] = agent.name.type agent_dict["actions"] = [ _action_to_operation(action) for action in agent_dto.actions ] operations_defaults = { OCF_CHECK_LEVEL_INSTANCE_ATTRIBUTE_NAME: None, "automatic": False, "on_target": False, } agent_dict["default_actions"] = ([ operation_dto_to_legacy_dict(op, operations_defaults) for op in get_default_operations(agent) ] if describe else []) return agent_dict
def action_to_operation(action: ResourceAgentActionDto, keep_extra_keys: bool = False) -> ResourceOperationOut: """ Transform agent action data to CIB operation data """ # This function bridges new agent framework, which provides data in # dataclasses, to old resource create code and transforms new data # structures to a format expected by the old code. When resource create is # overhauled, this fuction is expected to be removed. operation = {} for key, value in to_dict(action).items(): if key == "depth": # "None" values are not put to CIB, so this keeps the key in place # while making sure it's not put in CIB. I'm not sure why depth == # 0 is treated like this, but I keep it in place so the behavior is # the same as it has been for a long time. If pcs starts using # depth / OCF_CHECK_LEVEL or there is other demand for it, consider # changing this so value of "0" is put in CIB. operation["OCF_CHECK_LEVEL"] = None if value == "0" else value elif key == "start_delay": operation["start-delay"] = value elif key in OPERATION_ATTRIBUTES or keep_extra_keys: operation[key] = value return operation
def setUp(self): self.lib_call = mock.Mock() self.lib = mock.Mock(spec_set=["resource"]) self.lib.resource = mock.Mock(spec_set=["get_resource_relations_tree"]) self.lib.resource.get_resource_relations_tree = self.lib_call self.lib_call.return_value = dto.to_dict( ResourceRelationDto( RelationEntityDto( "d1", ResourceRelationType.RSC_PRIMITIVE, [], { "class": "ocf", "provider": "pacemaker", "type": "Dummy", }, ), [ ResourceRelationDto( RelationEntityDto( "order1", ResourceRelationType.ORDER, [], { "first-action": "start", "first": "d1", "then-action": "start", "then": "d2", "kind": "Mandatory", "symmetrical": "true", }, ), [ ResourceRelationDto( RelationEntityDto( "d2", ResourceRelationType.RSC_PRIMITIVE, [], { "class": "ocf", "provider": "heartbeat", "type": "Dummy", }, ), [], False, ), ], False, ), ResourceRelationDto( RelationEntityDto( "inner:g1", ResourceRelationType.INNER_RESOURCES, [], {}, ), [ ResourceRelationDto( RelationEntityDto( "g1", ResourceRelationType.RSC_GROUP, [], {}), [], True, ), ], False, ), ], False, ))
def test_order_loop(self): resources_members = ["order-d1-d2-mandatory", "order-d2-d1-mandatory"] resources = { "d1": self.primitive_fixture("d1", resources_members), "d2": self.primitive_fixture("d2", resources_members), } order_fixture = lambda r1, r2: RelationEntityDto( f"order-{r1}-{r2}-mandatory", ResourceRelationType.ORDER, members=[r1, r2], metadata={ "id": f"order-{r1}-{r2}-mandatory", "first": r1, "first-action": "start", "then": r2, "then-action": "start", "kind": "Mandatory", }, ) relations = { "order-d1-d2-mandatory": order_fixture("d1", "d2"), "order-d2-d1-mandatory": order_fixture("d2", "d1"), } expected = dict( relation_entity=dto.to_dict(resources["d1"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict( relations["order-d1-d2-mandatory"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict(resources["d2"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict( relations["order-d2-d1-mandatory"]), is_leaf=True, members=[], ), ], ), ], ), dict( relation_entity=dto.to_dict( relations["order-d2-d1-mandatory"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict(resources["d2"]), is_leaf=True, members=[], ), ], ), ], ) self.assertEqual( expected, dto.to_dict( lib.ResourceRelationTreeBuilder( resources, relations).get_tree("d1").to_dto()), )
def test_simple_order_set(self): res_list = ("d1", "d2", "d3", "d4", "d5", "d6") resources_members = ["pcs_rsc_order_set_1"] resources = { _id: self.primitive_fixture(_id, resources_members) for _id in res_list } relations = { "pcs_rsc_order_set_1": RelationEntityDto( "pcs_rsc_order_set_1", ResourceRelationType.ORDER_SET, members=["d1", "d2", "d3", "d4", "d5", "d6"], metadata={ "id": "pcs_rsc_order_set_1", "sets": [ { "id": "pcs_rsc_set_1", "metadata": { "id": "pcs_rsc_set_1", "sequential": "true", "require-all": "true", "action": "start", }, "members": ["d1", "d3", "d2"], }, { "id": "pcs_rsc_set_2", "metadata": { "id": "pcs_rsc_set_2", "sequential": "false", "require-all": "false", "action": "stop", }, "members": ["d6", "d5", "d4"], }, ], "kind": "Serialize", "symmetrical": "true", }, ), } get_res = lambda _id: dict( relation_entity=dto.to_dict(resources[_id]), is_leaf=False, members=[], ) expected = dict( relation_entity=dto.to_dict(resources["d5"]), is_leaf=False, members=[ dict( relation_entity=dto.to_dict( relations["pcs_rsc_order_set_1"]), is_leaf=False, members=[ get_res(_id) for _id in ("d1", "d2", "d3", "d4", "d6") ], ) ], ) self.assertEqual( expected, dto.to_dict( lib.ResourceRelationTreeBuilder( resources, relations).get_tree("d5").to_dto()), )
def test_simple_to_dict(self): self.assertEqual(to_dict(self.simple_dto), self.simple_dict)
def status_all_sites_plaintext( env: LibraryEnvironment, hide_inactive_resources: bool = False, verbose: bool = False, ) -> List[Mapping[str, Any]]: """ Return local site's and all remote sites' status as plaintext env -- LibraryEnvironment hide_inactive_resources -- if True, do not display non-running resources verbose -- if True, display more info """ # The command does not provide an option to skip offline / unreacheable / # misbehaving nodes. # The point of such skipping is to stop a command if it is unable to make # changes on all nodes. The user can then decide to proceed anyway and # make changes on the skipped nodes later manually. # This command only reads from nodes so it automatically asks other nodes # if one is offline / misbehaving. class SiteData(): def __init__( self, local: bool, role: DrRole, target_list: Iterable[RequestTarget], ) -> None: self.local = local self.role = role self.target_list = target_list self.status_loaded = False self.status_plaintext = "" if env.ghost_file_codes: raise LibraryError( reports.live_environment_required(env.ghost_file_codes)) report_processor = env.report_processor report_list, dr_config = _load_dr_config(env.get_dr_env().config) report_processor.report_list(report_list) if report_processor.has_errors: raise LibraryError() site_data_list = [] target_factory = env.get_node_target_factory() # get local nodes local_nodes, report_list = get_existing_nodes_names( env.get_corosync_conf()) report_processor.report_list(report_list) report_list, local_targets = target_factory.get_target_list_with_reports( local_nodes, skip_non_existing=True, ) report_processor.report_list(report_list) site_data_list.append(SiteData(True, dr_config.local_role, local_targets)) # get remote sites' nodes for conf_remote_site in dr_config.get_remote_site_list(): report_list, remote_targets = ( target_factory.get_target_list_with_reports( conf_remote_site.node_name_list, skip_non_existing=True, )) report_processor.report_list(report_list) site_data_list.append( SiteData(False, conf_remote_site.role, remote_targets)) if report_processor.has_errors: raise LibraryError() # get all statuses for site_data in site_data_list: com_cmd = GetFullClusterStatusPlaintext( report_processor, hide_inactive_resources=hide_inactive_resources, verbose=verbose, ) com_cmd.set_targets(site_data.target_list) site_data.status_loaded, site_data.status_plaintext = run_com_cmd( env.get_node_communicator(), com_cmd) return [ dto.to_dict( DrSiteStatusDto( local_site=site_data.local, site_role=site_data.role, status_plaintext=site_data.status_plaintext, status_successfully_obtained=site_data.status_loaded, )) for site_data in site_data_list ]
def test_any_is_dict(self): dto = DtoWithAny("a", {1: "1", 2: "2"}) self.assertEqual(dict(field_a="a", field_b={ 1: "1", 2: "2" }), to_dict(dto))
def test_nested_to_dict(self): self.assertEqual(to_dict(self.nested_dto), self.nested_dict)
def assert_dto_equal(self, expected, actual): self.assertEqual(dto.to_dict(expected), dto.to_dict(actual))
def test_any_is_list(self): dto = DtoWithAny("a", [1, 2]) self.assertEqual(dict(field_a="a", field_b=[1, 2]), to_dict(dto))
def test_unfence_failure_agent_script_failed(self): self._unfence_failure_common_calls() self.config.http.corosync.get_corosync_online_targets( node_labels=self.existing_nodes ) self.config.http.scsi.unfence_node( communication_list=[ dict( label=self.existing_nodes[0], raw_data=json.dumps( dict( node=self.existing_nodes[0], original_devices=DEVICES_1, updated_devices=DEVICES_2, ) ), ), dict( label=self.existing_nodes[1], raw_data=json.dumps( dict( node=self.existing_nodes[1], original_devices=DEVICES_1, updated_devices=DEVICES_2, ) ), output=json.dumps( dto.to_dict( communication.dto.InternalCommunicationResultDto( status=communication.const.COM_STATUS_ERROR, status_msg="error", report_list=[ reports.ReportItem.error( reports.messages.StonithUnfencingFailed( "errB" ) ).to_dto() ], data=None, ) ) ), ), dict( label=self.existing_nodes[2], raw_data=json.dumps( dict( node=self.existing_nodes[2], original_devices=DEVICES_1, updated_devices=DEVICES_2, ) ), ), ], ) self.env_assist.assert_raise_library_error(self.command()) self.env_assist.assert_reports( [ fixture.error( reports.codes.STONITH_UNFENCING_FAILED, reason="errB", context=reports.dto.ReportItemContextDto( node=self.existing_nodes[1], ), ), ] )