Blame SOURCES/bz1305049-01-pcs-does-not-support-ticket-constraints.patch

15f218
From be8876832da345e7e16827bd6c50e262380d6979 Mon Sep 17 00:00:00 2001
15f218
From: Ivan Devat <idevat@redhat.com>
15f218
Date: Wed, 14 Sep 2016 09:04:57 +0200
15f218
Subject: [PATCH] squash bz1305049 pcs does not support "ticket" con
15f218
15f218
d147ba4a51d0 do not use suffix "no-role" in ticket constraints
15f218
15f218
066cf217ec45 add constraint ticket remove command
15f218
---
15f218
 pcs/cli/common/lib_wrapper.py                  |  1 +
15f218
 pcs/cli/constraint_ticket/command.py           |  6 ++
15f218
 pcs/cli/constraint_ticket/test/test_command.py | 22 +++++++
15f218
 pcs/constraint.py                              |  1 +
15f218
 pcs/lib/cib/constraint/ticket.py               | 29 ++++++++-
15f218
 pcs/lib/cib/test/test_constraint_ticket.py     | 89 +++++++++++++++++++++++++-
15f218
 pcs/lib/commands/constraint/ticket.py          | 12 ++++
15f218
 pcs/pcs.8                                      |  3 +
15f218
 pcs/test/test_constraints.py                   | 36 +++++++++++
15f218
 pcs/usage.py                                   |  3 +
15f218
 10 files changed, 199 insertions(+), 3 deletions(-)
15f218
15f218
diff --git a/pcs/cli/common/lib_wrapper.py b/pcs/cli/common/lib_wrapper.py
15f218
index 94a1311..99bfe35 100644
15f218
--- a/pcs/cli/common/lib_wrapper.py
15f218
+++ b/pcs/cli/common/lib_wrapper.py
15f218
@@ -132,6 +132,7 @@ def load_module(env, middleware_factory, name):
15f218
                 'set': constraint_ticket.create_with_set,
15f218
                 'show': constraint_ticket.show,
15f218
                 'add': constraint_ticket.create,
15f218
+                'remove': constraint_ticket.remove,
15f218
             }
15f218
         )
15f218
 
15f218
diff --git a/pcs/cli/constraint_ticket/command.py b/pcs/cli/constraint_ticket/command.py
15f218
index ab70434..0ed4fdd 100644
15f218
--- a/pcs/cli/constraint_ticket/command.py
15f218
+++ b/pcs/cli/constraint_ticket/command.py
15f218
@@ -52,6 +52,12 @@ def add(lib, argv, modificators):
15f218
         duplication_alowed=modificators["force"],
15f218
     )
15f218
 
15f218
+def remove(lib, argv, modificators):
15f218
+    if len(argv) != 2:
15f218
+        raise CmdLineInputError()
15f218
+    ticket, resource_id = argv
15f218
+    lib.constraint_ticket.remove(ticket, resource_id)
15f218
+
15f218
 def show(lib, argv, modificators):
