Blame SOURCES/bz1315371-02-use-recipient-id-as-identifier-instead-of-its-value.patch

15f218
From 8eef21a7bbfdcba709515529a40fadc1f5386b70 Mon Sep 17 00:00:00 2001
15f218
From: Ondrej Mular <omular@redhat.com>
15f218
Date: Fri, 8 Jul 2016 16:43:16 +0200
15f218
Subject: [PATCH 1/2] lib: use recipient id as identifier instead of its value
15f218
15f218
---
15f218
 pcs/common/report_codes.py          |   3 +-
15f218
 pcs/lib/cib/alert.py                | 129 +++++++----
15f218
 pcs/lib/cib/test/test_alert.py      | 449 +++++++++++++++++++++++++++++++-----
15f218
 pcs/lib/commands/alert.py           |  45 +++-
15f218
 pcs/lib/commands/test/test_alert.py | 111 ++++++---
15f218
 pcs/lib/reports.py                  |  29 ++-
15f218
 6 files changed, 597 insertions(+), 169 deletions(-)
15f218
15f218
diff --git a/pcs/common/report_codes.py b/pcs/common/report_codes.py
15f218
index 2b39938..53f2ccb 100644
15f218
--- a/pcs/common/report_codes.py
15f218
+++ b/pcs/common/report_codes.py
15f218
@@ -7,6 +7,7 @@ from __future__ import (
15f218
 
15f218
 # force cathegories
15f218
 FORCE_ACTIVE_RRP = "ACTIVE_RRP"
15f218
+FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE = "FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE"
15f218
 FORCE_CONSTRAINT_DUPLICATE = "CONSTRAINT_DUPLICATE"
15f218
 FORCE_CONSTRAINT_MULTIINSTANCE_RESOURCE = "CONSTRAINT_MULTIINSTANCE_RESOURCE"
15f218
 FORCE_LOAD_THRESHOLD = "LOAD_THRESHOLD"
15f218
@@ -22,7 +23,7 @@ AGENT_NOT_FOUND = "AGENT_NOT_FOUND"
15f218
 BAD_CLUSTER_STATE_FORMAT = 'BAD_CLUSTER_STATE_FORMAT'
15f218
 CIB_ALERT_NOT_FOUND = "CIB_ALERT_NOT_FOUND"
15f218
 CIB_ALERT_RECIPIENT_ALREADY_EXISTS = "CIB_ALERT_RECIPIENT_ALREADY_EXISTS"
15f218
-CIB_ALERT_RECIPIENT_NOT_FOUND = "CIB_ALERT_RECIPIENT_NOT_FOUND"
15f218
+CIB_ALERT_RECIPIENT_VALUE_INVALID = "CIB_ALERT_RECIPIENT_VALUE_INVALID"
15f218
 CIB_CANNOT_FIND_MANDATORY_SECTION = "CIB_CANNOT_FIND_MANDATORY_SECTION"
15f218
 CIB_LOAD_ERROR_BAD_FORMAT = "CIB_LOAD_ERROR_BAD_FORMAT"
15f218
 CIB_LOAD_ERROR = "CIB_LOAD_ERROR"
15f218
diff --git a/pcs/lib/cib/alert.py b/pcs/lib/cib/alert.py
15f218
index 6b72996..b5fe88c 100644
15f218
--- a/pcs/lib/cib/alert.py
15f218
+++ b/pcs/lib/cib/alert.py
15f218
@@ -7,14 +7,16 @@ from __future__ import (
15f218
 
15f218
 from lxml import etree
15f218
 
15f218
+from pcs.common import report_codes
15f218
 from pcs.lib import reports
15f218
-from pcs.lib.errors import LibraryError
15f218
+from pcs.lib.errors import LibraryError, ReportItemSeverity as Severities
15f218
 from pcs.lib.cib.nvpair import update_nvset, get_nvset
15f218
 from pcs.lib.cib.tools import (
15f218
     check_new_id_applicable,
15f218
     get_sub_element,
15f218
     find_unique_id,
15f218
     get_alerts,
15f218
+    validate_id_does_not_exist,
15f218
 )
15f218
 
15f218
 
15f218
@@ -61,7 +63,7 @@ def _update_optional_attribute(element, attribute, value):
15f218
 def get_alert_by_id(tree, alert_id):
15f218
     """
15f218
     Returns alert element with specified id.
15f218
-    Raises AlertNotFound if alert with specified id doesn't exist.
15f218
+    Raises LibraryError if alert with specified id doesn't exist.
15f218
 
15f218
     tree -- cib etree node
15f218
     alert_id -- id of alert
15f218
@@ -72,25 +74,53 @@ def get_alert_by_id(tree, alert_id):
15f218
     return alert
15f218
 
15f218
 
15f218
-def get_recipient(alert, recipient_value):
15f218
+def get_recipient_by_id(tree, recipient_id):
15f218
     """
15f218
     Returns recipient element with value recipient_value which belong to
15f218
     specified alert.
15f218
-    Raises RecipientNotFound if recipient doesn't exist.
15f218
+    Raises LibraryError if recipient doesn't exist.
15f218
 
15f218
-    alert -- parent element of required recipient
15f218
-    recipient_value -- value of recipient
15f218
+    tree -- cib etree node
15f218
+    recipient_id -- id of recipient
15f218
     """
15f218
-    recipient = alert.find(
15f218
-        "./recipient[@value='{0}']".format(recipient_value)
15f218
+    recipient = get_alerts(tree).find(
15f218
+        "./alert/recipient[@id='{0}']".format(recipient_id)
15f218
     )
15f218
     if recipient is None:
15f218
-        raise LibraryError(reports.cib_alert_recipient_not_found(
15f218
-            alert.get("id"), recipient_value
15f218
-        ))
15f218
+        raise LibraryError(reports.id_not_found(recipient_id, "Recipient"))
15f218
     return recipient
15f218
 
15f218
 
15f218
+def ensure_recipient_value_is_unique(
15f218
+    reporter, alert, recipient_value, recipient_id="", allow_duplicity=False
15f218
+):
15f218
+    """
15f218
+    Ensures that recipient_value is unique in alert.
15f218
+
15f218
+    reporter -- report processor
15f218
+    alert -- alert
15f218
+    recipient_value -- recipient value
15f218
+    recipient_id -- recipient id of to which value belongs to
15f218
+    allow_duplicity -- if True only warning will be shown if value already
15f218
+        exists
15f218
+    """
15f218
+    recipient_list = alert.xpath(
15f218
+        "./recipient[@value='{value}' and @id!='{id}']".format(
15f218
+            value=recipient_value, id=recipient_id
15f218
+        )
15f218
+    )
15f218
+    if recipient_list:
15f218
+        reporter.process(reports.cib_alert_recipient_already_exists(
15f218
+            alert.get("id", None),
15f218
+            recipient_value,
15f218
+            Severities.WARNING if allow_duplicity else Severities.ERROR,
15f218
+            forceable=(
15f218
+                None if allow_duplicity
15f218
+                else report_codes.FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE
15f218
+            )
15f218
+        ))
15f218
+
15f218
+
15f218
 def create_alert(tree, alert_id, path, description=""):
15f218
     """
15f218
     Create new alert element. Returns newly created element.
15f218
@@ -116,7 +146,7 @@ def create_alert(tree, alert_id, path, description=""):
15f218
 def update_alert(tree, alert_id, path, description=None):
15f218
     """
15f218
     Update existing alert. Return updated alert element.
15f218
-    Raises AlertNotFound if alert with specified id doesn't exist.
15f218
+    Raises LibraryError if alert with specified id doesn't exist.
15f218
 
15f218
     tree -- cib etree node
15f218
     alert_id -- id of alert to be updated
15f218
@@ -134,7 +164,7 @@ def update_alert(tree, alert_id, path, description=None):
15f218
 def remove_alert(tree, alert_id):
15f218
     """
15f218
     Remove alert with specified id.
15f218
-    Raises AlertNotFound if alert with specified id doesn't exist.
15f218
+    Raises LibraryError if alert with specified id doesn't exist.
15f218
 
15f218
     tree -- cib etree node
15f218
     alert_id -- id of alert which should be removed
15f218
@@ -144,36 +174,38 @@ def remove_alert(tree, alert_id):
15f218
 
15f218
 
15f218
 def add_recipient(
15f218
+    reporter,
15f218
     tree,
15f218
     alert_id,
15f218
     recipient_value,
15f218
-    description=""
15f218
+    recipient_id=None,
15f218
+    description="",
15f218
+    allow_same_value=False
15f218
 ):
15f218
     """
15f218
     Add recipient to alert with specified id. Returns added recipient element.
15f218
-    Raises AlertNotFound if alert with specified id doesn't exist.
15f218
+    Raises LibraryError if alert with specified recipient_id doesn't exist.
15f218
     Raises LibraryError if recipient already exists.
15f218
 
15f218
+    reporter -- report processor
15f218
     tree -- cib etree node
15f218
     alert_id -- id of alert which should be parent of new recipient
15f218
     recipient_value -- value of recipient
15f218
+    recipient_id -- id of new recipient, if None it will be generated
15f218
     description -- description of recipient
15f218
+    allow_same_value -- if True unique recipient value is not required
15f218
     """
15f218
-    alert = get_alert_by_id(tree, alert_id)
15f218
+    if recipient_id is None:
15f218
+        recipient_id = find_unique_id(tree, "{0}-recipient".format(alert_id))
15f218
+    else:
15f218
+        validate_id_does_not_exist(tree, recipient_id)
15f218
 
15f218
-    recipient = alert.find(
15f218
-        "./recipient[@value='{0}']".format(recipient_value)
15f218
+    alert = get_alert_by_id(tree, alert_id)
15f218
+    ensure_recipient_value_is_unique(
15f218
+        reporter, alert, recipient_value, allow_duplicity=allow_same_value
15f218
     )
15f218
-    if recipient is not None:
15f218
-        raise LibraryError(reports.cib_alert_recipient_already_exists(
15f218
-            alert_id, recipient_value
15f218
-        ))
15f218
-
15f218
     recipient = etree.SubElement(
15f218
-        alert,
15f218
-        "recipient",
15f218
-        id=find_unique_id(tree, "{0}-recipient".format(alert_id)),
15f218
-        value=recipient_value
15f218
+        alert, "recipient", id=recipient_id, value=recipient_value
15f218
     )
15f218
 
15f218
     if description:
15f218
@@ -182,38 +214,49 @@ def add_recipient(
15f218
     return recipient
15f218
 
15f218
 
15f218
-def update_recipient(tree, alert_id, recipient_value, description):
15f218
+def update_recipient(
15f218
+    reporter,
15f218
+    tree,
15f218
+    recipient_id,
15f218
+    recipient_value=None,
15f218
+    description=None,
15f218
+    allow_same_value=False
15f218
+):
15f218
     """
15f218
     Update specified recipient. Returns updated recipient element.
15f218
-    Raises AlertNotFound if alert with specified id doesn't exist.
15f218
-    Raises RecipientNotFound if recipient doesn't exist.
15f218
+    Raises LibraryError if recipient doesn't exist.
15f218
 
15f218
+    reporter -- report processor
15f218
     tree -- cib etree node
15f218
-    alert_id -- id of alert, parent element of recipient
15f218
-    recipient_value -- recipient value
15f218
+    recipient_id -- id of recipient to be updated
15f218
+    recipient_value -- recipient value, stay unchanged if None
15f218
     description -- description, if empty it will be removed, stay unchanged
15f218
         if None
15f218
+    allow_same_value -- if True unique recipient value is not required
15f218
     """
15f218
-    recipient = get_recipient(
15f218
-        get_alert_by_id(tree, alert_id), recipient_value
15f218
-    )
15f218
+    recipient = get_recipient_by_id(tree, recipient_id)
15f218
+    if recipient_value is not None:
15f218
+        ensure_recipient_value_is_unique(
15f218
+            reporter,
15f218
+            recipient.getparent(),
15f218
+            recipient_value,
15f218
+            recipient_id=recipient_id,
15f218
+            allow_duplicity=allow_same_value
15f218
+        )
15f218
+        recipient.set("value", recipient_value)
15f218
     _update_optional_attribute(recipient, "description", description)
15f218
     return recipient
15f218
 
15f218
 
15f218
-def remove_recipient(tree, alert_id, recipient_value):
15f218
+def remove_recipient(tree, recipient_id):
15f218
     """
15f218
     Remove specified recipient.
15f218
-    Raises AlertNotFound if alert with specified id doesn't exist.
15f218
-    Raises RecipientNotFound if recipient doesn't exist.
15f218
+    Raises LibraryError if recipient doesn't exist.
15f218
 
15f218
     tree -- cib etree node
15f218
-    alert_id -- id of alert, parent element of recipient
15f218
-    recipient_value -- recipient value
15f218
+    recipient_id -- id of recipient to be removed
15f218
     """
15f218
-    recipient = get_recipient(
15f218
-        get_alert_by_id(tree, alert_id), recipient_value
15f218
-    )
15f218
+    recipient = get_recipient_by_id(tree, recipient_id)
15f218
     recipient.getparent().remove(recipient)
15f218
 
15f218
 
15f218
diff --git a/pcs/lib/cib/test/test_alert.py b/pcs/lib/cib/test/test_alert.py
15f218
index c387aaf..50eaef6 100644
15f218
--- a/pcs/lib/cib/test/test_alert.py
15f218
+++ b/pcs/lib/cib/test/test_alert.py
15f218
@@ -15,8 +15,10 @@ from pcs.lib.errors import ReportItemSeverity as severities
15f218
 from pcs.test.tools.assertions import(
15f218
     assert_raise_library_error,
15f218
     assert_xml_equal,
15f218
+    assert_report_item_list_equal,
15f218
 )
15f218
 from pcs.test.tools.pcs_mock import mock
15f218
+from pcs.test.tools.custom_mock import MockLibraryReportProcessor
15f218
 
15f218
 
15f218
 @mock.patch("pcs.lib.cib.alert.update_nvset")
15f218
@@ -129,54 +131,146 @@ class GetAlertByIdTest(TestCase):
15f218
         )
15f218
 
15f218
 
15f218
-class GetRecipientTest(TestCase):
15f218
+class GetRecipientByIdTest(TestCase):
15f218
     def setUp(self):
15f218
         self.xml = etree.XML(
15f218
             """
15f218
-                <alert id="alert-1">
15f218
-                    <recipient id="rec-1" value="value1"/>
15f218
-                    <recipient id="rec-2" value="value2"/>
15f218
-                    <not_recipient value="value3"/>
15f218
-                    <recipients>
15f218
-                        <recipient id="rec-4" value="value4"/>
15f218
-                    </recipients>
15f218
-                </alert>
15f218
+                <cib>
15f218
+                    <configuration>
15f218
+                        <alerts>
15f218
+                            <alert id="alert-1">
15f218
+                                <recipient id="rec-1" value="value1"/>
15f218
+                                <not_recipient id="rec-3" value="value3"/>
15f218
+                                <recipients>
15f218
+                                    <recipient id="rec-4" value="value4"/>
15f218
+                                </recipients>
15f218
+                            </alert>
15f218
+                            <recipient id="rec-2" value="value2"/>
15f218
+                        </alerts>
15f218
+                        <alert id="alert-2"/>
15f218
+                    </configuration>
15f218
+                </cib>
15f218
             """
15f218
         )
15f218
 
15f218
     def test_exist(self):
15f218
         assert_xml_equal(
15f218
-            '<recipient id="rec-2" value="value2"/>',
15f218
-            etree.tostring(alert.get_recipient(self.xml, "value2")).decode()
15f218
+            '<recipient id="rec-1" value="value1"/>',
15f218
+            etree.tostring(
15f218
+                alert.get_recipient_by_id(self.xml, "rec-1")
15f218
+            ).decode()
15f218
         )
15f218
 
15f218
     def test_different_place(self):
15f218
         assert_raise_library_error(
15f218
-            lambda: alert.get_recipient(self.xml, "value4"),
15f218
+            lambda: alert.get_recipient_by_id(self.xml, "rec-4"),
15f218
             (
15f218
                 severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
+                report_codes.ID_NOT_FOUND,
15f218
                 {
15f218
-                    "alert": "alert-1",
15f218
-                    "recipient": "value4"
15f218
+                    "id": "rec-4",
15f218
+                    "id_description": "Recipient"
15f218
+                }
15f218
+            )
15f218
+        )
15f218
+
15f218
+    def test_not_in_alert(self):
15f218
+        assert_raise_library_error(
15f218
+            lambda: alert.get_recipient_by_id(self.xml, "rec-2"),
15f218
+            (
15f218
+                severities.ERROR,
15f218
+                report_codes.ID_NOT_FOUND,
15f218
+                {
15f218
+                    "id": "rec-2",
15f218
+                    "id_description": "Recipient"
15f218
                 }
15f218
             )
15f218
         )
15f218
 
15f218
     def test_not_recipient(self):
15f218
         assert_raise_library_error(
15f218
-            lambda: alert.get_recipient(self.xml, "value3"),
15f218
+            lambda: alert.get_recipient_by_id(self.xml, "rec-3"),
15f218
             (
15f218
                 severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
+                report_codes.ID_NOT_FOUND,
15f218
                 {
15f218
-                    "alert": "alert-1",
15f218
-                    "recipient": "value3"
15f218
+                    "id": "rec-3",
15f218
+                    "id_description": "Recipient"
15f218
                 }
15f218
             )
15f218
         )
15f218
 
15f218
 
15f218
+class EnsureRecipientValueIsUniqueTest(TestCase):
15f218
+    def setUp(self):
15f218
+        self.mock_reporter = MockLibraryReportProcessor()
15f218
+        self.alert = etree.Element("alert", id="alert-1")
15f218
+        self.recipient = etree.SubElement(
15f218
+            self.alert, "recipient", id="rec-1", value="value1"
15f218
+        )
15f218
+
15f218
+    def test_is_unique_no_duplicity_allowed(self):
15f218
+        alert.ensure_recipient_value_is_unique(
15f218
+            self.mock_reporter, self.alert, "value2"
15f218
+        )
15f218
+        self.assertEqual(0, len(self.mock_reporter.report_item_list))
15f218
+
15f218
+    def test_same_recipient_no_duplicity_allowed(self):
15f218
+        alert.ensure_recipient_value_is_unique(
15f218
+            self.mock_reporter, self.alert, "value1", recipient_id="rec-1"
15f218
+        )
15f218
+        self.assertEqual(0, len(self.mock_reporter.report_item_list))
15f218
+
15f218
+    def test_same_recipient_duplicity_allowed(self):
15f218
+        alert.ensure_recipient_value_is_unique(
15f218
+            self.mock_reporter, self.alert, "value1", recipient_id="rec-1",
15f218
+            allow_duplicity=True
15f218
+        )
15f218
+        self.assertEqual(0, len(self.mock_reporter.report_item_list))
15f218
+
15f218
+    def test_not_unique_no_duplicity_allowed(self):
15f218
+        report_item = (
15f218
+            severities.ERROR,
15f218
+            report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
+            {
15f218
+                "alert": "alert-1",
15f218
+                "recipient": "value1"
15f218
+            },
15f218
+            report_codes.FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE
15f218
+        )
15f218
+        assert_raise_library_error(
15f218
+            lambda: alert.ensure_recipient_value_is_unique(
15f218
+                self.mock_reporter, self.alert, "value1"
15f218
+            ),
15f218
+            report_item
15f218
+        )
15f218
+        assert_report_item_list_equal(
15f218
+            self.mock_reporter.report_item_list, [report_item]
15f218
+        )
15f218
+
15f218
+    def test_is_unique_duplicity_allowed(self):
15f218
+        alert.ensure_recipient_value_is_unique(
15f218
+            self.mock_reporter, self.alert, "value2", allow_duplicity=True
15f218
+        )
15f218
+        self.assertEqual(0, len(self.mock_reporter.report_item_list))
15f218
+
15f218
+    def test_not_unique_duplicity_allowed(self):
15f218
+        alert.ensure_recipient_value_is_unique(
15f218
+            self.mock_reporter, self.alert, "value1", allow_duplicity=True
15f218
+        )
15f218
+        assert_report_item_list_equal(
15f218
+            self.mock_reporter.report_item_list,
15f218
+            [(
15f218
+                severities.WARNING,
15f218
+                report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
+                {
15f218
+                    "alert": "alert-1",
15f218
+                    "recipient": "value1"
15f218
+                }
15f218
+            )]
15f218
+        )
15f218
+
15f218
+
15f218
 class CreateAlertTest(TestCase):
15f218
     def setUp(self):
15f218
         self.tree = etree.XML(
15f218
@@ -462,6 +556,7 @@ class RemoveAlertTest(TestCase):
15f218
 
15f218
 class AddRecipientTest(TestCase):
15f218
     def setUp(self):
15f218
+        self.mock_reporter = MockLibraryReportProcessor()
15f218
         self.tree = etree.XML(
15f218
             """
15f218
             <cib>
15f218
@@ -476,11 +571,40 @@ class AddRecipientTest(TestCase):
15f218
             """
15f218
         )
15f218
 
15f218
-    def test_success(self):
15f218
+    def test_with_id(self):
15f218
+        assert_xml_equal(
15f218
+            '<recipient id="my-recipient" value="value1"/>',
15f218
+            etree.tostring(
15f218
+                alert.add_recipient(
15f218
+                    self.mock_reporter, self.tree, "alert", "value1",
15f218
+                    "my-recipient"
15f218
+                )
15f218
+            ).decode()
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <cib>
15f218
+                <configuration>
15f218
+                    <alerts>
15f218
+                        <alert id="alert" path="/path">
15f218
+                            <recipient id="alert-recipient" value="test_val"/>
15f218
+                            <recipient id="my-recipient" value="value1"/>
15f218
+                        </alert>
15f218
+                    </alerts>
15f218
+                </configuration>
15f218
+            </cib>
15f218
+            """,
15f218
+            etree.tostring(self.tree).decode()
15f218
+        )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
+
15f218
+    def test_without_id(self):
15f218
         assert_xml_equal(
15f218
             '<recipient id="alert-recipient-1" value="value1"/>',
15f218
             etree.tostring(
15f218
-                alert.add_recipient(self.tree, "alert", "value1")
15f218
+                alert.add_recipient(
15f218
+                    self.mock_reporter, self.tree, "alert", "value1"
15f218
+                )
15f218
             ).decode()
15f218
         )
15f218
         assert_xml_equal(
15f218
@@ -498,23 +622,85 @@ class AddRecipientTest(TestCase):
15f218
             """,
15f218
             etree.tostring(self.tree).decode()
15f218
         )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
 
15f218
-    def test_recipient_exist(self):
15f218
+    def test_id_exists(self):
15f218
         assert_raise_library_error(
15f218
-            lambda: alert.add_recipient(self.tree, "alert", "test_val"),
15f218
+            lambda: alert.add_recipient(
15f218
+                self.mock_reporter, self.tree, "alert", "value1",
15f218
+                "alert-recipient"
15f218
+            ),
15f218
             (
15f218
                 severities.ERROR,
15f218
+                report_codes.ID_ALREADY_EXISTS,
15f218
+                {"id": "alert-recipient"}
15f218
+            )
15f218
+        )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
+
15f218
+    def test_duplicity_of_value_not_allowed(self):
15f218
+        report_item = (
15f218
+            severities.ERROR,
15f218
+            report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
+            {
15f218
+                "alert": "alert",
15f218
+                "recipient": "test_val"
15f218
+            },
15f218
+            report_codes.FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE
15f218
+        )
15f218
+        assert_raise_library_error(
15f218
+            lambda: alert.add_recipient(
15f218
+                self.mock_reporter, self.tree, "alert", "test_val"
15f218
+            ),
15f218
+            report_item
15f218
+        )
15f218
+        assert_report_item_list_equal(
15f218
+            self.mock_reporter.report_item_list,
15f218
+            [report_item]
15f218
+        )
15f218
+
15f218
+    def test_duplicity_of_value_allowed(self):
15f218
+        assert_xml_equal(
15f218
+            '<recipient id="alert-recipient-1" value="test_val"/>',
15f218
+            etree.tostring(
15f218
+                alert.add_recipient(
15f218
+                    self.mock_reporter, self.tree, "alert", "test_val",
15f218
+                    allow_same_value=True
15f218
+                )
15f218
+            ).decode()
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <cib>
15f218
+                <configuration>
15f218
+                    <alerts>
15f218
+                        <alert id="alert" path="/path">
15f218
+                            <recipient id="alert-recipient" value="test_val"/>
15f218
+                            <recipient id="alert-recipient-1" value="test_val"/>
15f218
+                        </alert>
15f218
+                    </alerts>
15f218
+                </configuration>
15f218
+            </cib>
15f218
+            """,
15f218
+            etree.tostring(self.tree).decode()
15f218
+        )
15f218
+        assert_report_item_list_equal(
15f218
+            self.mock_reporter.report_item_list,
15f218
+            [(
15f218
+                severities.WARNING,
15f218
                 report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
                 {
15f218
-                    "recipient": "test_val",
15f218
-                    "alert": "alert"
15f218
+                    "alert": "alert",
15f218
+                    "recipient": "test_val"
15f218
                 }
15f218
-            )
15f218
+            )]
15f218
         )
15f218
 
15f218
     def test_alert_not_exist(self):
15f218
         assert_raise_library_error(
15f218
-            lambda: alert.add_recipient(self.tree, "alert1", "test_val"),
15f218
+            lambda: alert.add_recipient(
15f218
+                self.mock_reporter, self.tree, "alert1", "test_val"
15f218
+            ),
15f218
             (
15f218
                 severities.ERROR,
15f218
                 report_codes.CIB_ALERT_NOT_FOUND,
15f218
@@ -532,7 +718,8 @@ class AddRecipientTest(TestCase):
15f218
             />
15f218
             """,
15f218
             etree.tostring(alert.add_recipient(
15f218
-                self.tree, "alert", "value1", "desc"
15f218
+                self.mock_reporter, self.tree, "alert", "value1",
15f218
+                description="desc"
15f218
             )).decode()
15f218
         )
15f218
         assert_xml_equal(
15f218
@@ -554,10 +741,12 @@ class AddRecipientTest(TestCase):
15f218
             """,
15f218
             etree.tostring(self.tree).decode()
15f218
         )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
 
15f218
 
15f218
 class UpdateRecipientTest(TestCase):
15f218
     def setUp(self):
15f218
+        self.mock_reporter = MockLibraryReportProcessor()
15f218
         self.tree = etree.XML(
15f218
             """
15f218
             <cib>
15f218
@@ -577,6 +766,157 @@ class UpdateRecipientTest(TestCase):
15f218
             """
15f218
         )
15f218
 
15f218
+    def test_update_value(self):
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <recipient id="alert-recipient" value="new_val"/>
15f218
+            """,
15f218
+            etree.tostring(alert.update_recipient(
15f218
+                self.mock_reporter, self.tree, "alert-recipient",
15f218
+                recipient_value="new_val"
15f218
+            )).decode()
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <cib>
15f218
+                <configuration>
15f218
+                    <alerts>
15f218
+                        <alert id="alert" path="/path">
15f218
+                            <recipient id="alert-recipient" value="new_val"/>
15f218
+                            
15f218
+                                id="alert-recipient-1"
15f218
+                                value="value1"
15f218
+                                description="desc"
15f218
+                            />
15f218
+                        </alert>
15f218
+                    </alerts>
15f218
+                </configuration>
15f218
+            </cib>
15f218
+            """,
15f218
+            etree.tostring(self.tree).decode()
15f218
+        )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
+
15f218
+    def test_update_same_value_no_duplicity_allowed(self):
15f218
+        assert_xml_equal(
15f218
+            '<recipient id="alert-recipient" value="test_val"/>',
15f218
+            etree.tostring(alert.update_recipient(
15f218
+                self.mock_reporter, self.tree, "alert-recipient",
15f218
+                recipient_value="test_val"
15f218
+            )).decode()
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <cib>
15f218
+                <configuration>
15f218
+                    <alerts>
15f218
+                        <alert id="alert" path="/path">
15f218
+                            <recipient id="alert-recipient" value="test_val"/>
15f218
+                            
15f218
+                                id="alert-recipient-1"
15f218
+                                value="value1"
15f218
+                                description="desc"
15f218
+                            />
15f218
+                        </alert>
15f218
+                    </alerts>
15f218
+                </configuration>
15f218
+            </cib>
15f218
+            """,
15f218
+            etree.tostring(self.tree).decode()
15f218
+        )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
+
15f218
+    def test_update_same_value_duplicity_allowed(self):
15f218
+        assert_xml_equal(
15f218
+            '<recipient id="alert-recipient" value="test_val"/>',
15f218
+            etree.tostring(alert.update_recipient(
15f218
+                self.mock_reporter, self.tree, "alert-recipient",
15f218
+                recipient_value="test_val", allow_same_value=True
15f218
+            )).decode()
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <cib>
15f218
+                <configuration>
15f218
+                    <alerts>
15f218
+                        <alert id="alert" path="/path">
15f218
+                            <recipient id="alert-recipient" value="test_val"/>
15f218
+                            
15f218
+                                id="alert-recipient-1"
15f218
+                                value="value1"
15f218
+                                description="desc"
15f218
+                            />
15f218
+                        </alert>
15f218
+                    </alerts>
15f218
+                </configuration>
15f218
+            </cib>
15f218
+            """,
15f218
+            etree.tostring(self.tree).decode()
15f218
+        )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
+
15f218
+    def test_duplicity_of_value_not_allowed(self):
15f218
+        report_item = (
15f218
+            severities.ERROR,
15f218
+            report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
+            {
15f218
+                "alert": "alert",
15f218
+                "recipient": "value1"
15f218
+            },
15f218
+            report_codes.FORCE_ALERT_RECIPIENT_VALUE_NOT_UNIQUE
15f218
+        )
15f218
+        assert_raise_library_error(
15f218
+            lambda: alert.update_recipient(
15f218
+                self.mock_reporter, self.tree, "alert-recipient", "value1"
15f218
+            ),
15f218
+            report_item
15f218
+        )
15f218
+        assert_report_item_list_equal(
15f218
+            self.mock_reporter.report_item_list,
15f218
+            [report_item]
15f218
+        )
15f218
+
15f218
+    def test_duplicity_of_value_allowed(self):
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <recipient id="alert-recipient" value="value1"/>
15f218
+            """,
15f218
+            etree.tostring(alert.update_recipient(
15f218
+                self.mock_reporter, self.tree, "alert-recipient",
15f218
+                recipient_value="value1", allow_same_value=True
15f218
+            )).decode()
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+            <cib>
15f218
+                <configuration>
15f218
+                    <alerts>
15f218
+                        <alert id="alert" path="/path">
15f218
+                            <recipient id="alert-recipient" value="value1"/>
15f218
+                            
15f218
+                                id="alert-recipient-1"
15f218
+                                value="value1"
15f218
+                                description="desc"
15f218
+                            />
15f218
+                        </alert>
15f218
+                    </alerts>
15f218
+                </configuration>
15f218
+            </cib>
15f218
+            """,
15f218
+            etree.tostring(self.tree).decode()
15f218
+        )
15f218
+        assert_report_item_list_equal(
15f218
+            self.mock_reporter.report_item_list,
15f218
+            [(
15f218
+                severities.WARNING,
15f218
+                report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
+                {
15f218
+                    "alert": "alert",
15f218
+                    "recipient": "value1"
15f218
+                }
15f218
+            )]
15f218
+        )
15f218
+
15f218
     def test_add_description(self):
15f218
         assert_xml_equal(
15f218
             """
15f218
@@ -585,7 +925,8 @@ class UpdateRecipientTest(TestCase):
15f218
             />
15f218
             """,
15f218
             etree.tostring(alert.update_recipient(
15f218
-                self.tree, "alert", "test_val", "description"
15f218
+                self.mock_reporter, self.tree, "alert-recipient",
15f218
+                description="description"
15f218
             )).decode()
15f218
         )
15f218
         assert_xml_equal(
15f218
@@ -611,6 +952,7 @@ class UpdateRecipientTest(TestCase):
15f218
             """,
15f218
             etree.tostring(self.tree).decode()
15f218
         )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
 
15f218
     def test_update_description(self):
15f218
         assert_xml_equal(
15f218
@@ -620,7 +962,8 @@ class UpdateRecipientTest(TestCase):
15f218
             />
15f218
             """,
15f218
             etree.tostring(alert.update_recipient(
15f218
-                self.tree, "alert", "value1", "description"
15f218
+                self.mock_reporter, self.tree, "alert-recipient-1",
15f218
+                description="description"
15f218
             )).decode()
15f218
         )
15f218
         assert_xml_equal(
15f218
@@ -642,6 +985,7 @@ class UpdateRecipientTest(TestCase):
15f218
             """,
15f218
             etree.tostring(self.tree).decode()
15f218
         )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
 
15f218
     def test_remove_description(self):
15f218
         assert_xml_equal(
15f218
@@ -649,7 +993,10 @@ class UpdateRecipientTest(TestCase):
15f218
                 <recipient id="alert-recipient-1" value="value1"/>
15f218
             """,
15f218
             etree.tostring(
15f218
-               alert.update_recipient(self.tree, "alert", "value1", "")
15f218
+               alert.update_recipient(
15f218
+                   self.mock_reporter, self.tree, "alert-recipient-1",
15f218
+                   description=""
15f218
+               )
15f218
             ).decode()
15f218
         )
15f218
         assert_xml_equal(
15f218
@@ -667,26 +1014,18 @@ class UpdateRecipientTest(TestCase):
15f218
             """,
15f218
             etree.tostring(self.tree).decode()
15f218
         )
15f218
-
15f218
-    def test_alert_not_exists(self):
15f218
-        assert_raise_library_error(
15f218
-            lambda: alert.update_recipient(self.tree, "alert1", "test_val", ""),
15f218
-            (
15f218
-                severities.ERROR,
15f218
-                report_codes.CIB_ALERT_NOT_FOUND,
15f218
-                {"alert": "alert1"}
15f218
-            )
15f218
-        )
15f218
+        self.assertEqual([], self.mock_reporter.report_item_list)
15f218
 
15f218
     def test_recipient_not_exists(self):
15f218
         assert_raise_library_error(
15f218
-            lambda: alert.update_recipient(self.tree, "alert", "unknown", ""),
15f218
+            lambda: alert.update_recipient(
15f218
+                self.mock_reporter, self.tree, "recipient"),
15f218
             (
15f218
                 severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
+                report_codes.ID_NOT_FOUND,
15f218
                 {
15f218
-                    "alert": "alert",
15f218
-                    "recipient": "unknown"
15f218
+                    "id": "recipient",
15f218
+                    "id_description": "Recipient"
15f218
                 }
15f218
             )
15f218
         )
15f218
@@ -710,7 +1049,7 @@ class RemoveRecipientTest(TestCase):
15f218
         )
15f218
 
15f218
     def test_success(self):
15f218
-        alert.remove_recipient(self.tree, "alert", "val")
15f218
+        alert.remove_recipient(self.tree, "alert-recipient-2")
15f218
         assert_xml_equal(
15f218
             """
15f218
             <cib>
15f218
@@ -726,25 +1065,15 @@ class RemoveRecipientTest(TestCase):
15f218
             etree.tostring(self.tree).decode()
15f218
         )
15f218
 
15f218
-    def test_alert_not_exists(self):
15f218
-        assert_raise_library_error(
15f218
-            lambda: alert.remove_recipient(self.tree, "alert1", "test_val"),
15f218
-            (
15f218
-                severities.ERROR,
15f218
-                report_codes.CIB_ALERT_NOT_FOUND,
15f218
-                {"alert": "alert1"}
15f218
-            )
15f218
-        )
15f218
-
15f218
     def test_recipient_not_exists(self):
15f218
         assert_raise_library_error(
15f218
-            lambda: alert.remove_recipient(self.tree, "alert", "unknown"),
15f218
+            lambda: alert.remove_recipient(self.tree, "recipient"),
15f218
             (
15f218
                 severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
+                report_codes.ID_NOT_FOUND,
15f218
                 {
15f218
-                    "alert": "alert",
15f218
-                    "recipient": "unknown"
15f218
+                    "id": "recipient",
15f218
+                    "id_description": "Recipient"
15f218
                 }
15f218
             )
15f218
         )
15f218
diff --git a/pcs/lib/commands/alert.py b/pcs/lib/commands/alert.py
15f218
index 7371fbc..432d9d5 100644
15f218
--- a/pcs/lib/commands/alert.py
15f218
+++ b/pcs/lib/commands/alert.py
15f218
@@ -90,7 +90,9 @@ def add_recipient(
15f218
     recipient_value,
15f218
     instance_attribute_dict,
15f218
     meta_attribute_dict,
15f218
-    description=None
15f218
+    recipient_id=None,
15f218
+    description=None,
15f218
+    allow_same_value=False
15f218
 ):
15f218
     """
15f218
     Add new recipient to alert witch id alert_id.
15f218
@@ -100,7 +102,9 @@ def add_recipient(
15f218
     recipient_value -- value of new recipient
15f218
     instance_attribute_dict -- dictionary of instance attributes to update
15f218
     meta_attribute_dict -- dictionary of meta attributes to update
15f218
+    recipient_id -- id of new recipient, if None it will be generated
15f218
     description -- recipient description
15f218
+    allow_same_value -- if True unique recipient value is not required
15f218
     """
15f218
     if not recipient_value:
15f218
         raise LibraryError(
15f218
@@ -109,7 +113,13 @@ def add_recipient(
15f218
 
15f218
     cib = lib_env.get_cib(REQUIRED_CIB_VERSION)
15f218
     recipient = alert.add_recipient(
15f218
-        cib, alert_id, recipient_value, description
15f218
+        lib_env.report_processor,
15f218
+        cib,
15f218
+        alert_id,
15f218
+        recipient_value,
15f218
+        recipient_id=recipient_id,
15f218
+        description=description,
15f218
+        allow_same_value=allow_same_value
15f218
     )
15f218
     alert.update_instance_attributes(cib, recipient, instance_attribute_dict)
15f218
     alert.update_meta_attributes(cib, recipient, meta_attribute_dict)
15f218
@@ -119,26 +129,38 @@ def add_recipient(
15f218
 
15f218
 def update_recipient(
15f218
     lib_env,
15f218
-    alert_id,
15f218
-    recipient_value,
15f218
+    recipient_id,
15f218
     instance_attribute_dict,
15f218
     meta_attribute_dict,
15f218
-    description=None
15f218
+    recipient_value=None,
15f218
+    description=None,
15f218
+    allow_same_value=False
15f218
 ):
15f218
     """
15f218
     Update existing recipient.
15f218
 
15f218
     lib_env -- LibraryEnvironment
15f218
-    alert_id -- id of alert to which recipient belong
15f218
-    recipient_value -- recipient to be updated
15f218
+    recipient_id -- id of recipient to be updated
15f218
     instance_attribute_dict -- dictionary of instance attributes to update
15f218
     meta_attribute_dict -- dictionary of meta attributes to update
15f218
+    recipient_value -- new recipient value, if None old value will stay
15f218
+        unchanged
15f218
     description -- new description, if empty string, old description will be
15f218
         deleted, if None old value will stay unchanged
15f218
+    allow_same_value -- if True unique recipient value is not required
15f218
     """
15f218
+    if not recipient_value and recipient_value is not None:
15f218
+        raise LibraryError(
15f218
+            reports.cib_alert_recipient_invalid_value(recipient_value)
15f218
+        )
15f218
     cib = lib_env.get_cib(REQUIRED_CIB_VERSION)
15f218
     recipient = alert.update_recipient(
15f218
-        cib, alert_id, recipient_value, description
15f218
+        lib_env.report_processor,
15f218
+        cib,
15f218
+        recipient_id,
15f218
+        recipient_value=recipient_value,
15f218
+        description=description,
15f218
+        allow_same_value=allow_same_value
15f218
     )
15f218
     alert.update_instance_attributes(cib, recipient, instance_attribute_dict)
15f218
     alert.update_meta_attributes(cib, recipient, meta_attribute_dict)
15f218
@@ -146,16 +168,15 @@ def update_recipient(
15f218
     lib_env.push_cib(cib)
15f218
 
15f218
 
15f218
-def remove_recipient(lib_env, alert_id, recipient_value):
15f218
+def remove_recipient(lib_env, recipient_id):
15f218
     """
15f218
     Remove existing recipient.
15f218
 
15f218
     lib_env -- LibraryEnvironment
15f218
-    alert_id -- id of alert to which recipient belong
15f218
-    recipient_value -- recipient to be removed
15f218
+    recipient_id -- if of recipient to be removed
15f218
     """
15f218
     cib = lib_env.get_cib(REQUIRED_CIB_VERSION)
15f218
-    alert.remove_recipient(cib, alert_id, recipient_value)
15f218
+    alert.remove_recipient(cib, recipient_id)
15f218
     lib_env.push_cib(cib)
15f218
 
15f218
 
15f218
diff --git a/pcs/lib/commands/test/test_alert.py b/pcs/lib/commands/test/test_alert.py
15f218
index 34813df..bced45e 100644
15f218
--- a/pcs/lib/commands/test/test_alert.py
15f218
+++ b/pcs/lib/commands/test/test_alert.py
15f218
@@ -361,19 +361,17 @@ class AddRecipientTest(TestCase):
15f218
     def test_recipient_already_exists(self):
15f218
         assert_raise_library_error(
15f218
             lambda: cmd_alert.add_recipient(
15f218
-                self.mock_env, "alert", "value1", {}, {}
15f218
+                self.mock_env, "alert", "value1", {}, {},
15f218
+                recipient_id="alert-recipient"
15f218
             ),
15f218
             (
15f218
                 Severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
-                {
15f218
-                    "recipient": "value1",
15f218
-                    "alert": "alert"
15f218
-                }
15f218
+                report_codes.ID_ALREADY_EXISTS,
15f218
+                {"id": "alert-recipient"}
15f218
             )
15f218
         )
15f218
 
15f218
-    def test_success(self):
15f218
+    def test_without_id(self):
15f218
         cmd_alert.add_recipient(
15f218
             self.mock_env,
15f218
             "alert",
15f218
@@ -424,6 +422,58 @@ class AddRecipientTest(TestCase):
15f218
             self.mock_env._get_cib_xml()
15f218
         )
15f218
 
15f218
+    def test_with_id(self):
15f218
+        cmd_alert.add_recipient(
15f218
+            self.mock_env,
15f218
+            "alert",
15f218
+            "value",
15f218
+            {"attr1": "val1"},
15f218
+            {
15f218
+                "attr2": "val2",
15f218
+                "attr1": "val1"
15f218
+            },
15f218
+            recipient_id="my-recipient"
15f218
+        )
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+<cib validate-with="pacemaker-2.5">
15f218
+    <configuration>
15f218
+        <alerts>
15f218
+            <alert id="alert" path="path">
15f218
+                <recipient id="alert-recipient" value="value1"/>
15f218
+                <recipient id="my-recipient" value="value">
15f218
+                    
15f218
+                        id="my-recipient-meta_attributes"
15f218
+                    >
15f218
+                        
15f218
+                            id="my-recipient-meta_attributes-attr1"
15f218
+                            name="attr1"
15f218
+                            value="val1"
15f218
+                        />
15f218
+                        
15f218
+                            id="my-recipient-meta_attributes-attr2"
15f218
+                            name="attr2"
15f218
+                            value="val2"
15f218
+                        />
15f218
+                    </meta_attributes>
15f218
+                    
15f218
+                        id="my-recipient-instance_attributes"
15f218
+                    >
15f218
+                        
15f218
+                            id="my-recipient-instance_attributes-attr1"
15f218
+                            name="attr1"
15f218
+                            value="val1"
15f218
+                        />
15f218
+                    </instance_attributes>
15f218
+                </recipient>
15f218
+            </alert>
15f218
+        </alerts>
15f218
+    </configuration>
15f218
+</cib>
15f218
+            """,
15f218
+            self.mock_env._get_cib_xml()
15f218
+        )
15f218
+
15f218
 
15f218
 class UpdateRecipientTest(TestCase):
15f218
     def setUp(self):
15f218
@@ -470,29 +520,29 @@ class UpdateRecipientTest(TestCase):
15f218
             self.mock_log, self.mock_rep, cib_data=cib
15f218
         )
15f218
 
15f218
-    def test_alert_not_found(self):
15f218
+    def test_empty_value(self):
15f218
         assert_raise_library_error(
15f218
             lambda: cmd_alert.update_recipient(
15f218
-                self.mock_env, "unknown", "recipient", {}, {}
15f218
+                self.mock_env, "alert-recipient-1", {}, {}, recipient_value=""
15f218
             ),
15f218
             (
15f218
                 Severities.ERROR,
15f218
-                report_codes.CIB_ALERT_NOT_FOUND,
15f218
-                {"alert": "unknown"}
15f218
+                report_codes.CIB_ALERT_RECIPIENT_VALUE_INVALID,
15f218
+                {"recipient": ""}
15f218
             )
15f218
         )
15f218
 
15f218
     def test_recipient_not_found(self):
15f218
         assert_raise_library_error(
15f218
             lambda: cmd_alert.update_recipient(
15f218
-                self.mock_env, "alert", "recipient", {}, {}
15f218
+                self.mock_env, "recipient", {}, {}
15f218
             ),
15f218
             (
15f218
                 Severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
+                report_codes.ID_NOT_FOUND,
15f218
                 {
15f218
-                    "recipient": "recipient",
15f218
-                    "alert": "alert"
15f218
+                    "id": "recipient",
15f218
+                    "id_description": "Recipient"
15f218
                 }
15f218
             )
15f218
         )
15f218
@@ -500,14 +550,14 @@ class UpdateRecipientTest(TestCase):
15f218
     def test_update_all(self):
15f218
         cmd_alert.update_recipient(
15f218
             self.mock_env,
15f218
-            "alert",
15f218
-            "value",
15f218
+            "alert-recipient-1",
15f218
             {"attr1": "value"},
15f218
             {
15f218
                 "attr1": "",
15f218
                 "attr3": "new_val"
15f218
             },
15f218
-            "desc"
15f218
+            recipient_value="new_val",
15f218
+            description="desc"
15f218
         )
15f218
         assert_xml_equal(
15f218
             """
15f218
@@ -518,7 +568,7 @@ class UpdateRecipientTest(TestCase):
15f218
                 <recipient id="alert-recipient" value="value1"/>
15f218
                 
15f218
                     id="alert-recipient-1"
15f218
-                    value="value"
15f218
+                    value="new_val"
15f218
                     description="desc"
15f218
                 >
15f218
                     
15f218
@@ -575,35 +625,20 @@ class RemoveRecipientTest(TestCase):
15f218
             self.mock_log, self.mock_rep, cib_data=cib
15f218
         )
15f218
 
15f218
-    def test_alert_not_found(self):
15f218
-        assert_raise_library_error(
15f218
-            lambda: cmd_alert.remove_recipient(
15f218
-                self.mock_env, "unknown", "recipient"
15f218
-            ),
15f218
-            (
15f218
-                Severities.ERROR,
15f218
-                report_codes.CIB_ALERT_NOT_FOUND,
15f218
-                {"alert": "unknown"}
15f218
-            )
15f218
-        )
15f218
-
15f218
     def test_recipient_not_found(self):
15f218
         assert_raise_library_error(
15f218
             lambda: cmd_alert.remove_recipient(
15f218
-                self.mock_env, "alert", "recipient"
15f218
+                self.mock_env, "recipient"
15f218
             ),
15f218
             (
15f218
                 Severities.ERROR,
15f218
-                report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
-                {
15f218
-                    "recipient": "recipient",
15f218
-                    "alert": "alert"
15f218
-                }
15f218
+                report_codes.ID_NOT_FOUND,
15f218
+                {"id": "recipient"}
15f218
             )
15f218
         )
15f218
 
15f218
     def test_success(self):
15f218
-        cmd_alert.remove_recipient(self.mock_env, "alert", "value1")
15f218
+        cmd_alert.remove_recipient(self.mock_env, "alert-recipient")
15f218
         assert_xml_equal(
15f218
             """
15f218
             <cib validate-with="pacemaker-2.5">
15f218
diff --git a/pcs/lib/reports.py b/pcs/lib/reports.py
15f218
index 9ececf9..fc2670b 100644
15f218
--- a/pcs/lib/reports.py
15f218
+++ b/pcs/lib/reports.py
15f218
@@ -1654,40 +1654,39 @@ def cluster_restart_required_to_apply_changes():
15f218
     )
15f218
 
15f218
 
15f218
-def cib_alert_recipient_already_exists(alert_id, recipient_value):
15f218
+def cib_alert_recipient_already_exists(
15f218
+    alert_id, recipient_value, severity=ReportItemSeverity.ERROR, forceable=None
15f218
+):
15f218
     """
15f218
-    Error that recipient already exists.
15f218
+    Recipient with specified value already exists in alert with id 'alert_id'
15f218
 
15f218
     alert_id -- id of alert to which recipient belongs
15f218
     recipient_value -- value of recipient
15f218
     """
15f218
-    return ReportItem.error(
15f218
+    return ReportItem(
15f218
         report_codes.CIB_ALERT_RECIPIENT_ALREADY_EXISTS,
15f218
-        "Recipient '{recipient}' in alert '{alert}' already exists.",
15f218
+        severity,
15f218
+        "Recipient '{recipient}' in alert '{alert}' already exists",
15f218
         info={
15f218
             "recipient": recipient_value,
15f218
             "alert": alert_id
15f218
-        }
15f218
+        },
15f218
+        forceable=forceable
15f218
     )
15f218
 
15f218
 
15f218
-def cib_alert_recipient_not_found(alert_id, recipient_value):
15f218
+def cib_alert_recipient_invalid_value(recipient_value):
15f218
     """
15f218
-    Specified recipient not found.
15f218
+    Invalid recipient value.
15f218
 
15f218
-    alert_id -- id of alert to which recipient should belong
15f218
     recipient_value -- recipient value
15f218
     """
15f218
     return ReportItem.error(
15f218
-        report_codes.CIB_ALERT_RECIPIENT_NOT_FOUND,
15f218
-        "Recipient '{recipient}' not found in alert '{alert}'.",
15f218
-        info={
15f218
-            "recipient": recipient_value,
15f218
-            "alert": alert_id
15f218
-        }
15f218
+        report_codes.CIB_ALERT_RECIPIENT_VALUE_INVALID,
15f218
+        "Recipient value '{recipient}' is not valid.",
15f218
+        info={"recipient": recipient_value}
15f218
     )
15f218
 
15f218
-
15f218
 def cib_alert_not_found(alert_id):
15f218
     """
15f218
     Alert with specified id doesn't exist.
15f218
-- 
15f218
1.8.3.1
15f218
15f218
15f218
From b8155a2bfa79bb71429953eb756e393c18926e4c Mon Sep 17 00:00:00 2001
15f218
From: Ondrej Mular <omular@redhat.com>
15f218
Date: Sat, 9 Jul 2016 13:54:00 +0200
15f218
Subject: [PATCH 2/2] cli: use recipient id as identifier instead of its value
15f218
15f218
---
15f218
 pcs/alert.py           |  32 +++++-----
15f218
 pcs/pcs.8              |   8 +--
15f218
 pcs/test/test_alert.py | 165 ++++++++++++++++++++++++++++++++++++++++---------
15f218
 pcs/usage.py           |  14 +++--
15f218
 4 files changed, 164 insertions(+), 55 deletions(-)
15f218
15f218
diff --git a/pcs/alert.py b/pcs/alert.py
15f218
index d3a6e28..4786f57 100644
15f218
--- a/pcs/alert.py
15f218
+++ b/pcs/alert.py
15f218
@@ -139,42 +139,44 @@ def recipient_add(lib, argv, modifiers):
15f218
 
15f218
     sections = parse_cmd_sections(argv[2:], ["options", "meta"])
15f218
     main_args = prepare_options(sections["main"])
15f218
-    ensure_only_allowed_options(main_args, ["description"])
15f218
+    ensure_only_allowed_options(main_args, ["description", "id"])
15f218
 
15f218
     lib.alert.add_recipient(
15f218
         alert_id,
15f218
         recipient_value,
15f218
         prepare_options(sections["options"]),
15f218
         prepare_options(sections["meta"]),
15f218
-        main_args.get("description", None)
15f218
+        recipient_id=main_args.get("id", None),
15f218
+        description=main_args.get("description", None),
15f218
+        allow_same_value=modifiers["force"]
15f218
     )
15f218
 
15f218
 
15f218
 def recipient_update(lib, argv, modifiers):
15f218
-    if len(argv) < 2:
15f218
+    if len(argv) < 1:
15f218
         raise CmdLineInputError()
15f218
 
15f218
-    alert_id = argv[0]
15f218
-    recipient_value = argv[1]
15f218
+    recipient_id = argv[0]
15f218
 
15f218
-    sections = parse_cmd_sections(argv[2:], ["options", "meta"])
15f218
+    sections = parse_cmd_sections(argv[1:], ["options", "meta"])
15f218
     main_args = prepare_options(sections["main"])
15f218
-    ensure_only_allowed_options(main_args, ["description"])
15f218
+    ensure_only_allowed_options(main_args, ["description", "value"])
15f218
 
15f218
     lib.alert.update_recipient(
15f218
-        alert_id,
15f218
-        recipient_value,
15f218
+        recipient_id,
15f218
         prepare_options(sections["options"]),
15f218
         prepare_options(sections["meta"]),
15f218
-        main_args.get("description", None)
15f218
+        recipient_value=main_args.get("value", None),
15f218
+        description=main_args.get("description", None),
15f218
+        allow_same_value=modifiers["force"]
15f218
     )
15f218
 
15f218
 
15f218
 def recipient_remove(lib, argv, modifiers):
15f218
-    if len(argv) != 2:
15f218
+    if len(argv) != 1:
15f218
         raise CmdLineInputError()
15f218
 
15f218
-    lib.alert.remove_recipient(argv[0], argv[1])
15f218
+    lib.alert.remove_recipient(argv[0])
15f218
 
15f218
 
15f218
 def _nvset_to_str(nvset_obj):
15f218
@@ -219,9 +221,9 @@ def _alert_to_str(alert):
15f218
 
15f218
 
15f218
 def _recipient_to_str(recipient):
15f218
-    return ["Recipient: {value}".format(value=recipient["value"])] + indent(
15f218
-        __description_attributes_to_str(recipient), 1
15f218
-    )
15f218
+    return ["Recipient: {id} (value={value})".format(
15f218
+        value=recipient["value"], id=recipient["id"]
15f218
+    )] + indent(__description_attributes_to_str(recipient), 1)
15f218
 
15f218
 
15f218
 def print_alert_config(lib, argv, modifiers):
15f218
diff --git a/pcs/pcs.8 b/pcs/pcs.8
15f218
index 4426444..223ef1b 100644
15f218
--- a/pcs/pcs.8
15f218
+++ b/pcs/pcs.8
15f218
@@ -666,13 +666,13 @@ Update existing alert with specified id.
15f218
 remove <alert\-id>
15f218
 Remove alert with specified id.
15f218
 .TP
15f218
-recipient add <alert\-id> <recipient\-value> [description=<description>] [options [<option>=<value>]...] [meta [<meta\-option>=<value>]...]
15f218
+recipient add <alert\-id> <recipient\-value> [id=<recipient\-id>] [description=<description>] [options [<option>=<value>]...] [meta [<meta-option>=<value>]...]
15f218
 Add new recipient to specified alert.
15f218
 .TP
15f218
-recipient update <alert\-id> <recipient\-value> [description=<description>] [options [<option>=<value>]...] [meta [<meta\-option>=<value>]...]
15f218
-Update existing recipient identified by alert and it's value.
15f218
+recipient update <recipient\-id> [value=<recipient\-value>] [description=<description>] [options [<option>=<value>]...] [meta [<meta-option>=<value>]...]
15f218
+Update existing recipient identified by it's id.
15f218
 .TP
15f218
-recipient remove <alert\-id> <recipient\-value>
15f218
+recipient remove <recipient\-id>
15f218
 Remove specified recipient.
15f218
 .SH EXAMPLES
15f218
 .TP
15f218
diff --git a/pcs/test/test_alert.py b/pcs/test/test_alert.py
15f218
index 905dc9f..bb61600 100644
15f218
--- a/pcs/test/test_alert.py
15f218
+++ b/pcs/test/test_alert.py
15f218
@@ -246,12 +246,12 @@ Alerts:
15f218
 Alerts:
15f218
  Alert: alert (path=test)
15f218
   Recipients:
15f218
-   Recipient: rec_value
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
 """
15f218
         )
15f218
         self.assert_pcs_success(
15f218
-            "alert recipient add alert rec_value2 description=description "
15f218
-            "options o1=1 o2=2 meta m1=v1 m2=v2"
15f218
+            "alert recipient add alert rec_value2 id=my-recipient "
15f218
+            "description=description options o1=1 o2=2 meta m1=v1 m2=v2"
15f218
         )
15f218
         self.assert_pcs_success(
15f218
             "alert config",
15f218
@@ -259,26 +259,56 @@ Alerts:
15f218
 Alerts:
15f218
  Alert: alert (path=test)
15f218
   Recipients:
15f218
-   Recipient: rec_value
15f218
-   Recipient: rec_value2
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
+   Recipient: my-recipient (value=rec_value2)
15f218
     Description: description
15f218
     Options: o1=1 o2=2
15f218
     Meta options: m1=v1 m2=v2
15f218
 """
15f218
         )
15f218
 
15f218
-    def test_no_alert(self):
15f218
+    def test_already_exists(self):
15f218
+        self.assert_pcs_success("alert create path=test")
15f218
+        self.assert_pcs_success("alert recipient add alert rec_value id=rec")
15f218
         self.assert_pcs_fail(
15f218
-            "alert recipient add alert rec_value",
15f218
-            "Error: Alert 'alert' not found.\n"
15f218
+            "alert recipient add alert value id=rec",
15f218
+            "Error: 'rec' already exists\n"
15f218
+        )
15f218
+        self.assert_pcs_fail(
15f218
+            "alert recipient add alert value id=alert",
15f218
+            "Error: 'alert' already exists\n"
15f218
         )
15f218
 
15f218
-    def test_already_exists(self):
15f218
+    def test_same_value(self):
15f218
         self.assert_pcs_success("alert create path=test")
15f218
-        self.assert_pcs_success("alert recipient add alert rec_value")
15f218
+        self.assert_pcs_success("alert recipient add alert rec_value id=rec")
15f218
         self.assert_pcs_fail(
15f218
             "alert recipient add alert rec_value",
15f218
-            "Error: Recipient 'rec_value' in alert 'alert' already exists.\n"
15f218
+            "Error: Recipient 'rec_value' in alert 'alert' already exists, "
15f218
+            "use --force to override\n"
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: rec (value=rec_value)
15f218
+"""
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert recipient add alert rec_value --force",
15f218
+            "Warning: Recipient 'rec_value' in alert 'alert' already exists\n"
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: rec (value=rec_value)
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
+"""
15f218
         )
15f218
 
15f218
 
15f218
@@ -296,14 +326,14 @@ class UpdateRecipientAlert(PcsAlertTest):
15f218
 Alerts:
15f218
  Alert: alert (path=test)
15f218
   Recipients:
15f218
-   Recipient: rec_value
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
     Description: description
15f218
     Options: o1=1 o2=2
15f218
     Meta options: m1=v1 m2=v2
15f218
 """
15f218
         )
15f218
         self.assert_pcs_success(
15f218
-            "alert recipient update alert rec_value description=desc "
15f218
+            "alert recipient update alert-recipient value=new description=desc "
15f218
             "options o1= o2=v2 o3=3 meta m1= m2=2 m3=3"
15f218
         )
15f218
         self.assert_pcs_success(
15f218
@@ -312,24 +342,99 @@ Alerts:
15f218
 Alerts:
15f218
  Alert: alert (path=test)
15f218
   Recipients:
15f218
-   Recipient: rec_value
15f218
+   Recipient: alert-recipient (value=new)
15f218
+    Description: desc
15f218
+    Options: o2=v2 o3=3
15f218
+    Meta options: m2=2 m3=3
15f218
+"""
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert recipient update alert-recipient value=new"
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: alert-recipient (value=new)
15f218
     Description: desc
15f218
     Options: o2=v2 o3=3
15f218
     Meta options: m2=2 m3=3
15f218
 """
15f218
         )
15f218
 
15f218
-    def test_no_alert(self):
15f218
+    def test_value_exists(self):
15f218
+        self.assert_pcs_success("alert create path=test")
15f218
+        self.assert_pcs_success("alert recipient add alert rec_value")
15f218
+        self.assert_pcs_success("alert recipient add alert value")
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
+   Recipient: alert-recipient-1 (value=value)
15f218
+"""
15f218
+        )
15f218
         self.assert_pcs_fail(
15f218
-            "alert recipient update alert rec_value description=desc",
15f218
-            "Error: Alert 'alert' not found.\n"
15f218
+            "alert recipient update alert-recipient value=value",
15f218
+            "Error: Recipient 'value' in alert 'alert' already exists, "
15f218
+            "use --force to override\n"
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert recipient update alert-recipient value=value --force",
15f218
+            "Warning: Recipient 'value' in alert 'alert' already exists\n"
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: alert-recipient (value=value)
15f218
+   Recipient: alert-recipient-1 (value=value)
15f218
+"""
15f218
+        )
15f218
+
15f218
+    def test_value_same_as_previous(self):
15f218
+        self.assert_pcs_success("alert create path=test")
15f218
+        self.assert_pcs_success("alert recipient add alert rec_value")
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
+"""
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert recipient update alert-recipient value=rec_value"
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+  Recipients:
15f218
+   Recipient: alert-recipient (value=rec_value)
15f218
+"""
15f218
         )
