3130 lines
114 KiB
Diff
3130 lines
114 KiB
Diff
|
|
From 75a8e52584a71087ae734c427870c9ea8d7935c7 Mon Sep 17 00:00:00 2001
|
||
|
|
From: Peter Romancik <promanci@redhat.com>
|
||
|
|
Date: Tue, 30 Jan 2024 18:02:50 +0100
|
||
|
|
Subject: [PATCH] fixes after review
|
||
|
|
|
||
|
|
---
|
||
|
|
pcs/common/const.py | 2 -
|
||
|
|
pcs/common/reports/codes.py | 20 +-
|
||
|
|
pcs/common/reports/messages.py | 187 +---
|
||
|
|
pcs/lib/commands/status.py | 16 +-
|
||
|
|
pcs/lib/pacemaker/status.py | 527 ++++++-----
|
||
|
|
pcs_test/resources/crm_mon.all_resources.xml | 75 +-
|
||
|
|
.../tier0/common/reports/test_messages.py | 155 +---
|
||
|
|
pcs_test/tier0/lib/commands/test_status.py | 303 ++++--
|
||
|
|
pcs_test/tier0/lib/pacemaker/test_status.py | 865 ++++++++----------
|
||
|
|
9 files changed, 968 insertions(+), 1182 deletions(-)
|
||
|
|
|
||
|
|
diff --git a/pcs/common/const.py b/pcs/common/const.py
|
||
|
|
index 32175677..00d1b7e7 100644
|
||
|
|
--- a/pcs/common/const.py
|
||
|
|
+++ b/pcs/common/const.py
|
||
|
|
@@ -14,14 +14,12 @@ PCMK_ROLE_PROMOTED = PcmkRoleType("Promoted")
|
||
|
|
PCMK_ROLE_UNPROMOTED = PcmkRoleType("Unpromoted")
|
||
|
|
PCMK_ROLE_PROMOTED_LEGACY = PcmkRoleType("Master")
|
||
|
|
PCMK_ROLE_UNPROMOTED_LEGACY = PcmkRoleType("Slave")
|
||
|
|
-PCMK_ROLE_UNKNOWN = PcmkRoleType("Unknown")
|
||
|
|
PCMK_STATUS_ROLE_STARTED = PcmkStatusRoleType("Started")
|
||
|
|
PCMK_STATUS_ROLE_STOPPED = PcmkStatusRoleType("Stopped")
|
||
|
|
PCMK_STATUS_ROLE_PROMOTED = PcmkStatusRoleType("Promoted")
|
||
|
|
PCMK_STATUS_ROLE_UNPROMOTED = PcmkStatusRoleType("Unpromoted")
|
||
|
|
PCMK_STATUS_ROLE_STARTING = PcmkStatusRoleType("Starting")
|
||
|
|
PCMK_STATUS_ROLE_STOPPING = PcmkStatusRoleType("Stopping")
|
||
|
|
-PCMK_STATUS_ROLE_UNKNOWN = PcmkStatusRoleType("Unknown")
|
||
|
|
PCMK_ON_FAIL_ACTION_IGNORE = PcmkOnFailAction("ignore")
|
||
|
|
PCMK_ON_FAIL_ACTION_BLOCK = PcmkOnFailAction("block")
|
||
|
|
PCMK_ON_FAIL_ACTION_DEMOTE = PcmkOnFailAction("demote")
|
||
|
|
diff --git a/pcs/common/reports/codes.py b/pcs/common/reports/codes.py
|
||
|
|
index 417a3f4a..f9614331 100644
|
||
|
|
--- a/pcs/common/reports/codes.py
|
||
|
|
+++ b/pcs/common/reports/codes.py
|
||
|
|
@@ -50,6 +50,7 @@ AGENT_SELF_VALIDATION_SKIPPED_UPDATED_RESOURCE_MISCONFIGURED = M(
|
||
|
|
)
|
||
|
|
AGENT_SELF_VALIDATION_RESULT = M("AGENT_SELF_VALIDATION_RESULT")
|
||
|
|
BAD_CLUSTER_STATE_FORMAT = M("BAD_CLUSTER_STATE_FORMAT")
|
||
|
|
+BAD_CLUSTER_STATE = M("BAD_CLUSTER_STATE")
|
||
|
|
BOOTH_ADDRESS_DUPLICATION = M("BOOTH_ADDRESS_DUPLICATION")
|
||
|
|
BOOTH_ALREADY_IN_CIB = M("BOOTH_ALREADY_IN_CIB")
|
||
|
|
BOOTH_AUTHFILE_NOT_USED = M("BOOTH_AUTHFILE_NOT_USED")
|
||
|
|
@@ -156,28 +157,9 @@ CLUSTER_RESTART_REQUIRED_TO_APPLY_CHANGES = M(
|
||
|
|
CLUSTER_SETUP_SUCCESS = M("CLUSTER_SETUP_SUCCESS")
|
||
|
|
CLUSTER_START_STARTED = M("CLUSTER_START_STARTED")
|
||
|
|
CLUSTER_START_SUCCESS = M("CLUSTER_START_SUCCESS")
|
||
|
|
-CLUSTER_STATUS_BUNDLE_DIFFERENT_REPLICAS = M(
|
||
|
|
- "CLUSTER_STATUS_BUNDLE_DIFFERENT_REPLICAS"
|
||
|
|
-)
|
||
|
|
CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT = M(
|
||
|
|
"CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT"
|
||
|
|
)
|
||
|
|
-CLUSTER_STATUS_BUNDLE_REPLICA_INVALID_COUNT = M(
|
||
|
|
- "CLUSTER_STATUS_BUNDLE_REPLICA_INVALID_COUNT"
|
||
|
|
-)
|
||
|
|
-CLUSTER_STATUS_BUNDLE_REPLICA_MISSING_REMOTE = M(
|
||
|
|
- "CLUSTER_STATUS_BUNDLE_REPLICA_MISSING_REMOTE"
|
||
|
|
-)
|
||
|
|
-CLUSTER_STATUS_BUNDLE_REPLICA_NO_CONTAINER = M(
|
||
|
|
- "CLUSTER_STATUS_BUNDLE_REPLICA_NO_CONTAINER"
|
||
|
|
-)
|
||
|
|
-CLUSTER_STATUS_CLONE_MEMBERS_DIFFERENT_IDS = M(
|
||
|
|
- "CLUSTER_STATUS_CLONE_MEMBERS_DIFFERENT_IDS"
|
||
|
|
-)
|
||
|
|
-CLUSTER_STATUS_CLONE_MIXED_MEMBERS = M("CLUSTER_STATUS_CLONE_MIXED_MEMBERS")
|
||
|
|
-CLUSTER_STATUS_EMPTY_NODE_NAME = M("CLUSTER_STATUS_EMPTY_NODE_NAME")
|
||
|
|
-CLUSTER_STATUS_UNEXPECTED_MEMBER = M("CLUSTER_STATUS_UNEXPECTED_MEMBER")
|
||
|
|
-CLUSTER_STATUS_UNKNOWN_PCMK_ROLE = M("CLUSTER_STATUS_UNKNOWN_PCMK_ROLE")
|
||
|
|
CLUSTER_UUID_ALREADY_SET = M("CLUSTER_UUID_ALREADY_SET")
|
||
|
|
CLUSTER_WILL_BE_DESTROYED = M("CLUSTER_WILL_BE_DESTROYED")
|
||
|
|
COMMAND_INVALID_PAYLOAD = M("COMMAND_INVALID_PAYLOAD")
|
||
|
|
diff --git a/pcs/common/reports/messages.py b/pcs/common/reports/messages.py
|
||
|
|
index 1e98711c..8b9bc63e 100644
|
||
|
|
--- a/pcs/common/reports/messages.py
|
||
|
|
+++ b/pcs/common/reports/messages.py
|
||
|
|
@@ -3277,183 +3277,32 @@ class BadClusterStateFormat(ReportItemMessage):
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusUnknownPcmkRole(ReportItemMessage):
|
||
|
|
+class BadClusterState(ReportItemMessage):
|
||
|
|
"""
|
||
|
|
- Value of pcmk role in the status xml is not valid
|
||
|
|
+ crm_mon xml output is invalid despite conforming to the schema
|
||
|
|
|
||
|
|
- role -- value of the role attribute
|
||
|
|
- resource_id -- id of the resource
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- role: Optional[str]
|
||
|
|
- resource_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_UNKNOWN_PCMK_ROLE
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return (
|
||
|
|
- "Attribute of resource with id '{id}' "
|
||
|
|
- "contains {invalid} pcmk role{role}."
|
||
|
|
- ).format(
|
||
|
|
- id=self.resource_id,
|
||
|
|
- invalid="empty" if not self.role else "invalid",
|
||
|
|
- role=f" '{self.role}'" if self.role else "",
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusEmptyNodeName(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Resource in the status xml contains node with empty name
|
||
|
|
-
|
||
|
|
- resource_id -- id of the resource
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- resource_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_EMPTY_NODE_NAME
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return (
|
||
|
|
- f"Resource with id '{self.resource_id}' contains node "
|
||
|
|
- "with empty name."
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusUnexpectedMember(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Unexpected resource type is present in present as child element
|
||
|
|
- in another resource type
|
||
|
|
-
|
||
|
|
- resource_id -- id of the outer resource
|
||
|
|
- resource_type -- type of the outer resource
|
||
|
|
- member_id -- id of the unexpected member
|
||
|
|
- expected_type -- valid types for members
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- resource_id: str
|
||
|
|
- resource_type: str
|
||
|
|
- member_id: str
|
||
|
|
- expected_types: list[str]
|
||
|
|
- _code = codes.CLUSTER_STATUS_UNEXPECTED_MEMBER
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return (
|
||
|
|
- f"Unexpected resource '{self.member_id}' inside of resource "
|
||
|
|
- f"'{self.resource_id}' of type '{self.resource_type}'. "
|
||
|
|
- f"Only resources of type {format_list(self.expected_types, '|')} "
|
||
|
|
- f"can be in {self.resource_type}."
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusCloneMixedMembers(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Members of multiple types are present in a clone in the status xml
|
||
|
|
-
|
||
|
|
- member_id -- id of the unexpected member
|
||
|
|
- clone_id -- id of the clone
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- clone_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_CLONE_MIXED_MEMBERS
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return f"Primitive and group members mixed in clone '{self.clone_id}'."
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusCloneMembersDifferentIds(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Clone instances in crm_mon status xml have different ids
|
||
|
|
-
|
||
|
|
- clone_id -- id of the clone
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- clone_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_CLONE_MEMBERS_DIFFERENT_IDS
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return f"Members with different ids in clone '{self.clone_id}'."
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusBundleReplicaNoContainer(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Bundle replica is missing implicit container resource in the status xml
|
||
|
|
-
|
||
|
|
- bundle_id -- id of the bundle
|
||
|
|
- replica_id -- id of the replica
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- bundle_id: str
|
||
|
|
- replica_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_BUNDLE_REPLICA_NO_CONTAINER
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return (
|
||
|
|
- f"Replica '{self.replica_id}' of bundle '{self.bundle_id}' "
|
||
|
|
- "is missing implicit container resource."
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusBundleReplicaMissingRemote(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Bundle replica is missing implicit pacemaker remote resource
|
||
|
|
- in the status xml
|
||
|
|
-
|
||
|
|
- bundle_id -- id of the bundle
|
||
|
|
- replica_id -- id of the replica
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- bundle_id: str
|
||
|
|
- replica_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_BUNDLE_REPLICA_MISSING_REMOTE
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return (
|
||
|
|
- f"Replica '{self.replica_id}' of bundle '{self.bundle_id}' is "
|
||
|
|
- "missing implicit pacemaker remote resource while it must be "
|
||
|
|
- "present."
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusBundleReplicaInvalidCount(ReportItemMessage):
|
||
|
|
+ reason -- error description
|
||
|
|
"""
|
||
|
|
- Bundle replica is has invalid number of members in the status xml
|
||
|
|
|
||
|
|
- bundle_id -- id of the bundle
|
||
|
|
- replica_id -- id of the replica
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- bundle_id: str
|
||
|
|
- replica_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_BUNDLE_REPLICA_INVALID_COUNT
|
||
|
|
+ reason: Optional[str] = None
|
||
|
|
+ _code = codes.BAD_CLUSTER_STATE
|
||
|
|
|
||
|
|
@property
|
||
|
|
def message(self) -> str:
|
||
|
|
return (
|
||
|
|
- f"Replica '{self.replica_id}' of bundle '{self.bundle_id}' has "
|
||
|
|
- f"invalid number of members. Expecting 2-4 members."
|
||
|
|
+ "Cannot load cluster status, xml does not describe valid cluster "
|
||
|
|
+ f"status{format_optional(self.reason, template=': {}')}."
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class ClusterStatusBundleMemberIdAsImplicit(ReportItemMessage):
|
||
|
|
"""
|
||
|
|
- Member of bundle in cluster status xml has the same id as one of
|
||
|
|
- the implicit resources
|
||
|
|
+ Member of bundle in cluster status xml has the same id as one of the
|
||
|
|
+ implicit resources
|
||
|
|
|
||
|
|
bundle_id -- id of the bundle
|
||
|
|
- member_id -- id if the bundle member
|
||
|
|
+ bad_ids -- ids of the bad members
|
||
|
|
"""
|
||
|
|
|
||
|
|
bundle_id: str
|
||
|
|
@@ -3474,22 +3323,6 @@ class ClusterStatusBundleMemberIdAsImplicit(ReportItemMessage):
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
-@dataclass(frozen=True)
|
||
|
|
-class ClusterStatusBundleDifferentReplicas(ReportItemMessage):
|
||
|
|
- """
|
||
|
|
- Replicas of bundle are different in the cluster status xml
|
||
|
|
-
|
||
|
|
- bundle_id -- id of the bundle
|
||
|
|
- """
|
||
|
|
-
|
||
|
|
- bundle_id: str
|
||
|
|
- _code = codes.CLUSTER_STATUS_BUNDLE_DIFFERENT_REPLICAS
|
||
|
|
-
|
||
|
|
- @property
|
||
|
|
- def message(self) -> str:
|
||
|
|
- return f"Replicas of bundle '{self.bundle_id}' are not the same."
|
||
|
|
-
|
||
|
|
-
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class WaitForIdleStarted(ReportItemMessage):
|
||
|
|
"""
|
||
|
|
diff --git a/pcs/lib/commands/status.py b/pcs/lib/commands/status.py
|
||
|
|
index 8b644ac1..f9ec6160 100644
|
||
|
|
--- a/pcs/lib/commands/status.py
|
||
|
|
+++ b/pcs/lib/commands/status.py
|
||
|
|
@@ -49,7 +49,11 @@ from pcs.lib.pacemaker.live import (
|
||
|
|
get_cluster_status_xml_raw,
|
||
|
|
get_ticket_status_text,
|
||
|
|
)
|
||
|
|
-from pcs.lib.pacemaker.status import status_xml_to_dto
|
||
|
|
+from pcs.lib.pacemaker.status import (
|
||
|
|
+ ClusterStatusParser,
|
||
|
|
+ ClusterStatusParsingError,
|
||
|
|
+ cluster_status_parsing_error_to_report,
|
||
|
|
+)
|
||
|
|
from pcs.lib.resource_agent.const import STONITH_ACTION_REPLACED_BY
|
||
|
|
from pcs.lib.sbd import get_sbd_service_name
|
||
|
|
|
||
|
|
@@ -79,7 +83,15 @@ def resources_status(env: LibraryEnvironment) -> ResourcesStatusDto:
|
||
|
|
"""
|
||
|
|
status_xml = env.get_cluster_state()
|
||
|
|
|
||
|
|
- return status_xml_to_dto(env.report_processor, status_xml)
|
||
|
|
+ parser = ClusterStatusParser(status_xml)
|
||
|
|
+ try:
|
||
|
|
+ dto = parser.status_xml_to_dto()
|
||
|
|
+ except ClusterStatusParsingError as e:
|
||
|
|
+ raise LibraryError(cluster_status_parsing_error_to_report(e)) from e
|
||
|
|
+
|
||
|
|
+ env.report_processor.report_list(parser.get_warnings())
|
||
|
|
+
|
||
|
|
+ return dto
|
||
|
|
|
||
|
|
|
||
|
|
def full_cluster_status_plaintext(
|
||
|
|
diff --git a/pcs/lib/pacemaker/status.py b/pcs/lib/pacemaker/status.py
|
||
|
|
index 722ce03f..a86ede55 100644
|
||
|
|
--- a/pcs/lib/pacemaker/status.py
|
||
|
|
+++ b/pcs/lib/pacemaker/status.py
|
||
|
|
@@ -1,3 +1,4 @@
|
||
|
|
+from collections import Counter
|
||
|
|
from typing import (
|
||
|
|
Optional,
|
||
|
|
Sequence,
|
||
|
|
@@ -9,14 +10,11 @@ from lxml.etree import _Element
|
||
|
|
|
||
|
|
from pcs.common import reports
|
||
|
|
from pcs.common.const import (
|
||
|
|
- PCMK_ROLE_UNKNOWN,
|
||
|
|
PCMK_ROLES,
|
||
|
|
- PCMK_STATUS_ROLE_UNKNOWN,
|
||
|
|
PCMK_STATUS_ROLES,
|
||
|
|
PcmkRoleType,
|
||
|
|
PcmkStatusRoleType,
|
||
|
|
)
|
||
|
|
-from pcs.common.reports import ReportProcessor
|
||
|
|
from pcs.common.status_dto import (
|
||
|
|
AnyResourceStatusDto,
|
||
|
|
BundleReplicaStatusDto,
|
||
|
|
@@ -26,7 +24,7 @@ from pcs.common.status_dto import (
|
||
|
|
PrimitiveStatusDto,
|
||
|
|
ResourcesStatusDto,
|
||
|
|
)
|
||
|
|
-from pcs.lib.errors import LibraryError
|
||
|
|
+from pcs.common.str_tools import format_list
|
||
|
|
from pcs.lib.pacemaker.values import is_true
|
||
|
|
|
||
|
|
_PRIMITIVE_TAG = "resource"
|
||
|
|
@@ -36,31 +34,137 @@ _BUNDLE_TAG = "bundle"
|
||
|
|
_REPLICA_TAG = "replica"
|
||
|
|
|
||
|
|
|
||
|
|
+class ClusterStatusParsingError(Exception):
|
||
|
|
+ def __init__(self, resource_id: str):
|
||
|
|
+ self.resource_id = resource_id
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class EmptyResourceIdError(ClusterStatusParsingError):
|
||
|
|
+ def __init__(self):
|
||
|
|
+ super().__init__("")
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class EmptyNodeNameError(ClusterStatusParsingError):
|
||
|
|
+ pass
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class UnknownPcmkRoleError(ClusterStatusParsingError):
|
||
|
|
+ def __init__(self, resource_id: str, role: str):
|
||
|
|
+ super().__init__(resource_id)
|
||
|
|
+ self.role = role
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class UnexpectedMemberError(ClusterStatusParsingError):
|
||
|
|
+ def __init__(
|
||
|
|
+ self,
|
||
|
|
+ resource_id: str,
|
||
|
|
+ resource_type: str,
|
||
|
|
+ member_id: str,
|
||
|
|
+ expected_types: list[str],
|
||
|
|
+ ):
|
||
|
|
+ super().__init__(resource_id)
|
||
|
|
+ self.resource_type = resource_type
|
||
|
|
+ self.member_id = member_id
|
||
|
|
+ self.expected_types = expected_types
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class MixedMembersError(ClusterStatusParsingError):
|
||
|
|
+ pass
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class DifferentMemberIdsError(ClusterStatusParsingError):
|
||
|
|
+ pass
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class BundleReplicaMissingImplicitResourceError(ClusterStatusParsingError):
|
||
|
|
+ def __init__(
|
||
|
|
+ self, resource_id: str, replica_id: str, implicit_resource_type: str
|
||
|
|
+ ):
|
||
|
|
+ super().__init__(resource_id)
|
||
|
|
+ self.replica_id = replica_id
|
||
|
|
+ self.implicit_type = implicit_resource_type
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class BundleReplicaInvalidMemberCountError(ClusterStatusParsingError):
|
||
|
|
+ def __init__(self, resource_id: str, replica_id: str):
|
||
|
|
+ super().__init__(resource_id)
|
||
|
|
+ self.replica_id = replica_id
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class BundleDifferentReplicas(ClusterStatusParsingError):
|
||
|
|
+ pass
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+class BundleSameIdAsImplicitResourceError(Exception):
|
||
|
|
+ def __init__(self, bundle_id: str, bad_ids: list[str]):
|
||
|
|
+ self.bundle_id = bundle_id
|
||
|
|
+ self.bad_ids = bad_ids
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+def cluster_status_parsing_error_to_report(
|
||
|
|
+ e: ClusterStatusParsingError,
|
||
|
|
+) -> reports.ReportItem:
|
||
|
|
+ reason = ""
|
||
|
|
+ if isinstance(e, EmptyResourceIdError):
|
||
|
|
+ reason = "Resource with empty id."
|
||
|
|
+ elif isinstance(e, EmptyNodeNameError):
|
||
|
|
+ reason = (
|
||
|
|
+ f"Resource with id '{e.resource_id}' contains node with empty name."
|
||
|
|
+ )
|
||
|
|
+ elif isinstance(e, UnknownPcmkRoleError):
|
||
|
|
+ reason = (
|
||
|
|
+ f"Resource with id '{e.resource_id}' contains unknown "
|
||
|
|
+ f"pcmk role '{e.role}'."
|
||
|
|
+ )
|
||
|
|
+ elif isinstance(e, UnexpectedMemberError):
|
||
|
|
+ reason = (
|
||
|
|
+ f"Unexpected resource '{e.member_id}' inside of resource "
|
||
|
|
+ f"'{e.resource_id}' of type '{e.resource_type}'. "
|
||
|
|
+ f"Only resources of type {format_list(e.expected_types, '|')} "
|
||
|
|
+ f"can be in {e.resource_type}."
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ elif isinstance(e, MixedMembersError):
|
||
|
|
+ reason = (
|
||
|
|
+ f"Primitive and group members mixed in clone '{e.resource_id}'."
|
||
|
|
+ )
|
||
|
|
+ elif isinstance(e, DifferentMemberIdsError):
|
||
|
|
+ reason = f"Members with different ids in resource '{e.resource_id}'."
|
||
|
|
+ elif isinstance(e, BundleReplicaMissingImplicitResourceError):
|
||
|
|
+ reason = (
|
||
|
|
+ f"Replica '{e.replica_id}' of bundle '{e.resource_id}' "
|
||
|
|
+ f"is missing implicit {e.implicit_type} resource."
|
||
|
|
+ )
|
||
|
|
+ elif isinstance(e, BundleReplicaInvalidMemberCountError):
|
||
|
|
+ reason = (
|
||
|
|
+ f"Replica '{e.replica_id}' of bundle '{e.resource_id}' has "
|
||
|
|
+ "invalid number of members."
|
||
|
|
+ )
|
||
|
|
+ elif isinstance(e, BundleDifferentReplicas):
|
||
|
|
+ reason = f"Replicas of bundle '{e.resource_id}' are not the same."
|
||
|
|
+
|
||
|
|
+ return reports.ReportItem(
|
||
|
|
+ reports.ReportItemSeverity.error(),
|
||
|
|
+ reports.messages.BadClusterState(reason),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+
|
||
|
|
def _primitive_to_dto(
|
||
|
|
- reporter: ReportProcessor,
|
||
|
|
- primitive_el: _Element,
|
||
|
|
- remove_clone_suffix: bool = False,
|
||
|
|
+ primitive_el: _Element, remove_clone_suffix: bool = False
|
||
|
|
) -> PrimitiveStatusDto:
|
||
|
|
- resource_id = _get_resource_id(reporter, primitive_el)
|
||
|
|
+ resource_id = _get_resource_id(primitive_el)
|
||
|
|
if remove_clone_suffix:
|
||
|
|
resource_id = _remove_clone_suffix(resource_id)
|
||
|
|
|
||
|
|
- role = _get_role(reporter, primitive_el, resource_id)
|
||
|
|
- target_role = _get_target_role(reporter, primitive_el, resource_id)
|
||
|
|
+ role = _get_role(primitive_el)
|
||
|
|
+ target_role = _get_target_role(primitive_el)
|
||
|
|
|
||
|
|
node_names = [
|
||
|
|
str(node.get("name")) for node in primitive_el.iterfind("node")
|
||
|
|
]
|
||
|
|
|
||
|
|
if node_names and any(not name for name in node_names):
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusEmptyNodeName(resource_id)
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- if reporter.has_errors:
|
||
|
|
- raise LibraryError()
|
||
|
|
+ raise EmptyNodeNameError(resource_id)
|
||
|
|
|
||
|
|
return PrimitiveStatusDto(
|
||
|
|
resource_id,
|
||
|
|
@@ -82,87 +186,70 @@ def _primitive_to_dto(
|
||
|
|
|
||
|
|
|
||
|
|
def _group_to_dto(
|
||
|
|
- reporter: ReportProcessor,
|
||
|
|
- group_el: _Element,
|
||
|
|
- remove_clone_suffix: bool = False,
|
||
|
|
+ group_el: _Element, remove_clone_suffix: bool = False
|
||
|
|
) -> GroupStatusDto:
|
||
|
|
# clone suffix is added even when the clone is non unique
|
||
|
|
- group_id = _remove_clone_suffix(_get_resource_id(reporter, group_el))
|
||
|
|
- members = []
|
||
|
|
+ group_id = _remove_clone_suffix(_get_resource_id(group_el))
|
||
|
|
+ member_list = []
|
||
|
|
|
||
|
|
for member in group_el:
|
||
|
|
if member.tag == _PRIMITIVE_TAG:
|
||
|
|
- members.append(
|
||
|
|
- _primitive_to_dto(reporter, member, remove_clone_suffix)
|
||
|
|
- )
|
||
|
|
+ member_list.append(_primitive_to_dto(member, remove_clone_suffix))
|
||
|
|
else:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusUnexpectedMember(
|
||
|
|
- group_id, "group", str(member.get("id")), ["primitive"]
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
+ raise UnexpectedMemberError(
|
||
|
|
+ group_id, "group", str(member.get("id")), ["primitive"]
|
||
|
|
)
|
||
|
|
|
||
|
|
- if reporter.has_errors:
|
||
|
|
- raise LibraryError()
|
||
|
|
-
|
||
|
|
return GroupStatusDto(
|
||
|
|
group_id,
|
||
|
|
is_true(group_el.get("maintenance", "false")),
|
||
|
|
group_el.get("description"),
|
||
|
|
is_true(group_el.get("managed", "false")),
|
||
|
|
is_true(group_el.get("disabled", "false")),
|
||
|
|
- members,
|
||
|
|
+ member_list,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _clone_to_dto(
|
||
|
|
- reporter: ReportProcessor,
|
||
|
|
- clone_el: _Element,
|
||
|
|
- _remove_clone_suffix: bool = False,
|
||
|
|
+ clone_el: _Element, _remove_clone_suffix: bool = False
|
||
|
|
) -> CloneStatusDto:
|
||
|
|
- clone_id = _get_resource_id(reporter, clone_el)
|
||
|
|
+ clone_id = _get_resource_id(clone_el)
|
||
|
|
is_unique = is_true(clone_el.get("unique", "false"))
|
||
|
|
|
||
|
|
- target_role = _get_target_role(reporter, clone_el, clone_id)
|
||
|
|
+ target_role = _get_target_role(clone_el)
|
||
|
|
|
||
|
|
- primitives = []
|
||
|
|
- groups = []
|
||
|
|
+ primitive_list = []
|
||
|
|
+ group_list = []
|
||
|
|
|
||
|
|
for member in clone_el:
|
||
|
|
if member.tag == _PRIMITIVE_TAG:
|
||
|
|
- primitives.append(_primitive_to_dto(reporter, member, is_unique))
|
||
|
|
+ primitive_list.append(_primitive_to_dto(member, is_unique))
|
||
|
|
elif member.tag == _GROUP_TAG:
|
||
|
|
- groups.append(_group_to_dto(reporter, member, is_unique))
|
||
|
|
+ group_list.append(_group_to_dto(member, is_unique))
|
||
|
|
else:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusUnexpectedMember(
|
||
|
|
- clone_id,
|
||
|
|
- "clone",
|
||
|
|
- str(member.get("id")),
|
||
|
|
- ["primitive", "group"],
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
+ raise UnexpectedMemberError(
|
||
|
|
+ clone_id, "clone", str(member.get("id")), ["primitive", "group"]
|
||
|
|
)
|
||
|
|
|
||
|
|
- reporter.report_list(
|
||
|
|
- _validate_mixed_instance_types(primitives, groups, clone_id)
|
||
|
|
- )
|
||
|
|
+ if primitive_list and group_list:
|
||
|
|
+ raise MixedMembersError(clone_id)
|
||
|
|
|
||
|
|
- instances: Union[list[PrimitiveStatusDto], list[GroupStatusDto]]
|
||
|
|
- if primitives:
|
||
|
|
- reporter.report_list(
|
||
|
|
- _validate_primitive_instance_ids(primitives, clone_id)
|
||
|
|
- )
|
||
|
|
- instances = primitives
|
||
|
|
+ instance_list: Union[list[PrimitiveStatusDto], list[GroupStatusDto]]
|
||
|
|
+ if primitive_list:
|
||
|
|
+ if len(set(res.resource_id for res in primitive_list)) > 1:
|
||
|
|
+ raise DifferentMemberIdsError(clone_id)
|
||
|
|
+ instance_list = primitive_list
|
||
|
|
else:
|
||
|
|
- reporter.report_list(_validate_group_instance_ids(groups, clone_id))
|
||
|
|
- instances = groups
|
||
|
|
+ group_ids = set(group.resource_id for group in group_list)
|
||
|
|
+ children_ids = set(
|
||
|
|
+ tuple(child.resource_id for child in group.members)
|
||
|
|
+ for group in group_list
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ if len(group_ids) > 1 or len(children_ids) > 1:
|
||
|
|
+ raise DifferentMemberIdsError(clone_id)
|
||
|
|
|
||
|
|
- if reporter.has_errors:
|
||
|
|
- raise LibraryError()
|
||
|
|
+ instance_list = group_list
|
||
|
|
|
||
|
|
return CloneStatusDto(
|
||
|
|
clone_id,
|
||
|
|
@@ -175,30 +262,23 @@ def _clone_to_dto(
|
||
|
|
is_true(clone_el.get("failed", "false")),
|
||
|
|
is_true(clone_el.get("failure_ignored", "false")),
|
||
|
|
target_role,
|
||
|
|
- instances,
|
||
|
|
+ instance_list,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _bundle_to_dto(
|
||
|
|
- reporter: ReportProcessor,
|
||
|
|
- bundle_el: _Element,
|
||
|
|
- _remove_clone_suffix: bool = False,
|
||
|
|
-) -> Optional[BundleStatusDto]:
|
||
|
|
- bundle_id = _get_resource_id(reporter, bundle_el)
|
||
|
|
+ bundle_el: _Element, _remove_clone_suffix: bool = False
|
||
|
|
+) -> BundleStatusDto:
|
||
|
|
+ bundle_id = _get_resource_id(bundle_el)
|
||
|
|
bundle_type = str(bundle_el.get("type"))
|
||
|
|
|
||
|
|
- replicas = []
|
||
|
|
- for replica in bundle_el.iterfind(_REPLICA_TAG):
|
||
|
|
- replica_dto = _replica_to_dto(reporter, replica, bundle_id, bundle_type)
|
||
|
|
- if replica_dto is None:
|
||
|
|
- # skip this bundle in status
|
||
|
|
- return None
|
||
|
|
- replicas.append(replica_dto)
|
||
|
|
-
|
||
|
|
- reporter.report_list(_validate_replicas(replicas, bundle_id))
|
||
|
|
+ replica_list = [
|
||
|
|
+ _replica_to_dto(replica, bundle_id, bundle_type)
|
||
|
|
+ for replica in bundle_el.iterfind(_REPLICA_TAG)
|
||
|
|
+ ]
|
||
|
|
|
||
|
|
- if reporter.has_errors:
|
||
|
|
- raise LibraryError()
|
||
|
|
+ if not _replicas_valid(replica_list):
|
||
|
|
+ raise BundleDifferentReplicas(bundle_id)
|
||
|
|
|
||
|
|
return BundleStatusDto(
|
||
|
|
bundle_id,
|
||
|
|
@@ -209,87 +289,79 @@ def _bundle_to_dto(
|
||
|
|
bundle_el.get("description"),
|
||
|
|
is_true(bundle_el.get("managed", "false")),
|
||
|
|
is_true(bundle_el.get("failed", "false")),
|
||
|
|
- replicas,
|
||
|
|
+ replica_list,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
-_TAG_TO_FUNCTION = {
|
||
|
|
- _PRIMITIVE_TAG: _primitive_to_dto,
|
||
|
|
- _GROUP_TAG: _group_to_dto,
|
||
|
|
- _CLONE_TAG: _clone_to_dto,
|
||
|
|
- _BUNDLE_TAG: _bundle_to_dto,
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-def status_xml_to_dto(
|
||
|
|
- reporter: ReportProcessor, status: _Element
|
||
|
|
-) -> ResourcesStatusDto:
|
||
|
|
- """
|
||
|
|
- Return dto containing status of configured resources in the cluster
|
||
|
|
-
|
||
|
|
- reporter -- ReportProcessor
|
||
|
|
- status -- status xml document from crm_mon, validated using
|
||
|
|
- the appropriate rng schema
|
||
|
|
- """
|
||
|
|
- resources = cast(list[_Element], status.xpath("resources/*"))
|
||
|
|
-
|
||
|
|
- resource_dtos = [
|
||
|
|
- _TAG_TO_FUNCTION[resource.tag](reporter, resource)
|
||
|
|
- for resource in resources
|
||
|
|
- if resource.tag in _TAG_TO_FUNCTION
|
||
|
|
- ]
|
||
|
|
+class ClusterStatusParser:
|
||
|
|
+ TAG_TO_FUNCTION = {
|
||
|
|
+ _PRIMITIVE_TAG: _primitive_to_dto,
|
||
|
|
+ _GROUP_TAG: _group_to_dto,
|
||
|
|
+ _CLONE_TAG: _clone_to_dto,
|
||
|
|
+ _BUNDLE_TAG: _bundle_to_dto,
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ def __init__(self, status: _Element):
|
||
|
|
+ self.status = status
|
||
|
|
+ self.warnings: reports.ReportItemList = []
|
||
|
|
+
|
||
|
|
+ def status_xml_to_dto(self) -> ResourcesStatusDto:
|
||
|
|
+ """
|
||
|
|
+ Return dto containing status of configured resources in the cluster
|
||
|
|
+
|
||
|
|
+ status -- status xml document from crm_mon, validated using
|
||
|
|
+ the appropriate rng schema
|
||
|
|
+ """
|
||
|
|
+ resource_list = cast(list[_Element], self.status.xpath("resources/*"))
|
||
|
|
+
|
||
|
|
+ resource_dto_list = []
|
||
|
|
+ for resource in resource_list:
|
||
|
|
+ try:
|
||
|
|
+ resource_dto = cast(
|
||
|
|
+ AnyResourceStatusDto,
|
||
|
|
+ self.TAG_TO_FUNCTION[resource.tag](resource),
|
||
|
|
+ )
|
||
|
|
+ resource_dto_list.append(resource_dto)
|
||
|
|
+ except BundleSameIdAsImplicitResourceError as e:
|
||
|
|
+ # This is the only error that the user can cause directly by
|
||
|
|
+ # setting the name of the bundle member to be same as one of
|
||
|
|
+ # the implicitly created resource.
|
||
|
|
+ # We only skip such bundles while still providing status of the
|
||
|
|
+ # other resources.
|
||
|
|
+ self.warnings.append(
|
||
|
|
+ reports.ReportItem.warning(
|
||
|
|
+ reports.messages.ClusterStatusBundleMemberIdAsImplicit(
|
||
|
|
+ e.bundle_id, e.bad_ids
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
|
||
|
|
- if reporter.has_errors:
|
||
|
|
- raise LibraryError()
|
||
|
|
+ return ResourcesStatusDto(resource_dto_list)
|
||
|
|
|
||
|
|
- return ResourcesStatusDto(
|
||
|
|
- cast(
|
||
|
|
- list[AnyResourceStatusDto],
|
||
|
|
- [dto for dto in resource_dtos if dto is not None],
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
+ def get_warnings(self) -> reports.ReportItemList:
|
||
|
|
+ return self.warnings
|
||
|
|
|
||
|
|
|
||
|
|
-def _get_resource_id(reporter: ReportProcessor, resource: _Element) -> str:
|
||
|
|
+def _get_resource_id(resource: _Element) -> str:
|
||
|
|
resource_id = resource.get("id")
|
||
|
|
if not resource_id:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.InvalidIdIsEmpty("resource id")
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
+ raise EmptyResourceIdError()
|
||
|
|
return str(resource_id)
|
||
|
|
|
||
|
|
|
||
|
|
-def _get_role(
|
||
|
|
- reporter: ReportProcessor, resource: _Element, resource_id: str
|
||
|
|
-) -> PcmkStatusRoleType:
|
||
|
|
+def _get_role(resource: _Element) -> PcmkStatusRoleType:
|
||
|
|
role = resource.get("role")
|
||
|
|
if role is None or role not in PCMK_STATUS_ROLES:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.warning(
|
||
|
|
- reports.messages.ClusterStatusUnknownPcmkRole(role, resource_id)
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- return PCMK_STATUS_ROLE_UNKNOWN
|
||
|
|
+ raise UnknownPcmkRoleError(str(resource.get("id")), str(role))
|
||
|
|
return PcmkStatusRoleType(role)
|
||
|
|
|
||
|
|
|
||
|
|
-def _get_target_role(
|
||
|
|
- reporter: ReportProcessor, resource: _Element, resource_id: str
|
||
|
|
-) -> Optional[PcmkRoleType]:
|
||
|
|
+def _get_target_role(resource: _Element) -> Optional[PcmkRoleType]:
|
||
|
|
target_role = resource.get("target_role")
|
||
|
|
if target_role is None:
|
||
|
|
return None
|
||
|
|
if target_role not in PCMK_ROLES:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.warning(
|
||
|
|
- reports.messages.ClusterStatusUnknownPcmkRole(
|
||
|
|
- target_role, resource_id
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- return PCMK_ROLE_UNKNOWN
|
||
|
|
+ raise UnknownPcmkRoleError(str(resource.get("id")), target_role)
|
||
|
|
return PcmkRoleType(target_role)
|
||
|
|
|
||
|
|
|
||
|
|
@@ -299,130 +371,66 @@ def _remove_clone_suffix(resource_id: str) -> str:
|
||
|
|
return resource_id
|
||
|
|
|
||
|
|
|
||
|
|
-def _validate_mixed_instance_types(
|
||
|
|
- primitives: list[PrimitiveStatusDto],
|
||
|
|
- groups: list[GroupStatusDto],
|
||
|
|
- clone_id: str,
|
||
|
|
-) -> reports.ReportItemList:
|
||
|
|
- if primitives and groups:
|
||
|
|
- return [
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusCloneMixedMembers(clone_id)
|
||
|
|
- )
|
||
|
|
- ]
|
||
|
|
- return []
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-def _validate_primitive_instance_ids(
|
||
|
|
- instances: list[PrimitiveStatusDto], clone_id: str
|
||
|
|
-) -> reports.ReportItemList:
|
||
|
|
- if len(set(res.resource_id for res in instances)) > 1:
|
||
|
|
- return [
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusCloneMembersDifferentIds(clone_id)
|
||
|
|
- )
|
||
|
|
- ]
|
||
|
|
- return []
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-def _validate_group_instance_ids(
|
||
|
|
- instances: list[GroupStatusDto], clone_id: str
|
||
|
|
-) -> reports.ReportItemList:
|
||
|
|
- group_ids = set(group.resource_id for group in instances)
|
||
|
|
- children_ids = set(
|
||
|
|
- tuple(child.resource_id for child in group.members)
|
||
|
|
- for group in instances
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- if len(group_ids) > 1 or len(children_ids) > 1:
|
||
|
|
- return [
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusCloneMembersDifferentIds(clone_id)
|
||
|
|
- )
|
||
|
|
- ]
|
||
|
|
- return []
|
||
|
|
-
|
||
|
|
-
|
||
|
|
def _replica_to_dto(
|
||
|
|
- reporter: ReportProcessor,
|
||
|
|
- replica_el: _Element,
|
||
|
|
- bundle_id: str,
|
||
|
|
- bundle_type: str,
|
||
|
|
-) -> Optional[BundleReplicaStatusDto]:
|
||
|
|
+ replica_el: _Element, bundle_id: str, bundle_type: str
|
||
|
|
+) -> BundleReplicaStatusDto:
|
||
|
|
replica_id = str(replica_el.get("id"))
|
||
|
|
|
||
|
|
- resources = [
|
||
|
|
- _primitive_to_dto(reporter, resource)
|
||
|
|
+ resource_list = [
|
||
|
|
+ _primitive_to_dto(resource)
|
||
|
|
for resource in replica_el.iterfind(_PRIMITIVE_TAG)
|
||
|
|
]
|
||
|
|
|
||
|
|
- duplicate_ids = _find_duplicate_ids(resources)
|
||
|
|
+ duplicate_ids = [
|
||
|
|
+ id
|
||
|
|
+ for id, count in Counter(
|
||
|
|
+ resource.resource_id for resource in resource_list
|
||
|
|
+ ).items()
|
||
|
|
+ if count > 1
|
||
|
|
+ ]
|
||
|
|
+
|
||
|
|
if duplicate_ids:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.warning(
|
||
|
|
- reports.messages.ClusterStatusBundleMemberIdAsImplicit(
|
||
|
|
- bundle_id, duplicate_ids
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- return None
|
||
|
|
+ raise BundleSameIdAsImplicitResourceError(bundle_id, duplicate_ids)
|
||
|
|
|
||
|
|
# TODO pacemaker will probably add prefix
|
||
|
|
# "pcmk-internal" to all implicit resources
|
||
|
|
|
||
|
|
- container_resource = _get_implicit_resource(
|
||
|
|
- resources,
|
||
|
|
+ container_resource = _pop_implicit_resource(
|
||
|
|
+ resource_list,
|
||
|
|
f"{bundle_id}-{bundle_type}-{replica_id}",
|
||
|
|
True,
|
||
|
|
f"ocf:heartbeat:{bundle_type}",
|
||
|
|
)
|
||
|
|
|
||
|
|
if container_resource is None:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusBundleReplicaNoContainer(
|
||
|
|
- bundle_id, replica_id
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
+ raise BundleReplicaMissingImplicitResourceError(
|
||
|
|
+ bundle_id, replica_id, "container"
|
||
|
|
)
|
||
|
|
- raise LibraryError()
|
||
|
|
|
||
|
|
- remote_resource = _get_implicit_resource(
|
||
|
|
- resources, f"{bundle_id}-{replica_id}", True, "ocf:pacemaker:remote"
|
||
|
|
+ remote_resource = _pop_implicit_resource(
|
||
|
|
+ resource_list, f"{bundle_id}-{replica_id}", True, "ocf:pacemaker:remote"
|
||
|
|
)
|
||
|
|
|
||
|
|
# implicit ip address resource might be present
|
||
|
|
ip_resource = None
|
||
|
|
- if (remote_resource is not None and len(resources) == 2) or (
|
||
|
|
- remote_resource is None and len(resources) == 1
|
||
|
|
+ if (remote_resource is not None and len(resource_list) == 2) or (
|
||
|
|
+ remote_resource is None and len(resource_list) == 1
|
||
|
|
):
|
||
|
|
- ip_resource = _get_implicit_resource(
|
||
|
|
- resources, f"{bundle_id}-ip-", False, "ocf:heartbeat:IPaddr2"
|
||
|
|
+ ip_resource = _pop_implicit_resource(
|
||
|
|
+ resource_list, f"{bundle_id}-ip-", False, "ocf:heartbeat:IPaddr2"
|
||
|
|
)
|
||
|
|
|
||
|
|
- if remote_resource is None and resources:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusBundleReplicaMissingRemote(
|
||
|
|
- bundle_id, replica_id
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
+ if remote_resource is None and resource_list:
|
||
|
|
+ raise BundleReplicaMissingImplicitResourceError(
|
||
|
|
+ bundle_id, replica_id, "remote"
|
||
|
|
)
|
||
|
|
- raise LibraryError()
|
||
|
|
|
||
|
|
member = None
|
||
|
|
if remote_resource:
|
||
|
|
- if len(resources) == 1:
|
||
|
|
- member = resources[0]
|
||
|
|
+ if len(resource_list) == 1:
|
||
|
|
+ member = resource_list[0]
|
||
|
|
else:
|
||
|
|
- reporter.report(
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusBundleReplicaInvalidCount(
|
||
|
|
- bundle_id, replica_id
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- raise LibraryError()
|
||
|
|
+ raise BundleReplicaInvalidMemberCountError(bundle_id, replica_id)
|
||
|
|
|
||
|
|
return BundleReplicaStatusDto(
|
||
|
|
replica_id,
|
||
|
|
@@ -433,24 +441,13 @@ def _replica_to_dto(
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
-def _find_duplicate_ids(resources: Sequence[AnyResourceStatusDto]) -> list[str]:
|
||
|
|
- seen = set()
|
||
|
|
- duplicates = []
|
||
|
|
- for resource in resources:
|
||
|
|
- if resource.resource_id in seen:
|
||
|
|
- duplicates.append(resource.resource_id)
|
||
|
|
- else:
|
||
|
|
- seen.add(resource.resource_id)
|
||
|
|
- return duplicates
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-def _get_implicit_resource(
|
||
|
|
- primitives: list[PrimitiveStatusDto],
|
||
|
|
+def _pop_implicit_resource(
|
||
|
|
+ primitive_list: list[PrimitiveStatusDto],
|
||
|
|
expected_id: str,
|
||
|
|
exact_match: bool,
|
||
|
|
resource_agent: str,
|
||
|
|
) -> Optional[PrimitiveStatusDto]:
|
||
|
|
- for primitive in primitives:
|
||
|
|
+ for primitive in primitive_list:
|
||
|
|
matching_id = (
|
||
|
|
exact_match
|
||
|
|
and primitive.resource_id == expected_id
|
||
|
|
@@ -459,36 +456,28 @@ def _get_implicit_resource(
|
||
|
|
)
|
||
|
|
|
||
|
|
if matching_id and primitive.resource_agent == resource_agent:
|
||
|
|
- primitives.remove(primitive)
|
||
|
|
+ primitive_list.remove(primitive)
|
||
|
|
return primitive
|
||
|
|
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
-def _validate_replicas(
|
||
|
|
- replicas: Sequence[BundleReplicaStatusDto], bundle_id: str
|
||
|
|
-) -> reports.ReportItemList:
|
||
|
|
- if not replicas:
|
||
|
|
- return []
|
||
|
|
+def _replicas_valid(replica_list: Sequence[BundleReplicaStatusDto]) -> bool:
|
||
|
|
+ if not replica_list:
|
||
|
|
+ return True
|
||
|
|
|
||
|
|
- member = replicas[0].member
|
||
|
|
- ip = replicas[0].ip_address
|
||
|
|
- container = replicas[0].container
|
||
|
|
+ member = replica_list[0].member
|
||
|
|
+ ip = replica_list[0].ip_address
|
||
|
|
+ container = replica_list[0].container
|
||
|
|
|
||
|
|
- for replica in replicas:
|
||
|
|
+ for replica in replica_list:
|
||
|
|
if (
|
||
|
|
not _cmp_replica_members(member, replica.member, True)
|
||
|
|
or not _cmp_replica_members(ip, replica.ip_address, False)
|
||
|
|
or not _cmp_replica_members(container, replica.container, False)
|
||
|
|
):
|
||
|
|
- return [
|
||
|
|
- reports.ReportItem.error(
|
||
|
|
- reports.messages.ClusterStatusBundleDifferentReplicas(
|
||
|
|
- bundle_id
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- ]
|
||
|
|
- return []
|
||
|
|
+ return False
|
||
|
|
+ return True
|
||
|
|
|
||
|
|
|
||
|
|
def _cmp_replica_members(
|
||
|
|
diff --git a/pcs_test/resources/crm_mon.all_resources.xml b/pcs_test/resources/crm_mon.all_resources.xml
|
||
|
|
index e493d308..f11db064 100644
|
||
|
|
--- a/pcs_test/resources/crm_mon.all_resources.xml
|
||
|
|
+++ b/pcs_test/resources/crm_mon.all_resources.xml
|
||
|
|
@@ -1,40 +1,53 @@
|
||
|
|
-<pacemaker-result api-version="2.30" request="crm_mon --output-as xml">
|
||
|
|
+<pacemaker-result api-version="2.30" request="crm_mon --one-shot --inactive --output-as xml">
|
||
|
|
<summary>
|
||
|
|
- <stack type="corosync" />
|
||
|
|
- <current_dc present="false" />
|
||
|
|
- <last_update time="Wed Nov 6 13:45:41 2019" />
|
||
|
|
- <last_change time="Wed Nov 6 10:42:54 2019" user="hacluster" client="crmd" origin="node1" />
|
||
|
|
- <nodes_configured number="0" />
|
||
|
|
- <resources_configured number="0" disabled="0" blocked="0" />
|
||
|
|
+ <stack type="unknown"/>
|
||
|
|
+ <current_dc present="false"/>
|
||
|
|
+ <last_update time="Wed Jan 31 12:03:35 2024"/>
|
||
|
|
+ <last_change time="Thu Aug 23 16:49:17 2012" user="" client="crmd" origin="rh7-3"/>
|
||
|
|
+ <nodes_configured number="0"/>
|
||
|
|
+ <resources_configured number="16" disabled="8" blocked="0"/>
|
||
|
|
<cluster_options stonith-enabled="true" symmetric-cluster="true" no-quorum-policy="stop" maintenance-mode="false" stop-all-resources="false" stonith-timeout-ms="60000" priority-fencing-delay-ms="0"/>
|
||
|
|
</summary>
|
||
|
|
- <nodes>
|
||
|
|
- <node name="node1" id="1" online="true" standby="false" standby_onfail="false" maintenance="false" pending="false" unclean="false" health="green" feature_set="3.17.4" shutdown="false" expected_up="true" is_dc="false" resources_running="16" type="member"/>
|
||
|
|
- </nodes>
|
||
|
|
+ <nodes/>
|
||
|
|
<resources>
|
||
|
|
- <resource id="dummy" resource_agent="ocf:pacemaker:Dummy" role="Started" active="true" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
|
||
|
|
- <node name="node1" id="1" cached="true"/>
|
||
|
|
- </resource>
|
||
|
|
- <group id="group" number_resources="2" maintenance="false" managed="true" disabled="false">
|
||
|
|
- <resource id="grouped" resource_agent="ocf:pacemaker:Dummy" role="Started" active="true" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
|
||
|
|
- <node name="node1" id="1" cached="true"/>
|
||
|
|
- </resource>
|
||
|
|
- </group>
|
||
|
|
- <clone id="clone" multi_state="false" unique="false" maintenance="false" managed="true" disabled="false" failed="false" failure_ignored="false">
|
||
|
|
- <resource id="cloned" resource_agent="ocf:pacemaker:Dummy" role="Started" active="true" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
|
||
|
|
- <node name="node1" id="1" cached="true"/>
|
||
|
|
- </resource>
|
||
|
|
- </clone>
|
||
|
|
- <bundle id="bundle" type="podman" image="localhost/pcmktest:http" unique="false" maintenance="false" managed="true" failed="false">
|
||
|
|
+ <bundle id="B1" type="docker" image="pcs:test" unique="true" maintenance="false" managed="false" failed="false">
|
||
|
|
<replica id="0">
|
||
|
|
- <resource id="bundle-ip-192.168.122.250" resource_agent="ocf:heartbeat:IPaddr2" role="Started" active="true" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
|
||
|
|
- <node name="node1" id="1" cached="true"/>
|
||
|
|
- </resource>
|
||
|
|
- <resource id="bundle-podman-0" resource_agent="ocf:heartbeat:podman" role="Started" active="true" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="1">
|
||
|
|
- <node name="node1" id="1" cached="true"/>
|
||
|
|
- </resource>
|
||
|
|
+ <resource id="B1-ip-192.168.100.200" resource_agent="ocf:heartbeat:IPaddr2" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="B1-docker-0" resource_agent="ocf:heartbeat:docker" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </replica>
|
||
|
|
+ <replica id="1">
|
||
|
|
+ <resource id="B1-ip-192.168.100.201" resource_agent="ocf:heartbeat:IPaddr2" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="B1-docker-1" resource_agent="ocf:heartbeat:docker" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </replica>
|
||
|
|
+ <replica id="2">
|
||
|
|
+ <resource id="B1-ip-192.168.100.202" resource_agent="ocf:heartbeat:IPaddr2" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="B1-docker-2" resource_agent="ocf:heartbeat:docker" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </replica>
|
||
|
|
+ <replica id="3">
|
||
|
|
+ <resource id="B1-ip-192.168.100.203" resource_agent="ocf:heartbeat:IPaddr2" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="B1-docker-3" resource_agent="ocf:heartbeat:docker" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
</replica>
|
||
|
|
</bundle>
|
||
|
|
+ <resource id="R7" resource_agent="ocf:pacemaker:Dummy" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="S2" resource_agent="stonith:fence_kdump" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <group id="G2" number_resources="2" maintenance="false" managed="true" disabled="false">
|
||
|
|
+ <resource id="R5" resource_agent="ocf:pacemaker:Dummy" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="S1" resource_agent="stonith:fence_kdump" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </group>
|
||
|
|
+ <clone id="G1-clone" multi_state="true" unique="false" maintenance="false" managed="true" disabled="false" failed="false" failure_ignored="false">
|
||
|
|
+ <group id="G1:0" number_resources="3" maintenance="false" managed="true" disabled="false">
|
||
|
|
+ <resource id="R2" resource_agent="ocf:pacemaker:Stateful" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="R3" resource_agent="ocf:pacemaker:Stateful" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="R4" resource_agent="ocf:pacemaker:Stateful" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </group>
|
||
|
|
+ </clone>
|
||
|
|
+ <clone id="R6-clone" multi_state="false" unique="false" maintenance="false" managed="true" disabled="false" failed="false" failure_ignored="false">
|
||
|
|
+ <resource id="R6" resource_agent="ocf:pacemaker:Dummy" role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </clone>
|
||
|
|
</resources>
|
||
|
|
- <status code="0" message="OK" />
|
||
|
|
+ <tickets>
|
||
|
|
+ <ticket id="custom-ticket1" status="revoked" standby="false"/>
|
||
|
|
+ <ticket id="ticket2" status="revoked" standby="false"/>
|
||
|
|
+ </tickets>
|
||
|
|
+ <status code="0" message="OK"/>
|
||
|
|
</pacemaker-result>
|
||
|
|
diff --git a/pcs_test/tier0/common/reports/test_messages.py b/pcs_test/tier0/common/reports/test_messages.py
|
||
|
|
index b60360e4..48eb730c 100644
|
||
|
|
--- a/pcs_test/tier0/common/reports/test_messages.py
|
||
|
|
+++ b/pcs_test/tier0/common/reports/test_messages.py
|
||
|
|
@@ -2195,6 +2195,26 @@ class BadClusterStateFormat(NameBuildTest):
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
+class BadClusterState(NameBuildTest):
|
||
|
|
+ def test_no_reason(self):
|
||
|
|
+ self.assert_message_from_report(
|
||
|
|
+ (
|
||
|
|
+ "Cannot load cluster status, xml does not describe "
|
||
|
|
+ "valid cluster status."
|
||
|
|
+ ),
|
||
|
|
+ reports.BadClusterState(),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_reason(self):
|
||
|
|
+ self.assert_message_from_report(
|
||
|
|
+ (
|
||
|
|
+ "Cannot load cluster status, xml does not describe "
|
||
|
|
+ "valid cluster status: sample reason."
|
||
|
|
+ ),
|
||
|
|
+ reports.BadClusterState("sample reason"),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+
|
||
|
|
class WaitForIdleStarted(NameBuildTest):
|
||
|
|
def test_timeout(self):
|
||
|
|
timeout = 20
|
||
|
|
@@ -5818,143 +5838,26 @@ class CannotCreateDefaultClusterPropertySet(NameBuildTest):
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
-class ClusterStatusBundleDifferentReplicas(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- "Replicas of bundle 'bundle' are not the same.",
|
||
|
|
- reports.ClusterStatusBundleDifferentReplicas("bundle"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
class ClusterStatusBundleMemberIdAsImplicit(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
+ def test_one(self):
|
||
|
|
self.assert_message_from_report(
|
||
|
|
(
|
||
|
|
- "Skipping bundle 'bundle': resource 'test' has "
|
||
|
|
- "the same id as some of the implicit bundle resources."
|
||
|
|
- ),
|
||
|
|
- reports.ClusterStatusBundleMemberIdAsImplicit("bundle", ["test"]),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- def test_multiple_ids(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- (
|
||
|
|
- "Skipping bundle 'bundle': resources 'test1', 'test2' have "
|
||
|
|
+ "Skipping bundle 'resource-bundle': resource 'resource' has "
|
||
|
|
"the same id as some of the implicit bundle resources."
|
||
|
|
),
|
||
|
|
reports.ClusterStatusBundleMemberIdAsImplicit(
|
||
|
|
- "bundle", ["test1", "test2"]
|
||
|
|
- ),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusBundleReplicaInvalidCount(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- (
|
||
|
|
- "Replica '0' of bundle 'bundle' has invalid number of members. "
|
||
|
|
- "Expecting 2-4 members."
|
||
|
|
+ "resource-bundle", ["resource"]
|
||
|
|
),
|
||
|
|
- reports.ClusterStatusBundleReplicaInvalidCount("bundle", "0"),
|
||
|
|
)
|
||
|
|
|
||
|
|
-
|
||
|
|
-class ClusterStatusBundleReplicaMissingRemote(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- (
|
||
|
|
- "Replica '0' of bundle 'bundle' is missing implicit pacemaker "
|
||
|
|
- "remote resource while it must be present."
|
||
|
|
- ),
|
||
|
|
- reports.ClusterStatusBundleReplicaMissingRemote("bundle", "0"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusBundleReplicaNoContainer(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- (
|
||
|
|
- "Replica '0' of bundle 'bundle' is missing implicit container "
|
||
|
|
- "resource."
|
||
|
|
- ),
|
||
|
|
- reports.ClusterStatusBundleReplicaNoContainer("bundle", "0"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusCloneMembersDifferentIds(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- "Members with different ids in clone 'clone'.",
|
||
|
|
- reports.ClusterStatusCloneMembersDifferentIds("clone"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusCloneMixedMembers(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- "Primitive and group members mixed in clone 'clone'.",
|
||
|
|
- reports.ClusterStatusCloneMixedMembers("clone"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusEmptyNodeName(NameBuildTest):
|
||
|
|
- def test_message(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- "Resource with id 'resource' contains node with empty name.",
|
||
|
|
- reports.ClusterStatusEmptyNodeName("resource"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusUnexpectedMember(NameBuildTest):
|
||
|
|
- def test_one_expected(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- (
|
||
|
|
- "Unexpected resource 'member' inside of resource 'resource' of "
|
||
|
|
- "type 'group'. Only resources of type 'primitive' "
|
||
|
|
- "can be in group."
|
||
|
|
- ),
|
||
|
|
- reports.ClusterStatusUnexpectedMember(
|
||
|
|
- resource_id="resource",
|
||
|
|
- resource_type="group",
|
||
|
|
- member_id="member",
|
||
|
|
- expected_types=["primitive"],
|
||
|
|
- ),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- def test_multiple_expected(self):
|
||
|
|
+ def test_multiple(self):
|
||
|
|
self.assert_message_from_report(
|
||
|
|
(
|
||
|
|
- "Unexpected resource 'member' inside of resource 'resource' of "
|
||
|
|
- "type 'clone'. Only resources of type 'group'|'primitive' "
|
||
|
|
- "can be in clone."
|
||
|
|
- ),
|
||
|
|
- reports.ClusterStatusUnexpectedMember(
|
||
|
|
- resource_id="resource",
|
||
|
|
- resource_type="clone",
|
||
|
|
- member_id="member",
|
||
|
|
- expected_types=["primitive", "group"],
|
||
|
|
+ "Skipping bundle 'resource-bundle': resources 'resource-0', "
|
||
|
|
+ "'resource-1' have the same id as some of the implicit bundle "
|
||
|
|
+ "resources."
|
||
|
|
),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
-
|
||
|
|
-class ClusterStatusUnknownPcmkRole(NameBuildTest):
|
||
|
|
- def test_no_role(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- "Attribute of resource with id 'resource' contains empty pcmk role.",
|
||
|
|
- reports.ClusterStatusUnknownPcmkRole(None, "resource"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- def test_empty_role(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- "Attribute of resource with id 'resource' contains empty pcmk role.",
|
||
|
|
- reports.ClusterStatusUnknownPcmkRole("", "resource"),
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- def test_role(self):
|
||
|
|
- self.assert_message_from_report(
|
||
|
|
- (
|
||
|
|
- "Attribute of resource with id 'resource' contains invalid "
|
||
|
|
- "pcmk role 'NotValidRole'."
|
||
|
|
+ reports.ClusterStatusBundleMemberIdAsImplicit(
|
||
|
|
+ "resource-bundle", ["resource-0", "resource-1"]
|
||
|
|
),
|
||
|
|
- reports.ClusterStatusUnknownPcmkRole("NotValidRole", "resource"),
|
||
|
|
)
|
||
|
|
diff --git a/pcs_test/tier0/lib/commands/test_status.py b/pcs_test/tier0/lib/commands/test_status.py
|
||
|
|
index a5a395b5..3b6b7665 100644
|
||
|
|
--- a/pcs_test/tier0/lib/commands/test_status.py
|
||
|
|
+++ b/pcs_test/tier0/lib/commands/test_status.py
|
||
|
|
@@ -1,5 +1,7 @@
|
||
|
|
+# pylint: disable=too-many-lines
|
||
|
|
import os
|
||
|
|
from textwrap import dedent
|
||
|
|
+from typing import Optional
|
||
|
|
from unittest import (
|
||
|
|
TestCase,
|
||
|
|
mock,
|
||
|
|
@@ -7,7 +9,11 @@ from unittest import (
|
||
|
|
|
||
|
|
from pcs import settings
|
||
|
|
from pcs.common import file_type_codes
|
||
|
|
-from pcs.common.const import PCMK_STATUS_ROLE_STARTED
|
||
|
|
+from pcs.common.const import (
|
||
|
|
+ PCMK_ROLE_STOPPED,
|
||
|
|
+ PCMK_STATUS_ROLE_STOPPED,
|
||
|
|
+ PcmkRoleType,
|
||
|
|
+)
|
||
|
|
from pcs.common.reports import codes as report_codes
|
||
|
|
from pcs.common.status_dto import (
|
||
|
|
BundleReplicaStatusDto,
|
||
|
|
@@ -1267,24 +1273,27 @@ class FullClusterStatusPlaintextBoothWarning(FullClusterStatusPlaintextBase):
|
||
|
|
|
||
|
|
|
||
|
|
def _fixture_primitive_resource_dto(
|
||
|
|
- resource_id: str, resource_agent: str
|
||
|
|
+ resource_id: str,
|
||
|
|
+ resource_agent: str,
|
||
|
|
+ target_role: Optional[PcmkRoleType] = None,
|
||
|
|
+ managed: bool = True,
|
||
|
|
) -> PrimitiveStatusDto:
|
||
|
|
return PrimitiveStatusDto(
|
||
|
|
- resource_id,
|
||
|
|
- resource_agent,
|
||
|
|
- PCMK_STATUS_ROLE_STARTED,
|
||
|
|
- None,
|
||
|
|
- True,
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- None,
|
||
|
|
- False,
|
||
|
|
- True,
|
||
|
|
- False,
|
||
|
|
- ["node1"],
|
||
|
|
- None,
|
||
|
|
- None,
|
||
|
|
+ resource_id=resource_id,
|
||
|
|
+ resource_agent=resource_agent,
|
||
|
|
+ role=PCMK_STATUS_ROLE_STOPPED,
|
||
|
|
+ target_role=target_role,
|
||
|
|
+ active=False,
|
||
|
|
+ orphaned=False,
|
||
|
|
+ blocked=False,
|
||
|
|
+ maintenance=False,
|
||
|
|
+ description=None,
|
||
|
|
+ failed=False,
|
||
|
|
+ managed=managed,
|
||
|
|
+ failure_ignored=False,
|
||
|
|
+ node_names=[],
|
||
|
|
+ pending=None,
|
||
|
|
+ locked_to=None,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@@ -1303,7 +1312,7 @@ class ResourcesStatus(TestCase):
|
||
|
|
result = status.resources_status(self.env_assist.get_env())
|
||
|
|
self.assertEqual(result, ResourcesStatusDto([]))
|
||
|
|
|
||
|
|
- def test_bad_xml(self):
|
||
|
|
+ def test_bad_xml_format(self):
|
||
|
|
self.config.runner.pcmk.load_state(
|
||
|
|
resources="""
|
||
|
|
<resources>
|
||
|
|
@@ -1320,6 +1329,26 @@ class ResourcesStatus(TestCase):
|
||
|
|
False,
|
||
|
|
)
|
||
|
|
|
||
|
|
+ def test_bad_xml(self):
|
||
|
|
+ self.config.runner.pcmk.load_state(
|
||
|
|
+ resources="""
|
||
|
|
+ <resources>
|
||
|
|
+ <resource id="R7" role="NotPcmkRole" resource_agent="ocf:pacemaker:Dummy" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </resources>
|
||
|
|
+ """,
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ self.env_assist.assert_raise_library_error(
|
||
|
|
+ lambda: status.resources_status(self.env_assist.get_env()),
|
||
|
|
+ [
|
||
|
|
+ fixture.error(
|
||
|
|
+ report_codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Resource with id 'R7' contains unknown pcmk role 'NotPcmkRole'.",
|
||
|
|
+ ),
|
||
|
|
+ ],
|
||
|
|
+ False,
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
def test_all_resources(self):
|
||
|
|
self.config.runner.pcmk.load_state(
|
||
|
|
filename=rc("crm_mon.all_resources.xml")
|
||
|
|
@@ -1327,69 +1356,187 @@ class ResourcesStatus(TestCase):
|
||
|
|
|
||
|
|
result = status.resources_status(self.env_assist.get_env())
|
||
|
|
|
||
|
|
- self.assertTrue(len(result.resources) == 4)
|
||
|
|
self.assertEqual(
|
||
|
|
- result.resources[0],
|
||
|
|
- _fixture_primitive_resource_dto("dummy", "ocf:pacemaker:Dummy"),
|
||
|
|
- )
|
||
|
|
- self.assertEqual(
|
||
|
|
- result.resources[1],
|
||
|
|
- GroupStatusDto(
|
||
|
|
- "group",
|
||
|
|
- False,
|
||
|
|
- None,
|
||
|
|
- True,
|
||
|
|
- False,
|
||
|
|
- members=[
|
||
|
|
+ result,
|
||
|
|
+ ResourcesStatusDto(
|
||
|
|
+ [
|
||
|
|
+ BundleStatusDto(
|
||
|
|
+ resource_id="B1",
|
||
|
|
+ type="docker",
|
||
|
|
+ image="pcs:test",
|
||
|
|
+ unique=True,
|
||
|
|
+ maintenance=False,
|
||
|
|
+ description=None,
|
||
|
|
+ managed=False,
|
||
|
|
+ failed=False,
|
||
|
|
+ replicas=[
|
||
|
|
+ BundleReplicaStatusDto(
|
||
|
|
+ replica_id="0",
|
||
|
|
+ member=None,
|
||
|
|
+ remote=None,
|
||
|
|
+ container=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-docker-0",
|
||
|
|
+ "ocf:heartbeat:docker",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ip_address=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-ip-192.168.100.200",
|
||
|
|
+ "ocf:heartbeat:IPaddr2",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ),
|
||
|
|
+ BundleReplicaStatusDto(
|
||
|
|
+ replica_id="1",
|
||
|
|
+ member=None,
|
||
|
|
+ remote=None,
|
||
|
|
+ container=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-docker-1",
|
||
|
|
+ "ocf:heartbeat:docker",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ip_address=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-ip-192.168.100.201",
|
||
|
|
+ "ocf:heartbeat:IPaddr2",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ),
|
||
|
|
+ BundleReplicaStatusDto(
|
||
|
|
+ replica_id="2",
|
||
|
|
+ member=None,
|
||
|
|
+ remote=None,
|
||
|
|
+ container=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-docker-2",
|
||
|
|
+ "ocf:heartbeat:docker",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ip_address=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-ip-192.168.100.202",
|
||
|
|
+ "ocf:heartbeat:IPaddr2",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ),
|
||
|
|
+ BundleReplicaStatusDto(
|
||
|
|
+ replica_id="3",
|
||
|
|
+ member=None,
|
||
|
|
+ remote=None,
|
||
|
|
+ container=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-docker-3",
|
||
|
|
+ "ocf:heartbeat:docker",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ip_address=_fixture_primitive_resource_dto(
|
||
|
|
+ "B1-ip-192.168.100.203",
|
||
|
|
+ "ocf:heartbeat:IPaddr2",
|
||
|
|
+ target_role=PCMK_ROLE_STOPPED,
|
||
|
|
+ managed=False,
|
||
|
|
+ ),
|
||
|
|
+ ),
|
||
|
|
+ ],
|
||
|
|
+ ),
|
||
|
|
_fixture_primitive_resource_dto(
|
||
|
|
- "grouped", "ocf:pacemaker:Dummy"
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- ),
|
||
|
|
- )
|
||
|
|
- self.assertEqual(
|
||
|
|
- result.resources[2],
|
||
|
|
- CloneStatusDto(
|
||
|
|
- "clone",
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- None,
|
||
|
|
- True,
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- None,
|
||
|
|
- instances=[
|
||
|
|
+ "R7", "ocf:pacemaker:Dummy"
|
||
|
|
+ ),
|
||
|
|
_fixture_primitive_resource_dto(
|
||
|
|
- "cloned", "ocf:pacemaker:Dummy"
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
+ "S2", "stonith:fence_kdump"
|
||
|
|
+ ),
|
||
|
|
+ GroupStatusDto(
|
||
|
|
+ resource_id="G2",
|
||
|
|
+ maintenance=False,
|
||
|
|
+ description=None,
|
||
|
|
+ managed=True,
|
||
|
|
+ disabled=False,
|
||
|
|
+ members=[
|
||
|
|
+ _fixture_primitive_resource_dto(
|
||
|
|
+ "R5", "ocf:pacemaker:Dummy"
|
||
|
|
+ ),
|
||
|
|
+ _fixture_primitive_resource_dto(
|
||
|
|
+ "S1", "stonith:fence_kdump"
|
||
|
|
+ ),
|
||
|
|
+ ],
|
||
|
|
+ ),
|
||
|
|
+ CloneStatusDto(
|
||
|
|
+ resource_id="G1-clone",
|
||
|
|
+ multi_state=True,
|
||
|
|
+ unique=False,
|
||
|
|
+ maintenance=False,
|
||
|
|
+ description=None,
|
||
|
|
+ managed=True,
|
||
|
|
+ disabled=False,
|
||
|
|
+ failed=False,
|
||
|
|
+ failure_ignored=False,
|
||
|
|
+ target_role=None,
|
||
|
|
+ instances=[
|
||
|
|
+ GroupStatusDto(
|
||
|
|
+ resource_id="G1",
|
||
|
|
+ maintenance=False,
|
||
|
|
+ description=None,
|
||
|
|
+ managed=True,
|
||
|
|
+ disabled=False,
|
||
|
|
+ members=[
|
||
|
|
+ _fixture_primitive_resource_dto(
|
||
|
|
+ "R2", "ocf:pacemaker:Stateful"
|
||
|
|
+ ),
|
||
|
|
+ _fixture_primitive_resource_dto(
|
||
|
|
+ "R3", "ocf:pacemaker:Stateful"
|
||
|
|
+ ),
|
||
|
|
+ _fixture_primitive_resource_dto(
|
||
|
|
+ "R4", "ocf:pacemaker:Stateful"
|
||
|
|
+ ),
|
||
|
|
+ ],
|
||
|
|
+ )
|
||
|
|
+ ],
|
||
|
|
+ ),
|
||
|
|
+ CloneStatusDto(
|
||
|
|
+ resource_id="R6-clone",
|
||
|
|
+ multi_state=False,
|
||
|
|
+ unique=False,
|
||
|
|
+ maintenance=False,
|
||
|
|
+ description=None,
|
||
|
|
+ managed=True,
|
||
|
|
+ disabled=False,
|
||
|
|
+ failed=False,
|
||
|
|
+ failure_ignored=False,
|
||
|
|
+ target_role=None,
|
||
|
|
+ instances=[
|
||
|
|
+ _fixture_primitive_resource_dto(
|
||
|
|
+ "R6", "ocf:pacemaker:Dummy"
|
||
|
|
+ )
|
||
|
|
+ ],
|
||
|
|
+ ),
|
||
|
|
+ ]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- self.assertEqual(
|
||
|
|
- result.resources[3],
|
||
|
|
- BundleStatusDto(
|
||
|
|
- "bundle",
|
||
|
|
- "podman",
|
||
|
|
- "localhost/pcmktest:http",
|
||
|
|
- False,
|
||
|
|
- False,
|
||
|
|
- None,
|
||
|
|
- True,
|
||
|
|
- False,
|
||
|
|
- [
|
||
|
|
- BundleReplicaStatusDto(
|
||
|
|
- "0",
|
||
|
|
- None,
|
||
|
|
- None,
|
||
|
|
- _fixture_primitive_resource_dto(
|
||
|
|
- "bundle-podman-0", "ocf:heartbeat:podman"
|
||
|
|
- ),
|
||
|
|
- _fixture_primitive_resource_dto(
|
||
|
|
- "bundle-ip-192.168.122.250", "ocf:heartbeat:IPaddr2"
|
||
|
|
- ),
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- ),
|
||
|
|
+
|
||
|
|
+ def test_bundle_skip(self):
|
||
|
|
+ self.config.runner.pcmk.load_state(
|
||
|
|
+ resources="""
|
||
|
|
+ <resources>
|
||
|
|
+ <bundle id="B1" type="docker" image="pcs:test" unique="true" maintenance="false" managed="false" failed="false">
|
||
|
|
+ <replica id="0">
|
||
|
|
+ <resource id="B1-0" resource_agent="ocf:heartbeat:Dummy" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="true" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="B1-docker-0" resource_agent="ocf:heartbeat:docker" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ <resource id="B1-0" resource_agent="ocf:pacemaker:remote" role="Stopped" target_role="Stopped" active="false" orphaned="false" blocked="false" maintenance="false" managed="false" failed="false" failure_ignored="false" nodes_running_on="0"/>
|
||
|
|
+ </replica>
|
||
|
|
+ </bundle>
|
||
|
|
+ </resources>
|
||
|
|
+ """,
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ result = status.resources_status(self.env_assist.get_env())
|
||
|
|
+ self.assertEqual(result, ResourcesStatusDto([]))
|
||
|
|
+ self.env_assist.assert_reports(
|
||
|
|
+ [
|
||
|
|
+ fixture.warn(
|
||
|
|
+ report_codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
+ bundle_id="B1",
|
||
|
|
+ bad_ids=["B1-0"],
|
||
|
|
+ )
|
||
|
|
+ ]
|
||
|
|
)
|
||
|
|
diff --git a/pcs_test/tier0/lib/pacemaker/test_status.py b/pcs_test/tier0/lib/pacemaker/test_status.py
|
||
|
|
index 451fb584..778e97a6 100644
|
||
|
|
--- a/pcs_test/tier0/lib/pacemaker/test_status.py
|
||
|
|
+++ b/pcs_test/tier0/lib/pacemaker/test_status.py
|
||
|
|
@@ -11,11 +11,9 @@ from lxml import etree
|
||
|
|
from pcs.common import reports
|
||
|
|
from pcs.common.const import (
|
||
|
|
PCMK_ROLE_STARTED,
|
||
|
|
- PCMK_ROLE_UNKNOWN,
|
||
|
|
PCMK_ROLES,
|
||
|
|
PCMK_STATUS_ROLE_STARTED,
|
||
|
|
PCMK_STATUS_ROLE_STOPPED,
|
||
|
|
- PCMK_STATUS_ROLE_UNKNOWN,
|
||
|
|
PCMK_STATUS_ROLE_UNPROMOTED,
|
||
|
|
PCMK_STATUS_ROLES,
|
||
|
|
PCMK_STATUS_ROLES_PENDING,
|
||
|
|
@@ -34,10 +32,9 @@ from pcs.lib.pacemaker import status
|
||
|
|
|
||
|
|
from pcs_test.tools import fixture
|
||
|
|
from pcs_test.tools.assertions import (
|
||
|
|
- assert_raise_library_error,
|
||
|
|
+ assert_report_item_equal,
|
||
|
|
assert_report_item_list_equal,
|
||
|
|
)
|
||
|
|
-from pcs_test.tools.custom_mock import MockLibraryReportProcessor
|
||
|
|
|
||
|
|
|
||
|
|
def fixture_primitive_xml(
|
||
|
|
@@ -327,34 +324,163 @@ def fixture_crm_mon_xml(resources: list[str]) -> str:
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
+class TestParsingErrorToReport(TestCase):
|
||
|
|
+ # pylint: disable=no-self-use
|
||
|
|
+
|
||
|
|
+ def test_empty_resource_id(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.EmptyResourceIdError()
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Resource with empty id.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_empty_node_name(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.EmptyNodeNameError("resource")
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Resource with id 'resource' contains node with empty name.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_unknow_pcmk_role(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.UnknownPcmkRoleError("resource", "NotPcmkRole")
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Resource with id 'resource' contains unknown pcmk role 'NotPcmkRole'.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_unexpected_member_group(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.UnexpectedMemberError(
|
||
|
|
+ "resource", "group", "member", ["primitive"]
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason=(
|
||
|
|
+ "Unexpected resource 'member' inside of resource "
|
||
|
|
+ "'resource' of type 'group'. Only resources of type "
|
||
|
|
+ "'primitive' can be in group."
|
||
|
|
+ ),
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_unexpected_member_clone(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.UnexpectedMemberError(
|
||
|
|
+ "resource", "clone", "member", ["primitive", "group"]
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason=(
|
||
|
|
+ "Unexpected resource 'member' inside of resource "
|
||
|
|
+ "'resource' of type 'clone'. Only resources of type "
|
||
|
|
+ "'group'|'primitive' can be in clone."
|
||
|
|
+ ),
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_mixed_members(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.MixedMembersError("resource")
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Primitive and group members mixed in clone 'resource'.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_different_member_ids(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.DifferentMemberIdsError("resource")
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Members with different ids in resource 'resource'.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_bundle_replica_missing_implicit(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.BundleReplicaMissingImplicitResourceError(
|
||
|
|
+ "resource", "0", "container"
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Replica '0' of bundle 'resource' is missing implicit container resource.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_bundle_replica_invalid_member_count(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.BundleReplicaInvalidMemberCountError("resource", "0")
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Replica '0' of bundle 'resource' has invalid number of members.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ def test_bundle_different_replicas(self):
|
||
|
|
+ report = status.cluster_status_parsing_error_to_report(
|
||
|
|
+ status.BundleDifferentReplicas("resource")
|
||
|
|
+ )
|
||
|
|
+ assert_report_item_equal(
|
||
|
|
+ report,
|
||
|
|
+ fixture.error(
|
||
|
|
+ reports.codes.BAD_CLUSTER_STATE,
|
||
|
|
+ reason="Replicas of bundle 'resource' are not the same.",
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+
|
||
|
|
class TestPrimitiveStatusToDto(TestCase):
|
||
|
|
# pylint: disable=protected-access
|
||
|
|
- def setUp(self):
|
||
|
|
- self.report_processor = MockLibraryReportProcessor()
|
||
|
|
-
|
||
|
|
def test_simple(self):
|
||
|
|
primitive_xml = etree.fromstring(fixture_primitive_xml())
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(self.report_processor, primitive_xml)
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml)
|
||
|
|
|
||
|
|
self.assertEqual(result, fixture_primitive_dto())
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_empty_node_list(self):
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
fixture_primitive_xml(role=PCMK_STATUS_ROLE_STOPPED, node_names=[])
|
||
|
|
)
|
||
|
|
- result = status._primitive_to_dto(self.report_processor, primitive_xml)
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
fixture_primitive_dto(role=PCMK_STATUS_ROLE_STOPPED, node_names=[]),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_all_attributes(self):
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
@@ -363,7 +489,7 @@ class TestPrimitiveStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(self.report_processor, primitive_xml)
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -371,74 +497,40 @@ class TestPrimitiveStatusToDto(TestCase):
|
||
|
|
target_role=PCMK_STATUS_ROLE_STOPPED, add_optional_args=True
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_remove_clone_suffix(self):
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
fixture_primitive_xml(resource_id="resource:0")
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(
|
||
|
|
- self.report_processor, primitive_xml, True
|
||
|
|
- )
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml, True)
|
||
|
|
|
||
|
|
self.assertEqual(result, fixture_primitive_dto())
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_running_on_multiple_nodes(self):
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
fixture_primitive_xml(node_names=["node1", "node2", "node3"])
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(self.report_processor, primitive_xml)
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
fixture_primitive_dto(node_names=["node1", "node2", "node3"]),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_empty_node_name(self):
|
||
|
|
primitive_xml = etree.fromstring(fixture_primitive_xml(node_names=[""]))
|
||
|
|
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._primitive_to_dto(
|
||
|
|
- self.report_processor, primitive_xml
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_EMPTY_NODE_NAME,
|
||
|
|
- resource_id="resource",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.EmptyNodeNameError) as cm:
|
||
|
|
+ status._primitive_to_dto(primitive_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
|
||
|
|
def test_empty_resource_id(self):
|
||
|
|
primitive_xml = etree.fromstring(fixture_primitive_xml(resource_id=""))
|
||
|
|
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._primitive_to_dto(
|
||
|
|
- self.report_processor, primitive_xml
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.INVALID_ID_IS_EMPTY,
|
||
|
|
- id_description="resource id",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.EmptyResourceIdError):
|
||
|
|
+ status._primitive_to_dto(primitive_xml)
|
||
|
|
|
||
|
|
def test_role(self):
|
||
|
|
for role in PCMK_STATUS_ROLES:
|
||
|
|
@@ -446,35 +538,17 @@ class TestPrimitiveStatusToDto(TestCase):
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
fixture_primitive_xml(role=role)
|
||
|
|
)
|
||
|
|
-
|
||
|
|
- result = status._primitive_to_dto(
|
||
|
|
- self.report_processor, primitive_xml
|
||
|
|
- )
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml)
|
||
|
|
self.assertEqual(result, fixture_primitive_dto(role=role))
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_invalid_role(self):
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
fixture_primitive_xml(role="NotPcmkRole")
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(self.report_processor, primitive_xml)
|
||
|
|
-
|
||
|
|
- self.assertEqual(
|
||
|
|
- result, fixture_primitive_dto(role=PCMK_STATUS_ROLE_UNKNOWN)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_UNKNOWN_PCMK_ROLE,
|
||
|
|
- role="NotPcmkRole",
|
||
|
|
- resource_id="resource",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._primitive_to_dto(primitive_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
|
||
|
|
def test_target_role(self):
|
||
|
|
for role in PCMK_ROLES:
|
||
|
|
@@ -483,76 +557,47 @@ class TestPrimitiveStatusToDto(TestCase):
|
||
|
|
fixture_primitive_xml(target_role=role)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(
|
||
|
|
- self.report_processor, primitive_xml
|
||
|
|
- )
|
||
|
|
+ result = status._primitive_to_dto(primitive_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result, fixture_primitive_dto(target_role=role)
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_invalid_target_role(self):
|
||
|
|
for value in PCMK_STATUS_ROLES_PENDING + ("NotPcmkRole",):
|
||
|
|
with self.subTest(value=value):
|
||
|
|
- self.setUp()
|
||
|
|
primitive_xml = etree.fromstring(
|
||
|
|
fixture_primitive_xml(target_role=value)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._primitive_to_dto(
|
||
|
|
- self.report_processor, primitive_xml
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- self.assertEqual(
|
||
|
|
- result, fixture_primitive_dto(target_role=PCMK_ROLE_UNKNOWN)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_UNKNOWN_PCMK_ROLE,
|
||
|
|
- role=value,
|
||
|
|
- resource_id="resource",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._primitive_to_dto(primitive_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
|
||
|
|
|
||
|
|
class TestGroupStatusToDto(TestCase):
|
||
|
|
# pylint: disable=protected-access
|
||
|
|
- def setUp(self):
|
||
|
|
- self.report_processor = MockLibraryReportProcessor()
|
||
|
|
-
|
||
|
|
def test_all_attributes(self):
|
||
|
|
group_xml = etree.fromstring(
|
||
|
|
fixture_group_xml(description="Test description")
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._group_to_dto(self.report_processor, group_xml)
|
||
|
|
+ result = status._group_to_dto(group_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result, fixture_group_dto(description="Test description")
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_single_member(self):
|
||
|
|
group_xml = etree.fromstring(
|
||
|
|
fixture_group_xml(members=[fixture_primitive_xml()])
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._group_to_dto(self.report_processor, group_xml)
|
||
|
|
+ result = status._group_to_dto(group_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result, fixture_group_dto(members=[fixture_primitive_dto()])
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_multiple_members(self):
|
||
|
|
group_xml = etree.fromstring(
|
||
|
|
@@ -564,7 +609,7 @@ class TestGroupStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._group_to_dto(self.report_processor, group_xml)
|
||
|
|
+ result = status._group_to_dto(group_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -575,9 +620,6 @@ class TestGroupStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_multiple_members_different_state(self):
|
||
|
|
group_xml = etree.fromstring(
|
||
|
|
@@ -594,7 +636,7 @@ class TestGroupStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._group_to_dto(self.report_processor, group_xml)
|
||
|
|
+ result = status._group_to_dto(group_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -610,10 +652,31 @@ class TestGroupStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
+
|
||
|
|
+ def test_member_invalid_role(self):
|
||
|
|
+ group_xml = etree.fromstring(
|
||
|
|
+ fixture_group_xml(
|
||
|
|
+ members=[fixture_primitive_xml(role="NotPcmkRole")]
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._group_to_dto(group_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
+ self.assertEqual(cm.exception.role, "NotPcmkRole")
|
||
|
|
+
|
||
|
|
+ def test_member_invalid_target_role(self):
|
||
|
|
+ group_xml = etree.fromstring(
|
||
|
|
+ fixture_group_xml(
|
||
|
|
+ members=[fixture_primitive_xml(target_role="NotPcmkRole")]
|
||
|
|
+ )
|
||
|
|
)
|
||
|
|
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._group_to_dto(group_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
+ self.assertEqual(cm.exception.role, "NotPcmkRole")
|
||
|
|
+
|
||
|
|
def test_invalid_member(self):
|
||
|
|
resources = {
|
||
|
|
"inner-group": '<group id="inner-group" number_resources="0" maintenance="false" managed="true" disabled="false" />',
|
||
|
|
@@ -623,31 +686,17 @@ class TestGroupStatusToDto(TestCase):
|
||
|
|
|
||
|
|
for resource_id, member in resources.items():
|
||
|
|
with self.subTest(value=resource_id):
|
||
|
|
- self.setUp()
|
||
|
|
group_xml = etree.fromstring(
|
||
|
|
fixture_group_xml(
|
||
|
|
resource_id="outer-group", members=[member]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- # pylint: disable=cell-var-from-loop
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._group_to_dto(
|
||
|
|
- self.report_processor, group_xml
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_UNEXPECTED_MEMBER,
|
||
|
|
- resource_id="outer-group",
|
||
|
|
- resource_type="group",
|
||
|
|
- member_id=resource_id,
|
||
|
|
- expected_types=["primitive"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.UnexpectedMemberError) as cm:
|
||
|
|
+ status._group_to_dto(group_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "outer-group")
|
||
|
|
+ self.assertEqual(cm.exception.member_id, resource_id)
|
||
|
|
+ self.assertEqual(cm.exception.expected_types, ["primitive"])
|
||
|
|
|
||
|
|
def test_remove_clone_suffix(self):
|
||
|
|
group_xml = etree.fromstring(
|
||
|
|
@@ -657,21 +706,15 @@ class TestGroupStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._group_to_dto(self.report_processor, group_xml, True)
|
||
|
|
+ result = status._group_to_dto(group_xml, True)
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
fixture_group_dto(members=[fixture_primitive_dto()]),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
|
||
|
|
class TestCloneStatusToDto(TestCase):
|
||
|
|
# pylint: disable=protected-access
|
||
|
|
- def setUp(self):
|
||
|
|
- self.report_processor = MockLibraryReportProcessor()
|
||
|
|
-
|
||
|
|
def test_all_attributes(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
fixture_clone_xml(
|
||
|
|
@@ -680,7 +723,7 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -688,23 +731,17 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
description="Test description", target_role=PCMK_ROLE_STARTED
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_primitive_member(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
fixture_clone_xml(instances=[fixture_primitive_xml()])
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result, fixture_clone_dto(instances=[fixture_primitive_dto()])
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_primitive_member_multiple(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -716,7 +753,7 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -727,9 +764,6 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_primitive_member_unique(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -744,7 +778,7 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -756,9 +790,6 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_primitive_member_promotable(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -772,7 +803,7 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -786,10 +817,31 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
+
|
||
|
|
+ def test_primitive_member_invalid_role(self):
|
||
|
|
+ clone_xml = etree.fromstring(
|
||
|
|
+ fixture_clone_xml(
|
||
|
|
+ instances=[fixture_primitive_xml(role="NotPcmkRole")]
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
+ self.assertEqual(cm.exception.role, "NotPcmkRole")
|
||
|
|
+
|
||
|
|
+ def test_primitive_member_invalid_target_role(self):
|
||
|
|
+ clone_xml = etree.fromstring(
|
||
|
|
+ fixture_clone_xml(
|
||
|
|
+ instances=[fixture_primitive_xml(target_role="NotPcmkRole")]
|
||
|
|
+ )
|
||
|
|
)
|
||
|
|
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
+ self.assertEqual(cm.exception.role, "NotPcmkRole")
|
||
|
|
+
|
||
|
|
def test_primitive_member_different_ids(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
fixture_clone_xml(
|
||
|
|
@@ -802,18 +854,9 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_CLONE_MEMBERS_DIFFERENT_IDS,
|
||
|
|
- clone_id="resource-clone",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.DifferentMemberIdsError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource-clone")
|
||
|
|
|
||
|
|
def test_group_member(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -830,7 +873,7 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -843,9 +886,6 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_group_member_unique(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -869,7 +909,7 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- result = status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
+ result = status._clone_to_dto(clone_xml)
|
||
|
|
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -883,9 +923,6 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
],
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_group_member_different_group_ids(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -903,18 +940,9 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_CLONE_MEMBERS_DIFFERENT_IDS,
|
||
|
|
- clone_id="resource-clone",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.DifferentMemberIdsError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource-clone")
|
||
|
|
|
||
|
|
def test_group_member_different_primitive_ids(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -937,18 +965,9 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_CLONE_MEMBERS_DIFFERENT_IDS,
|
||
|
|
- clone_id="resource-clone",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.DifferentMemberIdsError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource-clone")
|
||
|
|
|
||
|
|
def test_primitive_member_types_mixed(self):
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
@@ -965,18 +984,9 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._clone_to_dto(self.report_processor, clone_xml)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_CLONE_MIXED_MEMBERS,
|
||
|
|
- clone_id="resource-clone",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.MixedMembersError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource-clone")
|
||
|
|
|
||
|
|
def test_invalid_member(self):
|
||
|
|
resources = {
|
||
|
|
@@ -985,61 +995,43 @@ class TestCloneStatusToDto(TestCase):
|
||
|
|
}
|
||
|
|
for resource_id, element in resources.items():
|
||
|
|
with self.subTest(value=resource_id):
|
||
|
|
- self.setUp()
|
||
|
|
clone_xml = etree.fromstring(
|
||
|
|
- fixture_clone_xml(instances=[element])
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- # pylint: disable=cell-var-from-loop
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._clone_to_dto(
|
||
|
|
- self.report_processor, clone_xml
|
||
|
|
+ fixture_clone_xml(
|
||
|
|
+ resource_id="outer-clone", instances=[element]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_UNEXPECTED_MEMBER,
|
||
|
|
- resource_id="resource-clone",
|
||
|
|
- resource_type="clone",
|
||
|
|
- member_id=resource_id,
|
||
|
|
- expected_types=["primitive", "group"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
+
|
||
|
|
+ with self.assertRaises(status.UnexpectedMemberError) as cm:
|
||
|
|
+ status._clone_to_dto(clone_xml)
|
||
|
|
+
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "outer-clone")
|
||
|
|
+ self.assertEqual(cm.exception.member_id, resource_id)
|
||
|
|
+ self.assertEqual(
|
||
|
|
+ cm.exception.expected_types, ["primitive", "group"]
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
# pylint: disable=protected-access
|
||
|
|
def setUp(self):
|
||
|
|
- self.report_processor = MockLibraryReportProcessor()
|
||
|
|
+ self.bundle_id = "resource-bundle"
|
||
|
|
+ self.bundle_type = "podman"
|
||
|
|
|
||
|
|
def test_no_member_no_ip(self):
|
||
|
|
replica_xml = etree.fromstring(fixture_replica_xml())
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
self.assertEqual(result, fixture_replica_dto())
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_no_member(self):
|
||
|
|
replica_xml = etree.fromstring(fixture_replica_xml(ip=True))
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
self.assertEqual(result, fixture_replica_dto(ip=True))
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_member(self):
|
||
|
|
replica_xml = etree.fromstring(
|
||
|
|
@@ -1051,10 +1043,8 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -1063,9 +1053,6 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
member=fixture_primitive_dto(node_names=["resource-bundle-0"]),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_member_no_ip(self):
|
||
|
|
replica_xml = etree.fromstring(
|
||
|
|
@@ -1076,10 +1063,8 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
@@ -1087,10 +1072,35 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
member=fixture_primitive_dto(node_names=["resource-bundle-0"])
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
+
|
||
|
|
+ def test_invalid_role(self):
|
||
|
|
+ replica_xml = etree.fromstring(
|
||
|
|
+ fixture_replica_xml(
|
||
|
|
+ member=fixture_primitive_xml(role="NotPcmkRole")
|
||
|
|
+ )
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
+ )
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
+ self.assertEqual(cm.exception.role, "NotPcmkRole")
|
||
|
|
+
|
||
|
|
+ def test_invalid_target_role(self):
|
||
|
|
+ replica_xml = etree.fromstring(
|
||
|
|
+ fixture_replica_xml(
|
||
|
|
+ member=fixture_primitive_xml(target_role="NotPcmkRole")
|
||
|
|
+ )
|
||
|
|
)
|
||
|
|
|
||
|
|
+ with self.assertRaises(status.UnknownPcmkRoleError) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
+ )
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource")
|
||
|
|
+ self.assertEqual(cm.exception.role, "NotPcmkRole")
|
||
|
|
+
|
||
|
|
def test_no_container(self):
|
||
|
|
replica_xml = etree.fromstring(
|
||
|
|
"""
|
||
|
|
@@ -1108,44 +1118,28 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleReplicaMissingImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_REPLICA_NO_CONTAINER,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- replica_id="0",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(cm.exception.replica_id, "0")
|
||
|
|
+ self.assertEqual(cm.exception.implicit_type, "container")
|
||
|
|
|
||
|
|
def test_empty_replica(self):
|
||
|
|
replica_xml = etree.fromstring('<replica id="0" />')
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleReplicaMissingImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_REPLICA_NO_CONTAINER,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- replica_id="0",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(cm.exception.replica_id, "0")
|
||
|
|
+ self.assertEqual(cm.exception.implicit_type, "container")
|
||
|
|
|
||
|
|
def test_member_no_remote(self):
|
||
|
|
replica_xml = etree.fromstring(
|
||
|
|
@@ -1159,23 +1153,15 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleReplicaMissingImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_REPLICA_MISSING_REMOTE,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- replica_id="0",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(cm.exception.replica_id, "0")
|
||
|
|
+ self.assertEqual(cm.exception.implicit_type, "remote")
|
||
|
|
|
||
|
|
def test_member_same_id_as_container(self):
|
||
|
|
# xml taken from crm_mon output
|
||
|
|
@@ -1198,22 +1184,14 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
</replica>
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
- result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
- )
|
||
|
|
- self.assertTrue(result is None)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- bad_ids=["resource-bundle-podman-0"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleSameIdAsImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
+ )
|
||
|
|
+ self.assertEqual(cm.exception.bundle_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(cm.exception.bad_ids, ["resource-bundle-podman-0"])
|
||
|
|
|
||
|
|
def test_member_same_id_as_remote(self):
|
||
|
|
# xml taken from crm_mon output
|
||
|
|
@@ -1233,22 +1211,14 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
</replica>
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
- result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
- )
|
||
|
|
- self.assertTrue(result is None)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- bad_ids=["resource-bundle-0"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleSameIdAsImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
+ )
|
||
|
|
+ self.assertEqual(cm.exception.bundle_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(cm.exception.bad_ids, ["resource-bundle-0"])
|
||
|
|
|
||
|
|
def test_member_same_id_as_ip(self):
|
||
|
|
# xml taken from crm_mon output
|
||
|
|
@@ -1271,22 +1241,15 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
</replica>
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
-
|
||
|
|
- result = status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
- )
|
||
|
|
- self.assertTrue(result is None)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- bad_ids=["resource-bundle-ip-192.168.122.250"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleSameIdAsImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
+ )
|
||
|
|
+ self.assertEqual(cm.exception.bundle_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(
|
||
|
|
+ cm.exception.bad_ids, ["resource-bundle-ip-192.168.122.250"]
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_too_many_members(self):
|
||
|
|
@@ -1312,42 +1275,27 @@ class TestBundleReplicaStatusToDto(TestCase):
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
- bundle_id = "resource-bundle"
|
||
|
|
- bundle_type = "podman"
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._replica_to_dto(
|
||
|
|
- self.report_processor, replica_xml, bundle_id, bundle_type
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleReplicaInvalidMemberCountError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._replica_to_dto(
|
||
|
|
+ replica_xml, self.bundle_id, self.bundle_type
|
||
|
|
)
|
||
|
|
- )
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_REPLICA_INVALID_COUNT,
|
||
|
|
- bundle_id=bundle_id,
|
||
|
|
- replica_id="0",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, self.bundle_id)
|
||
|
|
+ self.assertEqual(cm.exception.replica_id, "0")
|
||
|
|
|
||
|
|
|
||
|
|
class TestBundleStatusToDto(TestCase):
|
||
|
|
# pylint: disable=protected-access
|
||
|
|
- def setUp(self):
|
||
|
|
- self.report_processor = MockLibraryReportProcessor()
|
||
|
|
-
|
||
|
|
def test_no_member(self):
|
||
|
|
bundle_xml = etree.fromstring(
|
||
|
|
fixture_bundle_xml(replicas=[fixture_replica_xml()])
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status._bundle_to_dto(self.report_processor, bundle_xml, False)
|
||
|
|
+ result = status._bundle_to_dto(bundle_xml, False)
|
||
|
|
self.assertEqual(
|
||
|
|
result, fixture_bundle_dto(replicas=[fixture_replica_dto()])
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_member(self):
|
||
|
|
bundle_xml = etree.fromstring(
|
||
|
|
@@ -1362,7 +1310,7 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- result = status._bundle_to_dto(self.report_processor, bundle_xml, False)
|
||
|
|
+ result = status._bundle_to_dto(bundle_xml, False)
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
fixture_bundle_dto(
|
||
|
|
@@ -1376,9 +1324,6 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_multiple_replicas(self):
|
||
|
|
bundle_xml = etree.fromstring(
|
||
|
|
@@ -1401,7 +1346,7 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- result = status._bundle_to_dto(self.report_processor, bundle_xml, False)
|
||
|
|
+ result = status._bundle_to_dto(bundle_xml, False)
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
fixture_bundle_dto(
|
||
|
|
@@ -1423,9 +1368,6 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
|
||
|
|
def test_same_id_as_implicit(self):
|
||
|
|
bundle_xml = etree.fromstring(
|
||
|
|
@@ -1447,18 +1389,13 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
</bundle>
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
- result = status._bundle_to_dto(self.report_processor, bundle_xml, False)
|
||
|
|
- self.assertTrue(result is None)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
- bundle_id="resource-bundle",
|
||
|
|
- bad_ids=["resource-bundle-0"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleSameIdAsImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._bundle_to_dto(bundle_xml, False)
|
||
|
|
+ self.assertEqual(cm.exception.bundle_id, "resource-bundle")
|
||
|
|
+ self.assertEqual(cm.exception.bad_ids, ["resource-bundle-0"])
|
||
|
|
|
||
|
|
def test_same_id_as_implicit_multiple_replicas(self):
|
||
|
|
bundle_xml = etree.fromstring(
|
||
|
|
@@ -1491,18 +1428,12 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
</bundle>
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
- result = status._bundle_to_dto(self.report_processor, bundle_xml, False)
|
||
|
|
- self.assertTrue(result is None)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.warn(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
- bundle_id="resource-bundle",
|
||
|
|
- bad_ids=["resource-bundle-1"],
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(
|
||
|
|
+ status.BundleSameIdAsImplicitResourceError
|
||
|
|
+ ) as cm:
|
||
|
|
+ status._bundle_to_dto(bundle_xml, False)
|
||
|
|
+ self.assertEqual(cm.exception.bundle_id, "resource-bundle")
|
||
|
|
+ self.assertEqual(cm.exception.bad_ids, ["resource-bundle-1"])
|
||
|
|
|
||
|
|
def test_replicas_different(self):
|
||
|
|
replicas = {
|
||
|
|
@@ -1522,8 +1453,6 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
}
|
||
|
|
for name, element in replicas.items():
|
||
|
|
with self.subTest(value=name):
|
||
|
|
- self.setUp()
|
||
|
|
-
|
||
|
|
bundle_xml = etree.fromstring(
|
||
|
|
fixture_bundle_xml(
|
||
|
|
replicas=[
|
||
|
|
@@ -1537,47 +1466,29 @@ class TestBundleStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- # pylint: disable=cell-var-from-loop
|
||
|
|
- assert_raise_library_error(
|
||
|
|
- lambda: status._bundle_to_dto(
|
||
|
|
- self.report_processor, bundle_xml
|
||
|
|
- )
|
||
|
|
- )
|
||
|
|
-
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
- [
|
||
|
|
- fixture.error(
|
||
|
|
- reports.codes.CLUSTER_STATUS_BUNDLE_DIFFERENT_REPLICAS,
|
||
|
|
- bundle_id="resource-bundle",
|
||
|
|
- )
|
||
|
|
- ],
|
||
|
|
- )
|
||
|
|
+ with self.assertRaises(status.BundleDifferentReplicas) as cm:
|
||
|
|
+ status._bundle_to_dto(bundle_xml)
|
||
|
|
+ self.assertEqual(cm.exception.resource_id, "resource-bundle")
|
||
|
|
|
||
|
|
|
||
|
|
class TestResourcesStatusToDto(TestCase):
|
||
|
|
- def setUp(self):
|
||
|
|
- self.report_processor = MockLibraryReportProcessor()
|
||
|
|
-
|
||
|
|
def test_empty_resources(self):
|
||
|
|
status_xml = etree.fromstring(fixture_crm_mon_xml([]))
|
||
|
|
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
self.assertEqual(result, ResourcesStatusDto([]))
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
+ assert_report_item_list_equal(parser.get_warnings(), [])
|
||
|
|
|
||
|
|
def test_single_primitive(self):
|
||
|
|
status_xml = etree.fromstring(
|
||
|
|
fixture_crm_mon_xml([fixture_primitive_xml()])
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
self.assertEqual(result, ResourcesStatusDto([fixture_primitive_dto()]))
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
+ assert_report_item_list_equal(parser.get_warnings(), [])
|
||
|
|
|
||
|
|
def test_single_group(self):
|
||
|
|
status_xml = etree.fromstring(
|
||
|
|
@@ -1586,16 +1497,15 @@ class TestResourcesStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
ResourcesStatusDto(
|
||
|
|
[fixture_group_dto(members=[fixture_primitive_dto()])]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
+ assert_report_item_list_equal(parser.get_warnings(), [])
|
||
|
|
|
||
|
|
def test_single_clone(self):
|
||
|
|
status_xml = etree.fromstring(
|
||
|
|
@@ -1604,16 +1514,15 @@ class TestResourcesStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
ResourcesStatusDto(
|
||
|
|
[fixture_clone_dto(instances=[fixture_primitive_dto()])]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
+ assert_report_item_list_equal(parser.get_warnings(), [])
|
||
|
|
|
||
|
|
def test_single_bundle(self):
|
||
|
|
status_xml = etree.fromstring(
|
||
|
|
@@ -1633,7 +1542,8 @@ class TestResourcesStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
self.assertEqual(
|
||
|
|
result,
|
||
|
|
ResourcesStatusDto(
|
||
|
|
@@ -1651,9 +1561,7 @@ class TestResourcesStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
- assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list, []
|
||
|
|
- )
|
||
|
|
+ assert_report_item_list_equal(parser.get_warnings(), [])
|
||
|
|
|
||
|
|
def test_all_resource_types(self):
|
||
|
|
status_xml = etree.fromstring(
|
||
|
|
@@ -1675,30 +1583,30 @@ class TestResourcesStatusToDto(TestCase):
|
||
|
|
]
|
||
|
|
)
|
||
|
|
)
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
|
||
|
|
- self.assertEqual(result.resources[0], fixture_primitive_dto())
|
||
|
|
- self.assertEqual(
|
||
|
|
- result.resources[1],
|
||
|
|
- fixture_group_dto(members=[fixture_primitive_dto()]),
|
||
|
|
- )
|
||
|
|
self.assertEqual(
|
||
|
|
- result.resources[2],
|
||
|
|
- fixture_clone_dto(instances=[fixture_primitive_dto()]),
|
||
|
|
- )
|
||
|
|
- self.assertEqual(
|
||
|
|
- result.resources[3],
|
||
|
|
- fixture_bundle_dto(
|
||
|
|
- replicas=[
|
||
|
|
- fixture_replica_dto(
|
||
|
|
- ip=True,
|
||
|
|
- member=fixture_primitive_dto(
|
||
|
|
- node_names=["resource-bundle-0"]
|
||
|
|
- ),
|
||
|
|
- )
|
||
|
|
+ result,
|
||
|
|
+ ResourcesStatusDto(
|
||
|
|
+ [
|
||
|
|
+ fixture_primitive_dto(),
|
||
|
|
+ fixture_group_dto(members=[fixture_primitive_dto()]),
|
||
|
|
+ fixture_clone_dto(instances=[fixture_primitive_dto()]),
|
||
|
|
+ fixture_bundle_dto(
|
||
|
|
+ replicas=[
|
||
|
|
+ fixture_replica_dto(
|
||
|
|
+ ip=True,
|
||
|
|
+ member=fixture_primitive_dto(
|
||
|
|
+ node_names=["resource-bundle-0"]
|
||
|
|
+ ),
|
||
|
|
+ )
|
||
|
|
+ ]
|
||
|
|
+ ),
|
||
|
|
]
|
||
|
|
),
|
||
|
|
)
|
||
|
|
+ assert_report_item_list_equal(parser.get_warnings(), [])
|
||
|
|
|
||
|
|
def test_skip_bundle(self):
|
||
|
|
status_xml = etree.fromstring(
|
||
|
|
@@ -1726,11 +1634,12 @@ class TestResourcesStatusToDto(TestCase):
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
- result = status.status_xml_to_dto(self.report_processor, status_xml)
|
||
|
|
+ parser = status.ClusterStatusParser(status_xml)
|
||
|
|
+ result = parser.status_xml_to_dto()
|
||
|
|
|
||
|
|
self.assertEqual(result, ResourcesStatusDto([fixture_primitive_dto()]))
|
||
|
|
assert_report_item_list_equal(
|
||
|
|
- self.report_processor.report_item_list,
|
||
|
|
+ parser.get_warnings(),
|
||
|
|
[
|
||
|
|
fixture.warn(
|
||
|
|
reports.codes.CLUSTER_STATUS_BUNDLE_MEMBER_ID_AS_IMPLICIT,
|
||
|
|
--
|
||
|
|
2.25.1
|
||
|
|
|