15f218
     """
15f218
     show all ticket constraints
15f218
diff --git a/pcs/cli/constraint_ticket/test/test_command.py b/pcs/cli/constraint_ticket/test/test_command.py
15f218
index d40d421..9ca7817 100644
15f218
--- a/pcs/cli/constraint_ticket/test/test_command.py
15f218
+++ b/pcs/cli/constraint_ticket/test/test_command.py
15f218
@@ -65,3 +65,25 @@ class AddTest(TestCase):
15f218
             resource_in_clone_alowed=True,
15f218
             duplication_alowed=True,
15f218
         )
15f218
+
15f218
+class RemoveTest(TestCase):
15f218
+    def test_refuse_args_count(self):
15f218
+        self.assertRaises(CmdLineInputError, lambda: command.remove(
15f218
+            mock.MagicMock(),
15f218
+            ["TICKET"],
15f218
+            {},
15f218
+        ))
15f218
+        self.assertRaises(CmdLineInputError, lambda: command.remove(
15f218
+            mock.MagicMock(),
15f218
+            ["TICKET", "RESOURCE", "SOMETHING_ELSE"],
15f218
+            {},
15f218
+        ))
15f218
+
15f218
+    def test_call_library_remove_with_correct_attrs(self):
15f218
+        lib = mock.MagicMock(
15f218
+            constraint_ticket=mock.MagicMock(remove=mock.Mock())
15f218
+        )
15f218
+        command.remove(lib, ["TICKET", "RESOURCE"], {})
15f218
+        lib.constraint_ticket.remove.assert_called_once_with(
15f218
+            "TICKET", "RESOURCE",
15f218
+        )
15f218
diff --git a/pcs/constraint.py b/pcs/constraint.py
15f218
index e32f1a3..d8415b6 100644
15f218
--- a/pcs/constraint.py
15f218
+++ b/pcs/constraint.py
15f218
@@ -90,6 +90,7 @@ def constraint_cmd(argv):
15f218
             command_map = {
15f218
                 "set": ticket_command.create_with_set,
15f218
                 "add": ticket_command.add,
15f218
+                "remove": ticket_command.remove,
15f218
                 "show": ticket_command.show,
15f218
             }
15f218
             sub_command = argv[0] if argv else "show"
15f218
diff --git a/pcs/lib/cib/constraint/ticket.py b/pcs/lib/cib/constraint/ticket.py
15f218
index 4154aac..c708794 100644
15f218
--- a/pcs/lib/cib/constraint/ticket.py
15f218
+++ b/pcs/lib/cib/constraint/ticket.py
15f218
@@ -39,7 +39,8 @@ def _validate_options_common(options):
15f218
 def _create_id(cib, ticket, resource_id, resource_role):
15f218
     return tools.find_unique_id(
15f218
         cib,
15f218
-        "-".join(('ticket', ticket, resource_id, resource_role))
15f218
+        "-".join(('ticket', ticket, resource_id))
15f218
+        +("-{0}".format(resource_role) if resource_role else "")
15f218
     )
15f218
 
15f218
 def prepare_options_with_set(cib, options, resource_set_list):
15f218
@@ -93,7 +94,7 @@ def prepare_options_plain(cib, options, ticket, resource_id):
15f218
             cib,
15f218
             options["ticket"],
15f218
             resource_id,
15f218
-            options["rsc-role"] if "rsc-role" in options else "no-role"
15f218
+            options.get("rsc-role", "")
15f218
         ),
15f218
         partial(tools.check_new_id_applicable, cib, DESCRIPTION)
15f218
     )
15f218
@@ -103,6 +104,30 @@ def create_plain(constraint_section, options):
15f218
     element.attrib.update(options)
15f218
     return element
15f218
 
15f218
+def remove_plain(constraint_section, ticket_key, resource_id):
15f218
+    ticket_element_list = constraint_section.xpath(
15f218
+        './/rsc_ticket[@ticket="{0}" and @rsc="{1}"]'
15f218
+        .format(ticket_key, resource_id)
15f218
+    )
15f218
+
15f218
+    for ticket_element in ticket_element_list:
15f218
+        ticket_element.getparent().remove(ticket_element)
15f218
+
15f218
+def remove_with_resource_set(constraint_section, ticket_key, resource_id):
15f218
+    ref_element_list = constraint_section.xpath(
15f218
+        './/rsc_ticket[@ticket="{0}"]/resource_set/resource_ref[@id="{1}"]'
15f218
+        .format(ticket_key, resource_id)
15f218
+    )
15f218
+
15f218
+    for ref_element in ref_element_list:
15f218
+        set_element = ref_element.getparent()
15f218
+        set_element.remove(ref_element)
15f218
+        if not len(set_element):
15f218
+            ticket_element = set_element.getparent()
15f218
+            ticket_element.remove(set_element)
15f218
+            if not len(ticket_element):
15f218
+                ticket_element.getparent().remove(ticket_element)
15f218
+
15f218
 def are_duplicate_plain(element, other_element):
15f218
     return all(
15f218
         element.attrib.get(name, "") == other_element.attrib.get(name, "")
15f218
diff --git a/pcs/lib/cib/test/test_constraint_ticket.py b/pcs/lib/cib/test/test_constraint_ticket.py
15f218
index ede748e..d3da004 100644
15f218
--- a/pcs/lib/cib/test/test_constraint_ticket.py
15f218
+++ b/pcs/lib/cib/test/test_constraint_ticket.py
15f218
@@ -8,10 +8,15 @@ from __future__ import (
15f218
 from functools import partial
15f218
 from pcs.test.tools.pcs_unittest import TestCase
15f218
 
15f218
+from lxml import etree
15f218
+
15f218
 from pcs.common import report_codes
15f218
 from pcs.lib.cib.constraint import ticket
15f218
 from pcs.lib.errors import ReportItemSeverity as severities
15f218
-from pcs.test.tools.assertions import assert_raise_library_error
15f218
+from pcs.test.tools.assertions import (
15f218
+    assert_raise_library_error,
15f218
+    assert_xml_equal,
15f218
+)
15f218
 from pcs.test.tools.pcs_unittest import mock
15f218
 
15f218
 
15f218
@@ -306,3 +311,85 @@ class AreDuplicateWithResourceSet(TestCase):
15f218
             Element({"ticket": "ticket_key"}),
15f218
             Element({"ticket": "X"}),
15f218
         ))
15f218
+
15f218
+class RemovePlainTest(TestCase):
15f218
+    def test_remove_tickets_constraints_for_resource(self):
15f218
+        constraint_section = etree.fromstring("""
15f218
+            <constraints>
15f218
+                <rsc_ticket id="t1" ticket="tA" rsc="rA"/>
15f218
+                <rsc_ticket id="t2" ticket="tA" rsc="rB"/>
15f218
+                <rsc_ticket id="t3" ticket="tA" rsc="rA"/>
15f218
+                <rsc_ticket id="t4" ticket="tB" rsc="rA"/>
15f218
+                <rsc_ticket id="t5" ticket="tB" rsc="rB"/>
15f218
+            </constraints>
15f218
+        """)
15f218
+
15f218
+        ticket.remove_plain(
15f218
+            constraint_section,
15f218
+            ticket_key="tA",
15f218
+            resource_id="rA",
15f218
+        )
15f218
+
15f218
+        assert_xml_equal(etree.tostring(constraint_section).decode(), """
15f218
+            <constraints>
15f218
+                <rsc_ticket id="t2" ticket="tA" rsc="rB"/>
15f218
+                <rsc_ticket id="t4" ticket="tB" rsc="rA"/>
15f218
+                <rsc_ticket id="t5" ticket="tB" rsc="rB"/>
15f218
+            </constraints>
15f218
+        """)
15f218
+
15f218
+class RemoveWithSetTest(TestCase):
15f218
+    def test_remove_resource_references_and_empty_remaining_parents(self):
15f218
+        constraint_section = etree.fromstring("""
15f218
+            <constraints>
15f218
+                <rsc_ticket id="t1" ticket="tA">
15f218
+                    <resource_set id="rs1">
15f218
+                        <resource_ref id="rA"/>
15f218
+                    </resource_set>
15f218
+                    <resource_set id="rs2">
15f218
+                        <resource_ref id="rA"/>
15f218
+                    </resource_set>
15f218
+                </rsc_ticket>
15f218
+
15f218
+                <rsc_ticket id="t2" ticket="tA">
15f218
+                    <resource_set id="rs3">
15f218
+                        <resource_ref id="rA"/>
15f218
+                        <resource_ref id="rB"/>
15f218
+                    </resource_set>
15f218
+                    <resource_set id="rs4">
15f218
+                        <resource_ref id="rA"/>
15f218
+                    </resource_set>
15f218
+                </rsc_ticket>
15f218
+
15f218
+                <rsc_ticket id="t3" ticket="tB">
15f218
+                    <resource_set id="rs5">
15f218
+                        <resource_ref id="rA"/>
15f218
+                    </resource_set>
15f218
+                </rsc_ticket>
15f218
+            </constraints>
15f218
+        """)
15f218
+
15f218
+        ticket.remove_with_resource_set(
15f218
+            constraint_section,
15f218
+            ticket_key="tA",
15f218
+            resource_id="rA"
15f218
+        )
15f218
+
15f218
+        assert_xml_equal(
15f218
+            """
15f218
+                <constraints>
15f218
+                    <rsc_ticket id="t2" ticket="tA">
15f218
+                        <resource_set id="rs3">
15f218
+                            <resource_ref id="rB"/>
15f218
+                        </resource_set>
15f218
+                    </rsc_ticket>
15f218
+
15f218
+                    <rsc_ticket id="t3" ticket="tB">
15f218
+                        <resource_set id="rs5">
15f218
+                            <resource_ref id="rA"/>
15f218
+                        </resource_set>
15f218
+                    </rsc_ticket>
15f218
+                </constraints>
15f218
+            """,
15f218
+            etree.tostring(constraint_section).decode()
15f218
+        )
15f218
diff --git a/pcs/lib/commands/constraint/ticket.py b/pcs/lib/commands/constraint/ticket.py
15f218
index e6960d5..2ea7afc 100644
15f218
--- a/pcs/lib/commands/constraint/ticket.py
15f218
+++ b/pcs/lib/commands/constraint/ticket.py
15f218
@@ -68,3 +68,15 @@ def create(
15f218
     )
15f218
 
15f218
     env.push_cib(cib)
15f218
+
15f218
+def remove(env, ticket_key, resource_id):
15f218
+    """
15f218
+    remove all ticket constraint from resource
15f218
+    If resource is in resource set with another resources then only resource ref
15f218
+    is removed. If resource is alone in resource set whole constraint is removed.
15f218
+    """
15f218
+    cib = env.get_cib()
15f218
+    constraint_section = get_constraints(cib)
15f218
+    ticket.remove_plain(constraint_section, ticket_key, resource_id)
15f218
+    ticket.remove_with_resource_set(constraint_section, ticket_key, resource_id)
15f218
+    env.push_cib(cib)
15f218
diff --git a/pcs/pcs.8 b/pcs/pcs.8
15f218
index 61abe67..40b146f 100644
15f218
--- a/pcs/pcs.8
15f218
+++ b/pcs/pcs.8
15f218
@@ -490,6 +490,9 @@ Create a ticket constraint for <resource id>. Available option is loss-policy=fe
15f218
 ticket set <resource1> [resourceN]... [options] [set <resourceX> ... [options]] [setoptions [constraint_options]]
15f218
 Create a ticket constraint with a resource set. Available options are sequential=true/false, require-all=true/false, action=start/promote/demote/stop and role=Stopped/Started/Master/Slave. Required constraint option is ticket=<ticket>. Optional constraint options are id=<constraint-id> and loss-policy=fence/stop/freeze/demote.
15f218
 .TP
15f218
+ticket remove <ticket> <resource id>
15f218
+Remove all ticket constraints with <ticket> from <resource id>.
15f218
+.TP
15f218
 remove [constraint id]...
15f218
 Remove constraint(s) or constraint rules with the specified id(s).
15f218
 .TP
15f218
diff --git a/pcs/test/test_constraints.py b/pcs/test/test_constraints.py
15f218
index 7c76e09..4007e90 100644
15f218
--- a/pcs/test/test_constraints.py
15f218
+++ b/pcs/test/test_constraints.py
15f218
@@ -2686,6 +2686,42 @@ class TicketAdd(ConstraintBaseTest):
15f218
             "  Master A loss-policy=fence ticket=T",
15f218
         ])
15f218
 
15f218
+class TicketRemoveTest(ConstraintBaseTest):
15f218
+    def test_remove_multiple_tickets(self):
15f218
+        #fixture
15f218
+        self.assert_pcs_success('constraint ticket add T A')
15f218
+        self.assert_pcs_success(
15f218
+            'constraint ticket add T A --force',
15f218
+            stdout_full=[
15f218
+                "Warning: duplicate constraint already exists",
15f218
+                "  A ticket=T (id:ticket-T-A)"
15f218
+            ]
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            'constraint ticket set A B setoptions ticket=T'
15f218
+        )
15f218
+        self.assert_pcs_success(
15f218
+            'constraint ticket set A setoptions ticket=T'
15f218
+        )
15f218
+        self.assert_pcs_success("constraint ticket show", stdout_full=[
15f218
+            "Ticket Constraints:",
15f218
+            "  A ticket=T",
15f218
+            "  A ticket=T",
15f218
+            "  Resource Sets:",
15f218
+            "    set A B setoptions ticket=T",
15f218
+            "    set A setoptions ticket=T",
15f218
+        ])
15f218
+
15f218
+        #test
15f218
+        self.assert_pcs_success("constraint ticket remove T A")
15f218
+
15f218
+        self.assert_pcs_success("constraint ticket show", stdout_full=[
15f218
+            "Ticket Constraints:",
15f218
+            "  Resource Sets:",
15f218
+            "    set B setoptions ticket=T",
15f218
+        ])
15f218
+
15f218
+
15f218
 class TicketShow(ConstraintBaseTest):
15f218
     def test_show_set(self):
15f218
         self.assert_pcs_success('constraint ticket set A B setoptions ticket=T')
15f218
diff --git a/pcs/usage.py b/pcs/usage.py
15f218
index 9d4617f..764e3fc 100644
15f218
--- a/pcs/usage.py
15f218
+++ b/pcs/usage.py
15f218
@@ -1011,6 +1011,9 @@ Commands:
15f218
         Required constraint option is ticket=<ticket>. Optional constraint
15f218
         options are id=<constraint-id> and loss-policy=fence/stop/freeze/demote.
15f218
 
15f218
+    ticket remove <ticket> <resource id>
15f218
+        Remove all ticket constraints with <ticket> from <resource id>.
15f218
+
15f218
     remove [constraint id]...
15f218
         Remove constraint(s) or constraint rules with the specified id(s).
15f218
 
15f218
-- 
15f218
1.8.3.1
15f218