15f218
 
15f218
     def test_no_recipient(self):
15f218
+        self.assert_pcs_fail(
15f218
+            "alert recipient update rec description=desc",
15f218
+            "Error: Recipient 'rec' does not exist\n"
15f218
+        )
15f218
+
15f218
+    def test_empty_value(self):
15f218
         self.assert_pcs_success("alert create path=test")
15f218
+        self.assert_pcs_success("alert recipient add alert rec_value id=rec")
15f218
         self.assert_pcs_fail(
15f218
-            "alert recipient update alert rec_value description=desc",
15f218
-            "Error: Recipient 'rec_value' not found in alert 'alert'.\n"
15f218
+            "alert recipient update rec value=",
15f218
+            "Error: Recipient value '' is not valid.\n"
15f218
         )
15f218
 
15f218
 
15f218
@@ -337,27 +442,27 @@ Alerts:
15f218
 class RemoveRecipientTest(PcsAlertTest):
15f218
     def test_success(self):
15f218
         self.assert_pcs_success("alert create path=test")
15f218
-        self.assert_pcs_success("alert recipient add alert rec_value")
15f218
+        self.assert_pcs_success("alert recipient add alert rec_value id=rec")
15f218
         self.assert_pcs_success(
15f218
             "alert config",
15f218
             """\
15f218
 Alerts:
15f218
  Alert: alert (path=test)
15f218
   Recipients:
15f218
-   Recipient: rec_value
15f218
+   Recipient: rec (value=rec_value)
15f218
 """
15f218
         )
