import pcs-0.10.4-6.el8

This commit is contained in:
CentOS Sources 2020-04-28 05:32:49 -04:00 committed by Andrew Lukoshko
commit 400a5be185
15 changed files with 9721 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
SOURCES/HAM-logo.png
SOURCES/backports-3.11.4.gem
SOURCES/daemons-1.3.1.gem
SOURCES/ethon-0.11.0.gem
SOURCES/eventmachine-1.2.7.gem
SOURCES/ffi-1.9.25.gem
SOURCES/json-2.1.0.gem
SOURCES/mustermann-1.0.3.gem
SOURCES/open4-1.3.4-1.gem
SOURCES/pcs-0.10.4.tar.gz
SOURCES/pcs-web-ui-0.1.2.tar.gz
SOURCES/pcs-web-ui-node-modules-0.1.2.tar.xz
SOURCES/pyagentx-0.4.pcs.2.tar.gz
SOURCES/rack-2.0.6.gem
SOURCES/rack-protection-2.0.4.gem
SOURCES/rack-test-1.0.0.gem
SOURCES/sinatra-2.0.4.gem
SOURCES/thin-1.7.2.gem
SOURCES/tilt-2.0.9.gem
SOURCES/tornado-6.0.3.tar.gz

20
.pcs.metadata Normal file
View File

