|
|
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 |
|