15f218
-        self.assert_pcs_success("alert recipient remove alert rec_value")
15f218
-
15f218
-    def test_no_alert(self):
15f218
-        self.assert_pcs_fail(
15f218
-            "alert recipient remove alert rec_value",
15f218
-            "Error: Alert 'alert' not found.\n"
15f218
+        self.assert_pcs_success("alert recipient remove rec")
15f218
+        self.assert_pcs_success(
15f218
+            "alert config",
15f218
+            """\
15f218
+Alerts:
15f218
+ Alert: alert (path=test)
15f218
+"""
15f218
         )
15f218
 
15f218
     def test_no_recipient(self):
15f218
-        self.assert_pcs_success("alert create path=test")
15f218
         self.assert_pcs_fail(
15f218
-            "alert recipient remove alert rec_value",
15f218
-            "Error: Recipient 'rec_value' not found in alert 'alert'.\n"
15f218
+            "alert recipient remove rec",
15f218
+            "Error: Recipient 'rec' does not exist\n"
15f218
         )
15f218
diff --git a/pcs/usage.py b/pcs/usage.py
15f218
index ee53a2f..77b496e 100644
15f218
--- a/pcs/usage.py
15f218
+++ b/pcs/usage.py
15f218
@@ -1402,15 +1402,17 @@ Commands:
15f218
     remove <alert-id>
15f218
         Remove alert with specified id.
15f218
 
15f218
-    recipient add <alert-id> <recipient-value> [description=<description>]
15f218
-            [options [<option>=<value>]...] [meta [<meta-option>=<value>]...]
15f218
+    recipient add <alert-id> <recipient-value> [id=<recipient-id>]
15f218
+            [description=<description>] [options [<option>=<value>]...]
15f218
+            [meta [<meta-option>=<value>]...]
15f218
         Add new recipient to specified alert.
15f218
 
15f218
-    recipient update <alert-id> <recipient-value> [description=<description>]
15f218
-            [options [<option>=<value>]...] [meta [<meta-option>=<value>]...]
15f218
-        Update existing recipient identified by alert and it's value.
15f218
+    recipient update <recipient-id> [value=<recipient-value>]
15f218
+            [description=<description>] [options [<option>=<value>]...]
15f218
+            [meta [<meta-option>=<value>]...]
15f218
+        Update existing recipient identified by it's id.
15f218
 
15f218
-    recipient remove <alert-id> <recipient-value>
15f218
+    recipient remove <recipient-id>
15f218
         Remove specified recipient.
15f218
 """
15f218
     if pout:
15f218
-- 
15f218
1.8.3.1
15f218