@ -0,0 +1,20 @@
679a4ce22a33ffd4d704261a17c00cff98d9499a SOURCES/HAM-logo.png
edf08f3a0d9e202048857d78ddda44e59294084c SOURCES/backports-3.11.4.gem
e28c1e78d1a6e34e80f4933b494f1e0501939dd3 SOURCES/daemons-1.3.1.gem
3c921ceeb2847be8cfa25704be74923e233786bd SOURCES/ethon-0.11.0.gem
7a5b2896e210fac9759c786ee4510f265f75b481 SOURCES/eventmachine-1.2.7.gem
86fa011857f977254ccf39f507587310f9ade768 SOURCES/ffi-1.9.25.gem
8b9e81a2a6ff57f97bec1f65940c61cc6b6d81be SOURCES/json-2.1.0.gem
2d090e7d3cd2a35efeaeacf006100fb83b828686 SOURCES/mustermann-1.0.3.gem
41a7fe9f8e3e02da5ae76c821b89c5b376a97746 SOURCES/open4-1.3.4-1.gem
d2b649f271580b18d39efffa93f62b55291ef55d SOURCES/pcs-0.10.4.tar.gz
8ac1291ce8f56073b74149ac56acc094337a3298 SOURCES/pcs-web-ui-0.1.2.tar.gz
52599fe9c17bda8cc0cad1acf830a9114b8b6db6 SOURCES/pcs-web-ui-node-modules-0.1.2.tar.xz
3176b2f2b332c2b6bf79fe882e83feecf3d3f011 SOURCES/pyagentx-0.4.pcs.2.tar.gz
b15267e1f94e69238a00a6f1bd48fb7683c03a78 SOURCES/rack-2.0.6.gem
c1376e5678322b401d988d261762a78bf2cf3361 SOURCES/rack-protection-2.0.4.gem
4c99cf0a82372a1bc5968c1551d9e606b68b4879 SOURCES/rack-test-1.0.0.gem
1c85f05c874bc8c0bf9c40291ea2d430090cdfd9 SOURCES/sinatra-2.0.4.gem
41395e86322ffd31f3a7aef1f697bda3e1e2d6b9 SOURCES/thin-1.7.2.gem
55a75a80e29731d072fe44dfaf865479b65c27fd SOURCES/tilt-2.0.9.gem
126c66189fc5b26a39c9b54eb17254652cca8b27 SOURCES/tornado-6.0.3.tar.gz

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,130 @@
From 8058591d0d79942bf6c61f105a180592bac7cf69 Mon Sep 17 00:00:00 2001
From: Ondrej Mular <omular@redhat.com>
Date: Thu, 28 Nov 2019 16:57:24 +0100
Subject: [PATCH 2/3] fix error msg when cluster is not set up
---
CHANGELOG.md | 4 +++
pcs/cluster.py | 3 +++
pcs/lib/commands/qdevice.py | 2 ++
pcs_test/tier0/lib/commands/test_qdevice.py | 27 +++++++++++++++++++--
4 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 889436c3..5a7ec377 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,11 @@
- It is possible to configure a disaster-recovery site and display its status
([rhbz#1676431])
+### Fixed
+- Error messages in cases when cluster is not set up ([rhbz#1743731])
+
[rhbz#1676431]: https://bugzilla.redhat.com/show_bug.cgi?id=1676431
+[rhbz#1743731]: https://bugzilla.redhat.com/show_bug.cgi?id=1743731
## [0.10.4] - 2019-11-28
diff --git a/pcs/cluster.py b/pcs/cluster.py
index 9473675f..0e9b3365 100644
--- a/pcs/cluster.py
+++ b/pcs/cluster.py
@@ -190,6 +190,9 @@ def start_cluster(argv):
wait_for_nodes_started(nodes, wait_timeout)
return
+ if not utils.hasCorosyncConf():
+ utils.err("cluster is not currently configured on this node")
+
print("Starting Cluster...")
service_list = ["corosync"]
if utils.need_to_handle_qdevice_service():
diff --git a/pcs/lib/commands/qdevice.py b/pcs/lib/commands/qdevice.py
index 3d7af234..41f7c296 100644
--- a/pcs/lib/commands/qdevice.py
+++ b/pcs/lib/commands/qdevice.py
@@ -81,6 +81,8 @@ def qdevice_start(lib_env, model):
start qdevice now on local host
"""
_check_model(model)
+ if not qdevice_net.qdevice_initialized():
+ raise LibraryError(reports.qdevice_not_initialized(model))
_service_start(lib_env, qdevice_net.qdevice_start)
def qdevice_stop(lib_env, model, proceed_if_used=False):
diff --git a/pcs_test/tier0/lib/commands/test_qdevice.py b/pcs_test/tier0/lib/commands/test_qdevice.py
index b2c83ca4..af23db61 100644
--- a/pcs_test/tier0/lib/commands/test_qdevice.py
+++ b/pcs_test/tier0/lib/commands/test_qdevice.py
@@ -689,6 +689,7 @@ class QdeviceNetDisableTest(QdeviceTestCase):
)
+@mock.patch("pcs.lib.corosync.qdevice_net.qdevice_initialized")
@mock.patch("pcs.lib.external.start_service")
@mock.patch.object(
LibraryEnvironment,
@@ -696,9 +697,11 @@ class QdeviceNetDisableTest(QdeviceTestCase):
lambda self: "mock_runner"
)
class QdeviceNetStartTest(QdeviceTestCase):
- def test_success(self, mock_net_start):
+ def test_success(self, mock_net_start, mock_qdevice_initialized):
+ mock_qdevice_initialized.return_value = True
lib.qdevice_start(self.lib_env, "net")
mock_net_start.assert_called_once_with("mock_runner", "corosync-qnetd")
+ mock_qdevice_initialized.assert_called_once_with()
assert_report_item_list_equal(
self.mock_reporter.report_item_list,
[
@@ -719,11 +722,12 @@ class QdeviceNetStartTest(QdeviceTestCase):
]
)
- def test_failed(self, mock_net_start):
+ def test_failed(self, mock_net_start, mock_qdevice_initialized):
mock_net_start.side_effect = StartServiceError(
"test service",
"test error"
)
+ mock_qdevice_initialized.return_value = True
assert_raise_library_error(
lambda: lib.qdevice_start(self.lib_env, "net"),
@@ -737,6 +741,7 @@ class QdeviceNetStartTest(QdeviceTestCase):
)
)
mock_net_start.assert_called_once_with("mock_runner", "corosync-qnetd")
+ mock_qdevice_initialized.assert_called_once_with()
assert_report_item_list_equal(
self.mock_reporter.report_item_list,
[
@@ -750,6 +755,24 @@ class QdeviceNetStartTest(QdeviceTestCase):
]
)
+ def test_qdevice_not_initialized(
+ self, mock_net_start, mock_qdevice_initialized
+ ):
+ mock_qdevice_initialized.return_value = False
+
+ assert_raise_library_error(
+ lambda: lib.qdevice_start(self.lib_env, "net"),
+ (
+ severity.ERROR,
+ report_codes.QDEVICE_NOT_INITIALIZED,
+ {
+ "model": "net",
+ }
+ )
+ )
+ mock_net_start.assert_not_called()
+ mock_qdevice_initialized.assert_called_once_with()
+
@mock.patch("pcs.lib.corosync.qdevice_net.qdevice_status_cluster_text")
@mock.patch("pcs.lib.external.stop_service")
--
2.21.0

View File

@ -0,0 +1,40 @@
From e4ab588efe0f4cc6b5fcf0853293c93bd4f31604 Mon Sep 17 00:00:00 2001
From: Ondrej Mular <omular@redhat.com>
Date: Wed, 29 Jan 2020 13:13:45 +0100
Subject: [PATCH 4/7] link to sbd man page from `sbd enable` doc
---
pcs/pcs.8 | 2 +-
pcs/usage.py | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/pcs/pcs.8 b/pcs/pcs.8
index 651fda83..ff2ba0b0 100644
--- a/pcs/pcs.8
+++ b/pcs/pcs.8
@@ -531,7 +531,7 @@ history update
Update fence history from all nodes.
.TP
sbd enable [watchdog=<path>[@<node>]]... [device=<path>[@<node>]]... [<SBD_OPTION>=<value>]... [\fB\-\-no\-watchdog\-validation\fR]
-Enable SBD in cluster. Default path for watchdog device is /dev/watchdog. Allowed SBD options: SBD_WATCHDOG_TIMEOUT (default: 5), SBD_DELAY_START (default: no), SBD_STARTMODE (default: always) and SBD_TIMEOUT_ACTION. It is possible to specify up to 3 devices per node. If \fB\-\-no\-watchdog\-validation\fR is specified, validation of watchdogs will be skipped.
+Enable SBD in cluster. Default path for watchdog device is /dev/watchdog. Allowed SBD options: SBD_WATCHDOG_TIMEOUT (default: 5), SBD_DELAY_START (default: no), SBD_STARTMODE (default: always) and SBD_TIMEOUT_ACTION. SBD options are documented in sbd(8) man page. It is possible to specify up to 3 devices per node. If \fB\-\-no\-watchdog\-validation\fR is specified, validation of watchdogs will be skipped.
.B WARNING: Cluster has to be restarted in order to apply these changes.
diff --git a/pcs/usage.py b/pcs/usage.py
index e4f5af32..30c63964 100644
--- a/pcs/usage.py
+++ b/pcs/usage.py
@@ -1147,7 +1147,8 @@ Commands:
Enable SBD in cluster. Default path for watchdog device is
/dev/watchdog. Allowed SBD options: SBD_WATCHDOG_TIMEOUT (default: 5),
SBD_DELAY_START (default: no), SBD_STARTMODE (default: always) and
- SBD_TIMEOUT_ACTION. It is possible to specify up to 3 devices per node.
+ SBD_TIMEOUT_ACTION. SBD options are documented in sbd(8) man page. It
+ is possible to specify up to 3 devices per node.
If --no-watchdog-validation is specified, validation of watchdogs will
be skipped.
--
2.21.1

View File

@ -0,0 +1,636 @@
From e56f42bf31ae0a52618fe8754fd0b2ae623e6a7a Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Thu, 12 Dec 2019 14:46:44 +0100
Subject: [PATCH 1/7] squash bz1781303 fix safe-disabling clones, groups,
bundles
fix simulate_cib_error report
Putting only one CIB in the report is not enough info. Both original and
changed CIB as well as crm_simulate output would be needed. All that
info can be seen in debug messages. So there is no need to put it in the
report.
---
pcs/cli/common/console_report.py | 7 +-
pcs/lib/cib/resource/common.py | 21 +-
pcs/lib/commands/resource.py | 27 +-
pcs/lib/pacemaker/live.py | 8 +-
pcs/lib/reports.py | 4 +-
.../tier0/cli/common/test_console_report.py | 10 +-
.../tier0/lib/cib/test_resource_common.py | 60 ++++-
.../resource/test_resource_enable_disable.py | 242 +++++++++++++++++-
pcs_test/tier0/lib/pacemaker/test_live.py | 7 -
9 files changed, 350 insertions(+), 36 deletions(-)
diff --git a/pcs/cli/common/console_report.py b/pcs/cli/common/console_report.py
index d349c823..60dbb2a0 100644
--- a/pcs/cli/common/console_report.py
+++ b/pcs/cli/common/console_report.py
@@ -1269,8 +1269,11 @@ CODE_TO_MESSAGE_BUILDER_MAP = {
,
codes.CIB_SIMULATE_ERROR: lambda info:
- "Unable to simulate changes in CIB: {reason}\n{cib}"
- .format(**info)
+ "Unable to simulate changes in CIB{_reason}"
+ .format(
+ _reason=format_optional(info["reason"], ": {0}"),
+ **info
+ )
,
codes.CIB_PUSH_FORCED_FULL_DUE_TO_CRM_FEATURE_SET: lambda info:
diff --git a/pcs/lib/cib/resource/common.py b/pcs/lib/cib/resource/common.py
index f1891003..e30c5e69 100644
--- a/pcs/lib/cib/resource/common.py
+++ b/pcs/lib/cib/resource/common.py
@@ -1,8 +1,9 @@
from collections import namedtuple
from typing import (
cast,
+ List,
Optional,
- Sequence,
+ Set,
)
from xml.etree.ElementTree import Element
@@ -114,7 +115,23 @@ def find_primitives(resource_el):
return [resource_el]
return []
-def get_inner_resources(resource_el: Element) -> Sequence[Element]:
+def get_all_inner_resources(resource_el: Element) -> Set[Element]:
+ """
+ Return all inner resources (both direct and indirect) of a resource
+ Example: for a clone containing a group, this function will return both
+ the group and the resources inside the group
+
+ resource_el -- resource element to get its inner resources
+ """
+ all_inner: Set[Element] = set()
+ to_process = set([resource_el])
+ while to_process:
+ new_inner = get_inner_resources(to_process.pop())
+ to_process.update(set(new_inner) - all_inner)
+ all_inner.update(new_inner)
+ return all_inner
+
+def get_inner_resources(resource_el: Element) -> List[Element]:
"""
Return list of inner resources (direct descendants) of a resource
specified as resource_el.
diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py
index 1b652ea4..4f975c7f 100644
--- a/pcs/lib/commands/resource.py
+++ b/pcs/lib/commands/resource.py
@@ -802,7 +802,28 @@ def disable_safe(env, resource_ids, strict, wait):
with resource_environment(
env, wait, resource_ids, _ensure_disabled_after_wait(True)
) as resources_section:
- _disable_validate_and_edit_cib(env, resources_section, resource_ids)
+ id_provider = IdProvider(resources_section)
+ resource_el_list = _find_resources_or_raise(
+ resources_section,
+ resource_ids
+ )
+ env.report_processor.process_list(
+ _resource_list_enable_disable(
+ resource_el_list,
+ resource.common.disable,
+ id_provider,
+ env.get_cluster_state()
+ )
+ )
+
+ inner_resources_names_set = set()
+ for resource_el in resource_el_list:
+ inner_resources_names_set.update({
+ inner_resource_el.get("id")
+ for inner_resource_el
+ in resource.common.get_all_inner_resources(resource_el)
+ })
+
plaintext_status, transitions, dummy_cib = simulate_cib(
env.cmd_runner(),
get_root(resources_section)
@@ -830,6 +851,10 @@ def disable_safe(env, resource_ids, strict, wait):
exclude=resource_ids
)
)
+
+ # Stopping a clone stops all its inner resources. That should not block
+ # stopping the clone.
+ other_affected = other_affected - inner_resources_names_set
if other_affected:
raise LibraryError(
reports.resource_disable_affects_other_resources(
diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py
index 83274af0..233f2e2d 100644
--- a/pcs/lib/pacemaker/live.py
+++ b/pcs/lib/pacemaker/live.py
@@ -271,7 +271,7 @@ def simulate_cib_xml(runner, cib_xml):
transitions_file = write_tmpfile(None)
except OSError as e:
raise LibraryError(
- reports.cib_simulate_error(format_os_error(e), cib_xml)
+ reports.cib_simulate_error(format_os_error(e))
)
cmd = [
@@ -284,7 +284,7 @@ def simulate_cib_xml(runner, cib_xml):
stdout, stderr, retval = runner.run(cmd, stdin_string=cib_xml)
if retval != 0:
raise LibraryError(
- reports.cib_simulate_error(stderr.strip(), cib_xml)
+ reports.cib_simulate_error(stderr.strip())
)
try:
@@ -297,7 +297,7 @@ def simulate_cib_xml(runner, cib_xml):
return stdout, transitions_xml, new_cib_xml
except OSError as e:
raise LibraryError(
- reports.cib_simulate_error(format_os_error(e), cib_xml)
+ reports.cib_simulate_error(format_os_error(e))
)
def simulate_cib(runner, cib):
@@ -319,7 +319,7 @@ def simulate_cib(runner, cib):
)
except (etree.XMLSyntaxError, etree.DocumentInvalid) as e:
raise LibraryError(
- reports.cib_simulate_error(str(e), cib_xml)
+ reports.cib_simulate_error(str(e))
)
### wait for idle
diff --git a/pcs/lib/reports.py b/pcs/lib/reports.py
index 1f081007..c9b4a25d 100644
--- a/pcs/lib/reports.py
+++ b/pcs/lib/reports.py
@@ -1935,18 +1935,16 @@ def cib_diff_error(reason, cib_old, cib_new):
}
)
-def cib_simulate_error(reason, cib):
+def cib_simulate_error(reason):
"""
cannot simulate effects a CIB would have on a live cluster
string reason -- error description
- string cib -- the CIB whose effects were to be simulated
"""
return ReportItem.error(
report_codes.CIB_SIMULATE_ERROR,
info={
"reason": reason,
- "cib": cib,
}
)
diff --git a/pcs_test/tier0/cli/common/test_console_report.py b/pcs_test/tier0/cli/common/test_console_report.py
index 0d0c2457..29e9614d 100644
--- a/pcs_test/tier0/cli/common/test_console_report.py
+++ b/pcs_test/tier0/cli/common/test_console_report.py
@@ -2238,8 +2238,14 @@ class CibDiffError(NameBuildTest):
class CibSimulateError(NameBuildTest):
def test_success(self):
self.assert_message_from_report(
- "Unable to simulate changes in CIB: error message\n<cib />",
- reports.cib_simulate_error("error message", "<cib />")
+ "Unable to simulate changes in CIB: error message",
+ reports.cib_simulate_error("error message")
+ )
+
+ def test_empty_reason(self):
+ self.assert_message_from_report(
+ "Unable to simulate changes in CIB",
+ reports.cib_simulate_error("")
)
diff --git a/pcs_test/tier0/lib/cib/test_resource_common.py b/pcs_test/tier0/lib/cib/test_resource_common.py
index ebba09da..cd716ba2 100644
--- a/pcs_test/tier0/lib/cib/test_resource_common.py
+++ b/pcs_test/tier0/lib/cib/test_resource_common.py
@@ -200,10 +200,12 @@ class FindOneOrMoreResources(TestCase):
class FindResourcesMixin:
+ _iterable_type = list
+
def assert_find_resources(self, input_resource_id, output_resource_ids):
self.assertEqual(
- output_resource_ids,
- [
+ self._iterable_type(output_resource_ids),
+ self._iterable_type([
element.get("id", "")
for element in
self._tested_fn(
@@ -211,7 +213,7 @@ class FindResourcesMixin:
'.//*[@id="{0}"]'.format(input_resource_id)
)
)
- ]
+ ])
)
def test_group(self):
@@ -235,6 +237,27 @@ class FindResourcesMixin:
def test_bundle_with_primitive(self):
self.assert_find_resources("H-bundle", ["H"])
+ def test_primitive(self):
+ raise NotImplementedError()
+
+ def test_primitive_in_clone(self):
+ raise NotImplementedError()
+
+ def test_primitive_in_master(self):
+ raise NotImplementedError()
+
+ def test_primitive_in_group(self):
+ raise NotImplementedError()
+
+ def test_primitive_in_bundle(self):
+ raise NotImplementedError()
+
+ def test_cloned_group(self):
+ raise NotImplementedError()
+
+ def test_mastered_group(self):
+ raise NotImplementedError()
+
class FindPrimitives(TestCase, FindResourcesMixin):
_tested_fn = staticmethod(common.find_primitives)
@@ -266,6 +289,37 @@ class FindPrimitives(TestCase, FindResourcesMixin):
self.assert_find_resources("F-master", ["F1", "F2"])
+class GetAllInnerResources(TestCase, FindResourcesMixin):
+ _iterable_type = set
+ _tested_fn = staticmethod(common.get_all_inner_resources)
+
+ def test_primitive(self):
+ self.assert_find_resources("A", set())
+
+ def test_primitive_in_clone(self):
+ self.assert_find_resources("B", set())
+
+ def test_primitive_in_master(self):
+ self.assert_find_resources("C", set())
+
+ def test_primitive_in_group(self):
+ self.assert_find_resources("D1", set())
+ self.assert_find_resources("D2", set())
+ self.assert_find_resources("E1", set())
+ self.assert_find_resources("E2", set())
+ self.assert_find_resources("F1", set())
+ self.assert_find_resources("F2", set())
+
+ def test_primitive_in_bundle(self):
+ self.assert_find_resources("H", set())
+
+ def test_cloned_group(self):
+ self.assert_find_resources("E-clone", {"E", "E1", "E2"})
+
+ def test_mastered_group(self):
+ self.assert_find_resources("F-master", {"F", "F1", "F2"})
+
+
class GetInnerResources(TestCase, FindResourcesMixin):
_tested_fn = staticmethod(common.get_inner_resources)
diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py b/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py
index 634f0f33..62899940 100644
--- a/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py
+++ b/pcs_test/tier0/lib/commands/resource/test_resource_enable_disable.py
@@ -1729,12 +1729,6 @@ class DisableSimulate(TestCase):
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some stderr",
- # curently, there is no way to normalize xml with our lxml
- # version 4.2.3, so this never passes equality tests
- # cib=self.config.calls.get(
- # "runner.pcmk.simulate_cib"
- # ).check_stdin.expected_stdin
- # ,
),
],
expected_in_processor=False
@@ -1988,12 +1982,6 @@ class DisableSafeMixin():
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some stderr",
- # curently, there is no way to normalize xml with our lxml
- # version 4.2.3, so this never passes equality tests
- # cib=self.config.calls.get(
- # "runner.pcmk.simulate_cib"
- # ).check_stdin.expected_stdin
- # ,
),
],
expected_in_processor=False
@@ -2118,6 +2106,236 @@ class DisableSafeMixin():
fixture.report_resource_not_running("B"),
])
+ def test_inner_resources(self, mock_write_tmpfile):
+ cib_xml = """
+ <resources>
+ <primitive id="A" />
+ <clone id="B-clone">
+ <primitive id="B" />
+ </clone>
+ <master id="C-master">
+ <primitive id="C" />
+ </master>
+ <group id="D">
+ <primitive id="D1" />
+ <primitive id="D2" />
+ </group>
+ <clone id="E-clone">
+ <group id="E">
+ <primitive id="E1" />
+ <primitive id="E2" />
+ </group>
+ </clone>
+ <master id="F-master">
+ <group id="F">
+ <primitive id="F1" />
+ <primitive id="F2" />
+ </group>
+ </master>
+ <bundle id="G-bundle" />
+ <bundle id="H-bundle">
+ <primitive id="H" />
+ </bundle>
+ </resources>
+ """
+ status_xml = """
+ <resources>
+ <resource id="A" managed="true" />
+ <clone id="B-clone" managed="true" multi_state="false"
+ unique="false"
+ >
+ <resource id="B" managed="true" />
+ <resource id="B" managed="true" />
+ </clone>
+ <clone id="C-master" managed="true" multi_state="true"
+ unique="false"
+ >
+ <resource id="C" managed="true" />
+ <resource id="C" managed="true" />
+ </clone>
+ <group id="D" number_resources="2">
+ <resource id="D1" managed="true" />
+ <resource id="D2" managed="true" />
+ </group>
+ <clone id="E-clone" managed="true" multi_state="false"
+ unique="false"
+ >
+ <group id="E:0" number_resources="2">
+ <resource id="E1" managed="true" />
+ <resource id="E2" managed="true" />
+ </group>
+ <group id="E:1" number_resources="2">
+ <resource id="E1" managed="true" />
+ <resource id="E2" managed="true" />
+ </group>
+ </clone>
+ <clone id="F-master" managed="true" multi_state="true"
+ unique="false"
+ >
+ <group id="F:0" number_resources="2">
+ <resource id="F1" managed="true" />
+ <resource id="F2" managed="true" />
+ </group>
+ <group id="F:1" number_resources="2">
+ <resource id="F1" managed="true" />
+ <resource id="F2" managed="true" />
+ </group>
+ </clone>
+ <bundle id="H-bundle" type="docker" image="pcmktest:http"
+ unique="false" managed="true" failed="false"
+ >
+ <replica id="0">
+ <resource id="H" />
+ </replica>
+ <replica id="1">
+ <resource id="H" />
+ </replica>
+ </bundle>
+ </resources>
+ """
+ synapses = []
+ index = 0
+ for res_name, is_clone in [
+ ("A", False),
+ ("B", True),
+ ("C", True),
+ ("D1", False),
+ ("D2", False),
+ ("E1", True),
+ ("E2", True),
+ ("F1", True),
+ ("F2", True),
+ ("H", False),
+ ]:
+ if is_clone:
+ synapses.append(f"""
+ <synapse>
+ <action_set>
+ <rsc_op id="{index}" operation="stop" on_node="node1">
+ <primitive id="{res_name}" long_id="{res_name}:0" />
+ </rsc_op>
+ </action_set>
+ </synapse>
+ <synapse>
+ <action_set>
+ <rsc_op id="{index + 1}" operation="stop" on_node="node2">
+ <primitive id="{res_name}" long_id="{res_name}:1" />
+ </rsc_op>
+ </action_set>
+ </synapse>
+ """)
+ index += 2
+ else:
+ synapses.append(f"""
+ <synapse>
+ <action_set>
+ <rsc_op id="{index}" operation="stop" on_node="node1">
+ <primitive id="{res_name}" />
+ </rsc_op>
+ </action_set>
+ </synapse>
+ """)
+ index += 1
+ transitions_xml = (
+ "<transition_graph>" + "\n".join(synapses) + "</transition_graph>"
+ )
+
+ self.tmpfile_transitions.read.return_value = transitions_xml
+ mock_write_tmpfile.side_effect = [
+ self.tmpfile_new_cib, self.tmpfile_transitions,
+ AssertionError("No other write_tmpfile call expected")
+ ]
+ (self.config
+ .runner.cib.load(resources=cib_xml)
+ .runner.pcmk.load_state(resources=status_xml)
+ )
+ self.config.runner.pcmk.simulate_cib(
+ self.tmpfile_new_cib.name,
+ self.tmpfile_transitions.name,
+ stdout="simulate output",
+ resources="""
+ <resources>
+ <primitive id="A" />
+ <clone id="B-clone">
+ <meta_attributes id="B-clone-meta_attributes">
+ <nvpair name="target-role" value="Stopped"
+ id="B-clone-meta_attributes-target-role"
+ />
+ </meta_attributes>
+ <primitive id="B" />
+ </clone>
+ <master id="C-master">
+ <meta_attributes id="C-master-meta_attributes">
+ <nvpair name="target-role" value="Stopped"
+ id="C-master-meta_attributes-target-role"
+ />
+ </meta_attributes>
+ <primitive id="C" />
+ </master>
+ <group id="D">
+ <meta_attributes id="D-meta_attributes">
+ <nvpair name="target-role" value="Stopped"
+ id="D-meta_attributes-target-role"
+ />
+ </meta_attributes>
+ <primitive id="D1" />
+ <primitive id="D2" />
+ </group>
+ <clone id="E-clone">
+ <meta_attributes id="E-clone-meta_attributes">
+ <nvpair name="target-role" value="Stopped"
+ id="E-clone-meta_attributes-target-role"
+ />
+ </meta_attributes>
+ <group id="E">
+ <primitive id="E1" />
+ <primitive id="E2" />
+ </group>
+ </clone>
+ <master id="F-master">
+ <meta_attributes id="F-master-meta_attributes">
+ <nvpair name="target-role" value="Stopped"
+ id="F-master-meta_attributes-target-role"
+ />
+ </meta_attributes>
+ <group id="F">
+ <primitive id="F1" />
+ <primitive id="F2" />
+ </group>
+ </master>
+ <bundle id="G-bundle" />
+ <bundle id="H-bundle">
+ <meta_attributes id="H-bundle-meta_attributes">
+ <nvpair name="target-role" value="Stopped"
+ id="H-bundle-meta_attributes-target-role"
+ />
+ </meta_attributes>
+ <primitive id="H" />
+ </bundle>
+ </resources>
+ """
+ )
+ self.env_assist.assert_raise_library_error(
+ lambda: resource.disable_safe(
+ self.env_assist.get_env(),
+ ["B-clone", "C-master", "D", "E-clone", "F-master", "H-bundle"],
+ self.strict,
+ False,
+ ),
+ [
+ fixture.error(
+ report_codes.RESOURCE_DISABLE_AFFECTS_OTHER_RESOURCES,
+ disabled_resource_list=[
+ "B-clone", "C-master", "D", "E-clone", "F-master",
+ "H-bundle"
+ ],
+ affected_resource_list=["A"],
+ crm_simulate_plaintext_output="simulate output",
+ ),
+ ],
+ expected_in_processor=False
+ )
+
@mock.patch("pcs.lib.pacemaker.live.write_tmpfile")
class DisableSafe(DisableSafeMixin, TestCase):
strict = False
diff --git a/pcs_test/tier0/lib/pacemaker/test_live.py b/pcs_test/tier0/lib/pacemaker/test_live.py
index dfebcb17..1ea5454e 100644
--- a/pcs_test/tier0/lib/pacemaker/test_live.py
+++ b/pcs_test/tier0/lib/pacemaker/test_live.py
@@ -686,7 +686,6 @@ class SimulateCibXml(LibraryPacemakerTest):
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some error",
- cib="<cib />",
),
)
mock_runner.run.assert_not_called()
@@ -703,7 +702,6 @@ class SimulateCibXml(LibraryPacemakerTest):
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some error",
- cib="<cib />",
),
)
mock_runner.run.assert_not_called()
@@ -729,7 +727,6 @@ class SimulateCibXml(LibraryPacemakerTest):
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some error",
- cib="<cib />",
),
)
@@ -755,7 +752,6 @@ class SimulateCibXml(LibraryPacemakerTest):
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some error",
- cib="<cib />",
),
)
@@ -782,7 +778,6 @@ class SimulateCibXml(LibraryPacemakerTest):
fixture.error(
report_codes.CIB_SIMULATE_ERROR,
reason="some error",
- cib="<cib />",
),
)
@@ -819,7 +814,6 @@ class SimulateCib(TestCase):
"Start tag expected, '<' not found, line 1, column 1 "
"(<string>, line 1)"
),
- cib=self.cib_xml,
),
)
@@ -835,7 +829,6 @@ class SimulateCib(TestCase):
"Start tag expected, '<' not found, line 1, column 1 "
"(<string>, line 1)"
),
- cib=self.cib_xml,
),
)
--
2.21.1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,533 @@
From 770252b476bc342ea08da2bc5b83de713463d14a Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com>
Date: Thu, 12 Mar 2020 15:32:31 +0100
Subject: [PATCH 1/2] send request from python to ruby more directly
Rack protection middleware is launched before
TornadoCommunicationMiddleware. When request parts are unpacked in
TornadoCommunicationMiddleware they are not checked by rack protection.
This commit changes communication between python and ruby - request is
sent to ruby more directly (without need to unpack request in sinatra
middleware).
---
pcs/daemon/ruby_pcsd.py | 217 ++++++++++++++--------
pcs_test/tier0/daemon/app/fixtures_app.py | 7 +-
pcs_test/tier0/daemon/test_ruby_pcsd.py | 61 ++----
pcsd/rserver.rb | 39 ++--
4 files changed, 175 insertions(+), 149 deletions(-)
diff --git a/pcs/daemon/ruby_pcsd.py b/pcs/daemon/ruby_pcsd.py
index e612f8da..53c53eaf 100644
--- a/pcs/daemon/ruby_pcsd.py
+++ b/pcs/daemon/ruby_pcsd.py
@@ -7,8 +7,8 @@ from time import time as now
import pycurl
from tornado.gen import convert_yielded
from tornado.web import HTTPError
-from tornado.httputil import split_host_and_port, HTTPServerRequest
-from tornado.httpclient import AsyncHTTPClient
+from tornado.httputil import HTTPServerRequest, HTTPHeaders
+from tornado.httpclient import AsyncHTTPClient, HTTPClientError
from tornado.curl_httpclient import CurlError
@@ -29,6 +29,11 @@ RUBY_LOG_LEVEL_MAP = {
"DEBUG": logging.DEBUG,
}
+__id_dict = {"id": 0}
+def get_request_id():
+ __id_dict["id"] += 1
+ return __id_dict["id"]
+
class SinatraResult(namedtuple("SinatraResult", "headers, status, body")):
@classmethod
def from_response(cls, response):
@@ -60,6 +65,59 @@ def process_response_logs(rb_log_list):
group_id=group_id
)
+class RubyDaemonRequest(namedtuple(
+ "RubyDaemonRequest",
+ "request_type, path, query, headers, method, body"
+)):
+ def __new__(
+ cls,
+ request_type,
+ http_request: HTTPServerRequest = None,
+ payload=None,
+ ):
+ headers = http_request.headers if http_request else HTTPHeaders()
+ headers.add("X-Pcsd-Type", request_type)
+ if payload:
+ headers.add(
+ "X-Pcsd-Payload",
+ b64encode(json.dumps(payload).encode()).decode()
+ )
+ return super(RubyDaemonRequest, cls).__new__(
+ cls,
+ request_type,
+ http_request.path if http_request else "",
+ http_request.query if http_request else "",
+ headers,
+ http_request.method if http_request else "GET",
+ http_request.body if http_request else None,
+ )
+
+ @property
+ def url(self):
+ # We do not need location for communication with ruby itself since we
+ # communicate via unix socket. But it is required by AsyncHTTPClient so
+ # "localhost" is used.
+ query = f"?{self.query}" if self.query else ""
+ return f"localhost/{self.path}{query}"
+
+ @property
+ def is_get(self):
+ return self.method.upper() == "GET"
+
+ @property
+ def has_http_request_detail(self):
+ return self.path or self.query or self.method != "GET" or self.body
+
+def log_ruby_daemon_request(label, request: RubyDaemonRequest):
+ log.pcsd.debug("%s type: '%s'", label, request.request_type)
+ if request.has_http_request_detail:
+ log.pcsd.debug("%s path: '%s'", label, request.path)
+ if request.query:
+ log.pcsd.debug("%s query: '%s'", label, request.query)
+ log.pcsd.debug("%s method: '%s'", label, request.method)
+ if request.body:
+ log.pcsd.debug("%s body: '%s'", label, request.body)
+
class Wrapper:
def __init__(self, pcsd_ruby_socket, debug=False):
self.__debug = debug
@@ -67,74 +125,87 @@ class Wrapper:
self.__client = AsyncHTTPClient()
self.__pcsd_ruby_socket = pcsd_ruby_socket
- @staticmethod
- def get_sinatra_request(request: HTTPServerRequest):
- host, port = split_host_and_port(request.host)
- return {"env": {
- "PATH_INFO": request.path,
- "QUERY_STRING": request.query,
- "REMOTE_ADDR": request.remote_ip,
- "REMOTE_HOST": request.host,
- "REQUEST_METHOD": request.method,
- "REQUEST_URI": f"{request.protocol}://{request.host}{request.uri}",
- "SCRIPT_NAME": "",
- "SERVER_NAME": host,
- "SERVER_PORT": port,
- "SERVER_PROTOCOL": request.version,
- "HTTP_HOST": request.host,
- "HTTP_ACCEPT": "*/*",
- "HTTP_COOKIE": ";".join([
- v.OutputString() for v in request.cookies.values()
- ]),
- "HTTPS": "on" if request.protocol == "https" else "off",
- "HTTP_VERSION": request.version,
- "REQUEST_PATH": request.path,
- "rack.input": request.body.decode("utf8"),
- }}
-
def prepare_curl_callback(self, curl):
curl.setopt(pycurl.UNIX_SOCKET_PATH, self.__pcsd_ruby_socket)
curl.setopt(pycurl.TIMEOUT, 70)
- async def send_to_ruby(self, request_json):
- # We do not need location for communication with ruby itself since we
- # communicate via unix socket. But it is required by AsyncHTTPClient so
- # "localhost" is used.
- tornado_request = b64encode(request_json.encode()).decode()
- return (await self.__client.fetch(
- "localhost",
- method="POST",
- body=f"TORNADO_REQUEST={tornado_request}",
- prepare_curl_callback=self.prepare_curl_callback,
- )).body
-
- async def run_ruby(self, request_type, request=None):
- """
- request_type: SINATRA_GUI|SINATRA_REMOTE|SYNC_CONFIGS
- request: result of get_sinatra_request|None
- i.e. it has structure returned by get_sinatra_request if the request
- is not None - so we can get SERVER_NAME and SERVER_PORT
- """
- request = request or {}
- request.update({"type": request_type})
- request_json = json.dumps(request)
-
- if self.__debug:
- log.pcsd.debug("Ruby daemon request: '%s'", request_json)
+ async def send_to_ruby(self, request: RubyDaemonRequest):
try:
- ruby_response = await self.send_to_ruby(request_json)
+ return (await self.__client.fetch(
+ request.url,
+ headers=request.headers,
+ method=request.method,
+ # Tornado enforces body=None for GET method:
+ # Even with `allow_nonstandard_methods` we disallow GET with a
+ # body (because libcurl doesn't allow it unless we use
+ # CUSTOMREQUEST). While the spec doesn't forbid clients from
+ # sending a body, it arguably disallows the server from doing
+ # anything with them.
+ body=(request.body if not request.is_get else None),
+ prepare_curl_callback=self.prepare_curl_callback,
+ )).body
except CurlError as e:
+ # This error we can get e.g. when ruby daemon is down.
log.pcsd.error(
"Cannot connect to ruby daemon (message: '%s'). Is it running?",
e
)
raise HTTPError(500)
+ except HTTPClientError as e:
+ # This error we can get e.g. when rack protection raises exception.
+ log.pcsd.error(
+ (
+ "Got error from ruby daemon (message: '%s')."
+ " Try checking system logs (e.g. journal, systemctl status"
+ " pcsd.service) for more information.."
+ ),
+ e
+ )
+ raise HTTPError(500)
+
+ async def run_ruby(
+ self,
+ request_type,
+ http_request: HTTPServerRequest = None,
+ payload=None,
+ ):
+ request = RubyDaemonRequest(request_type, http_request, payload)
+ request_id = get_request_id()
+
+ def log_request():
+ log_ruby_daemon_request(
+ f"Ruby daemon request (id: {request_id})",
+ request,
+ )
+
+ if self.__debug:
+ log_request()
+
+ return self.process_ruby_response(
+ f"Ruby daemon response (id: {request_id})",
+ log_request,
+ await self.send_to_ruby(request),
+ )
+
+ def process_ruby_response(self, label, log_request, ruby_response):
+ """
+ Return relevant part of unpacked ruby response. As a side effect
+ relevant logs are writen.
+ string label -- is used as a log prefix
+ callable log_request -- is used to log request when some errors happen;
+ we want to log request before error even if there is not debug mode
+ string ruby_response -- body of response from ruby; it should contain
+ json with dictionary with response specific keys
+ """
try:
response = json.loads(ruby_response)
if "error" in response:
+ if not self.__debug:
+ log_request()
log.pcsd.error(
- "Ruby daemon response contains an error: '%s'",
+ "%s contains an error: '%s'",
+ label,
json.dumps(response)
)
raise HTTPError(500)
@@ -144,56 +215,52 @@ class Wrapper:
body = b64decode(response.pop("body"))
if self.__debug:
log.pcsd.debug(
- "Ruby daemon response (without logs and body): '%s'",
+ "%s (without logs and body): '%s'",
+ label,
json.dumps(response)
)
- log.pcsd.debug("Ruby daemon response body: '%s'", body)
+ log.pcsd.debug("%s body: '%s'", label, body)
response["body"] = body
elif self.__debug:
log.pcsd.debug(
- "Ruby daemon response (without logs): '%s'",
+ "%s (without logs): '%s'",
+ label,
json.dumps(response)
)
process_response_logs(logs)
return response
except (json.JSONDecodeError, binascii.Error) as e:
if self.__debug:
- log.pcsd.debug("Ruby daemon response: '%s'", ruby_response)
+ log.pcsd.debug("%s: '%s'", label, ruby_response)
+ else:
+ log_request()
+
log.pcsd.error("Cannot decode json from ruby pcsd wrapper: '%s'", e)
raise HTTPError(500)
async def request_gui(
self, request: HTTPServerRequest, user, groups, is_authenticated
) -> SinatraResult:
- sinatra_request = self.get_sinatra_request(request)
# Sessions handling was removed from ruby. However, some session
# information is needed for ruby code (e.g. rendering some parts of
# templates). So this information must be sent to ruby by another way.
- sinatra_request.update({
- "session": {
+ return SinatraResult.from_response(
+ await convert_yielded(self.run_ruby(SINATRA_GUI, request, {
"username": user,
"groups": groups,
"is_authenticated": is_authenticated,
- }
- })
- response = await convert_yielded(self.run_ruby(
- SINATRA_GUI,
- sinatra_request
- ))
- return SinatraResult.from_response(response)
+ }))
+ )
async def request_remote(self, request: HTTPServerRequest) -> SinatraResult:
- response = await convert_yielded(self.run_ruby(
- SINATRA_REMOTE,
- self.get_sinatra_request(request)
- ))
- return SinatraResult.from_response(response)
+ return SinatraResult.from_response(
+ await convert_yielded(self.run_ruby(SINATRA_REMOTE, request))
+ )
async def sync_configs(self):
try:
- response = await convert_yielded(self.run_ruby(SYNC_CONFIGS))
- return response["next"]
+ return (await convert_yielded(self.run_ruby(SYNC_CONFIGS)))["next"]
except HTTPError:
log.pcsd.error("Config synchronization failed")
return int(now()) + DEFAULT_SYNC_CONFIG_DELAY
diff --git a/pcs_test/tier0/daemon/app/fixtures_app.py b/pcs_test/tier0/daemon/app/fixtures_app.py
index 8d5b8f4c..590203b4 100644
--- a/pcs_test/tier0/daemon/app/fixtures_app.py
+++ b/pcs_test/tier0/daemon/app/fixtures_app.py
@@ -20,7 +20,12 @@ class RubyPcsdWrapper(ruby_pcsd.Wrapper):
self.headers = {"Some": "value"}
self.body = b"Success action"
- async def run_ruby(self, request_type, request=None):
+ async def run_ruby(
+ self,
+ request_type,
+ http_request=None,
+ payload=None,
+ ):
if request_type != self.request_type:
raise AssertionError(
f"Wrong request type: expected '{self.request_type}'"
diff --git a/pcs_test/tier0/daemon/test_ruby_pcsd.py b/pcs_test/tier0/daemon/test_ruby_pcsd.py
index 28f14c87..32eb74cc 100644
--- a/pcs_test/tier0/daemon/test_ruby_pcsd.py
+++ b/pcs_test/tier0/daemon/test_ruby_pcsd.py
@@ -4,7 +4,7 @@ from base64 import b64encode
from unittest import TestCase, mock
from urllib.parse import urlencode
-from tornado.httputil import HTTPServerRequest
+from tornado.httputil import HTTPServerRequest, HTTPHeaders
from tornado.testing import AsyncTestCase, gen_test
from tornado.web import HTTPError
@@ -22,46 +22,17 @@ def create_http_request():
return HTTPServerRequest(
method="POST",
uri="/pcsd/uri",
- headers={"Cookie": "cookie1=first;cookie2=second"},
+ headers=HTTPHeaders({"Cookie": "cookie1=first;cookie2=second"}),
body=str.encode(urlencode({"post-key": "post-value"})),
host="pcsd-host:2224"
)
-class GetSinatraRequest(TestCase):
- def test_translate_request(self):
- # pylint: disable=invalid-name
- self.maxDiff = None
- self.assertEqual(
- create_wrapper().get_sinatra_request(create_http_request()),
- {
- 'env': {
- 'HTTPS': 'off',
- 'HTTP_ACCEPT': '*/*',
- 'HTTP_COOKIE': 'cookie1=first;cookie2=second',
- 'HTTP_HOST': 'pcsd-host:2224',
- 'HTTP_VERSION': 'HTTP/1.0',
- 'PATH_INFO': '/pcsd/uri',
- 'QUERY_STRING': '',
- 'REMOTE_ADDR': None, # It requires complicated request args
- 'REMOTE_HOST': 'pcsd-host:2224',
- 'REQUEST_METHOD': 'POST',
- 'REQUEST_PATH': '/pcsd/uri',
- 'REQUEST_URI': 'http://pcsd-host:2224/pcsd/uri',
- 'SCRIPT_NAME': '',
- 'SERVER_NAME': 'pcsd-host',
- 'SERVER_PORT': 2224,
- 'SERVER_PROTOCOL': 'HTTP/1.0',
- 'rack.input': 'post-key=post-value'
- }
- }
- )
-
patch_ruby_pcsd = create_patcher(ruby_pcsd)
class RunRuby(AsyncTestCase):
def setUp(self):
self.ruby_response = ""
- self.request = self.create_request()
+ self.request = ruby_pcsd.RubyDaemonRequest(ruby_pcsd.SYNC_CONFIGS)
self.wrapper = create_wrapper()
patcher = mock.patch.object(
self.wrapper,
@@ -72,14 +43,10 @@ class RunRuby(AsyncTestCase):
patcher.start()
super().setUp()
- async def send_to_ruby(self, request_json):
- self.assertEqual(json.loads(request_json), self.request)
+ async def send_to_ruby(self, ruby_request):
+ self.assertEqual(ruby_request, self.request)
return self.ruby_response
- @staticmethod
- def create_request(_type=ruby_pcsd.SYNC_CONFIGS):
- return {"type": _type}
-
def set_run_result(self, run_result):
self.ruby_response = json.dumps({**run_result, "logs": []})
@@ -125,10 +92,10 @@ class RunRuby(AsyncTestCase):
"body": b64encode(str.encode(body)).decode(),
})
http_request = create_http_request()
- self.request = {
- **self.create_request(ruby_pcsd.SINATRA_REMOTE),
- **self.wrapper.get_sinatra_request(http_request),
- }
+ self.request = ruby_pcsd.RubyDaemonRequest(
+ ruby_pcsd.SINATRA_REMOTE,
+ http_request,
+ )
result = yield self.wrapper.request_remote(http_request)
self.assert_sinatra_result(result, headers, status, body)
@@ -148,15 +115,15 @@ class RunRuby(AsyncTestCase):
"body": b64encode(str.encode(body)).decode(),
})
http_request = create_http_request()
- self.request = {
- **self.create_request(ruby_pcsd.SINATRA_GUI),
- **self.wrapper.get_sinatra_request(http_request),
- "session": {
+ self.request = ruby_pcsd.RubyDaemonRequest(
+ ruby_pcsd.SINATRA_GUI,
+ http_request,
+ {
"username": user,
"groups": groups,
"is_authenticated": is_authenticated,
}
- }
+ )
result = yield self.wrapper.request_gui(
http_request,
user=user,
diff --git a/pcsd/rserver.rb b/pcsd/rserver.rb
index 6002a73c..4b58f252 100644
--- a/pcsd/rserver.rb
+++ b/pcsd/rserver.rb
@@ -11,42 +11,25 @@ def pack_response(response)
return [200, {}, [response.to_json.to_str]]
end
-def unpack_request(transport_env)
- return JSON.parse(Base64.strict_decode64(
- transport_env["rack.request.form_hash"]["TORNADO_REQUEST"]
- ))
-end
-
class TornadoCommunicationMiddleware
def initialize(app)
@app = app
end
- def call(transport_env)
+ def call(env)
Thread.current[:pcsd_logger_container] = []
begin
- request = unpack_request(transport_env)
+ type = env["HTTP_X_PCSD_TYPE"]
- if ["sinatra_gui", "sinatra_remote"].include?(request["type"])
- if request["type"] == "sinatra_gui"
- session = request["session"]
+ if ["sinatra_gui", "sinatra_remote"].include?(type)
+ if type == "sinatra_gui"
+ session = JSON.parse(Base64.strict_decode64(env["HTTP_X_PCSD_PAYLOAD"]))
Thread.current[:tornado_username] = session["username"]
Thread.current[:tornado_groups] = session["groups"]
Thread.current[:tornado_is_authenticated] = session["is_authenticated"]
end
- # Keys rack.input and rack.errors are required. We make sure they are
- # there.
- request_env = request["env"]
- request_env["rack.input"] = StringIO.new(request_env["rack.input"])
- request_env["rack.errors"] = StringIO.new()
-
- status, headers, body = @app.call(request_env)
-
- rack_errors = request_env['rack.errors'].string()
- if not rack_errors.empty?()
- $logger.error(rack_errors)
- end
+ status, headers, body = @app.call(env)
return pack_response({
:status => status,
@@ -56,16 +39,20 @@ class TornadoCommunicationMiddleware
})
end
- if request["type"] == "sync_configs"
+ if type == "sync_configs"
return pack_response({
:next => Time.now.to_i + run_cfgsync(),
:logs => Thread.current[:pcsd_logger_container],
})
end
- raise "Unexpected value for key 'type': '#{request['type']}'"
+ return pack_response({
+ :error => "Unexpected value for key 'type': '#{type}'"
+ })
rescue => e
- return pack_response({:error => "Processing request error: '#{e}'"})
+ return pack_response({
+ :error => "Processing request error: '#{e}' '#{e.backtrace}'"
+ })
end
end
end
--
2.21.1

View File

@ -0,0 +1,367 @@
From 9fbeeed4e43dc37800de3c3f0cf6f7520dc31ccf Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Mon, 20 Jan 2020 12:34:55 +0100
Subject: [PATCH] tests: update for pacemaker-2.0.3-4
---
pcs_test/tier0/test_resource.py | 59 +++++++++++++-------------
pcs_test/tier0/test_stonith.py | 75 +++++++++++++++++----------------
pcs_test/tools/assertions.py | 24 +++++++++--
3 files changed, 88 insertions(+), 70 deletions(-)
diff --git a/pcs_test/tier0/test_resource.py b/pcs_test/tier0/test_resource.py
index b8b85dd2..45d98dff 100644
--- a/pcs_test/tier0/test_resource.py
+++ b/pcs_test/tier0/test_resource.py
@@ -10,6 +10,7 @@ from pcs_test.tier0.cib_resource.common import ResourceTest
from pcs_test.tools.assertions import (
ac,
AssertPcsMixin,
+ assert_pcs_status,
)
from pcs_test.tools.bin_mock import get_mock_settings
from pcs_test.tools.cib import get_assert_pcs_effect_mixin
@@ -953,11 +954,11 @@ monitor interval=20 (A-monitor-interval-20)
o,r = pcs(temp_cib, "resource status")
assert r == 0
if PCMK_2_0_3_PLUS:
- ac(o,"""\
+ assert_pcs_status(o,"""\
* Resource Group: AGroup:
- * A1\t(ocf::heartbeat:Dummy):\t Stopped
- * A2\t(ocf::heartbeat:Dummy):\t Stopped
- * A3\t(ocf::heartbeat:Dummy):\t Stopped
+ * A1\t(ocf::heartbeat:Dummy):\tStopped
+ * A2\t(ocf::heartbeat:Dummy):\tStopped
+ * A3\t(ocf::heartbeat:Dummy):\tStopped
""")
else:
ac(o,"""\
@@ -1208,19 +1209,19 @@ monitor interval=20 (A-monitor-interval-20)
output, returnVal = pcs(temp_cib, "resource")
assert returnVal == 0
if PCMK_2_0_3_PLUS:
- ac(output, """\
- * F\t(ocf::heartbeat:Dummy):\t Stopped
- * G\t(ocf::heartbeat:Dummy):\t Stopped
- * H\t(ocf::heartbeat:Dummy):\t Stopped
+ assert_pcs_status(output, """\
+ * F\t(ocf::heartbeat:Dummy):\tStopped
+ * G\t(ocf::heartbeat:Dummy):\tStopped
+ * H\t(ocf::heartbeat:Dummy):\tStopped
* Resource Group: RGA:
- * A\t(ocf::heartbeat:Dummy):\t Stopped
- * B\t(ocf::heartbeat:Dummy):\t Stopped
- * C\t(ocf::heartbeat:Dummy):\t Stopped
- * E\t(ocf::heartbeat:Dummy):\t Stopped
- * D\t(ocf::heartbeat:Dummy):\t Stopped
- * K\t(ocf::heartbeat:Dummy):\t Stopped
- * J\t(ocf::heartbeat:Dummy):\t Stopped
- * I\t(ocf::heartbeat:Dummy):\t Stopped
+ * A\t(ocf::heartbeat:Dummy):\tStopped
+ * B\t(ocf::heartbeat:Dummy):\tStopped
+ * C\t(ocf::heartbeat:Dummy):\tStopped
+ * E\t(ocf::heartbeat:Dummy):\tStopped
+ * D\t(ocf::heartbeat:Dummy):\tStopped
+ * K\t(ocf::heartbeat:Dummy):\tStopped
+ * J\t(ocf::heartbeat:Dummy):\tStopped
+ * I\t(ocf::heartbeat:Dummy):\tStopped
""")
else:
ac(output, """\
@@ -2004,9 +2005,9 @@ monitor interval=20 (A-monitor-interval-20)
o,r = pcs(temp_cib, "resource")
if PCMK_2_0_3_PLUS:
- ac(o,"""\
+ assert_pcs_status(o,"""\
* Resource Group: AG:
- * D1\t(ocf::heartbeat:Dummy):\t Stopped
+ * D1\t(ocf::heartbeat:Dummy):\tStopped
* Clone Set: D0-clone [D0]:
""")
else:
@@ -2348,10 +2349,10 @@ monitor interval=20 (A-monitor-interval-20)
o,r = pcs(temp_cib, "resource status")
assert r == 0
if PCMK_2_0_3_PLUS:
- ac(o,"""\
+ assert_pcs_status(o,"""\
* Resource Group: DGroup:
- * D1\t(ocf::heartbeat:Dummy):\t Stopped
- * D2\t(ocf::heartbeat:Dummy):\t Stopped
+ * D1\t(ocf::heartbeat:Dummy):\tStopped
+ * D2\t(ocf::heartbeat:Dummy):\tStopped
""")
else:
ac(o,"""\
@@ -3560,12 +3561,12 @@ Error: role must be: Stopped, Started, Slave or Master (use --force to override)
assert retVal == 0
output, retVal = pcs(temp_cib, "resource status")
if PCMK_2_0_3_PLUS:
- ac(output, outdent(
+ assert_pcs_status(output, outdent(
"""\
* Resource Group: dummies:
- * dummy1\t(ocf::heartbeat:Dummy):\t Stopped
- * dummy2\t(ocf::heartbeat:Dummy):\t Stopped
- * dummy3\t(ocf::heartbeat:Dummy):\t Stopped
+ * dummy1\t(ocf::heartbeat:Dummy):\tStopped
+ * dummy2\t(ocf::heartbeat:Dummy):\tStopped
+ * dummy3\t(ocf::heartbeat:Dummy):\tStopped
"""
))
else:
@@ -3652,12 +3653,12 @@ Error: role must be: Stopped, Started, Slave or Master (use --force to override)
assert retVal == 0
output, retVal = pcs(temp_cib, "resource status")
if PCMK_2_0_3_PLUS:
- ac(output, outdent(
+ assert_pcs_status(output, outdent(
"""\
* Resource Group: dummies:
- * dummy1\t(ocf::heartbeat:Dummy):\t Stopped
- * dummy2\t(ocf::heartbeat:Dummy):\t Stopped
- * dummy3\t(ocf::heartbeat:Dummy):\t Stopped
+ * dummy1\t(ocf::heartbeat:Dummy):\tStopped
+ * dummy2\t(ocf::heartbeat:Dummy):\tStopped
+ * dummy3\t(ocf::heartbeat:Dummy):\tStopped
"""
))
else:
diff --git a/pcs_test/tier0/test_stonith.py b/pcs_test/tier0/test_stonith.py
index 46938e75..097a79b9 100644
--- a/pcs_test/tier0/test_stonith.py
+++ b/pcs_test/tier0/test_stonith.py
@@ -517,13 +517,13 @@ class StonithTest(TestCase, AssertPcsMixin):
if PCMK_2_0_3_PLUS:
self.assert_pcs_success("stonith", outdent(
"""\
- * n1-ipmi\t(stonith:fence_apc):\t Stopped
- * n2-ipmi\t(stonith:fence_apc):\t Stopped
- * n1-apc1\t(stonith:fence_apc):\t Stopped
- * n1-apc2\t(stonith:fence_apc):\t Stopped
- * n2-apc1\t(stonith:fence_apc):\t Stopped
- * n2-apc2\t(stonith:fence_apc):\t Stopped
- * n2-apc3\t(stonith:fence_apc):\t Stopped
+ * n1-ipmi\t(stonith:fence_apc):\tStopped
+ * n2-ipmi\t(stonith:fence_apc):\tStopped
+ * n1-apc1\t(stonith:fence_apc):\tStopped
+ * n1-apc2\t(stonith:fence_apc):\tStopped
+ * n2-apc1\t(stonith:fence_apc):\tStopped
+ * n2-apc2\t(stonith:fence_apc):\tStopped
+ * n2-apc3\t(stonith:fence_apc):\tStopped
Target: rh7-1
Level 1 - n1-ipmi
Level 2 - n1-apc1,n1-apc2,n2-apc2
@@ -531,7 +531,7 @@ class StonithTest(TestCase, AssertPcsMixin):
Level 1 - n2-ipmi
Level 2 - n2-apc1,n2-apc2,n2-apc3
"""
- ))
+ ), despace=True)
else:
self.assert_pcs_success("stonith", outdent(
"""\
@@ -559,12 +559,12 @@ class StonithTest(TestCase, AssertPcsMixin):
if PCMK_2_0_3_PLUS:
self.assert_pcs_success("stonith", outdent(
"""\
- * n1-ipmi\t(stonith:fence_apc):\t Stopped
- * n2-ipmi\t(stonith:fence_apc):\t Stopped
- * n1-apc1\t(stonith:fence_apc):\t Stopped
- * n1-apc2\t(stonith:fence_apc):\t Stopped
- * n2-apc1\t(stonith:fence_apc):\t Stopped
- * n2-apc3\t(stonith:fence_apc):\t Stopped
+ * n1-ipmi\t(stonith:fence_apc):\tStopped
+ * n2-ipmi\t(stonith:fence_apc):\tStopped
+ * n1-apc1\t(stonith:fence_apc):\tStopped
+ * n1-apc2\t(stonith:fence_apc):\tStopped
+ * n2-apc1\t(stonith:fence_apc):\tStopped
+ * n2-apc3\t(stonith:fence_apc):\tStopped
Target: rh7-1
Level 1 - n1-ipmi
Level 2 - n1-apc1,n1-apc2
@@ -572,7 +572,7 @@ class StonithTest(TestCase, AssertPcsMixin):
Level 1 - n2-ipmi
Level 2 - n2-apc1,n2-apc3
"""
- ))
+ ), despace=True)
else:
self.assert_pcs_success("stonith", outdent(
"""\
@@ -599,11 +599,11 @@ class StonithTest(TestCase, AssertPcsMixin):
if PCMK_2_0_3_PLUS:
self.assert_pcs_success("stonith", outdent(
"""\
- * n1-ipmi\t(stonith:fence_apc):\t Stopped
- * n2-ipmi\t(stonith:fence_apc):\t Stopped
- * n1-apc1\t(stonith:fence_apc):\t Stopped
- * n1-apc2\t(stonith:fence_apc):\t Stopped
- * n2-apc3\t(stonith:fence_apc):\t Stopped
+ * n1-ipmi\t(stonith:fence_apc):\tStopped
+ * n2-ipmi\t(stonith:fence_apc):\tStopped
+ * n1-apc1\t(stonith:fence_apc):\tStopped
+ * n1-apc2\t(stonith:fence_apc):\tStopped
+ * n2-apc3\t(stonith:fence_apc):\tStopped
Target: rh7-1
Level 1 - n1-ipmi
Level 2 - n1-apc1,n1-apc2
@@ -611,7 +611,7 @@ class StonithTest(TestCase, AssertPcsMixin):
Level 1 - n2-ipmi
Level 2 - n2-apc3
"""
- ))
+ ), despace=True)
else:
self.assert_pcs_success("stonith", outdent(
"""\
@@ -637,17 +637,17 @@ class StonithTest(TestCase, AssertPcsMixin):
if PCMK_2_0_3_PLUS:
self.assert_pcs_success("stonith", outdent(
"""\
- * n1-ipmi\t(stonith:fence_apc):\t Stopped
- * n2-ipmi\t(stonith:fence_apc):\t Stopped
- * n1-apc1\t(stonith:fence_apc):\t Stopped
- * n1-apc2\t(stonith:fence_apc):\t Stopped
+ * n1-ipmi\t(stonith:fence_apc):\tStopped
+ * n2-ipmi\t(stonith:fence_apc):\tStopped
+ * n1-apc1\t(stonith:fence_apc):\tStopped
+ * n1-apc2\t(stonith:fence_apc):\tStopped
Target: rh7-1
Level 1 - n1-ipmi
Level 2 - n1-apc1,n1-apc2
Target: rh7-2
Level 1 - n2-ipmi
"""
- ))
+ ), despace=True)
else:
self.assert_pcs_success("stonith", outdent(
"""\
@@ -671,16 +671,16 @@ class StonithTest(TestCase, AssertPcsMixin):
if PCMK_2_0_3_PLUS:
self.assert_pcs_success("stonith", outdent(
"""\
- * n1-ipmi\t(stonith:fence_apc):\t Stopped
- * n2-ipmi\t(stonith:fence_apc):\t Stopped
- * n1-apc2\t(stonith:fence_apc):\t Stopped
+ * n1-ipmi\t(stonith:fence_apc):\tStopped
+ * n2-ipmi\t(stonith:fence_apc):\tStopped
+ * n1-apc2\t(stonith:fence_apc):\tStopped
Target: rh7-1
Level 1 - n1-ipmi
Level 2 - n1-apc2
Target: rh7-2
Level 1 - n2-ipmi
"""
- ))
+ ), despace=True)
else:
self.assert_pcs_success("stonith", outdent(
"""\
@@ -704,14 +704,14 @@ class StonithTest(TestCase, AssertPcsMixin):
if PCMK_2_0_3_PLUS:
self.assert_pcs_success("stonith", outdent(
"""\
- * n1-ipmi\t(stonith:fence_apc):\t Stopped
- * n2-ipmi\t(stonith:fence_apc):\t Stopped
+ * n1-ipmi\t(stonith:fence_apc):\tStopped
+ * n2-ipmi\t(stonith:fence_apc):\tStopped
Target: rh7-1
Level 1 - n1-ipmi
Target: rh7-2
Level 1 - n2-ipmi
"""
- ))
+ ), despace=True)
else:
self.assert_pcs_success("stonith", outdent(
"""\
@@ -1219,9 +1219,9 @@ class LevelConfig(LevelTestsBase):
if PCMK_2_0_3_PLUS:
result = outdent(
"""\
- * F1\t(stonith:fence_apc):\t Stopped
- * F2\t(stonith:fence_apc):\t Stopped
- * F3\t(stonith:fence_apc):\t Stopped
+ * F1\t(stonith:fence_apc):\tStopped
+ * F2\t(stonith:fence_apc):\tStopped
+ * F3\t(stonith:fence_apc):\tStopped
"""
)
else:
@@ -1234,7 +1234,8 @@ class LevelConfig(LevelTestsBase):
)
self.assert_pcs_success(
"stonith",
- result + "\n".join(indent(self.config_lines, 1)) + "\n"
+ result + "\n".join(indent(self.config_lines, 1)) + "\n",
+ despace=True
)
self.pcs_runner.mock_settings["corosync_conf_file"] = rc(
"corosync.conf"
diff --git a/pcs_test/tools/assertions.py b/pcs_test/tools/assertions.py
index db8f4df5..a2b7b4ac 100644
--- a/pcs_test/tools/assertions.py
+++ b/pcs_test/tools/assertions.py
@@ -59,7 +59,8 @@ class AssertPcsMixin:
)
def assert_pcs_success(
- self, command, stdout_full=None, stdout_start=None, stdout_regexp=None
+ self, command, stdout_full=None, stdout_start=None, stdout_regexp=None,
+ despace=False
):
full = stdout_full
if (
@@ -75,7 +76,8 @@ class AssertPcsMixin:
stdout_full=full,
stdout_start=stdout_start,
stdout_regexp=stdout_regexp,
- returncode=0
+ returncode=0,
+ despace=despace,
)
def assert_pcs_fail(
@@ -99,7 +101,7 @@ class AssertPcsMixin:
def assert_pcs_result(
self, command, stdout_full=None, stdout_start=None, stdout_regexp=None,
- returncode=0
+ returncode=0, despace=False
):
msg = (
"Please specify exactly one: stdout_start or stdout_full or"
@@ -162,7 +164,11 @@ class AssertPcsMixin:
)
else:
expected_full = self.__prepare_output(stdout_full)
- if stdout != expected_full:
+ if (
+ (despace and _despace(stdout) != _despace(expected_full))
+ or
+ (not despace and stdout != expected_full)
+ ):
self.assertEqual(
stdout,
expected_full,
@@ -386,3 +392,13 @@ def __report_item_equal(real_report_item, report_item_info):
)
)
)
+
+def assert_pcs_status(status1, status2):
+ if _despace(status1) != _despace(status2):
+ raise AssertionError(
+ "strings not equal:\n{0}".format(prepare_diff(status1, status2))
+ )
+
+def _despace(string):
+ # ignore whitespace changes between various pacemaker versions
+ return re.sub(r"[ \t]+", " ", string)
--
2.20.1

View File

@ -0,0 +1,541 @@
From ac0305a8b6bb040ef06dcbfff309c91321400d44 Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Mon, 27 Jan 2020 17:05:42 +0100
Subject: [PATCH 3/7] fix detecting fence history support
---
pcs/lib/commands/stonith.py | 38 ++++++++------
pcs/lib/pacemaker/live.py | 45 +++++++++-------
.../crm_mon.rng.with_fence_history.xml | 13 -----
.../crm_mon.rng.without_fence_history.xml | 9 ----
pcs_test/tier0/lib/commands/test_status.py | 35 +++----------
.../lib/commands/test_stonith_history.py | 52 ++++++-------------
pcs_test/tier0/lib/pacemaker/test_live.py | 31 ++++++++++-
.../tools/command_env/config_runner_pcmk.py | 41 +++++++++++++++
pcs_test/tools/command_env/mock_runner.py | 1 +
9 files changed, 141 insertions(+), 124 deletions(-)
delete mode 100644 pcs_test/resources/crm_mon.rng.with_fence_history.xml
delete mode 100644 pcs_test/resources/crm_mon.rng.without_fence_history.xml
diff --git a/pcs/lib/commands/stonith.py b/pcs/lib/commands/stonith.py
index c0849a54..ff87c852 100644
--- a/pcs/lib/commands/stonith.py
+++ b/pcs/lib/commands/stonith.py
@@ -1,3 +1,7 @@
+from typing import (
+ Optional,
+)
+
from pcs.lib import reports
from pcs.lib.cib import resource
from pcs.lib.cib.resource.common import are_meta_disabled
@@ -6,13 +10,14 @@ from pcs.lib.commands.resource import (
_ensure_disabled_after_wait,
resource_environment
)
+from pcs.lib.env import LibraryEnvironment
from pcs.lib.errors import LibraryError
from pcs.lib.pacemaker.live import (
FenceHistoryCommandErrorException,
fence_history_cleanup,
fence_history_text,
fence_history_update,
- is_fence_history_supported,
+ is_fence_history_supported_management,
)
from pcs.lib.pacemaker.values import validate_id
from pcs.lib.resource_agent import find_valid_stonith_agent_by_name as get_agent
@@ -162,51 +167,54 @@ def create_in_group(
put_after_adjacent,
)
-def history_get_text(env, node=None):
+def history_get_text(env: LibraryEnvironment, node: Optional[str] = None):
"""
Get full fencing history in plain text
- LibraryEnvironment env
- string node -- get history for the specified node or all nodes if None
+ env
+ node -- get history for the specified node or all nodes if None
"""
- if not is_fence_history_supported():
+ runner = env.cmd_runner()
+ if not is_fence_history_supported_management(runner):
raise LibraryError(reports.fence_history_not_supported())
try:
- return fence_history_text(env.cmd_runner(), node)
+ return fence_history_text(runner, node)
except FenceHistoryCommandErrorException as e:
raise LibraryError(
reports.fence_history_command_error(str(e), "show")
)
-def history_cleanup(env, node=None):
+def history_cleanup(env: LibraryEnvironment, node: Optional[str] = None):
"""
Clear fencing history
- LibraryEnvironment env
- string node -- clear history for the specified node or all nodes if None
+ env
+ node -- clear history for the specified node or all nodes if None
"""
- if not is_fence_history_supported():
+ runner = env.cmd_runner()
+ if not is_fence_history_supported_management(runner):
raise LibraryError(reports.fence_history_not_supported())
try:
- return fence_history_cleanup(env.cmd_runner(), node)
+ return fence_history_cleanup(runner, node)
except FenceHistoryCommandErrorException as e:
raise LibraryError(
reports.fence_history_command_error(str(e), "cleanup")
)
-def history_update(env):
+def history_update(env: LibraryEnvironment):
"""
Update fencing history in a cluster (sync with other nodes)
- LibraryEnvironment env
+ env
"""
- if not is_fence_history_supported():
+ runner = env.cmd_runner()
+ if not is_fence_history_supported_management(runner):
raise LibraryError(reports.fence_history_not_supported())
try:
- return fence_history_update(env.cmd_runner())
+ return fence_history_update(runner)
except FenceHistoryCommandErrorException as e:
raise LibraryError(
reports.fence_history_command_error(str(e), "update")
diff --git a/pcs/lib/pacemaker/live.py b/pcs/lib/pacemaker/live.py
index 233f2e2d..d6741441 100644
--- a/pcs/lib/pacemaker/live.py
+++ b/pcs/lib/pacemaker/live.py
@@ -1,6 +1,7 @@
import os.path
import re
from typing import (
+ Iterable,
List,
Tuple,
)
@@ -56,7 +57,7 @@ def get_cluster_status_text(
cmd.extend(["--show-detail", "--show-node-attributes", "--failcounts"])
# by default, pending and failed actions are displayed
# with verbose==True, we display the whole history
- if is_fence_history_supported():
+ if is_fence_history_supported_status(runner):
cmd.append("--fence-history=3")
stdout, stderr, retval = runner.run(cmd)
@@ -523,25 +524,15 @@ def _resource_move_ban_clear(
### fence history
-def is_fence_history_supported():
- try:
- crm_mon_rng = xml_fromstring(open(settings.crm_mon_schema, "r").read())
- # Namespaces must be provided otherwise xpath won't match anything.
- # 'None' namespace is not supported, so we rename it.
- namespaces_map = {
- "ns": crm_mon_rng.nsmap.pop(None)
- }
- history_elements = crm_mon_rng.xpath(
- ".//ns:element[@name='fence_history']",
- namespaces=namespaces_map
- )
- if history_elements:
- return True
- except (EnvironmentError, etree.XMLSyntaxError):
- # if we cannot tell for sure fence_history is supported, we will
- # continue as if it was not supported
- pass
- return False
+def is_fence_history_supported_status(runner: CommandRunner) -> bool:
+ return _is_in_pcmk_tool_help(
+ runner, "crm_mon", ["--fence-history"]
+ )
+
+def is_fence_history_supported_management(runner: CommandRunner) -> bool:
+ return _is_in_pcmk_tool_help(
+ runner, "stonith_admin", ["--history", "--broadcast", "--cleanup"]
+ )
def fence_history_cleanup(runner, node=None):
return _run_fence_history_command(runner, "--cleanup", node)
@@ -583,3 +574,17 @@ def __is_in_crm_resource_help(runner, text):
)
# help goes to stderr but we check stdout as well if that gets changed
return text in stderr or text in stdout
+
+def _is_in_pcmk_tool_help(
+ runner: CommandRunner, tool: str, text_list: Iterable[str]
+) -> bool:
+ stdout, stderr, dummy_retval = runner.run(
+ [__exec(tool), "--help-all"]
+ )
+ # Help goes to stderr but we check stdout as well if that gets changed. Use
+ # generators in all to return early.
+ return (
+ all(text in stderr for text in text_list)
+ or
+ all(text in stdout for text in text_list)
+ )
diff --git a/pcs_test/resources/crm_mon.rng.with_fence_history.xml b/pcs_test/resources/crm_mon.rng.with_fence_history.xml
deleted file mode 100644
index 45b380bd..00000000
--- a/pcs_test/resources/crm_mon.rng.with_fence_history.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
- <start>
- <ref name="element-crm_mon"/>
- </start>
- <define name="element-crm_mon">
- <element name="crm_mon">
- <optional>
- <element name="fence_history"/>
- </optional>
- </element>
- </define>
-</grammar>
diff --git a/pcs_test/resources/crm_mon.rng.without_fence_history.xml b/pcs_test/resources/crm_mon.rng.without_fence_history.xml
deleted file mode 100644
index f7efe52c..00000000
--- a/pcs_test/resources/crm_mon.rng.without_fence_history.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
- <start>
- <ref name="element-crm_mon"/>
- </start>
- <define name="element-crm_mon">
- <element name="crm_mon"/>
- </define>
-</grammar>
diff --git a/pcs_test/tier0/lib/commands/test_status.py b/pcs_test/tier0/lib/commands/test_status.py
index 517aa908..06878668 100644
--- a/pcs_test/tier0/lib/commands/test_status.py
+++ b/pcs_test/tier0/lib/commands/test_status.py
@@ -1,15 +1,12 @@
from textwrap import dedent
-from unittest import mock, TestCase
+from unittest import TestCase
-from pcs import settings
from pcs.common import file_type_codes, report_codes
from pcs.lib.commands import status
from pcs_test.tools import fixture
from pcs_test.tools.command_env import get_env_tools
from pcs_test.tools.misc import read_test_resource as rc_read
-crm_mon_rng_with_history = rc_read("crm_mon.rng.with_fence_history.xml")
-crm_mon_rng_without_history = rc_read("crm_mon.rng.without_fence_history.xml")
class FullClusterStatusPlaintext(TestCase):
def setUp(self):
@@ -212,11 +209,7 @@ class FullClusterStatusPlaintext(TestCase):
def test_success_live_verbose(self):
(self.config
.env.set_known_nodes(self.node_name_list)
- .fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng"
- )
+ .runner.pcmk.can_fence_history_status(stderr="not supported")
.runner.pcmk.load_state_plaintext(
verbose=True,
stdout="crm_mon cluster status",
@@ -288,11 +281,7 @@ class FullClusterStatusPlaintext(TestCase):
(self.config
.env.set_corosync_conf_data(rc_read("corosync.conf"))
.env.set_cib_data("<cib/>")
- .fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng"
- )
+ .runner.pcmk.can_fence_history_status(stderr="not supported")
.runner.pcmk.load_state_plaintext(
verbose=True, stdout="crm_mon cluster status",
)
@@ -320,11 +309,7 @@ class FullClusterStatusPlaintext(TestCase):
def test_success_verbose_inactive_and_fence_history(self):
(self.config
.env.set_known_nodes(self.node_name_list)
- .fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_with_history)(),
- name="fs.open.crm_mon_rng"
- )
+ .runner.pcmk.can_fence_history_status()
.runner.pcmk.load_state_plaintext(
verbose=True,
inactive=False,
@@ -375,11 +360,7 @@ class FullClusterStatusPlaintext(TestCase):
def _assert_success_with_ticket_status_failure(self, stderr="", msg=""):
(self.config
.env.set_known_nodes(self.node_name_list)
- .fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng"
- )
+ .runner.pcmk.can_fence_history_status(stderr="not supported")
.runner.pcmk.load_state_plaintext(
verbose=True,
stdout="crm_mon cluster status",
@@ -553,11 +534,7 @@ class FullClusterStatusPlaintext(TestCase):
(self.config
.env.set_known_nodes(self.node_name_list[1:])
- .fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng"
- )
+ .runner.pcmk.can_fence_history_status(stderr="not supported")
.runner.pcmk.load_state_plaintext(
verbose=True,
stdout="crm_mon cluster status",
diff --git a/pcs_test/tier0/lib/commands/test_stonith_history.py b/pcs_test/tier0/lib/commands/test_stonith_history.py
index e1bd35cb..cfdef13c 100644
--- a/pcs_test/tier0/lib/commands/test_stonith_history.py
+++ b/pcs_test/tier0/lib/commands/test_stonith_history.py
@@ -1,25 +1,16 @@
-from unittest import mock, TestCase
+from unittest import TestCase
from pcs_test.tools import fixture
from pcs_test.tools.command_env import get_env_tools
-from pcs_test.tools.misc import read_test_resource as rc_read
-from pcs import settings
from pcs.common import report_codes
from pcs.lib.commands import stonith
-crm_mon_rng_with_history = rc_read("crm_mon.rng.with_fence_history.xml")
-crm_mon_rng_without_history = rc_read("crm_mon.rng.without_fence_history.xml")
-
class HistoryGetText(TestCase):
def setUp(self):
self.env_assist, self.config = get_env_tools(test_case=self)
- self.config.fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_with_history)(),
- name="fs.open.crm_mon_rng"
- )
+ self.config.runner.pcmk.can_fence_history_manage()
def test_success_all_nodes(self):
history = (
@@ -68,11 +59,10 @@ class HistoryGetText(TestCase):
)
def test_history_not_supported(self):
- self.config.fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng",
- instead="fs.open.crm_mon_rng"
+ self.config.runner.pcmk.can_fence_history_manage(
+ stderr="not supported",
+ name="runner.pcmk.can_fence_history_manage",
+ instead="runner.pcmk.can_fence_history_manage",
)
self.env_assist.assert_raise_library_error(
lambda: stonith.history_get_text(self.env_assist.get_env()),
@@ -88,11 +78,7 @@ class HistoryGetText(TestCase):
class HistoryCleanup(TestCase):
def setUp(self):
self.env_assist, self.config = get_env_tools(test_case=self)
- self.config.fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_with_history)(),
- name="fs.open.crm_mon_rng"
- )
+ self.config.runner.pcmk.can_fence_history_manage()
def test_success_all_nodes(self):
msg = "cleaning up fencing-history for node *\n"
@@ -129,11 +115,10 @@ class HistoryCleanup(TestCase):
)
def test_history_not_supported(self):
- self.config.fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng",
- instead="fs.open.crm_mon_rng"
+ self.config.runner.pcmk.can_fence_history_manage(
+ stderr="not supported",
+ name="runner.pcmk.can_fence_history_manage",
+ instead="runner.pcmk.can_fence_history_manage",
)
self.env_assist.assert_raise_library_error(
lambda: stonith.history_cleanup(self.env_assist.get_env()),
@@ -149,11 +134,7 @@ class HistoryCleanup(TestCase):
class HistoryUpdate(TestCase):
def setUp(self):
self.env_assist, self.config = get_env_tools(test_case=self)
- self.config.fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_with_history)(),
- name="fs.open.crm_mon_rng"
- )
+ self.config.runner.pcmk.can_fence_history_manage()
def test_success_all_nodes(self):
msg = "gather fencing-history from all nodes\n"
@@ -182,11 +163,10 @@ class HistoryUpdate(TestCase):
)
def test_history_not_supported(self):
- self.config.fs.open(
- settings.crm_mon_schema,
- mock.mock_open(read_data=crm_mon_rng_without_history)(),
- name="fs.open.crm_mon_rng",
- instead="fs.open.crm_mon_rng"
+ self.config.runner.pcmk.can_fence_history_manage(
+ stderr="not supported",
+ name="runner.pcmk.can_fence_history_manage",
+ instead="runner.pcmk.can_fence_history_manage",
)
self.env_assist.assert_raise_library_error(
lambda: stonith.history_update(self.env_assist.get_env()),
diff --git a/pcs_test/tier0/lib/pacemaker/test_live.py b/pcs_test/tier0/lib/pacemaker/test_live.py
index 1ea5454e..d69d8b34 100644
--- a/pcs_test/tier0/lib/pacemaker/test_live.py
+++ b/pcs_test/tier0/lib/pacemaker/test_live.py
@@ -79,7 +79,7 @@ class GetClusterStatusXmlTest(LibraryPacemakerTest):
class GetClusterStatusText(TestCase):
def setUp(self):
self.mock_fencehistory_supported = mock.patch(
- "pcs.lib.pacemaker.live.is_fence_history_supported",
+ "pcs.lib.pacemaker.live.is_fence_history_supported_status",
return_value=True
)
self.mock_fencehistory_supported.start()
@@ -125,7 +125,7 @@ class GetClusterStatusText(TestCase):
def test_success_no_fence_history(self):
self.mock_fencehistory_supported.stop()
self.mock_fencehistory_supported = mock.patch(
- "pcs.lib.pacemaker.live.is_fence_history_supported",
+ "pcs.lib.pacemaker.live.is_fence_history_supported_status",
return_value=False
)
self.mock_fencehistory_supported.start()
@@ -1399,3 +1399,30 @@ class ResourcesWaitingTest(LibraryPacemakerTest):
mock_runner.run.assert_called_once_with(
[self.path("crm_resource"), "--wait"]
)
+
+
+class IsInPcmkToolHelp(TestCase):
+ # pylint: disable=protected-access
+ def test_all_in_stderr(self):
+ mock_runner = get_runner("", "ABCDE", 0)
+ self.assertTrue(
+ lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"])
+ )
+
+ def test_all_in_stdout(self):
+ mock_runner = get_runner("ABCDE", "", 0)
+ self.assertTrue(
+ lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"])
+ )
+
+ def test_some_in_stderr_all_in_stdout(self):
+ mock_runner = get_runner("ABCDE", "ABC", 0)
+ self.assertTrue(
+ lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"])
+ )
+
+ def test_some_in_stderr_some_in_stdout(self):
+ mock_runner = get_runner("CDE", "ABC", 0)
+ self.assertFalse(
+ lib._is_in_pcmk_tool_help(mock_runner, "", ["A", "C", "E"])
+ )
diff --git a/pcs_test/tools/command_env/config_runner_pcmk.py b/pcs_test/tools/command_env/config_runner_pcmk.py
index 5bb9755b..0580e8d6 100644
--- a/pcs_test/tools/command_env/config_runner_pcmk.py
+++ b/pcs_test/tools/command_env/config_runner_pcmk.py
@@ -70,11 +70,52 @@ def _fixture_state_node_xml(
class PcmkShortcuts():
+ #pylint: disable=too-many-public-methods
def __init__(self, calls):
self.__calls = calls
self.default_wait_timeout = DEFAULT_WAIT_TIMEOUT
self.default_wait_error_returncode = WAIT_TIMEOUT_EXPIRED_RETURNCODE
+ def can_fence_history_manage(
+ self,
+ name="runner.pcmk.can_fence_history_manage",
+ stderr="--history --cleanup --broadcast",
+ instead=None,
+ ):
+ """
+ Create a call to check if fence_history is supported by stonith_admin
+
+ string name -- key of the call
+ string stderr -- stonith_admin help text
+ string instead -- key of call instead of which this new call is to be
+ placed
+ """
+ self.__calls.place(
+ name,
+ RunnerCall("stonith_admin --help-all", stderr=stderr),
+ instead=instead,
+ )
+
+ def can_fence_history_status(
+ self,
+ name="runner.pcmk.can_fence_history_status",
+ stderr="--fence-history",
+ instead=None,
+ ):
+ """
+ Create a call to check if fence_history is supported by crm_mon
+
+ string name -- key of the call
+ string stderr -- crm_mon help text
+ string instead -- key of call instead of which this new call is to be
+ placed
+ """
+ self.__calls.place(
+ name,
+ RunnerCall("crm_mon --help-all", stderr=stderr),
+ instead=instead,
+ )
+
def fence_history_get(
self, name="runner.pcmk.fence_history_get", node=None, stdout="",
stderr="", returncode=0
diff --git a/pcs_test/tools/command_env/mock_runner.py b/pcs_test/tools/command_env/mock_runner.py
index 2fe43137..8b9cb771 100644
--- a/pcs_test/tools/command_env/mock_runner.py
+++ b/pcs_test/tools/command_env/mock_runner.py
@@ -61,6 +61,7 @@ COMMAND_COMPLETIONS = {
"crm_ticket": path.join(settings.pacemaker_binaries, "crm_ticket"),
"crm_verify": path.join(settings.pacemaker_binaries, "crm_verify"),
"sbd": settings.sbd_binary,
+ "stonith_admin": path.join(settings.pacemaker_binaries, "stonith_admin"),
}
def complete_command(command):
--
2.21.1

View File

@ -0,0 +1,54 @@
From 898cfe8212a5940dba6552196ddd243f912b5942 Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Tue, 11 Feb 2020 10:18:33 +0100
Subject: [PATCH 5/7] daemon: fix cookie options
---
pcs/daemon/app/session.py | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/pcs/daemon/app/session.py b/pcs/daemon/app/session.py
index b4d29add..dcbb4c23 100644
--- a/pcs/daemon/app/session.py
+++ b/pcs/daemon/app/session.py
@@ -4,10 +4,16 @@ from pcs.daemon.auth import check_user_groups, authorize_user
PCSD_SESSION = "pcsd.sid"
class Mixin:
- __session = None
"""
Mixin for tornado.web.RequestHandler
"""
+
+ __session = None
+ __cookie_options = {
+ "secure": True,
+ "httponly": True,
+ }
+
def initialize(self, session_storage: Storage):
self.__storage = session_storage
@@ -63,7 +69,7 @@ class Mixin:
"""
Write the session id into a response cookie.
"""
- self.set_cookie(PCSD_SESSION, self.session.sid)
+ self.set_cookie(PCSD_SESSION, self.session.sid, **self.__cookie_options)
def put_request_cookies_sid_to_response_cookies_sid(self):
"""
@@ -73,7 +79,9 @@ class Mixin:
#TODO this method should exist temporarily (for sinatra compatibility)
#pylint: disable=invalid-name
if self.__sid_from_client is not None:
- self.set_cookie(PCSD_SESSION, self.__sid_from_client)
+ self.set_cookie(
+ PCSD_SESSION, self.__sid_from_client, **self.__cookie_options
+ )
def was_sid_in_request_cookies(self):
return self.__sid_from_client is not None
--
2.21.1

View File

@ -0,0 +1,53 @@
From 10d13839883a96b35fc609eb51939ec97bc4aac6 Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com>
Date: Tue, 20 Nov 2018 15:03:56 +0100
Subject: [PATCH 2/2] do not support cluster setup with udp(u) transport
---
pcs/pcs.8 | 2 ++
pcs/usage.py | 1 +
pcsd/public/css/style.css | 3 +++
3 files changed, 6 insertions(+)
diff --git a/pcs/pcs.8 b/pcs/pcs.8
index ff2ba0b0..7278c8dc 100644
--- a/pcs/pcs.8
+++ b/pcs/pcs.8
@@ -283,6 +283,8 @@ By default, encryption is enabled with cipher=aes256 and hash=sha256. To disable
Transports udp and udpu:
.br
+WARNING: These transports are not supported in RHEL 8.
+.br
These transports are limited to one address per node. They do not support traffic encryption nor compression.
.br
Transport options are: ip_version, netmtu
diff --git a/pcs/usage.py b/pcs/usage.py
index 30c63964..60373d82 100644
--- a/pcs/usage.py
+++ b/pcs/usage.py
@@ -689,6 +689,7 @@ Commands:
hash=sha256. To disable encryption, set cipher=none and hash=none.
Transports udp and udpu:
+ WARNING: These transports are not supported in RHEL 8.
These transports are limited to one address per node. They do not
support traffic encryption nor compression.
Transport options are:
diff --git a/pcsd/public/css/style.css b/pcsd/public/css/style.css
index b857cbae..b8d48d92 100644
--- a/pcsd/public/css/style.css
+++ b/pcsd/public/css/style.css
@@ -949,6 +949,9 @@ table.args-table td.reg {
width: 6ch;
text-align: right;
}
+#csetup-transport .transport-types {
+ display: none;
+}
#csetup-transport-options.udp .knet-only,
#csetup-transport-options.knet .without-knet
{
--
2.21.1

View File

@ -0,0 +1,5 @@
---
BUNDLE_FROZEN: '1'
BUNDLE_PATH: vendor/bundle
BUNDLE_DISABLE_SHARED_GEMS: '1'
BUNDLE_BUILD: --with-ldflags="-Wl,-z,now -Wl,-z,relro"

View File

@ -0,0 +1,39 @@
From a6708c6bde467cfced3c4a950eadff0375908303 Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Thu, 23 Jan 2020 14:47:49 +0100
Subject: [PATCH 2/7] update a hint for 'resource create ... master'
---
pcs/cli/resource/parse_args.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/pcs/cli/resource/parse_args.py b/pcs/cli/resource/parse_args.py
index 92dddac9..86280edb 100644
--- a/pcs/cli/resource/parse_args.py
+++ b/pcs/cli/resource/parse_args.py
@@ -1,5 +1,5 @@
from pcs.cli.common.parse_args import group_by_keywords, prepare_options
-from pcs.cli.common.errors import CmdLineInputError, HINT_SYNTAX_CHANGE
+from pcs.cli.common.errors import CmdLineInputError, SEE_MAN_CHANGES
def parse_create_simple(arg_list):
@@ -51,7 +51,14 @@ def parse_create(arg_list):
# manpage.
# To be removed in the next significant version.
if e.message == "missing value of 'master' option":
- raise CmdLineInputError(message=e.message, hint=HINT_SYNTAX_CHANGE)
+ raise CmdLineInputError(
+ message=e.message,
+ hint=(
+ "Master/Slave resources have been renamed to promotable "
+ "clones, please use the 'promotable' keyword instead of "
+ "'master'. " + SEE_MAN_CHANGES
+ )
+ )
raise e
return parts
--
2.21.1

933
SPECS/pcs.spec Normal file
View File

@ -0,0 +1,933 @@
Name: pcs
Version: 0.10.4
Release: 6%{?dist}
# https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses
# GPLv2: pcs
# ASL 2.0: tornado
# MIT: handlebars
License: GPLv2 and ASL 2.0 and MIT
URL: https://github.com/ClusterLabs/pcs
Group: System Environment/Base
Summary: Pacemaker Configuration System
#building only for architectures with pacemaker and corosync available
ExclusiveArch: i686 x86_64 s390x ppc64le aarch64
%global version_or_commit %{version}
# %%global version_or_commit 5c3f35d2819b0e8be0dcbe0ee8f81b9b24b20b54
%global pcs_source_name %{name}-%{version_or_commit}
# ui_commit can be determined by hash, tag or branch
%global ui_commit 0.1.2
%global ui_src_name pcs-web-ui-%{ui_commit}
%global pcs_snmp_pkg_name pcs-snmp
%global pyagentx_version 0.4.pcs.2
%global tornado_version 6.0.3
%global version_rubygem_backports 3.11.4
%global version_rubygem_daemons 1.3.1
%global version_rubygem_ethon 0.11.0
%global version_rubygem_eventmachine 1.2.7
%global version_rubygem_ffi 1.9.25
%global version_rubygem_json 2.1.0
%global version_rubygem_mustermann 1.0.3
%global version_rubygem_open4 1.3.4
%global version_rubygem_rack 2.0.6
%global version_rubygem_rack_protection 2.0.4
%global version_rubygem_rack_test 1.0.0
%global version_rubygem_sinatra 2.0.4
%global version_rubygem_thin 1.7.2
%global version_rubygem_tilt 2.0.9
# We do not use _libdir macro because upstream is not prepared for it.
# Pcs does not include binaries and thus it should live in /usr/lib. Tornado
# and gems include binaries and thus it should live in /usr/lib64. But the
# path to tornado/gems is hardcoded in pcs sources. Modify hard links in pcs
# sources is not the way since then rpmdiff complains that the same file has
# different content in different architectures.
%global pcs_libdir %{_prefix}/lib
%global bundled_src_dir pcs/bundled
%global pcsd_public_dir pcsd/public
%global rubygem_cache_dir pcsd/vendor/cache
%global rubygem_bundle_dir pcsd/vendor/bundle/ruby
# mangling shebang in /usr/lib/pcsd/vendor/bundle/ruby/gems/rack-2.0.5/test/cgi/test from /usr/bin/env ruby to #!/usr/bin/ruby
#*** ERROR: ./usr/lib/pcsd/vendor/bundle/ruby/gems/rack-2.0.5/test/cgi/test.ru has shebang which doesn't start with '/' (../../bin/rackup)
#mangling shebang in /usr/lib/pcsd/vendor/bundle/ruby/gems/rack-2.0.5/test/cgi/rackup_stub.rb from /usr/bin/env ruby to #!/usr/bin/ruby
#*** WARNING: ./usr/lib/pcsd/vendor/bundle/ruby/gems/rack-2.0.5/test/cgi/sample_rackup.ru is executable but has empty or no shebang, removing executable bit
#*** WARNING: ./usr/lib/pcsd/vendor/bundle/ruby/gems/rack-2.0.5/test/cgi/lighttpd.conf is executable but has empty or no shebang, removing executable bit
#*** ERROR: ambiguous python shebang in /usr/lib/pcsd/vendor/bundle/ruby/gems/ffi-1.9.25/ext/ffi_c/libffi/generate-darwin-source-and-headers.py: #!/usr/bin/env python. Change it to python3 (or python2) explicitly.
%undefine __brp_mangle_shebangs
# https://fedoraproject.org/wiki/Changes/Avoid_usr_bin_python_in_RPM_Build#Python_bytecompilation
# Enforce python3 because bytecompilation of tornado produced warnings:
# DEPRECATION WARNING: python2 invoked with /usr/bin/python.
# Use /usr/bin/python3 or /usr/bin/python2
# /usr/bin/python will be removed or switched to Python 3 in the future.
%global __python %{__python3}
Source0: %{url}/archive/%{version_or_commit}/%{pcs_source_name}.tar.gz
Source1: HAM-logo.png
Source2: pcsd-bundle-config-2
Source41: https://github.com/ondrejmular/pyagentx/archive/v%{pyagentx_version}/pyagentx-%{pyagentx_version}.tar.gz
Source42: https://github.com/tornadoweb/tornado/archive/v%{tornado_version}/tornado-%{tornado_version}.tar.gz
Source81: https://rubygems.org/downloads/backports-%{version_rubygem_backports}.gem
Source82: https://rubygems.org/downloads/ethon-%{version_rubygem_ethon}.gem
Source83: https://rubygems.org/downloads/ffi-%{version_rubygem_ffi}.gem
Source84: https://rubygems.org/downloads/json-%{version_rubygem_json}.gem
Source86: https://rubygems.org/downloads/mustermann-%{version_rubygem_mustermann}.gem
# We needed to re-upload open4 rubygem because of issues with sources in gating.
# Unfortunately, there was no newer version available, therefore we had to
# change its 'version' ourselves.
Source87: https://rubygems.org/downloads/open4-%{version_rubygem_open4}.gem#/open4-%{version_rubygem_open4}-1.gem
Source88: https://rubygems.org/downloads/rack-%{version_rubygem_rack}.gem
Source89: https://rubygems.org/downloads/rack-protection-%{version_rubygem_rack_protection}.gem
Source90: https://rubygems.org/downloads/rack-test-%{version_rubygem_rack_test}.gem
Source91: https://rubygems.org/downloads/sinatra-%{version_rubygem_sinatra}.gem
Source92: https://rubygems.org/downloads/tilt-%{version_rubygem_tilt}.gem
Source93: https://rubygems.org/downloads/eventmachine-%{version_rubygem_eventmachine}.gem
Source94: https://rubygems.org/downloads/daemons-%{version_rubygem_daemons}.gem
Source95: https://rubygems.org/downloads/thin-%{version_rubygem_thin}.gem
Source100: https://github.com/idevat/pcs-web-ui/archive/%{ui_commit}/%{ui_src_name}.tar.gz
Source101: https://github.com/idevat/pcs-web-ui/releases/download/%{ui_commit}/pcs-web-ui-node-modules-%{ui_commit}.tar.xz
# Patches from upstream.
# They should come before downstream patches to avoid unnecessary conflicts.
# Z-streams are exception here: they can come from upstream but should be
# applied at the end to keep z-stream changes as straightforward as possible.
Patch1: bz1676431-01-Display-status-of-disaster-recovery.patch
Patch2: bz1743731-01-fix-error-msg-when-cluster-is-not-set-up.patch
Patch3: bz1792946-01-tests-update-for-pacemaker-2.0.3-4.patch
Patch4: bz1781303-01-fix-safe-disabling-clones-groups-bundles.patch
Patch5: update-a-hint-for-resource-create-master.patch
Patch6: bz1793574-01-fix-detecting-fence-history-support.patch
Patch7: bz1750427-01-link-to-sbd-man-page-from-sbd-enable-doc.patch
Patch8: daemon-fix-cookie-options.patch
Patch9: bz1783106-01-fix-sinatra-wrapper-performance-issue.patch
Patch10: bz1783106-02-send-request-from-python-to-ruby-more-directly.patch
# Downstream patches do not come from upstream. They adapt pcs for specific
# RHEL needs.
Patch101: do-not-support-cluster-setup-with-udp-u-transport.patch
# git for patches
BuildRequires: git
#printf from coreutils is used in makefile
BuildRequires: coreutils
BuildRequires: execstack
# python for pcs
BuildRequires: platform-python
BuildRequires: python3-devel
BuildRequires: platform-python-setuptools
BuildRequires: python3-pycurl
# gcc for compiling custom rubygems
BuildRequires: gcc
BuildRequires: gcc-c++
# ruby and gems for pcsd
BuildRequires: ruby >= 2.2.0
BuildRequires: ruby-devel
BuildRequires: rubygems
# ruby libraries for tests
BuildRequires: rubygem-test-unit
# for touching patch files (sanitization function)
BuildRequires: diffstat
# for post, preun and postun macros
BuildRequires: systemd
# for tests
BuildRequires: python3-lxml
BuildRequires: python3-pyOpenSSL
BuildRequires: pacemaker-cli >= 2.0.0
# BuildRequires: fence-agents-all
BuildRequires: fence-agents-apc
BuildRequires: fence-agents-scsi
BuildRequires: fence-agents-ipmilan
# for tests
%ifarch i686 x86_64
BuildRequires: fence-virt
%endif
BuildRequires: booth-site
# pcsd fonts and font management tools for creating symlinks to fonts
BuildRequires: fontconfig
BuildRequires: liberation-sans-fonts
BuildRequires: overpass-fonts
# Red Hat logo for creating symlink of favicon
BuildRequires: redhat-logos
# for building web ui
BuildRequires: npm
# python and libraries for pcs, setuptools for pcs entrypoint
Requires: platform-python
Requires: python3-lxml
Requires: platform-python-setuptools
Requires: python3-clufter => 0.70.0
Requires: python3-pycurl
# ruby and gems for pcsd
Requires: ruby >= 2.2.0
Requires: rubygems
# for killall
Requires: psmisc
# for working with certificates (validation etc.)
Requires: openssl
Requires: python3-pyOpenSSL
# cluster stack and related packages
Requires: pcmk-cluster-manager >= 2.0.0
Suggests: pacemaker
Requires: (corosync >= 2.99 if pacemaker)
# pcs enables corosync encryption by default so we require libknet1-plugins-all
Requires: (libknet1-plugins-all if corosync)
Requires: pacemaker-cli >= 2.0.0
# for post, preun and postun macros
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
# pam is used for authentication inside daemon (python ctypes)
# more details: https://bugzilla.redhat.com/show_bug.cgi?id=1717113
Requires: pam
# pcsd fonts
Requires: liberation-sans-fonts
Requires: overpass-fonts
# favicon Red Hat logo
Requires: redhat-logos
Provides: bundled(tornado) = %{tornado_version}
Provides: bundled(backports) = %{version_rubygem_backports}
Provides: bundled(daemons) = %{version_rubygem_daemons}
Provides: bundled(ethon) = %{version_rubygem_ethon}
Provides: bundled(eventmachine) = %{version_rubygem_eventmachine}
Provides: bundled(ffi) = %{version_rubygem_ffi}
Provides: bundled(json) = %{version_rubygem_json}
Provides: bundled(mustermann) = %{version_rubygem_mustermann}
Provides: bundled(open4) = %{version_rubygem_open4}
Provides: bundled(rack) = %{version_rubygem_rack}
Provides: bundled(rack) = %{version_rubygem_rack_protection}
Provides: bundled(rack) = %{version_rubygem_rack_test}
Provides: bundled(sinatra) = %{version_rubygem_sinatra}
Provides: bundled(thin) = %{version_rubygem_thin}
Provides: bundled(tilt) = %{version_rubygem_tilt}
%description
pcs is a corosync and pacemaker configuration tool. It permits users to
easily view, modify and create pacemaker based clusters.
# pcs-snmp package definition
%package -n %{pcs_snmp_pkg_name}
Group: System Environment/Base
Summary: Pacemaker cluster SNMP agent
# https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Good_Licenses
# GPLv2: pcs
# BSD-2-Clause: pyagentx
License: GPLv2 and BSD-2-Clause
URL: https://github.com/ClusterLabs/pcs
# tar for unpacking pyagetx source tar ball
BuildRequires: tar
Requires: pcs = %{version}-%{release}
Requires: pacemaker
Requires: net-snmp
Provides: bundled(pyagentx) = %{pyagentx_version}
%description -n %{pcs_snmp_pkg_name}
SNMP agent that provides information about pacemaker cluster to the master agent (snmpd)
%prep
%autosetup -p1 -S git -n %{pcs_source_name}
# -- following is inspired by python-simplejon.el5 --
# Update timestamps on the files touched by a patch, to avoid non-equal
# .pyc/.pyo files across the multilib peers within a build
update_times(){
# update_times <reference_file> <file_to_touch> ...
# set the access and modification times of each file_to_touch to the times
# of reference_file
# put all args to file_list
file_list=("$@")
# first argument is reference_file: so take it and remove from file_list
reference_file=${file_list[0]}
unset file_list[0]
for fname in ${file_list[@]}; do
touch -r $reference_file $fname
done
}
update_times_patch(){
# update_times_patch <patch_file_name>
# set the access and modification times of each file in patch to the times
# of patch_file_name
patch_file_name=$1
# diffstat
# -l lists only the filenames. No histogram is generated.
# -p override the logic that strips common pathnames,
# simulating the patch "-p" option. (Strip the smallest prefix containing
# num leading slashes from each file name found in the patch file)
update_times ${patch_file_name} `diffstat -p1 -l ${patch_file_name}`
}
update_times_patch %{PATCH1}
update_times_patch %{PATCH2}
update_times_patch %{PATCH3}
update_times_patch %{PATCH4}
update_times_patch %{PATCH5}
update_times_patch %{PATCH6}
update_times_patch %{PATCH7}
update_times_patch %{PATCH8}
update_times_patch %{PATCH9}
update_times_patch %{PATCH10}
update_times_patch %{PATCH101}
cp -f %SOURCE1 pcsd/public/images
# prepare dirs/files necessary for building web ui
# inside SOURCE100 is only directory %%{ui_src_name}
tar -xzf %SOURCE100 -C %{pcsd_public_dir}
tar -xf %SOURCE101 -C %{pcsd_public_dir}/%{ui_src_name}
# prepare dirs/files necessary for building all bundles
# -----------------------------------------------------
# 1) configuration for rubygems
mkdir -p pcsd/.bundle
cp -f %SOURCE2 pcsd/.bundle/config
# 2) rubygems sources
mkdir -p pcsd/vendor/cache
cp -f %SOURCE81 pcsd/vendor/cache
cp -f %SOURCE82 pcsd/vendor/cache
cp -f %SOURCE83 pcsd/vendor/cache
cp -f %SOURCE84 pcsd/vendor/cache
cp -f %SOURCE86 pcsd/vendor/cache
# For reason why we are renaming open4 rubygem, see comment of source
# definition above.
cp -f %SOURCE87 pcsd/vendor/cache/open4-%{version_rubygem_open4}.gem
cp -f %SOURCE88 pcsd/vendor/cache
cp -f %SOURCE89 pcsd/vendor/cache
cp -f %SOURCE90 pcsd/vendor/cache
cp -f %SOURCE91 pcsd/vendor/cache
cp -f %SOURCE92 pcsd/vendor/cache
cp -f %SOURCE93 pcsd/vendor/cache
cp -f %SOURCE94 pcsd/vendor/cache
cp -f %SOURCE95 pcsd/vendor/cache
# 3) dir for python bundles
mkdir -p %{bundled_src_dir}
# 4) sources for pyagentx
tar -xzf %SOURCE41 -C %{bundled_src_dir}
mv %{bundled_src_dir}/pyagentx-%{pyagentx_version} %{bundled_src_dir}/pyagentx
update_times %SOURCE41 `find %{bundled_src_dir}/pyagentx -follow`
cp %{bundled_src_dir}/pyagentx/LICENSE.txt pyagentx_LICENSE.txt
cp %{bundled_src_dir}/pyagentx/CONTRIBUTORS.txt pyagentx_CONTRIBUTORS.txt
cp %{bundled_src_dir}/pyagentx/README.md pyagentx_README.md
# 5) sources for tornado
tar -xzf %SOURCE42 -C %{bundled_src_dir}
mv %{bundled_src_dir}/tornado-%{tornado_version} %{bundled_src_dir}/tornado
update_times %SOURCE42 `find %{bundled_src_dir}/tornado -follow`
cp %{bundled_src_dir}/tornado/LICENSE tornado_LICENSE
cp %{bundled_src_dir}/tornado/README.rst tornado_README.rst
%build
%define debug_package %{nil}
%install
rm -rf $RPM_BUILD_ROOT
pwd
# build bundled rubygems (in main install it is disabled by BUILD_GEMS=false)
mkdir -p %{rubygem_bundle_dir}
gem install \
--force --verbose --no-rdoc --no-ri -l --no-user-install \
-i %{rubygem_bundle_dir} \
%{rubygem_cache_dir}/backports-%{version_rubygem_backports}.gem \
%{rubygem_cache_dir}/daemons-%{version_rubygem_daemons}.gem \
%{rubygem_cache_dir}/ethon-%{version_rubygem_ethon}.gem \
%{rubygem_cache_dir}/eventmachine-%{version_rubygem_eventmachine}.gem \
%{rubygem_cache_dir}/ffi-%{version_rubygem_ffi}.gem \
%{rubygem_cache_dir}/json-%{version_rubygem_json}.gem \
%{rubygem_cache_dir}/mustermann-%{version_rubygem_mustermann}.gem \
%{rubygem_cache_dir}/open4-%{version_rubygem_open4}.gem \
%{rubygem_cache_dir}/rack-protection-%{version_rubygem_rack_protection}.gem \
%{rubygem_cache_dir}/rack-test-%{version_rubygem_rack_test}.gem \
%{rubygem_cache_dir}/rack-%{version_rubygem_rack}.gem \
%{rubygem_cache_dir}/sinatra-%{version_rubygem_sinatra}.gem \
%{rubygem_cache_dir}/thin-%{version_rubygem_thin}.gem \
%{rubygem_cache_dir}/tilt-%{version_rubygem_tilt}.gem \
-- '--with-ldflags="-Wl,-z,relro -Wl,-z,ibt -Wl,-z,now -Wl,--gc-sections"' \
'--with-cflags="-O2 -ffunction-sections"'
# We can remove files required for gem compilation
rm -rf %{rubygem_bundle_dir}/gems/ffi-%{version_rubygem_ffi}/ext
rm -rf %{rubygem_bundle_dir}/gems/json-%{version_rubygem_json}/ext
# With this file there is "File is not stripped" problem during rpmdiff
# See https://docs.engineering.redhat.com/display/HTD/rpmdiff-elf-stripping
for fname in `find %{rubygem_bundle_dir}/extensions -type f -name "*.so"`; do
strip ${fname}
done
# build web ui and put it to pcsd
make -C %{pcsd_public_dir}/%{ui_src_name} build
mv %{pcsd_public_dir}/%{ui_src_name}/build pcsd/public/ui
rm -r %{pcsd_public_dir}/%{ui_src_name}
# main pcs install
make install \
DESTDIR=$RPM_BUILD_ROOT \
PREFIX=%{_prefix} \
SYSTEMD_UNIT_DIR=%{_unitdir} \
LIB_DIR=%{pcs_libdir} \
PYTHON=%{__python3} \
PYTHON_SITELIB=%{python3_sitelib} \
BASH_COMPLETION_DIR=%{_datadir}/bash-completion/completions \
BUNDLE_PYAGENTX_SRC_DIR=`readlink -f %{bundled_src_dir}/pyagentx` \
BUNDLE_TORNADO_SRC_DIR=`readlink -f %{bundled_src_dir}/tornado` \
BUILD_GEMS=false \
SYSTEMCTL_OVERRIDE=true \
hdrdir="%{_includedir}" \
rubyhdrdir="%{_includedir}" \
includedir="%{_includedir}"
# With this file there is "File is not stripped" problem during rpmdiff
# See https://docs.engineering.redhat.com/display/HTD/rpmdiff-elf-stripping
for fname in `find ${RPM_BUILD_ROOT}%{pcs_libdir}/pcs/bundled/packages/tornado/ -type f -name "*.so"`; do
strip ${fname}
done
# symlink favicon into pcsd directories
ln -fs /etc/favicon.png ${RPM_BUILD_ROOT}%{pcs_libdir}/%{pcsd_public_dir}/images/favicon.png
#after the ruby gem compilation we do not need ruby gems in the cache
rm -r -v $RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_cache_dir}
%check
# In the building environment LC_CTYPE is set to C which causes tests to fail
# due to python prints a warning about it to stderr. The following environment
# variable disables the warning.
# On the live system either UTF8 locale is set or the warning is emmited
# which breaks pcs. That is the correct behavior since with wrong locales it
# would be probably broken anyway.
# The main concern here is to make the tests pass.
# See https://fedoraproject.org/wiki/Changes/python3_c.utf-8_locale for details.
export PYTHONCOERCECLOCALE=0
run_all_tests(){
#run pcs tests
# disabled tests:
#
# pcs_test.tier0.lib.commands.test_resource_agent.DescribeAgentUtf8.test_describe
# For an unknown reason this test is failing in mock environment and
# passing outside the mock environment.
# TODO: Investigate the issue
BUNDLED_LIB_LOCATION=$RPM_BUILD_ROOT%{pcs_libdir}/pcs/bundled/packages \
%{__python3} pcs_test/suite.py -v --vanilla --all-but \
pcs_test.tier0.lib.commands.test_resource_agent.DescribeAgentUtf8.test_describe \
pcs_test.tier0.daemon.app.test_app_remote.SyncConfigMutualExclusive.test_get_not_locked \
pcs_test.tier0.daemon.app.test_app_remote.SyncConfigMutualExclusive.test_post_not_locked \
test_result_python=$?
#run pcsd tests and remove them
pcsd_dir=$RPM_BUILD_ROOT%{pcs_libdir}/pcsd
GEM_HOME=$RPM_BUILD_ROOT%{pcs_libdir}/%{rubygem_bundle_dir} ruby \
-I${pcsd_dir} \
-I${pcsd_dir}/test \
${pcsd_dir}/test/test_all_suite.rb
test_result_ruby=$?
if [ $test_result_python -ne 0 ]; then
return $test_result_python
fi
return $test_result_ruby
}
remove_all_tests() {
pcsd_dir=$RPM_BUILD_ROOT%{pcs_libdir}/pcsd
#remove pcsd tests, we do not distribute them in the rpm
rm -r -v ${pcsd_dir}/test
# remove javascript testing files
rm -r -v ${pcsd_dir}/public/js/dev
}
run_all_tests
remove_all_tests
%posttrans
# Make sure the new version of the daemon is runnning.
# Also, make sure to start pcsd-ruby if it hasn't been started or even
# installed before. This is done by restarting pcsd.service.
%{_bindir}/systemctl daemon-reload
%{_bindir}/systemctl try-restart pcsd.service
%post
%systemd_post pcsd.service
%systemd_post pcsd-ruby.service
%post -n %{pcs_snmp_pkg_name}
%systemd_post pcs_snmp_agent.service
%preun
%systemd_preun pcsd.service
%systemd_preun pcsd-ruby.service
%preun -n %{pcs_snmp_pkg_name}
%systemd_preun pcs_snmp_agent.service
%postun
%systemd_postun_with_restart pcsd.service
%systemd_postun_with_restart pcsd-ruby.service
%postun -n %{pcs_snmp_pkg_name}
%systemd_postun_with_restart pcs_snmp_agent.service
%files
%doc CHANGELOG.md
%doc README.md
%doc tornado_README.rst
%license tornado_LICENSE
%license COPYING
%{python3_sitelib}/pcs
%{python3_sitelib}/pcs-%{version}-py3.*.egg-info
%{_sbindir}/pcs
%{_sbindir}/pcsd
%{pcs_libdir}/pcs/pcs_internal
%{pcs_libdir}/pcsd/*
%{pcs_libdir}/pcsd/.bundle/config
%{pcs_libdir}/pcs/bundled/packages/tornado*
%{_unitdir}/pcsd.service
%{_unitdir}/pcsd-ruby.service
%{_datadir}/bash-completion/completions/pcs
%{_sharedstatedir}/pcsd
%{_sysconfdir}/pam.d/pcsd
%dir %{_var}/log/pcsd
%config(noreplace) %{_sysconfdir}/logrotate.d/pcsd
%config(noreplace) %{_sysconfdir}/sysconfig/pcsd
%ghost %config(noreplace) %attr(0600,root,root) %{_sharedstatedir}/pcsd/cfgsync_ctl
%ghost %config(noreplace) %attr(0600,root,root) %{_sharedstatedir}/pcsd/known-hosts
%ghost %config(noreplace) %attr(0600,root,root) %{_sharedstatedir}/pcsd/pcsd.cookiesecret
%ghost %config(noreplace) %attr(0600,root,root) %{_sharedstatedir}/pcsd/pcsd.crt
%ghost %config(noreplace) %attr(0600,root,root) %{_sharedstatedir}/pcsd/pcsd.key
%ghost %config(noreplace) %attr(0644,root,root) %{_sharedstatedir}/pcsd/pcs_settings.conf
%ghost %config(noreplace) %attr(0644,root,root) %{_sharedstatedir}/pcsd/pcs_users.conf
%{_mandir}/man8/pcs.*
%{_mandir}/man8/pcsd.*
%exclude %{pcs_libdir}/pcsd/*.debian
%exclude %{pcs_libdir}/pcsd/pcsd.service
%exclude %{pcs_libdir}/pcsd/pcsd-ruby.service
%exclude %{pcs_libdir}/pcsd/pcsd.conf
%exclude %{pcs_libdir}/pcsd/pcsd.8
%exclude %{pcs_libdir}/pcsd/public/js/dev/*
%exclude %{pcs_libdir}/pcsd/Gemfile
%exclude %{pcs_libdir}/pcsd/Gemfile.lock
%exclude %{pcs_libdir}/pcsd/Makefile
%exclude %{python3_sitelib}/pcs/bash_completion
%exclude %{python3_sitelib}/pcs/pcs.8
%exclude %{python3_sitelib}/pcs/pcs
%files -n %{pcs_snmp_pkg_name}
%{pcs_libdir}/pcs/pcs_snmp_agent
%{pcs_libdir}/pcs/bundled/packages/pyagentx*
%{_unitdir}/pcs_snmp_agent.service
%{_datadir}/snmp/mibs/PCMK-PCS*-MIB.txt
%{_mandir}/man8/pcs_snmp_agent.*
%config(noreplace) %{_sysconfdir}/sysconfig/pcs_snmp_agent
%doc CHANGELOG.md
%doc pyagentx_CONTRIBUTORS.txt
%doc pyagentx_README.md
%license COPYING
%license pyagentx_LICENSE.txt
%changelog
* Fri Mar 20 2020 Miroslav Lisik <mlisik@redhat.com> - 0.10.4-6
- Fixed communication between python and ruby daemons
- Resolves: rhbz#1783106
* Thu Feb 13 2020 Miroslav Lisik <mlisik@redhat.com> - 0.10.4-5
- Fixed link to sbd man page from `sbd enable` doc
- Fixed safe-disabling clones, groups, bundles
- Fixed sinatra wrapper performance issue
- Fixed detecting fence history support
- Fixed cookie options
- Updated hint for 'resource create ... master'
- Updated gating tests execution, smoke tests run from upstream sources
- Resolves: rhbz#1750427 rhbz#1781303 rhbz#1783106 rhbz#1793574
* Mon Jan 20 2020 Tomas Jelinek <tojeline@redhat.com> - 0.10.4-4
- Fix testsuite for pacemaker-2.0.3-4
- Resolves: rhbz#1792946
* Mon Dec 02 2019 Ivan Devat <idevat@redhat.com> - 0.10.4-3
- Added basic resource views in new webUI
- Resolves: rhbz#1744060
* Fri Nov 29 2019 Miroslav Lisik <mlisik@redhat.com> - 0.10.4-2
- Added disaster recovery support
- Fixed error message when cluster is not set up
- Removed '-g' option from rubygem's cflags because it does not generate .gnu_debugdata and option '-K' for strip command was removed
- Resolves: rhbz#1676431 rhbz#1743731
* Thu Nov 28 2019 Miroslav Lisik <mlisik@redhat.com> - 0.10.4-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Add '-g' to rubygem's cflags
- Resolves: rhbz#1743704 rhbz#1741586 rhbz#1750427
* Mon Nov 18 2019 Miroslav Lisik <mlisik@redhat.com> - 0.10.3-2
- Rebased to latest upstream sources (see CHANGELOG.md)
- Do not strip .gnu_debugdata section from binaries
- Resolves: rhbz#1631514 rhbz#1631519 rhbz#1734361 rhbz#1743704
* Mon Oct 21 2019 Miroslav Lisik <mlisik@redhat.com> - 0.10.3-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Resolves: rhbz#1442116 rhbz#1631514 rhbz#1631519 rhbz#1673835 rhbz#1698763 rhbz#1728890 rhbz#1734361 rhbz#1743704 rhbz#1743735 rhbz#1744056
* Tue Aug 13 2019 Tomas Jelinek <tojeline@redhat.com> - 0.10.2-4
- Generate 256 bytes long corosync authkey so clusters can start when FIPS is enabled
- Resolves: rhbz#1740218
* Mon Jul 08 2019 Ivan Devat <idevat@redhat.com> - 0.10.2-3
- Options starting with - and -- are no longer ignored for non-root users
- Resolves: rhbz#1725183
* Thu Jun 27 2019 Ivan Devat <idevat@redhat.com> - 0.10.2-2
- Fixed crashes in the `pcs host auth` command
- Command `pcs resource bundle reset` no longer accepts the container type
- Fixed id conflict with current bundle configuration in i`pcs resource bundle reset`
- Resolves: rhbz#1657166 rhbz#1676957
* Thu Jun 13 2019 Ivan Devat <idevat@redhat.com> - 0.10.2-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Added pam as required package
- An alternative webUI rebased to latest upstream sources
- Resolves: rhbz#1673907 rhbz#1679196 rhbz#1714663 rhbz#1717113
* Tue May 21 2019 Ivan Devat <idevat@redhat.com> - 0.10.1-9
- Added git as required package in tests/tests.yml
- Resolves: rhbz#1673907
* Mon May 20 2019 Ivan Devat <idevat@redhat.com> - 0.10.1-8
- Rebased to latest upstream sources (see CHANGELOG.md)
- Added an alternative webUI
- Resolves: rhbz#1673907 rhbz#1679197 rhbz#1667058
* Mon May 06 2019 Ondrej Mular <omular@redhat.com> - 0.10.1-7
- Enable upstream tests in gating
- Update tilt ruby gem
- Resolves: rhbz#1682129
* Thu May 02 2019 Ondrej Mular <omular@redhat.com> - 0.10.1-6
- Rebased to latest upstream sources (see CHANGELOG.md)
- Updated Red Hat logo
- Improved configuration files permissions in rpm
- Removed multi_json rubygem
- Excluded files required for building gems from rpm
- Resolves: rhbz#1625386 rhbz#1653316 rhbz#1655055 rhbz#1657166 rhbz#1659144 rhbz#1660702 rhbz#1664828 rhbz#1665404 rhbz#1667040 rhbz#1667053 rhbz#1667058 rhbz#1667090 rhbz#1668223 rhbz#1673822 rhbz#1673825 rhbz#1674005 rhbz#1676945 rhbz#1676957 rhbz#1682129 rhbz#1687562 rhbz#1687965 rhbz#1698373 rhbz#1700543
* Mon Mar 25 2019 Ondrej Mular <omular@redhat.com> - 0.10.1-5
- Enable gating
- Resolves: rhbz#1682129
* Wed Jan 30 2019 Ivan Devat <idevat@redhat.com> - 0.10.1-4
- Fixed crash when using unsupported options in commands `pcs status` and `pcs config`
- Resolves: rhbz#1668422
* Mon Jan 14 2019 Ivan Devat <idevat@redhat.com> - 0.10.1-3
- Fixed configuration names of link options that pcs puts to corosync.conf during cluster setup
- Fixed webUI issues in cluster setup
- Command `pcs resource show` was returned back and was signed as deprecated
- Added dependency on libknet1-plugins-all
- Resolves: rhbz#1661059 rhbz#1659051 rhbz#1664057
* Thu Dec 13 2018 Ondrej Mular <omular@redhat.com> - 0.10.1-2
- Fix documentation
- Resolves: rhbz#1656953
* Fri Nov 23 2018 Ivan Devát <idevat@redhat.com> - 0.10.1-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Udp/udpu transport is marked as unsupported
- Require pcmk-cluster-manager instead of pacemaker
- Require platform-python-setuptools instead of python3-setuptools
- Resolves: rhbz#1650109 rhbz#1183103 rhbz#1648942 rhbz#1650510 rhbz#1388398 rhbz#1651197 rhbz#1553736
* Fri Oct 26 2018 Ondrej Mular <omular@redhat.com> - 0.10.0.alpha.7-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Resolves: rhbz#1553736 rhbz#1615891 rhbz#1436217 rhbz#1596050 rhbz#1554310 rhbz#1553718 rhbz#1638852 rhbz#1620190 rhbz#1158816 rhbz#1640477
* Wed Oct 17 2018 Ondrej Mular <omular@redhat.com> - 0.10.0.alpha.6-3
- Add dependency on rubygems package
- Resolves: rhbz#1553736
* Thu Oct 04 2018 Ondrej Mular <omular@redhat.com> - 0.10.0.alpha.6-2
- Enable tests
- Cleanup of unnecessary bundle ruby-gem files
- Switch Require: python3 dependency to platform-python
- Added required linker flags
- Resolves: rhbz#1633613 rhbz#1630616
* Wed Sep 19 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.6-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Resolves: rhbz#1553736
* Thu Sep 13 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.5-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Resolves: rhbz#1553736 rhbz#1542288 rhbz#1619620
* Thu Sep 13 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.4-2
- Fixed symlinks correction for rubygem ffi
- Resolves: rhbz#1553736
* Wed Sep 12 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.4-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Resolves: rhbz#1549535 rhbz#1578955 rhbz#1158816 rhbz#1183103 rhbz#1536121 rhbz#1573344 rhbz#1619620 rhbz#1533866 rhbz#1578898 rhbz#1595829 rhbz#1605185 rhbz#1615420 rhbz#1566430 rhbz#1578891 rhbz#1591308 rhbz#1554953
* Mon Aug 06 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.3-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Removed ruby dependencies (the dependencies are bundled instead)
- Resolves: rhbz#1611990
* Thu Aug 02 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.2-1
- Rebased to latest upstream sources (see CHANGELOG.md)
* Wed Jul 18 2018 Ivan Devát <idevat@redhat.com> - 0.10.0.alpha.1-1
- Rebased to latest upstream sources (see CHANGELOG.md)
* Mon Apr 09 2018 Ondrej Mular <omular@redhat.com> - 0.9.164-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Fixed: CVE-2018-1086, CVE-2018-1079
* Mon Feb 26 2018 Ivan Devát <idevat@redhat.com> - 0.9.163-2
- Fixed crash when adding a node to a cluster
* Tue Feb 20 2018 Ivan Devát <idevat@redhat.com> - 0.9.163-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- Adapted for Rack 2 and Sinatra 2
* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 0.9.160-5
- Escape macros in %%changelog
* Thu Feb 08 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.160-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
* Sat Jan 20 2018 Björn Esser <besser82@fedoraproject.org> - 0.9.160-3
- Rebuilt for switch to libxcrypt
* Fri Jan 05 2018 Mamoru TASAKA <mtasaka@fedoraproject.org> - 0.9.160-2
- F-28: rebuild for ruby25
- Workaround for gem install option
* Wed Oct 18 2017 Ondrej Mular <omular@redhat.com> - 0.9.160-1
- Rebased to latest upstream sources (see CHANGELOG.md)
- All pcs tests are temporarily disabled because of issues in pacemaker.
* Thu Sep 14 2017 Ondrej Mular <omular@redhat.com> - 0.9.159-4
- Bundle rubygem-rack-protection which is being updated to 2.0.0 in Fedora.
- Removed setuptools patch.
- Disabled debuginfo subpackage.
* Thu Aug 03 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.159-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild
* Thu Jul 27 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.159-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
* Wed Jul 12 2017 Ondrej Mular <omular@redhat.com> - 0.9.159-1
- Rebased to latest upstream sources (see CHANGELOG.md)
* Tue May 23 2017 Tomas Jelinek <tojeline@redhat.com> - 0.9.156-3
- Fixed python locales issue preventing build-time tests to pass
- Bundle rubygem-tilt which is being retired from Fedora
* Thu Mar 23 2017 Tomas Jelinek <tojeline@redhat.com> - 0.9.156-2
- Fixed Cross-site scripting (XSS) vulnerability in web UI CVE-2017-2661
- Re-added support for clufter as it is now available for Python 3
* Wed Feb 22 2017 Tomas Jelinek <tojeline@redhat.com> - 0.9.156-1
- Rebased to latest upstream sources (see CHANGELOG.md)
* Sat Feb 11 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.155-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
* Thu Jan 12 2017 Vít Ondruch <vondruch@redhat.com> - 0.9.155-2
- Rebuilt for https://fedoraproject.org/wiki/Changes/Ruby_2.4
* Wed Jan 04 2017 Adam Williamson <awilliam@redhat.com> - 0.9.155-1
- Latest release 0.9.155
- Fix tests with Python 3.6 and lxml 3.7
- Package the license as license, not doc
- Use -f param for rm when wiping test directories as they are nested now
* Mon Dec 19 2016 Miro Hrončok <mhroncok@redhat.com>
- Rebuild for Python 3.6
* Tue Oct 18 2016 Tomas Jelinek <tojeline@redhat.com> - 0.9.154-2
- Fixed upgrading from pcs-0.9.150
* Thu Sep 22 2016 Tomas Jelinek <tojeline@redhat.com> - 0.9.154-1
- Re-synced to upstream sources
- Spec file cleanup and fixes
* Tue Jul 19 2016 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.150-2
- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages
* Mon Apr 11 2016 Tomas Jelinek <tojeline@redhat.com> - 0.9.150-1
- Re-synced to upstream sources
- Make pcs depend on python3
- Spec file cleanup
* Tue Feb 23 2016 Tomas Jelinek <tojeline@redhat.com> - 0.9.149-2
- Fixed rubygems issues which prevented pcsd from starting
- Added missing python-lxml dependency
* Thu Feb 18 2016 Tomas Jelinek <tojeline@redhat.com> - 0.9.149-1
- Re-synced to upstream sources
- Security fix for CVE-2016-0720, CVE-2016-0721
- Fixed rubygems issues which prevented pcsd from starting
- Rubygems built with RELRO
- Spec file cleanup
- Fixed multilib .pyc/.pyo issue
* Thu Feb 04 2016 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.144-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
* Tue Jan 12 2016 Vít Ondruch <vondruch@redhat.com> - 0.9.144-2
- Rebuilt for https://fedoraproject.org/wiki/Changes/Ruby_2.3
* Fri Sep 18 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.144-1
- Re-synced to upstream sources
* Tue Jun 23 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.141-2
- Added requirement for psmisc for killall
* Tue Jun 23 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.141-1
- Re-synced to upstream sources
* Thu Jun 18 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.140-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
* Fri Jun 05 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.140-1
- Re-synced to upstream sources
* Fri May 22 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.139-4
- Fix for CVE-2015-1848, CVE-2015-3983 (sessions not signed)
* Thu Mar 26 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.139-3
- Add BuildRequires: systemd (rhbz#1206253)
* Fri Feb 27 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.139-2
- Reflect clufter inclusion (rhbz#1180723)
* Thu Feb 19 2015 Tomas Jelinek <tojeline@redhat.com> - 0.9.139-1
- Re-synced to upstream sources
* Sat Jan 17 2015 Mamoru TASAKA <mtasaka@fedoraproject.org> - 0.9.115-5
- Rebuild for https://fedoraproject.org/wiki/Changes/Ruby_2.2
* Sun Aug 17 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.115-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
* Fri Jun 06 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.115-3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
* Tue May 06 2014 Tomas Jelinek <tojeline@redhat.com> - 0.9.115-2
- Rebuild to fix ruby dependencies
* Mon Apr 21 2014 Chris Feist <cfeist@redhat.com> - 0.9.115-1
- Re-synced to upstream sources
* Fri Dec 13 2013 Chris Feist <cfeist@redhat.com> - 0.9.102-1
- Re-synced to upstream sources
* Wed Jun 19 2013 Chris Feist <cfeist@redhat.com> - 0.9.48-1
- Rebuild with upstream sources
* Thu Jun 13 2013 Chris Feist <cfeist@redhat.com> - 0.9.44-5
- Added fixes for building rpam with ruby-2.0.0
* Mon Jun 03 2013 Chris Feist <cfeist@redhat.com> - 0.9.44-4
- Rebuild with upstream sources
* Tue May 07 2013 Chris Feist <cfeist@redhat.com> - 0.9.41-2
- Resynced to upstream sources
* Fri Apr 19 2013 Chris Feist <cfeist@redhat.com> - 0.9.39-1
- Fixed gem building
- Re-synced to upstream sources
* Mon Mar 25 2013 Chris Feist <cfeist@rehdat.com> - 0.9.36-4
- Don't try to build gems at all
* Mon Mar 25 2013 Chris Feist <cfeist@rehdat.com> - 0.9.36-3
- Removed all gems from build, will need to find pam package in the future
* Mon Mar 25 2013 Chris Feist <cfeist@redhat.com> - 0.9.36-2
- Removed duplicate libraries already present in fedora
* Mon Mar 18 2013 Chris Feist <cfeist@redhat.com> - 0.9.36-1
- Resynced to latest upstream
* Mon Mar 11 2013 Chris Feist <cfeist@redhat.com> - 0.9.33-1
- Resynched to latest upstream
- pcsd has been moved to /usr/lib to fix /usr/local packaging issues
* Thu Feb 21 2013 Chris Feist <cfeist@redhat.com> - 0.9.32-1
- Resynced to latest version of pcs/pcsd
* Mon Nov 05 2012 Chris Feist <cfeist@redhat.com> - 0.9.27-3
- Build on all archs
* Thu Oct 25 2012 Chris Feist <cfeist@redhat.com> - 0.9.27-2
- Resync to latest version of pcs
- Added pcsd daemon
* Mon Oct 08 2012 Chris Feist <cfeist@redhat.cmo> - 0.9.26-1
- Resync to latest version of pcs
* Thu Sep 20 2012 Chris Feist <cfeist@redhat.cmo> - 0.9.24-1
- Resync to latest version of pcs
* Thu Sep 20 2012 Chris Feist <cfeist@redhat.cmo> - 0.9.23-1
- Resync to latest version of pcs
* Wed Sep 12 2012 Chris Feist <cfeist@redhat.cmo> - 0.9.22-1
- Resync to latest version of pcs
* Thu Sep 06 2012 Chris Feist <cfeist@redhat.cmo> - 0.9.19-1
- Resync to latest version of pcs
* Tue Aug 07 2012 Chris Feist <cfeist@redhat.com> - 0.9.12-1
- Resync to latest version of pcs
* Fri Jul 20 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.3.1-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild
* Thu May 24 2012 Chris Feist <cfeist@redhat.com> - 0.9.4-1
- Resync to latest version of pcs
- Move cluster creation options to cluster sub command.
* Mon May 07 2012 Chris Feist <cfeist@redhat.com> - 0.9.3.1-1
- Resync to latest version of pcs which includes fixes to work with F17.
* Mon Mar 19 2012 Chris Feist <cfeist@redhat.com> - 0.9.2.4-1
- Resynced to latest version of pcs
* Mon Jan 23 2012 Chris Feist <cfeist@redhat.com> - 0.9.1-1
- Updated BuildRequires and %%doc section for fedora
* Fri Jan 20 2012 Chris Feist <cfeist@redhat.com> - 0.9.0-2
- Updated spec file for fedora specific changes
* Mon Jan 16 2012 Chris Feist <cfeist@redhat.com> - 0.9.0-1
- Initial Build