diff --git a/.gitignore b/.gitignore index b0123c2..970d968 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,17 @@ SOURCES/HAM-logo.png -SOURCES/backports-3.6.8.gem +SOURCES/backports-3.9.1.gem SOURCES/ethon-0.10.1.gem -SOURCES/ffi-1.9.17.gem +SOURCES/ffi-1.9.18.gem SOURCES/mock-1.0.1.tar.gz -SOURCES/multi_json-1.12.1.gem +SOURCES/multi_json-1.12.2.gem SOURCES/open4-1.3.4.gem SOURCES/orderedhash-0.0.6.gem -SOURCES/pcs-0.9.158.tar.gz +SOURCES/pcs-0.9.162.tar.gz +SOURCES/pyagentx-0.4.pcs.1.tar.gz SOURCES/rack-1.6.4.gem SOURCES/rack-protection-1.5.3.gem -SOURCES/rack-test-0.6.3.gem +SOURCES/rack-test-0.7.0.gem SOURCES/rpam-ruby19-1.2.1.gem SOURCES/sinatra-1.4.8.gem SOURCES/sinatra-contrib-1.4.7.gem -SOURCES/tilt-2.0.6.gem +SOURCES/tilt-2.0.8.gem diff --git a/.pcs.metadata b/.pcs.metadata index f56a5ce..e7a6117 100644 --- a/.pcs.metadata +++ b/.pcs.metadata @@ -1,16 +1,17 @@ 80dc7788a3468fb7dd362a4b8bedd9efb373de89 SOURCES/HAM-logo.png -5c9dd0d5552d242ee6bb338a9097e85f0a0a45d5 SOURCES/backports-3.6.8.gem +016e62f4967d7d5706bb3c2fce0dd0c004067151 SOURCES/backports-3.9.1.gem 0e362edc1035fa4adc3e52fcc27d15e796e6e9cf SOURCES/ethon-0.10.1.gem -499119750963bd1266b4184e169eb9da76462e2a SOURCES/ffi-1.9.17.gem +2d92e5efb7753c02d277cba9ad333bb9d51998b0 SOURCES/ffi-1.9.18.gem baa3446eb63557a24c4522dc5a61cfad082fa395 SOURCES/mock-1.0.1.tar.gz -b418d7b93e99a6f7d1acb70453470aace4599d1a SOURCES/multi_json-1.12.1.gem +f4d122fe287c5199040abbcd24c8082e1b0cdf93 SOURCES/multi_json-1.12.2.gem 41a7fe9f8e3e02da5ae76c821b89c5b376a97746 SOURCES/open4-1.3.4.gem 709cc95025009e5d221e37cb0777e98582146809 SOURCES/orderedhash-0.0.6.gem -20c9d1566e18693c291deb3a23c87cc86d23be3d SOURCES/pcs-0.9.158.tar.gz +c088d3046113a6f2a6aed22fd1e1fb3daf43b5b5 SOURCES/pcs-0.9.162.tar.gz +276a92c6d679a71bd0daaf12cb7b3616f1a89b72 SOURCES/pyagentx-0.4.pcs.1.tar.gz 0a1eea6d7bb903d8c075688534480e87d4151470 SOURCES/rack-1.6.4.gem 1c28529c1d7376c61faed80f3d3297905a14c2b3 SOURCES/rack-protection-1.5.3.gem -6fd5a7f881a65ef93b66e21556ef67fbe08a2fcc SOURCES/rack-test-0.6.3.gem +3f41699c1c19ff2e2353583afa70799ced351a36 SOURCES/rack-test-0.7.0.gem a90e5a60d99445404a3c29a66d953a5e9918976d SOURCES/rpam-ruby19-1.2.1.gem 3377f6140321523d7751bed3b2cc8a5201d8ec9f SOURCES/sinatra-1.4.8.gem 83742328f21b684d6ce6c4747710c6e975b608e7 SOURCES/sinatra-contrib-1.4.7.gem -f41d9747b29b38c1dc015bc71d5df691022d9716 SOURCES/tilt-2.0.6.gem +ac4b5bc216a961287b3c2ebde877c039d2f1a83d SOURCES/tilt-2.0.8.gem diff --git a/SOURCES/bz1165821-01-pcs-CLI-GUI-should-be-capable-of.patch b/SOURCES/bz1165821-01-pcs-CLI-GUI-should-be-capable-of.patch deleted file mode 100644 index 3cf3446..0000000 --- a/SOURCES/bz1165821-01-pcs-CLI-GUI-should-be-capable-of.patch +++ /dev/null @@ -1,240 +0,0 @@ -From 75488b2abdedb58715a21e365573a64e4ab1c324 Mon Sep 17 00:00:00 2001 -From: Ondrej Mular <omular@redhat.com> -Date: Tue, 30 May 2017 16:47:55 +0200 -Subject: [PATCH] squash bz1165821 pcs CLI/GUI should be capable of - -e60e02d store binary data in the corosync authkey file - -bf45303 cli: add option --no-hardened to 'cluster setup' - -97dff2f web UI: add option to create not hardened cluster ---- - pcs/cli/common/parse_args.py | 2 +- - pcs/cluster.py | 23 ++++++++++++++++------- - pcs/lib/tools.py | 5 ++++- - pcs/pcs.8 | 4 +++- - pcs/usage.py | 3 +++ - pcs/utils.py | 1 + - pcsd/pcs.rb | 1 + - pcsd/pcsd.rb | 3 ++- - pcsd/remote.rb | 3 +++ - pcsd/views/manage.erb | 9 +++++++++ - 10 files changed, 43 insertions(+), 11 deletions(-) - -diff --git a/pcs/cli/common/parse_args.py b/pcs/cli/common/parse_args.py -index e2250c7..5b87fbc 100644 ---- a/pcs/cli/common/parse_args.py -+++ b/pcs/cli/common/parse_args.py -@@ -32,7 +32,7 @@ PCS_LONG_OPTIONS = [ - "miss_count_const=", "fail_recv_const=", - "corosync_conf=", "cluster_conf=", - "booth-conf=", "booth-key=", -- "remote", "watchdog=", "device=", -+ "remote", "watchdog=", "device=", "no-hardened", - #in pcs status - do not display resorce status on inactive node - "hide-inactive", - # pcs resource (un)manage - enable or disable monitor operations -diff --git a/pcs/cluster.py b/pcs/cluster.py -index 0fc5e2c..0a9289b 100644 ---- a/pcs/cluster.py -+++ b/pcs/cluster.py -@@ -70,7 +70,11 @@ from pcs.lib.node import NodeAddresses, NodeAddressesList - from pcs.lib.nodes_task import check_corosync_offline_on_nodes, distribute_files - from pcs.lib import node_communication_format - import pcs.lib.pacemaker.live as lib_pacemaker --from pcs.lib.tools import environment_file_to_dict, generate_key -+from pcs.lib.tools import ( -+ environment_file_to_dict, -+ generate_binary_key, -+ generate_key, -+) - - def cluster_cmd(argv): - if len(argv) == 0: -@@ -381,7 +385,8 @@ def cluster_setup(argv): - node_list, - options["transport_options"], - options["totem_options"], -- options["quorum_options"] -+ options["quorum_options"], -+ modifiers["hardened"] - ) - process_library_reports(messages) - -@@ -453,11 +458,12 @@ def cluster_setup(argv): - file_definitions.update( - node_communication_format.pcmk_authkey_file(generate_key()) - ) -- file_definitions.update( -- node_communication_format.corosync_authkey_file( -- generate_key(random_bytes_count=128) -+ if modifiers["hardened"]: -+ file_definitions.update( -+ node_communication_format.corosync_authkey_file( -+ generate_binary_key(random_bytes_count=128) -+ ) - ) -- ) - - distribute_files( - lib_env.node_communicator(), -@@ -736,7 +742,8 @@ def cluster_setup_parse_options_cman(options, force=False): - return parsed, messages - - def cluster_setup_create_corosync_conf( -- cluster_name, node_list, transport_options, totem_options, quorum_options -+ cluster_name, node_list, transport_options, totem_options, quorum_options, -+ is_hardened - ): - messages = [] - -@@ -752,6 +759,8 @@ def cluster_setup_create_corosync_conf( - - totem_section.add_attribute("version", "2") - totem_section.add_attribute("cluster_name", cluster_name) -+ if not is_hardened: -+ totem_section.add_attribute("secauth", "off") - - transport_options_names = ( - "transport", -diff --git a/pcs/lib/tools.py b/pcs/lib/tools.py -index cd2d7f9..b9d7505 100644 ---- a/pcs/lib/tools.py -+++ b/pcs/lib/tools.py -@@ -9,7 +9,10 @@ import os - - - def generate_key(random_bytes_count=32): -- return binascii.hexlify(os.urandom(random_bytes_count)) -+ return binascii.hexlify(generate_binary_key(random_bytes_count)) -+ -+def generate_binary_key(random_bytes_count): -+ return os.urandom(random_bytes_count) - - def environment_file_to_dict(config): - """ -diff --git a/pcs/pcs.8 b/pcs/pcs.8 -index 4edfc72..aee8b3a 100644 ---- a/pcs/pcs.8 -+++ b/pcs/pcs.8 -@@ -205,7 +205,7 @@ Add specified utilization options to specified resource. If resource is not spec - auth [node] [...] [\fB\-u\fR username] [\fB\-p\fR password] [\fB\-\-force\fR] [\fB\-\-local\fR] - Authenticate pcs to pcsd on nodes specified, or on all nodes configured in the local cluster if no nodes are specified (authorization tokens are stored in ~/.pcs/tokens or /var/lib/pcsd/tokens for root). By default all nodes are also authenticated to each other, using \fB\-\-local\fR only authenticates the local node (and does not authenticate the remote nodes with each other). Using \fB\-\-force\fR forces re\-authentication to occur. - .TP --setup [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-local\fR] [\fB\-\-enable\fR] \fB\-\-name\fR <cluster name> <node1[,node1\-altaddr]> [<node2[,node2\-altaddr]>] [...] [\fB\-\-transport\fR udpu|udp] [\fB\-\-rrpmode\fR active|passive] [\fB\-\-addr0\fR <addr/net> [[[\fB\-\-mcast0\fR <address>] [\fB\-\-mcastport0\fR <port>] [\fB\-\-ttl0\fR <ttl>]] | [\fB\-\-broadcast0\fR]] [\fB\-\-addr1\fR <addr/net> [[[\fB\-\-mcast1\fR <address>] [\fB\-\-mcastport1\fR <port>] [\fB\-\-ttl1\fR <ttl>]] | [\fB\-\-broadcast1\fR]]]] [\fB\-\-wait_for_all\fR=<0|1>] [\fB\-\-auto_tie_breaker\fR=<0|1>] [\fB\-\-last_man_standing\fR=<0|1> [\fB\-\-last_man_standing_window\fR=<time in ms>]] [\fB\-\-ipv6\fR] [\fB\-\-token\fR <timeout>] [\fB\-\-token_coefficient\fR <timeout>] [\fB\-\-join\fR <timeout>] [\fB\-\-consensus\fR <timeout>] [\fB\-\-miss_count_const\fR <count>] [\fB\-\-fail_recv_const\fR <failures>] -+setup [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-local\fR] [\fB\-\-enable\fR] \fB\-\-name\fR <cluster name> <node1[,node1\-altaddr]> [<node2[,node2\-altaddr]>] [...] [\fB\-\-transport\fR udpu|udp] [\fB\-\-rrpmode\fR active|passive] [\fB\-\-addr0\fR <addr/net> [[[\fB\-\-mcast0\fR <address>] [\fB\-\-mcastport0\fR <port>] [\fB\-\-ttl0\fR <ttl>]] | [\fB\-\-broadcast0\fR]] [\fB\-\-addr1\fR <addr/net> [[[\fB\-\-mcast1\fR <address>] [\fB\-\-mcastport1\fR <port>] [\fB\-\-ttl1\fR <ttl>]] | [\fB\-\-broadcast1\fR]]]] [\fB\-\-wait_for_all\fR=<0|1>] [\fB\-\-auto_tie_breaker\fR=<0|1>] [\fB\-\-last_man_standing\fR=<0|1> [\fB\-\-last_man_standing_window\fR=<time in ms>]] [\fB\-\-ipv6\fR] [\fB\-\-token\fR <timeout>] [\fB\-\-token_coefficient\fR <timeout>] [\fB\-\-join\fR <timeout>] [\fB\-\-consensus\fR <timeout>] [\fB\-\-miss_count_const\fR <count>] [\fB\-\-fail_recv_const\fR <failures>] [\fB\-\-no\-hardened\fR] - Configure corosync and sync configuration out to listed nodes. \fB\-\-local\fR will only perform changes on the local node, \fB\-\-start\fR will also start the cluster on the specified nodes, \fB\-\-wait\fR will wait up to 'n' seconds for the nodes to start, \fB\-\-enable\fR will enable corosync and pacemaker on node startup, \fB\-\-transport\fR allows specification of corosync transport (default: udpu; udp for CMAN clusters), \fB\-\-rrpmode\fR allows you to set the RRP mode of the system. Currently only 'passive' is supported or tested (using 'active' is not recommended). The \fB\-\-wait_for_all\fR, \fB\-\-auto_tie_breaker\fR, \fB\-\-last_man_standing\fR, \fB\-\-last_man_standing_window\fR options are all documented in corosync's votequorum(5) man page. These options are not supported on CMAN clusters. - - \fB\-\-ipv6\fR will configure corosync to use ipv6 (instead of ipv4). This option is not supported on CMAN clusters. -@@ -222,6 +222,8 @@ Configure corosync and sync configuration out to listed nodes. \fB\-\-local\fR w - - \fB\-\-fail_recv_const\fR <failures> specifies how many rotations of the token without receiving any messages when messages should be received may occur before a new configuration is formed (default 2500 failures) - -+If \fB\-\-no\-hardened\fR is specified, the cluster will be set up in way that all corosync communication will be encrypted. -+ - - Configuring Redundant Ring Protocol (RRP) - -diff --git a/pcs/usage.py b/pcs/usage.py -index c73a103..c1ab00f 100644 ---- a/pcs/usage.py -+++ b/pcs/usage.py -@@ -576,6 +576,7 @@ Commands: - [--ipv6] [--token <timeout>] [--token_coefficient <timeout>] - [--join <timeout>] [--consensus <timeout>] - [--miss_count_const <count>] [--fail_recv_const <failures>] -+ [--no-hardened] - Configure corosync and sync configuration out to listed nodes. - --local will only perform changes on the local node, - --start will also start the cluster on the specified nodes, -@@ -611,6 +612,8 @@ Commands: - without receiving any messages when messages should be received - may occur before a new configuration is formed - (default 2500 failures) -+ If --no-hardened is specified, the cluster will be set up in way that all -+ corosync communication will be encrypted. - - Configuring Redundant Ring Protocol (RRP) - -diff --git a/pcs/utils.py b/pcs/utils.py -index 6515e5f..eec832f 100644 ---- a/pcs/utils.py -+++ b/pcs/utils.py -@@ -2882,6 +2882,7 @@ def get_modificators(): - "force": "--force" in pcs_options, - "full": "--full" in pcs_options, - "group": pcs_options.get("--group", None), -+ "hardened": "--no-hardened" not in pcs_options, - "monitor": "--monitor" in pcs_options, - "name": pcs_options.get("--name", None), - "no-default-ops": "--no-default-ops" in pcs_options, -diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb -index 9764a43..878296b 100644 ---- a/pcsd/pcs.rb -+++ b/pcsd/pcs.rb -@@ -1835,6 +1835,7 @@ def get_node_status(auth_user, cib_dom) - 'moving_resource_in_group', - 'unmanaged_resource', - 'alerts', -+ 'hardened_cluster', - ] - } - -diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb -index 33d999d..4d1964d 100644 ---- a/pcsd/pcsd.rb -+++ b/pcsd/pcsd.rb -@@ -568,7 +568,8 @@ already been added to pcsd. You may not add two clusters with the same name int - { - :clustername => @cluster_name, - :nodes => @nodes_rrp.join(';'), -- :options => options.to_json -+ :options => options.to_json, -+ :no_hardened => params[:no_hardened], - }, - true, - nil, -diff --git a/pcsd/remote.rb b/pcsd/remote.rb -index f353980..e37abb7 100644 ---- a/pcsd/remote.rb -+++ b/pcsd/remote.rb -@@ -964,6 +964,9 @@ def setup_cluster(params, request, auth_user) - end - nodes_options = nodes + options - nodes_options += options_udp if transport_udp -+ if params[:no_hardened] == "1" -+ nodes_options << "--no-hardened" -+ end - stdout, stderr, retval = run_cmd( - auth_user, PCS, "cluster", "setup", "--enable", "--start", "--async", - "--name", params[:clustername], *nodes_options -diff --git a/pcsd/views/manage.erb b/pcsd/views/manage.erb -index 39ab41f..a055449 100644 ---- a/pcsd/views/manage.erb -+++ b/pcsd/views/manage.erb -@@ -222,6 +222,9 @@ - <table> - <% transport_desc = "\ - Enables either udpu (unicast) or udp (multicast) cluster communication (default: udpu)"%> -+ <% hardened_desc = "\ -+Create cluster with encrypted corosync communication. This option may not work \ -+with pcs version lower than 0.9.159." %> - <% wait_for_all_desc = "\ - Enables Wait For All (WFA) feature (default: off). - -@@ -345,6 +348,12 @@ Specify ring 1 address for each node if you want to use RRP." %> - </select> - </td> - </tr> -+ <tr title="<%= h(hardened_desc) %>"><td align=right>Hardened:</td> -+ <td> -+ <label><input type="radio" name="no_hardened" value="0" checked="checked">Yes</label> -+ <label><input type="radio" name="no_hardened" value="1">No</label> -+ </td> -+ </tr> - <tr title="<%= h(wait_for_all_desc) %>"><td align=right>Wait for All:</td><td><input type=checkbox name="config-wait_for_all"></td></tr> - <tr title="<%= h(auto_tie_desc) %>"><td align=right>Auto Tie Breaker:</td><td><input type=checkbox name="config-auto_tie_breaker"></td></tr> - <tr title="<%= h(last_man_desc) %>"><td align=right>Last Man Standing:</td><td><input type=checkbox name="config-last_man_standing"></td></tr> --- -1.8.3.1 - diff --git a/SOURCES/bz1165821-02-pcs-CLI-GUI-should-be-capable-of.patch b/SOURCES/bz1165821-02-pcs-CLI-GUI-should-be-capable-of.patch deleted file mode 100644 index 9f5d2d3..0000000 --- a/SOURCES/bz1165821-02-pcs-CLI-GUI-should-be-capable-of.patch +++ /dev/null @@ -1,404 +0,0 @@ -From 0049f2b67b084006244f73a9a94979ba524a3bdd Mon Sep 17 00:00:00 2001 -From: Ondrej Mular <omular@redhat.com> -Date: Mon, 5 Jun 2017 10:14:16 +0200 -Subject: [PATCH] squash bz1165821 pcs CLI/GUI should be capable of - -ab3b909 change flag for hardened cluster to --encryption - -setup cluster wo corosync encryption by default ---- - pcs/cli/common/parse_args.py | 2 +- - pcs/cluster.py | 12 +++++++----- - pcs/pcs.8 | 4 ++-- - pcs/test/test_cluster.py | 24 ++++++++++++++++++++++++ - pcs/usage.py | 6 +++--- - pcs/utils.py | 2 +- - pcsd/pcsd.rb | 2 +- - pcsd/remote.rb | 4 ++-- - pcsd/views/manage.erb | 11 +++++++---- - 9 files changed, 48 insertions(+), 19 deletions(-) - -diff --git a/pcs/cli/common/parse_args.py b/pcs/cli/common/parse_args.py -index 5b87fbc..d72a6d4 100644 ---- a/pcs/cli/common/parse_args.py -+++ b/pcs/cli/common/parse_args.py -@@ -32,7 +32,7 @@ PCS_LONG_OPTIONS = [ - "miss_count_const=", "fail_recv_const=", - "corosync_conf=", "cluster_conf=", - "booth-conf=", "booth-key=", -- "remote", "watchdog=", "device=", "no-hardened", -+ "remote", "watchdog=", "device=", "encryption=", - #in pcs status - do not display resorce status on inactive node - "hide-inactive", - # pcs resource (un)manage - enable or disable monitor operations -diff --git a/pcs/cluster.py b/pcs/cluster.py -index 0a9289b..d896b0c 100644 ---- a/pcs/cluster.py -+++ b/pcs/cluster.py -@@ -303,6 +303,8 @@ def cluster_certkey(argv): - - def cluster_setup(argv): - modifiers = utils.get_modificators() -+ if modifiers["encryption"] not in ["0", "1"]: -+ utils.err("Invalid value for option --encryption") - if len(argv) < 2: - usage.cluster(["setup"]) - sys.exit(1) -@@ -386,7 +388,7 @@ def cluster_setup(argv): - options["transport_options"], - options["totem_options"], - options["quorum_options"], -- modifiers["hardened"] -+ modifiers["encryption"] == "1" - ) - process_library_reports(messages) - -@@ -458,7 +460,7 @@ def cluster_setup(argv): - file_definitions.update( - node_communication_format.pcmk_authkey_file(generate_key()) - ) -- if modifiers["hardened"]: -+ if modifiers["encryption"] == "1": - file_definitions.update( - node_communication_format.corosync_authkey_file( - generate_binary_key(random_bytes_count=128) -@@ -743,7 +745,7 @@ def cluster_setup_parse_options_cman(options, force=False): - - def cluster_setup_create_corosync_conf( - cluster_name, node_list, transport_options, totem_options, quorum_options, -- is_hardened -+ encrypted - ): - messages = [] - -@@ -758,9 +760,9 @@ def cluster_setup_create_corosync_conf( - corosync_conf.add_section(logging_section) - - totem_section.add_attribute("version", "2") -- totem_section.add_attribute("cluster_name", cluster_name) -- if not is_hardened: -+ if not encrypted: - totem_section.add_attribute("secauth", "off") -+ totem_section.add_attribute("cluster_name", cluster_name) - - transport_options_names = ( - "transport", -diff --git a/pcs/pcs.8 b/pcs/pcs.8 -index aee8b3a..446e7b3 100644 ---- a/pcs/pcs.8 -+++ b/pcs/pcs.8 -@@ -205,7 +205,7 @@ Add specified utilization options to specified resource. If resource is not spec - auth [node] [...] [\fB\-u\fR username] [\fB\-p\fR password] [\fB\-\-force\fR] [\fB\-\-local\fR] - Authenticate pcs to pcsd on nodes specified, or on all nodes configured in the local cluster if no nodes are specified (authorization tokens are stored in ~/.pcs/tokens or /var/lib/pcsd/tokens for root). By default all nodes are also authenticated to each other, using \fB\-\-local\fR only authenticates the local node (and does not authenticate the remote nodes with each other). Using \fB\-\-force\fR forces re\-authentication to occur. - .TP --setup [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-local\fR] [\fB\-\-enable\fR] \fB\-\-name\fR <cluster name> <node1[,node1\-altaddr]> [<node2[,node2\-altaddr]>] [...] [\fB\-\-transport\fR udpu|udp] [\fB\-\-rrpmode\fR active|passive] [\fB\-\-addr0\fR <addr/net> [[[\fB\-\-mcast0\fR <address>] [\fB\-\-mcastport0\fR <port>] [\fB\-\-ttl0\fR <ttl>]] | [\fB\-\-broadcast0\fR]] [\fB\-\-addr1\fR <addr/net> [[[\fB\-\-mcast1\fR <address>] [\fB\-\-mcastport1\fR <port>] [\fB\-\-ttl1\fR <ttl>]] | [\fB\-\-broadcast1\fR]]]] [\fB\-\-wait_for_all\fR=<0|1>] [\fB\-\-auto_tie_breaker\fR=<0|1>] [\fB\-\-last_man_standing\fR=<0|1> [\fB\-\-last_man_standing_window\fR=<time in ms>]] [\fB\-\-ipv6\fR] [\fB\-\-token\fR <timeout>] [\fB\-\-token_coefficient\fR <timeout>] [\fB\-\-join\fR <timeout>] [\fB\-\-consensus\fR <timeout>] [\fB\-\-miss_count_const\fR <count>] [\fB\-\-fail_recv_const\fR <failures>] [\fB\-\-no\-hardened\fR] -+setup [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-local\fR] [\fB\-\-enable\fR] \fB\-\-name\fR <cluster name> <node1[,node1\-altaddr]> [<node2[,node2\-altaddr]>] [...] [\fB\-\-transport\fR udpu|udp] [\fB\-\-rrpmode\fR active|passive] [\fB\-\-addr0\fR <addr/net> [[[\fB\-\-mcast0\fR <address>] [\fB\-\-mcastport0\fR <port>] [\fB\-\-ttl0\fR <ttl>]] | [\fB\-\-broadcast0\fR]] [\fB\-\-addr1\fR <addr/net> [[[\fB\-\-mcast1\fR <address>] [\fB\-\-mcastport1\fR <port>] [\fB\-\-ttl1\fR <ttl>]] | [\fB\-\-broadcast1\fR]]]] [\fB\-\-wait_for_all\fR=<0|1>] [\fB\-\-auto_tie_breaker\fR=<0|1>] [\fB\-\-last_man_standing\fR=<0|1> [\fB\-\-last_man_standing_window\fR=<time in ms>]] [\fB\-\-ipv6\fR] [\fB\-\-token\fR <timeout>] [\fB\-\-token_coefficient\fR <timeout>] [\fB\-\-join\fR <timeout>] [\fB\-\-consensus\fR <timeout>] [\fB\-\-miss_count_const\fR <count>] [\fB\-\-fail_recv_const\fR <failures>] [\fB\-\-encryption\fR 0|1] - Configure corosync and sync configuration out to listed nodes. \fB\-\-local\fR will only perform changes on the local node, \fB\-\-start\fR will also start the cluster on the specified nodes, \fB\-\-wait\fR will wait up to 'n' seconds for the nodes to start, \fB\-\-enable\fR will enable corosync and pacemaker on node startup, \fB\-\-transport\fR allows specification of corosync transport (default: udpu; udp for CMAN clusters), \fB\-\-rrpmode\fR allows you to set the RRP mode of the system. Currently only 'passive' is supported or tested (using 'active' is not recommended). The \fB\-\-wait_for_all\fR, \fB\-\-auto_tie_breaker\fR, \fB\-\-last_man_standing\fR, \fB\-\-last_man_standing_window\fR options are all documented in corosync's votequorum(5) man page. These options are not supported on CMAN clusters. - - \fB\-\-ipv6\fR will configure corosync to use ipv6 (instead of ipv4). This option is not supported on CMAN clusters. -@@ -222,7 +222,7 @@ Configure corosync and sync configuration out to listed nodes. \fB\-\-local\fR w - - \fB\-\-fail_recv_const\fR <failures> specifies how many rotations of the token without receiving any messages when messages should be received may occur before a new configuration is formed (default 2500 failures) - --If \fB\-\-no\-hardened\fR is specified, the cluster will be set up in way that all corosync communication will be encrypted. -+\fB\-\-encryption\fR 0|1 disables (0) or enables (1) corosync communication encryption (default 0) - - - Configuring Redundant Ring Protocol (RRP) -diff --git a/pcs/test/test_cluster.py b/pcs/test/test_cluster.py -index 2b7fd5a..5c7a4a1 100644 ---- a/pcs/test/test_cluster.py -+++ b/pcs/test/test_cluster.py -@@ -232,6 +232,7 @@ Warning: Unable to resolve hostname: nonexistant-address.invalid - corosync_conf = """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -290,6 +291,7 @@ Error: {0} already exists, use --force to overwrite - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -436,6 +438,7 @@ Error: {0} already exists, use --force to overwrite - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -476,6 +479,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -520,6 +524,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -560,6 +565,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -605,6 +611,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -646,6 +653,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -687,6 +695,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -727,6 +736,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -772,6 +782,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -817,6 +828,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - } -@@ -866,6 +878,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - } -@@ -1266,6 +1279,7 @@ Warning: Using udpu transport on a CMAN cluster, cluster restart is required aft - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - ip_version: ipv6 -@@ -1373,6 +1387,7 @@ Warning: --ipv6 ignored as it is not supported on CMAN clusters - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - rrp_mode: passive -@@ -1431,6 +1446,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - rrp_mode: passive -@@ -1489,6 +1505,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - rrp_mode: passive -@@ -1547,6 +1564,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - rrp_mode: passive -@@ -1614,6 +1632,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - rrp_mode: active -@@ -1679,6 +1698,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udp - rrp_mode: active -@@ -1754,6 +1774,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: udpu - rrp_mode: passive -@@ -1842,6 +1863,7 @@ logging { - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: test99 - transport: udpu - } -@@ -2426,6 +2448,7 @@ Warning: --last_man_standing_window ignored as it is not supported on CMAN clust - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: test99 - transport: udpu - token: 20000 -@@ -2669,6 +2692,7 @@ Warning: --token_coefficient ignored as it is not supported on CMAN clusters - ac(data, """\ - totem { - version: 2 -+ secauth: off - cluster_name: cname - transport: unknown - } -diff --git a/pcs/usage.py b/pcs/usage.py -index c1ab00f..d2262a6 100644 ---- a/pcs/usage.py -+++ b/pcs/usage.py -@@ -576,7 +576,7 @@ Commands: - [--ipv6] [--token <timeout>] [--token_coefficient <timeout>] - [--join <timeout>] [--consensus <timeout>] - [--miss_count_const <count>] [--fail_recv_const <failures>] -- [--no-hardened] -+ [--encryption 0|1] - Configure corosync and sync configuration out to listed nodes. - --local will only perform changes on the local node, - --start will also start the cluster on the specified nodes, -@@ -612,8 +612,8 @@ Commands: - without receiving any messages when messages should be received - may occur before a new configuration is formed - (default 2500 failures) -- If --no-hardened is specified, the cluster will be set up in way that all -- corosync communication will be encrypted. -+ --encryption 0|1 disables (0) or enables (1) corosync communication -+ encryption (default 0) - - Configuring Redundant Ring Protocol (RRP) - -diff --git a/pcs/utils.py b/pcs/utils.py -index eec832f..d6aabf4 100644 ---- a/pcs/utils.py -+++ b/pcs/utils.py -@@ -2879,10 +2879,10 @@ def get_modificators(): - "device": pcs_options.get("--device", []), - "disabled": "--disabled" in pcs_options, - "enable": "--enable" in pcs_options, -+ "encryption": pcs_options.get("--encryption", "0"), - "force": "--force" in pcs_options, - "full": "--full" in pcs_options, - "group": pcs_options.get("--group", None), -- "hardened": "--no-hardened" not in pcs_options, - "monitor": "--monitor" in pcs_options, - "name": pcs_options.get("--name", None), - "no-default-ops": "--no-default-ops" in pcs_options, -diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb -index 4d1964d..1026a36 100644 ---- a/pcsd/pcsd.rb -+++ b/pcsd/pcsd.rb -@@ -569,7 +569,7 @@ already been added to pcsd. You may not add two clusters with the same name int - :clustername => @cluster_name, - :nodes => @nodes_rrp.join(';'), - :options => options.to_json, -- :no_hardened => params[:no_hardened], -+ :encryption => params[:encryption], - }, - true, - nil, -diff --git a/pcsd/remote.rb b/pcsd/remote.rb -index e37abb7..af74790 100644 ---- a/pcsd/remote.rb -+++ b/pcsd/remote.rb -@@ -964,8 +964,8 @@ def setup_cluster(params, request, auth_user) - end - nodes_options = nodes + options - nodes_options += options_udp if transport_udp -- if params[:no_hardened] == "1" -- nodes_options << "--no-hardened" -+ if ['0', '1'].include?(params[:encryption]) -+ nodes_options << "--encryption=#{params[:encryption]}" - end - stdout, stderr, retval = run_cmd( - auth_user, PCS, "cluster", "setup", "--enable", "--start", "--async", -diff --git a/pcsd/views/manage.erb b/pcsd/views/manage.erb -index a055449..2b12aaa 100644 ---- a/pcsd/views/manage.erb -+++ b/pcsd/views/manage.erb -@@ -222,7 +222,7 @@ - <table> - <% transport_desc = "\ - Enables either udpu (unicast) or udp (multicast) cluster communication (default: udpu)"%> -- <% hardened_desc = "\ -+ <% encryption_desc = "\ - Create cluster with encrypted corosync communication. This option may not work \ - with pcs version lower than 0.9.159." %> - <% wait_for_all_desc = "\ -@@ -348,10 +348,13 @@ Specify ring 1 address for each node if you want to use RRP." %> - </select> - </td> - </tr> -- <tr title="<%= h(hardened_desc) %>"><td align=right>Hardened:</td> -+ <tr title="<%= h(encryption_desc) %>"><td align=right>Encryption:</td> - <td> -- <label><input type="radio" name="no_hardened" value="0" checked="checked">Yes</label> -- <label><input type="radio" name="no_hardened" value="1">No</label> -+ <select name="encryption"> -+ <option selected="selected">(Default)</option> -+ <option value="1">On</option> -+ <option value="0">Off</option> -+ </select> - </td> - </tr> - <tr title="<%= h(wait_for_all_desc) %>"><td align=right>Wait for All:</td><td><input type=checkbox name="config-wait_for_all"></td></tr> --- -1.8.3.1 - diff --git a/SOURCES/bz1176018-01-remote-guest-nodes-crashes-fixed.patch b/SOURCES/bz1176018-01-remote-guest-nodes-crashes-fixed.patch deleted file mode 100644 index ff95495..0000000 --- a/SOURCES/bz1176018-01-remote-guest-nodes-crashes-fixed.patch +++ /dev/null @@ -1,104 +0,0 @@ -From e13624ef5b2171516979827dcbe7ff03eb8247e5 Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek <tojeline@redhat.com> -Date: Wed, 31 May 2017 07:39:23 +0200 -Subject: [PATCH] squash 1176018 remote guest nodes crashes fixed - -c7a24e6 fix adding a node to a stopped cluster - -88ad6e6 fix 'pcs cluster restore' command for pcmk authkey - -e4b768c fix crash of 'pcs cluster destroy --all' - -6b15785 fix crash of 'pcs cluster setup --force' ---- - pcs/cluster.py | 33 +++++++++++++++++++++++++-------- - pcs/config.py | 4 ++-- - 2 files changed, 27 insertions(+), 10 deletions(-) - -diff --git a/pcs/cluster.py b/pcs/cluster.py -index d64194d..b47db4a 100644 ---- a/pcs/cluster.py -+++ b/pcs/cluster.py -@@ -425,9 +425,9 @@ def cluster_setup(argv): - else: - # verify and ensure no cluster is set up on the nodes - # checks that nodes are authenticated as well -+ lib_env = utils.get_lib_env() - if "--force" not in utils.pcs_options: - all_nodes_available = True -- lib_env = utils.get_lib_env() - for node in primary_addr_list: - available, message = utils.canAddNodeToCluster( - lib_env.node_communicator(), -@@ -1757,9 +1757,12 @@ def node_add(lib_env, node0, node1, modifiers): - NodeAddressesList([node_addr]), - ) - -+ # do not send pcmk authkey to guest and remote nodes, they either have -+ # it or are not working anyway -+ # if the cluster is stopped, we cannot get the cib anyway - _share_authkey( - lib_env, -- get_nodes(lib_env.get_corosync_conf(), lib_env.get_cib()), -+ get_nodes(lib_env.get_corosync_conf()), - node_addr, - allow_incomplete_distribution=modifiers["skip_offline_nodes"] - ) -@@ -2112,15 +2115,29 @@ def cluster_reload(argv): - # Code taken from cluster-clean script in pacemaker - def cluster_destroy(argv): - if "--all" in utils.pcs_options: -+ # destroy remote and guest nodes -+ cib = None - lib_env = utils.get_lib_env() -- all_remote_nodes = get_nodes(tree=lib_env.get_cib()) -- if len(all_remote_nodes) > 0: -- _destroy_pcmk_remote_env( -- lib_env, -- all_remote_nodes, -- allow_fails=True -+ try: -+ cib = lib_env.get_cib() -+ except LibraryError as e: -+ warn( -+ "Unable to load CIB to get guest and remote nodes from it, " -+ "those nodes will not be deconfigured." - ) -+ if cib is not None: -+ try: -+ all_remote_nodes = get_nodes(tree=cib) -+ if len(all_remote_nodes) > 0: -+ _destroy_pcmk_remote_env( -+ lib_env, -+ all_remote_nodes, -+ allow_fails=True -+ ) -+ except LibraryError as e: -+ utils.process_library_reports(e.args) - -+ # destroy full-stack nodes - destroy_cluster(utils.getNodesFromCorosyncConf()) - else: - print("Shutting down pacemaker/corosync services...") -diff --git a/pcs/config.py b/pcs/config.py -index 94191e1..5526eb5 100644 ---- a/pcs/config.py -+++ b/pcs/config.py -@@ -446,12 +446,12 @@ def config_backup_path_list(with_uid_gid=False, force_rhel6=None): - "uname": settings.pacemaker_uname, - "gname": settings.pacemaker_gname, - } -- pcmk_authkey_attrs = dict(cib_attrs) -- pcmk_authkey_attrs["mode"] = 0o440 - if with_uid_gid: - cib_attrs["uid"] = _get_uid(cib_attrs["uname"]) - cib_attrs["gid"] = _get_gid(cib_attrs["gname"]) - -+ pcmk_authkey_attrs = dict(cib_attrs) -+ pcmk_authkey_attrs["mode"] = 0o440 - file_list = { - "cib.xml": { - "path": os.path.join(settings.cib_dir, "cib.xml"), --- -1.8.3.1 - diff --git a/SOURCES/bz1176018-02-pcs-pcsd-should-be-able-to-config.patch b/SOURCES/bz1176018-02-pcs-pcsd-should-be-able-to-config.patch deleted file mode 100644 index 4464f97..0000000 --- a/SOURCES/bz1176018-02-pcs-pcsd-should-be-able-to-config.patch +++ /dev/null @@ -1,322 +0,0 @@ -From b8b59f772b2bbdb9728b32c674e69df851f82397 Mon Sep 17 00:00:00 2001 -From: Ivan Devat <idevat@redhat.com> -Date: Tue, 30 May 2017 16:56:50 +0200 -Subject: [PATCH] squash bz1176018 pcs/pcsd should be able to config - -43aeca1 fix --skip-offline without effect problem - -38de786 clean remote/guest node before pushing cib ---- - pcs/cli/cluster/command.py | 15 +++++---- - pcs/lib/commands/cluster.py | 65 ++++++++++++++++++++---------------- - pcs/lib/nodes_task.py | 13 ++++++-- - pcs/lib/test/test_nodes_task.py | 4 --- - pcs/test/test_cluster_pcmk_remote.py | 16 +++++---- - 5 files changed, 66 insertions(+), 47 deletions(-) - -diff --git a/pcs/cli/cluster/command.py b/pcs/cli/cluster/command.py -index f725326..963bd8c 100644 ---- a/pcs/cli/cluster/command.py -+++ b/pcs/cli/cluster/command.py -@@ -35,6 +35,7 @@ def node_add_remote(lib, arg_list, modifiers): - - parts = parse_resource_create_args(rest_args) - force = modifiers["force"] -+ skip_offline = modifiers["skip_offline_nodes"] - - lib.cluster.node_add_remote( - node_host, -@@ -42,8 +43,8 @@ def node_add_remote(lib, arg_list, modifiers): - parts["op"], - parts["meta"], - parts["options"], -- allow_incomplete_distribution=force, -- allow_pacemaker_remote_service_fail=force, -+ allow_incomplete_distribution=skip_offline, -+ allow_pacemaker_remote_service_fail=skip_offline, - allow_invalid_operation=force, - allow_invalid_instance_attributes=force, - use_default_operations=not modifiers["no-default-ops"], -@@ -58,7 +59,7 @@ def create_node_remove_remote(remove_resource): - arg_list[0], - remove_resource, - allow_remove_multiple_nodes=modifiers["force"], -- allow_pacemaker_remote_service_fail=modifiers["force"], -+ allow_pacemaker_remote_service_fail=modifiers["skip_offline_nodes"], - ) - return node_remove_remote - -@@ -71,14 +72,14 @@ def node_add_guest(lib, arg_list, modifiers): - resource_id = arg_list[1] - meta_options = prepare_options(arg_list[2:]) - -- force = modifiers["force"] -+ skip_offline = modifiers["skip_offline_nodes"] - - lib.cluster.node_add_guest( - node_name, - resource_id, - meta_options, -- allow_incomplete_distribution=force, -- allow_pacemaker_remote_service_fail=force, -+ allow_incomplete_distribution=skip_offline, -+ allow_pacemaker_remote_service_fail=skip_offline, - wait=modifiers["wait"], - ) - -@@ -89,7 +90,7 @@ def node_remove_guest(lib, arg_list, modifiers): - lib.cluster.node_remove_guest( - arg_list[0], - allow_remove_multiple_nodes=modifiers["force"], -- allow_pacemaker_remote_service_fail=modifiers["force"], -+ allow_pacemaker_remote_service_fail=modifiers["skip_offline_nodes"], - wait=modifiers["wait"], - ) - -diff --git a/pcs/lib/commands/cluster.py b/pcs/lib/commands/cluster.py -index 0bafef5..fe883f3 100644 ---- a/pcs/lib/commands/cluster.py -+++ b/pcs/lib/commands/cluster.py -@@ -21,13 +21,16 @@ from pcs.lib.errors import LibraryError - from pcs.lib.pacemaker import state - from pcs.lib.pacemaker.live import remove_node - --def _ensure_can_add_node_to_remote_cluster(env, node_addresses): -+def _ensure_can_add_node_to_remote_cluster( -+ env, node_addresses, warn_on_communication_exception=False -+): - report_items = [] - nodes_task.check_can_add_node_to_cluster( - env.node_communicator(), - node_addresses, - report_items, -- check_response=nodes_task.availability_checker_remote_node -+ check_response=nodes_task.availability_checker_remote_node, -+ warn_on_communication_exception=warn_on_communication_exception, - ) - env.report_processor.process_list(report_items) - -@@ -88,7 +91,11 @@ def _prepare_pacemaker_remote_environment( - return - - candidate_node = NodeAddresses(node_host) -- _ensure_can_add_node_to_remote_cluster(env, candidate_node) -+ _ensure_can_add_node_to_remote_cluster( -+ env, -+ candidate_node, -+ allow_incomplete_distribution -+ ) - _share_authkey( - env, - current_nodes, -@@ -296,17 +303,13 @@ def _find_resources_to_remove( - - return resource_element_list - --def _remove_pcmk_remote_from_cib( -- nodes, resource_element_list, get_host, remove_resource --): -+def _get_node_addresses_from_resources(nodes, resource_element_list, get_host): - node_addresses_set = set() - for resource_element in resource_element_list: - for node in nodes: - #remote nodes uses ring0 only - if get_host(resource_element) == node.ring0: - node_addresses_set.add(node) -- remove_resource(resource_element) -- - return sorted(node_addresses_set, key=lambda node: node.ring0) - - def _destroy_pcmk_remote_env(env, node_addresses_list, allow_fails): -@@ -382,28 +385,31 @@ def node_remove_remote( - allow_remove_multiple_nodes, - remote_node.find_node_resources, - ) -- node_addresses_list = _remove_pcmk_remote_from_cib( -+ -+ node_addresses_list = _get_node_addresses_from_resources( - get_nodes_remote(cib), - resource_element_list, - remote_node.get_host, -- lambda resource_element: remove_resource( -- resource_element.attrib["id"], -- is_remove_remote_context=True, -- ) - ) -+ - if not env.is_corosync_conf_live: - env.report_processor.process_list( - _report_skip_live_parts_in_remove(node_addresses_list) - ) -- return -+ else: -+ _destroy_pcmk_remote_env( -+ env, -+ node_addresses_list, -+ allow_pacemaker_remote_service_fail -+ ) - - #remove node from pcmk caches is currently integrated in remove_resource - #function -- _destroy_pcmk_remote_env( -- env, -- node_addresses_list, -- allow_pacemaker_remote_service_fail -- ) -+ for resource_element in resource_element_list: -+ remove_resource( -+ resource_element.attrib["id"], -+ is_remove_remote_context=True, -+ ) - - def node_remove_guest( - env, node_identifier, -@@ -435,29 +441,32 @@ def node_remove_guest( - guest_node.find_node_resources, - ) - -- node_addresses_list = _remove_pcmk_remote_from_cib( -+ node_addresses_list = _get_node_addresses_from_resources( - get_nodes_guest(cib), - resource_element_list, - guest_node.get_host, -- guest_node.unset_guest, - ) -- env.push_cib(cib, wait) - - if not env.is_corosync_conf_live: - env.report_processor.process_list( - _report_skip_live_parts_in_remove(node_addresses_list) - ) -- return -+ else: -+ _destroy_pcmk_remote_env( -+ env, -+ node_addresses_list, -+ allow_pacemaker_remote_service_fail -+ ) -+ -+ for resource_element in resource_element_list: -+ guest_node.unset_guest(resource_element) -+ -+ env.push_cib(cib, wait) - - #remove node from pcmk caches - for node_addresses in node_addresses_list: - remove_node(env.cmd_runner(), node_addresses.name) - -- _destroy_pcmk_remote_env( -- env, -- node_addresses_list, -- allow_pacemaker_remote_service_fail -- ) - - def node_clear(env, node_name, allow_clear_cluster_node=False): - """ -diff --git a/pcs/lib/nodes_task.py b/pcs/lib/nodes_task.py -index 703609b..6086c4b 100644 ---- a/pcs/lib/nodes_task.py -+++ b/pcs/lib/nodes_task.py -@@ -277,7 +277,8 @@ def availability_checker_remote_node( - - def check_can_add_node_to_cluster( - node_communicator, node, report_items, -- check_response=availability_checker_node -+ check_response=availability_checker_node, -+ warn_on_communication_exception=False, - ): - """ - Analyze result of node_available check if it is possible use the node as -@@ -294,13 +295,21 @@ def check_can_add_node_to_cluster( - node_communicator, - node, - "remote/node_available", -- safe_report_items -+ safe_report_items, -+ warn_on_communication_exception=warn_on_communication_exception - ) - report_items.extend(safe_report_items) - - if ReportListAnalyzer(safe_report_items).error_list: - return - -+ #If there was a communication error and --skip-offline is in effect, no -+ #exception was raised. If there is no result cannot process it. -+ #Note: the error may be caused by older pcsd daemon not supporting commands -+ #sent by newer client. -+ if not availability_info: -+ return -+ - is_in_expected_format = ( - isinstance(availability_info, dict) - and -diff --git a/pcs/lib/test/test_nodes_task.py b/pcs/lib/test/test_nodes_task.py -index 61ba132..5459337 100644 ---- a/pcs/lib/test/test_nodes_task.py -+++ b/pcs/lib/test/test_nodes_task.py -@@ -790,10 +790,6 @@ class CheckCanAddNodeToCluster(TestCase): - def test_report_no_dict_in_json_response(self): - self.assert_result_causes_invalid_format("bad answer") - -- def test_report_dict_without_mandatory_key(self): -- self.assert_result_causes_invalid_format({}) -- -- - class OnNodeTest(TestCase): - def setUp(self): - self.reporter = MockLibraryReportProcessor() -diff --git a/pcs/test/test_cluster_pcmk_remote.py b/pcs/test/test_cluster_pcmk_remote.py -index 5dc1633..0db4a5c 100644 ---- a/pcs/test/test_cluster_pcmk_remote.py -+++ b/pcs/test/test_cluster_pcmk_remote.py -@@ -399,11 +399,11 @@ class NodeRemoveRemote(ResourceTest): - self.assert_effect( - "cluster node remove-remote NODE-HOST", - "<resources/>", -- outdent( -+ fixture_nolive_remove_report(["NODE-HOST"]) + outdent( - """\ - Deleting Resource - NODE-NAME - """ -- ) + fixture_nolive_remove_report(["NODE-HOST"]) -+ ) - ) - - def test_success_remove_by_node_name(self): -@@ -411,11 +411,11 @@ class NodeRemoveRemote(ResourceTest): - self.assert_effect( - "cluster node remove-remote NODE-NAME", - "<resources/>", -- outdent( -+ fixture_nolive_remove_report(["NODE-HOST"]) + outdent( - """\ - Deleting Resource - NODE-NAME - """ -- ) + fixture_nolive_remove_report(["NODE-HOST"]) -+ ) - ) - - def test_refuse_on_duplicit(self): -@@ -431,13 +431,17 @@ class NodeRemoveRemote(ResourceTest): - self.assert_effect( - "cluster node remove-remote HOST-A --force", - "<resources/>", -+ -+ "Warning: multiple resource for 'HOST-A' found: 'HOST-A', 'NODE-NAME'\n" -+ + -+ fixture_nolive_remove_report(["HOST-A", "HOST-B"]) -+ + - outdent( - """\ -- Warning: multiple resource for 'HOST-A' found: 'HOST-A', 'NODE-NAME' - Deleting Resource - NODE-NAME - Deleting Resource - HOST-A - """ -- ) + fixture_nolive_remove_report(["HOST-A", "HOST-B"]) -+ ) - ) - - class NodeRemoveGuest(ResourceTest): --- -1.8.3.1 - diff --git a/SOURCES/bz1176018-03-don-t-call-remove-guest-node-when-f-is-used.patch b/SOURCES/bz1176018-03-don-t-call-remove-guest-node-when-f-is-used.patch deleted file mode 100644 index 359f147..0000000 --- a/SOURCES/bz1176018-03-don-t-call-remove-guest-node-when-f-is-used.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 1a2c01a82aa7e791a5d9925ec82792e764e53740 Mon Sep 17 00:00:00 2001 -From: Ivan Devat <idevat@redhat.com> -Date: Wed, 31 May 2017 10:44:22 +0200 -Subject: [PATCH] don't call remove guest node when -f is used - -`pcs cluster node remove-guest` now does not call `crm_node --remove` -when -f is used ---- - pcs/lib/commands/cluster.py | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/pcs/lib/commands/cluster.py b/pcs/lib/commands/cluster.py -index fe883f3..0b04d3d 100644 ---- a/pcs/lib/commands/cluster.py -+++ b/pcs/lib/commands/cluster.py -@@ -464,8 +464,9 @@ def node_remove_guest( - env.push_cib(cib, wait) - - #remove node from pcmk caches -- for node_addresses in node_addresses_list: -- remove_node(env.cmd_runner(), node_addresses.name) -+ if env.is_cib_live: -+ for node_addresses in node_addresses_list: -+ remove_node(env.cmd_runner(), node_addresses.name) - - - def node_clear(env, node_name, allow_clear_cluster_node=False): --- -1.8.3.1 - diff --git a/SOURCES/bz1284404-01-web-UI-fix-creating-a-new-cluster.patch b/SOURCES/bz1284404-01-web-UI-fix-creating-a-new-cluster.patch deleted file mode 100644 index f1b95f1..0000000 --- a/SOURCES/bz1284404-01-web-UI-fix-creating-a-new-cluster.patch +++ /dev/null @@ -1,138 +0,0 @@ -From e0496566d2634ee6e37939a7fd9b2ee25539df46 Mon Sep 17 00:00:00 2001 -From: Ondrej Mular <omular@redhat.com> -Date: Tue, 30 May 2017 16:46:48 +0200 -Subject: [PATCH] web UI: fix creating a new cluster - ---- - pcs/cli/common/parse_args.py | 2 +- - pcs/cluster.py | 5 ++++- - pcs/pcsd.py | 12 +++++++++--- - pcs/utils.py | 1 + - pcsd/pcs.rb | 3 ++- - pcsd/remote.rb | 4 ++-- - 6 files changed, 19 insertions(+), 8 deletions(-) - -diff --git a/pcs/cli/common/parse_args.py b/pcs/cli/common/parse_args.py -index 465cb96..e2250c7 100644 ---- a/pcs/cli/common/parse_args.py -+++ b/pcs/cli/common/parse_args.py -@@ -17,7 +17,7 @@ PCS_SHORT_OPTIONS = "hf:p:u:V" - PCS_LONG_OPTIONS = [ - "debug", "version", "help", "fullhelp", - "force", "skip-offline", "autocorrect", "interactive", "autodelete", -- "all", "full", "groups", "local", "wait", "config", -+ "all", "full", "groups", "local", "wait", "config", "async", - "start", "enable", "disabled", "off", "request-timeout=", - "pacemaker", "corosync", - "no-default-ops", "defaults", "nodesc", -diff --git a/pcs/cluster.py b/pcs/cluster.py -index b47db4a..0fc5e2c 100644 ---- a/pcs/cluster.py -+++ b/pcs/cluster.py -@@ -298,6 +298,7 @@ def cluster_certkey(argv): - - - def cluster_setup(argv): -+ modifiers = utils.get_modificators() - if len(argv) < 2: - usage.cluster(["setup"]) - sys.exit(1) -@@ -515,7 +516,9 @@ def cluster_setup(argv): - - # sync certificates as the last step because it restarts pcsd - print() -- pcsd.pcsd_sync_certs([], exit_after_error=False) -+ pcsd.pcsd_sync_certs( -+ [], exit_after_error=False, async_restart=modifiers["async"] -+ ) - if wait: - print() - wait_for_nodes_started(primary_addr_list, wait_timeout) -diff --git a/pcs/pcsd.py b/pcs/pcsd.py -index 629b4c0..7f7c660 100644 ---- a/pcs/pcsd.py -+++ b/pcs/pcsd.py -@@ -79,7 +79,7 @@ def pcsd_certkey(argv): - - print("Certificate and key updated, you may need to restart pcsd (service pcsd restart) for new settings to take effect") - --def pcsd_sync_certs(argv, exit_after_error=True): -+def pcsd_sync_certs(argv, exit_after_error=True, async_restart=False): - error = False - nodes_sync = argv if argv else utils.getNodesFromCorosyncConf() - nodes_restart = [] -@@ -117,7 +117,9 @@ def pcsd_sync_certs(argv, exit_after_error=True): - return - - print("Restarting pcsd on the nodes in order to reload the certificates...") -- pcsd_restart_nodes(nodes_restart, exit_after_error) -+ pcsd_restart_nodes( -+ nodes_restart, exit_after_error, async_restart=async_restart -+ ) - - def pcsd_clear_auth(argv): - output = [] -@@ -148,7 +150,7 @@ def pcsd_clear_auth(argv): - print("Error: " + o) - sys.exit(1) - --def pcsd_restart_nodes(nodes, exit_after_error=True): -+def pcsd_restart_nodes(nodes, exit_after_error=True, async_restart=False): - pcsd_data = { - "nodes": nodes, - } -@@ -188,6 +190,10 @@ def pcsd_restart_nodes(nodes, exit_after_error=True): - utils.err("Unable to restart pcsd", exit_after_error) - return - -+ if async_restart: -+ print("Not waiting for restart of pcsd on all nodes.") -+ return -+ - # check if the restart was performed already - error = False - for _ in range(5): -diff --git a/pcs/utils.py b/pcs/utils.py -index 4753b87..6515e5f 100644 ---- a/pcs/utils.py -+++ b/pcs/utils.py -@@ -2870,6 +2870,7 @@ def get_modificators(): - return { - "after": pcs_options.get("--after", None), - "all": "--all" in pcs_options, -+ "async": "--async" in pcs_options, - "autocorrect": "--autocorrect" in pcs_options, - "autodelete": "--autodelete" in pcs_options, - "before": pcs_options.get("--before", None), -diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb -index 930b4a0..9764a43 100644 ---- a/pcsd/pcs.rb -+++ b/pcsd/pcs.rb -@@ -1034,7 +1034,8 @@ def pcsd_restart() - # request - fork { - # let us send the response to the restart request -- sleep(3) -+ # we need little bit more time to finish some things when setting up cluster -+ sleep(10) - if ISSYSTEMCTL - exec("systemctl", "restart", "pcsd") - else -diff --git a/pcsd/remote.rb b/pcsd/remote.rb -index 005d45e..f353980 100644 ---- a/pcsd/remote.rb -+++ b/pcsd/remote.rb -@@ -965,8 +965,8 @@ def setup_cluster(params, request, auth_user) - nodes_options = nodes + options - nodes_options += options_udp if transport_udp - stdout, stderr, retval = run_cmd( -- auth_user, PCS, "cluster", "setup", "--enable", "--start", -- "--name", params[:clustername], *nodes_options -+ auth_user, PCS, "cluster", "setup", "--enable", "--start", "--async", -+ "--name", params[:clustername], *nodes_options - ) - if retval != 0 - return [ --- -1.8.3.1 - diff --git a/SOURCES/bz1284404-02-web-ui-fix-timeout-when-cluster-setup-takes-long.patch b/SOURCES/bz1284404-02-web-ui-fix-timeout-when-cluster-setup-takes-long.patch deleted file mode 100644 index e6dff97..0000000 --- a/SOURCES/bz1284404-02-web-ui-fix-timeout-when-cluster-setup-takes-long.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 1f64220204383bea38c52d5e96e0c5ba05e98ccb Mon Sep 17 00:00:00 2001 -From: Ivan Devat <idevat@redhat.com> -Date: Thu, 15 Jun 2017 11:46:12 +0200 -Subject: [PATCH] web UI: fix timeout when cluster setup takes long - ---- - pcsd/public/js/pcsd.js | 14 ++++++++++++-- - 1 file changed, 12 insertions(+), 2 deletions(-) - -diff --git a/pcsd/public/js/pcsd.js b/pcsd/public/js/pcsd.js -index b7ad72f..4754139 100644 ---- a/pcsd/public/js/pcsd.js -+++ b/pcsd/public/js/pcsd.js -@@ -1080,7 +1080,7 @@ function update_create_cluster_dialog(nodes, version_info) { - ajax_wrapper({ - type: "POST", - url: "/manage/newcluster", -- timeout: pcs_timeout, -+ timeout: 60*1000, - data: $('#create_new_cluster_form').serialize(), - success: function(data) { - if (data) { -@@ -1090,7 +1090,17 @@ function update_create_cluster_dialog(nodes, version_info) { - Pcs.update(); - }, - error: function (xhr, status, error) { -- alert(xhr.responseText); -+ var err_msg = ""; -+ if ((status == "timeout") || ($.trim(error) == "timeout")) { -+ err_msg = ( -+ "Operation takes longer to complete than expected. " + -+ "It may continue running in the background. Later, you can try " + -+ "to add this cluster as existing one." -+ ); -+ } else { -+ err_msg = xhr.responseText; -+ } -+ alert(err_msg); - $("#create_cluster_submit_btn").button("option", "disabled", false); - } - }); --- -1.8.3.1 - diff --git a/SOURCES/bz1367808-01-fix-formating-of-assertion-error-in-snmp.patch b/SOURCES/bz1367808-01-fix-formating-of-assertion-error-in-snmp.patch new file mode 100644 index 0000000..43aed3b --- /dev/null +++ b/SOURCES/bz1367808-01-fix-formating-of-assertion-error-in-snmp.patch @@ -0,0 +1,27 @@ +From b1c3a422b55f27cb6bc7b60b89a0c929eff0a030 Mon Sep 17 00:00:00 2001 +From: Ondrej Mular <omular@redhat.com> +Date: Tue, 21 Nov 2017 12:07:43 +0100 +Subject: [PATCH] fix formating of assertion error in snmp + +--- + pcs/snmp/agentx/updater.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/pcs/snmp/agentx/updater.py b/pcs/snmp/agentx/updater.py +index 7ac20ae..c63f96b 100644 +--- a/pcs/snmp/agentx/updater.py ++++ b/pcs/snmp/agentx/updater.py +@@ -82,8 +82,8 @@ def _str_oid_to_oid(sub_tree, str_oid): + sub_tree = _find_oid_in_sub_tree(sub_tree, section) + if sub_tree is None: + raise AssertionError( +- "oid section {0} ({1}) not found in {1} ({2})".format( +- section, str_oid, sub_tree.str_oid ++ "oid section '{0}' ({1}) not found in section '{2}'".format( ++ section, str_oid, oid_list[-1] if len(oid_list) else "." + ) + ) + oid_list.append(str(sub_tree.oid)) +-- +1.8.3.1 + diff --git a/SOURCES/bz1367808-02-change-snmp-agent-logfile-path.patch b/SOURCES/bz1367808-02-change-snmp-agent-logfile-path.patch new file mode 100644 index 0000000..425ca82 --- /dev/null +++ b/SOURCES/bz1367808-02-change-snmp-agent-logfile-path.patch @@ -0,0 +1,67 @@ +From 771e99602c7920b2473706a8bdf2e8a57e1d2aec Mon Sep 17 00:00:00 2001 +From: Ondrej Mular <omular@redhat.com> +Date: Tue, 28 Nov 2017 13:20:16 +0100 +Subject: [PATCH] change snmp agent logfile path + +--- + Makefile | 1 - + pcs/snmp/pcs_snmp_agent.logrotate | 10 ---------- + pcs/snmp/settings.py | 2 +- + pcsd/pcsd.logrotate | 2 +- + 4 files changed, 2 insertions(+), 13 deletions(-) + delete mode 100644 pcs/snmp/pcs_snmp_agent.logrotate + +diff --git a/Makefile b/Makefile +index 04cd62a..5d4aed8 100644 +--- a/Makefile ++++ b/Makefile +@@ -129,7 +129,6 @@ install: install_bundled_libs + install -d ${SNMP_MIB_DIR_FULL} + install -m 644 pcs/snmp/mibs/PCMK-PCS*-MIB.txt ${SNMP_MIB_DIR_FULL} + install -m 644 -D pcs/snmp/pcs_snmp_agent.conf ${DESTDIR}/etc/sysconfig/pcs_snmp_agent +- install -m 644 -D pcs/snmp/pcs_snmp_agent.logrotate ${DESTDIR}/etc/logrotate.d/pcs_snmp_agent + install -m 644 -D pcs/snmp/pcs_snmp_agent.8 ${DESTDIR}/${MANDIR}/man8/pcs_snmp_agent.8 + ifeq ($(IS_SYSTEMCTL),true) + install -d ${DESTDIR}/${systemddir}/system/ +diff --git a/pcs/snmp/pcs_snmp_agent.logrotate b/pcs/snmp/pcs_snmp_agent.logrotate +deleted file mode 100644 +index a53c21f..0000000 +--- a/pcs/snmp/pcs_snmp_agent.logrotate ++++ /dev/null +@@ -1,10 +0,0 @@ +-/var/log/pcs/snmp/pcs_snmp_agent.log { +- rotate 5 +- weekly +- missingok +- notifempty +- compress +- delaycompress +- copytruncate +- create 0600 root root +-} +diff --git a/pcs/snmp/settings.py b/pcs/snmp/settings.py +index 0559446..5f054ae 100644 +--- a/pcs/snmp/settings.py ++++ b/pcs/snmp/settings.py +@@ -4,7 +4,7 @@ from __future__ import ( + print_function, + ) + +-LOG_FILE = "/var/log/pcs/pcs_snmp_agent.log" ++LOG_FILE = "/var/log/pcsd/pcs_snmp_agent.log" + ENTERPRISES_OID = "1.3.6.1.4.1" + PACEMAKER_OID = ENTERPRISES_OID + ".32723" + PCS_OID = PACEMAKER_OID + ".100" +diff --git a/pcsd/pcsd.logrotate b/pcsd/pcsd.logrotate +index 36d2529..d105cff 100644 +--- a/pcsd/pcsd.logrotate ++++ b/pcsd/pcsd.logrotate +@@ -1,4 +1,4 @@ +-/var/log/pcsd/pcsd.log { ++/var/log/pcsd/*.log { + rotate 5 + weekly + missingok +-- +1.8.3.1 + diff --git a/SOURCES/bz1373614-01-return-1-when-pcsd-is-unable-to-bind.patch b/SOURCES/bz1373614-01-return-1-when-pcsd-is-unable-to-bind.patch deleted file mode 100644 index b868744..0000000 --- a/SOURCES/bz1373614-01-return-1-when-pcsd-is-unable-to-bind.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 1cd8a8c7dbe3b5728caf68b6659fc59fe5b3031f Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek <tojeline@redhat.com> -Date: Wed, 31 May 2017 11:58:19 +0200 -Subject: [PATCH] return 1 when pcsd is unable to bind - ---- - pcsd/ssl.rb | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/pcsd/ssl.rb b/pcsd/ssl.rb -index 24ee059..1a41ab2 100644 ---- a/pcsd/ssl.rb -+++ b/pcsd/ssl.rb -@@ -99,8 +99,10 @@ def run_server(server, webrick_options, secondary_addrs) - rescue Errno::EADDRNOTAVAIL, Errno::EADDRINUSE => e - $logger.error 'Unable to bind to specified address(es), exiting' - $logger.error e.message -+ exit 1 - rescue SocketError => e - $logger.error e.message -+ exit 1 - end - end - --- -1.8.3.1 - diff --git a/SOURCES/bz1386114-01-fix-a-crash-in-adding-a-remote-node.patch b/SOURCES/bz1386114-01-fix-a-crash-in-adding-a-remote-node.patch deleted file mode 100644 index 0a499d9..0000000 --- a/SOURCES/bz1386114-01-fix-a-crash-in-adding-a-remote-node.patch +++ /dev/null @@ -1,26 +0,0 @@ -From a551c1f4b57cb678a9251d6fc7050a7970df3906 Mon Sep 17 00:00:00 2001 -From: Tomas Jelinek <tojeline@redhat.com> -Date: Wed, 31 May 2017 07:50:59 +0200 -Subject: [PATCH] fix a crash in adding a remote node - -... when an id conflict occurs ---- - pcs/lib/commands/cluster.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/pcs/lib/commands/cluster.py b/pcs/lib/commands/cluster.py -index 7386e3c..0bafef5 100644 ---- a/pcs/lib/commands/cluster.py -+++ b/pcs/lib/commands/cluster.py -@@ -189,7 +189,7 @@ def node_add_remote( - for report in report_list + list(e.args): - if report.code != report_codes.ID_ALREADY_EXISTS: - unified_report_list.append(report) -- elif report.info.get["id"] not in already_exists: -+ elif report.info["id"] not in already_exists: - unified_report_list.append(report) - already_exists.append(report.info["id"]) - report_list = unified_report_list --- -1.8.3.1 - diff --git a/SOURCES/bz1386114-02-deal-with-f-corosync_conf-if-create-remote-res.patch b/SOURCES/bz1386114-02-deal-with-f-corosync_conf-if-create-remote-res.patch deleted file mode 100644 index 76e28e8..0000000 --- a/SOURCES/bz1386114-02-deal-with-f-corosync_conf-if-create-remote-res.patch +++ /dev/null @@ -1,270 +0,0 @@ -From 698f63f743aa970b4af977e4a410e64ee7013fa4 Mon Sep 17 00:00:00 2001 -From: Ivan Devat <idevat@redhat.com> -Date: Tue, 30 May 2017 17:00:41 +0200 -Subject: [PATCH] deal with -f/--corosync_conf if create remote res. - -Do not request corosync.conf when `pcs resource create` with -f is used -for create remote (ocf:pacemaker:remote) or guest (meta remote-node). ---- - pcs/cli/common/lib_wrapper.py | 6 ++- - pcs/lib/commands/resource.py | 90 ++++++++++++++++++++++++++++--------------- - pcs/resource.py | 13 +++++-- - 3 files changed, 74 insertions(+), 35 deletions(-) - -diff --git a/pcs/cli/common/lib_wrapper.py b/pcs/cli/common/lib_wrapper.py -index 683ba4d..4d6ed9a 100644 ---- a/pcs/cli/common/lib_wrapper.py -+++ b/pcs/cli/common/lib_wrapper.py -@@ -318,7 +318,8 @@ def load_module(env, middleware_factory, name): - return bind_all( - env, - middleware.build( -- middleware_factory.cib -+ middleware_factory.cib, -+ middleware_factory.corosync_conf_existing, - ), - { - "bundle_create": resource.bundle_create, -@@ -338,7 +339,8 @@ def load_module(env, middleware_factory, name): - return bind_all( - env, - middleware.build( -- middleware_factory.cib -+ middleware_factory.cib, -+ middleware_factory.corosync_conf_existing, - ), - { - "create": stonith.create, -diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py -index a9f8271..3a060b8 100644 ---- a/pcs/lib/commands/resource.py -+++ b/pcs/lib/commands/resource.py -@@ -46,8 +46,12 @@ def resource_environment( - ]) - - def _validate_remote_connection( -- nodes, resource_id, instance_attributes, allow_not_suitable_command -+ resource_agent, nodes_to_validate_against, resource_id, instance_attributes, -+ allow_not_suitable_command - ): -+ if resource_agent.get_name() != remote_node.AGENT_NAME.full_name: -+ return [] -+ - report_list = [] - report_list.append( - reports.get_problem_creator( -@@ -58,7 +62,7 @@ def _validate_remote_connection( - - report_list.extend( - remote_node.validate_host_not_conflicts( -- nodes, -+ nodes_to_validate_against, - resource_id, - instance_attributes - ) -@@ -66,8 +70,8 @@ def _validate_remote_connection( - return report_list - - def _validate_guest_change( -- tree, nodes, meta_attributes, allow_not_suitable_command, -- detect_remove=False -+ tree, nodes_to_validate_against, meta_attributes, -+ allow_not_suitable_command, detect_remove=False - ): - if not guest_node.is_node_name_in_options(meta_attributes): - return [] -@@ -89,7 +93,7 @@ def _validate_guest_change( - report_list.extend( - guest_node.validate_conflicts( - tree, -- nodes, -+ nodes_to_validate_against, - node_name, - meta_attributes - ) -@@ -97,28 +101,54 @@ def _validate_guest_change( - - return report_list - --def _validate_special_cases( -- nodes, resource_agent, resources_section, resource_id, meta_attributes, -+def _get_nodes_to_validate_against(env, tree): -+ if not env.is_corosync_conf_live and env.is_cib_live: -+ raise LibraryError( -+ reports.live_environment_required(["COROSYNC_CONF"]) -+ ) -+ -+ if not env.is_cib_live and env.is_corosync_conf_live: -+ #we do not try to get corosync.conf from live cluster when cib is not -+ #taken from live cluster -+ return get_nodes(tree=tree) -+ -+ return get_nodes(env.get_corosync_conf(), tree) -+ -+ -+def _check_special_cases( -+ env, resource_agent, resources_section, resource_id, meta_attributes, - instance_attributes, allow_not_suitable_command - ): -- report_list = [] -- -- if resource_agent.get_name() == remote_node.AGENT_NAME.full_name: -- report_list.extend(_validate_remote_connection( -- nodes, -- resource_id, -- instance_attributes, -- allow_not_suitable_command, -- )) -+ if( -+ resource_agent.get_name() != remote_node.AGENT_NAME.full_name -+ and -+ not guest_node.is_node_name_in_options(meta_attributes) -+ ): -+ #if no special case happens we won't take care about corosync.conf that -+ #is needed for getting nodes to validate against -+ return -+ -+ nodes_to_validate_against = _get_nodes_to_validate_against( -+ env, -+ resources_section -+ ) - -+ report_list = [] -+ report_list.extend(_validate_remote_connection( -+ resource_agent, -+ nodes_to_validate_against, -+ resource_id, -+ instance_attributes, -+ allow_not_suitable_command, -+ )) - report_list.extend(_validate_guest_change( - resources_section, -- nodes, -+ nodes_to_validate_against, - meta_attributes, - allow_not_suitable_command, - )) - -- return report_list -+ env.report_processor.process_list(report_list) - - def create( - env, resource_id, resource_agent_name, -@@ -167,15 +197,15 @@ def create( - [resource_id], - ensure_disabled or resource.common.are_meta_disabled(meta_attributes), - ) as resources_section: -- env.report_processor.process_list(_validate_special_cases( -- get_nodes(env.get_corosync_conf(), resources_section), -+ _check_special_cases( -+ env, - resource_agent, - resources_section, - resource_id, - meta_attributes, - instance_attributes, - allow_not_suitable_command -- )) -+ ) - - primitive_element = resource.primitive.create( - env.report_processor, resources_section, -@@ -247,15 +277,15 @@ def _create_as_clone_common( - resource.common.is_clone_deactivated_by_meta(clone_meta_options) - ) - ) as resources_section: -- env.report_processor.process_list(_validate_special_cases( -- get_nodes(env.get_corosync_conf(), resources_section), -+ _check_special_cases( -+ env, - resource_agent, - resources_section, - resource_id, - meta_attributes, - instance_attributes, - allow_not_suitable_command -- )) -+ ) - - primitive_element = resource.primitive.create( - env.report_processor, resources_section, -@@ -325,15 +355,15 @@ def create_in_group( - [resource_id], - ensure_disabled or resource.common.are_meta_disabled(meta_attributes), - ) as resources_section: -- env.report_processor.process_list(_validate_special_cases( -- get_nodes(env.get_corosync_conf(), resources_section), -+ _check_special_cases( -+ env, - resource_agent, - resources_section, - resource_id, - meta_attributes, - instance_attributes, - allow_not_suitable_command -- )) -+ ) - - primitive_element = resource.primitive.create( - env.report_processor, resources_section, -@@ -406,15 +436,15 @@ def create_into_bundle( - disabled_after_wait=ensure_disabled, - required_cib_version=(2, 8, 0) - ) as resources_section: -- env.report_processor.process_list(_validate_special_cases( -- get_nodes(env.get_corosync_conf(), resources_section), -+ _check_special_cases( -+ env, - resource_agent, - resources_section, - resource_id, - meta_attributes, - instance_attributes, - allow_not_suitable_command -- )) -+ ) - - primitive_element = resource.primitive.create( - env.report_processor, resources_section, -diff --git a/pcs/resource.py b/pcs/resource.py -index 4d5f43a..dc6da13 100644 ---- a/pcs/resource.py -+++ b/pcs/resource.py -@@ -28,24 +28,31 @@ from pcs.cli.resource.parse_args import ( - parse_bundle_update_options, - parse_create as parse_create_args, - ) --from pcs.lib.env_tools import get_nodes - from pcs.lib.errors import LibraryError -+from pcs.lib.cib.resource import guest_node - import pcs.lib.pacemaker.live as lib_pacemaker - from pcs.lib.pacemaker.values import timeout_to_seconds - import pcs.lib.resource_agent as lib_ra - from pcs.cli.common.console_report import error, warn --from pcs.lib.commands.resource import _validate_guest_change -+from pcs.lib.commands.resource import( -+ _validate_guest_change, -+ _get_nodes_to_validate_against, -+) - - - RESOURCE_RELOCATE_CONSTRAINT_PREFIX = "pcs-relocate-" - - def _detect_guest_change(meta_attributes, allow_not_suitable_command): -+ if not guest_node.is_node_name_in_options(meta_attributes): -+ return -+ - env = utils.get_lib_env() - cib = env.get_cib() -+ nodes_to_validate_against = _get_nodes_to_validate_against(env, cib) - env.report_processor.process_list( - _validate_guest_change( - cib, -- get_nodes(env.get_corosync_conf(), cib), -+ nodes_to_validate_against, - meta_attributes, - allow_not_suitable_command, - detect_remove=True, --- -1.8.3.1 - diff --git a/SOURCES/bz1415197-01-fix-pcs-cluster-auth.patch b/SOURCES/bz1415197-01-fix-pcs-cluster-auth.patch new file mode 100644 index 0000000..c7d6aae --- /dev/null +++ b/SOURCES/bz1415197-01-fix-pcs-cluster-auth.patch @@ -0,0 +1,25 @@ +From 9ec76750dc457e0a445fd43bbc172ab3c3de1a23 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Thu, 1 Feb 2018 11:10:27 +0100 +Subject: [PATCH 1/2] fix 'pcs cluster auth' + +--- + pcsd/cfgsync.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pcsd/cfgsync.rb b/pcsd/cfgsync.rb +index 14b55736..36ae1abb 100644 +--- a/pcsd/cfgsync.rb ++++ b/pcsd/cfgsync.rb +@@ -795,7 +795,7 @@ module Cfgsync + # we run in a cluster so we need to sync the config + publisher = ConfigPublisher.new( + PCSAuth.getSuperuserAuth(), [config_new], nodes, cluster_name, +- new_tokens ++ new_tokens, new_ports + ) + old_configs, node_responses = publisher.publish() + if not old_configs.include?(config_new.class.name) +-- +2.13.6 + diff --git a/SOURCES/bz1415197-02-fix-pcs-cluster-auth.patch b/SOURCES/bz1415197-02-fix-pcs-cluster-auth.patch new file mode 100644 index 0000000..2483ab8 --- /dev/null +++ b/SOURCES/bz1415197-02-fix-pcs-cluster-auth.patch @@ -0,0 +1,25 @@ +From 4c2dc964999f57395f377d44594324e5ae3df1b0 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Thu, 1 Feb 2018 13:13:23 +0100 +Subject: [PATCH 2/2] fix 'pcs cluster auth' + +--- + pcsd/pcs.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb +index 68d2e7ed..43f58f22 100644 +--- a/pcsd/pcs.rb ++++ b/pcsd/pcs.rb +@@ -1247,7 +1247,7 @@ def pcs_auth(auth_user, nodes, username, password, force=false, local=true) + auth_responses.each { |node, response| + if 'ok' == response['status'] + new_tokens[node] = response['token'] +- ports[node] = nodes[node] ++ ports[node] = nodes[node] || PCSD_DEFAULT_PORT + end + } + if not new_tokens.empty? +-- +2.13.6 + diff --git a/SOURCES/bz1421702-01-gui-allow-forcing-resource-stonith-create-update.patch b/SOURCES/bz1421702-01-gui-allow-forcing-resource-stonith-create-update.patch new file mode 100644 index 0000000..713b08d --- /dev/null +++ b/SOURCES/bz1421702-01-gui-allow-forcing-resource-stonith-create-update.patch @@ -0,0 +1,114 @@ +From bca125ee785104fbffdcf487b22599e3c9980f06 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Tue, 5 Dec 2017 15:10:43 +0100 +Subject: [PATCH] gui: allow forcing resource|stonith create|update + +--- + pcsd/pcsd.rb | 3 +++ + pcsd/public/js/pcsd.js | 39 +++++++++++++++++++++++++-------------- + pcsd/remote.rb | 2 +- + 3 files changed, 29 insertions(+), 15 deletions(-) + +diff --git a/pcsd/pcsd.rb b/pcsd/pcsd.rb +index f97dabc..8f5ad81 100644 +--- a/pcsd/pcsd.rb ++++ b/pcsd/pcsd.rb +@@ -211,6 +211,9 @@ helpers do + if param == "disabled" + meta_options << 'meta' << 'target-role=Stopped' + end ++ if param == "force" and val ++ param_line << "--force" ++ end + } + return param_line + meta_options + end +diff --git a/pcsd/public/js/pcsd.js b/pcsd/public/js/pcsd.js +index b7e9a7a..2956530 100644 +--- a/pcsd/public/js/pcsd.js ++++ b/pcsd/public/js/pcsd.js +@@ -286,9 +286,24 @@ function create_node(form) { + }); + } + ++function create_resource_error_processing(error_message, form, update, stonith) { ++ var message = ( ++ "Unable to " + (update ? "update " : "add ") + name + "\n" + error_message ++ ); ++ if (message.indexOf('--force') == -1) { ++ alert(message); ++ } ++ else { ++ message = message.replace(', use --force to override', ''); ++ if (confirm(message + "\n\nDo you want to force the operation?")) { ++ create_resource(form, update, stonith, true) ++ } ++ } ++} ++ + // If update is set to true we update the resource instead of create it + // if stonith is set to true we update/create a stonith agent +-function create_resource(form, update, stonith) { ++function create_resource(form, update, stonith, force) { + var data = {}; + $($(form).serializeArray()).each(function(index, obj) { + data[obj.name] = obj.value; +@@ -303,6 +318,9 @@ function create_resource(form, update, stonith) { + } else { + name = "resource"; + } ++ if (force) { ++ data["force"] = force; ++ } + + ajax_wrapper({ + type: "POST", +@@ -312,7 +330,9 @@ function create_resource(form, update, stonith) { + success: function(returnValue) { + $('input.apply_changes').show(); + if (returnValue["error"] == "true") { +- alert(returnValue["stderr"]); ++ create_resource_error_processing( ++ returnValue["stderr"], form, update, stonith ++ ); + } else { + Pcs.update(); + if (!update) { +@@ -326,18 +346,9 @@ function create_resource(form, update, stonith) { + } + }, + error: function(xhr, status, error) { +- if (update) { +- alert( +- "Unable to update " + name + " " +- + ajax_simple_error(xhr, status, error) +- ); +- } +- else { +- alert( +- "Unable to add " + name + " " +- + ajax_simple_error(xhr, status, error) +- ); +- } ++ create_resource_error_processing( ++ ajax_simple_error(xhr, status, error), form, update, stonith ++ ); + $('input.apply_changes').show(); + } + }); +diff --git a/pcsd/remote.rb b/pcsd/remote.rb +index e1e95a8..518e668 100644 +--- a/pcsd/remote.rb ++++ b/pcsd/remote.rb +@@ -1494,7 +1494,7 @@ def update_resource (params, request, auth_user) + end + resource_group = params[:resource_group] + end +- if params[:resource_type] == "ocf:pacemaker:remote" ++ if params[:resource_type] == "ocf:pacemaker:remote" and not cmd.include?("--force") + # Workaround for Error: this command is not sufficient for create remote + # connection, use 'pcs cluster node add-remote', use --force to override. + # It is not possible to specify meta attributes so we don't need to take +-- +1.8.3.1 + diff --git a/SOURCES/bz1433016-02-make-container-type-mandatory-in-bundle-create.patch b/SOURCES/bz1433016-02-make-container-type-mandatory-in-bundle-create.patch deleted file mode 100644 index a9e2ee7..0000000 --- a/SOURCES/bz1433016-02-make-container-type-mandatory-in-bundle-create.patch +++ /dev/null @@ -1,447 +0,0 @@ -From 8d3bcf972d9f6aa7e568e203f749cc878cd6fd34 Mon Sep 17 00:00:00 2001 -From: Ivan Devat <idevat@redhat.com> -Date: Thu, 15 Jun 2017 11:46:12 +0200 -Subject: [PATCH] make container type mandatory in "bundle create" - ---- - pcs/cli/resource/parse_args.py | 4 +- - pcs/cli/resource/test/test_parse_args.py | 32 +++++++------- - pcs/pcs.8 | 4 +- - pcs/test/cib_resource/test_bundle.py | 75 +++++++++++++++++++------------- - pcs/test/cib_resource/test_create.py | 2 +- - pcs/test/test_constraints.py | 2 +- - pcs/test/test_resource.py | 2 +- - pcs/usage.py | 3 +- - 8 files changed, 67 insertions(+), 57 deletions(-) - -diff --git a/pcs/cli/resource/parse_args.py b/pcs/cli/resource/parse_args.py -index 366acac..1bdcd5b 100644 ---- a/pcs/cli/resource/parse_args.py -+++ b/pcs/cli/resource/parse_args.py -@@ -84,7 +84,7 @@ def _parse_bundle_groups(arg_list): - def parse_bundle_create_options(arg_list): - groups = _parse_bundle_groups(arg_list) - container_options = groups.get("container", []) -- container_type = None -+ container_type = "" - if container_options and "=" not in container_options[0]: - container_type = container_options.pop(0) - parts = { -@@ -101,8 +101,6 @@ def parse_bundle_create_options(arg_list): - ], - "meta": prepare_options(groups.get("meta", [])) - } -- if not parts["container_type"]: -- parts["container_type"] = "docker" - return parts - - def _split_bundle_map_update_op_and_options( -diff --git a/pcs/cli/resource/test/test_parse_args.py b/pcs/cli/resource/test/test_parse_args.py -index 0c936cc..791b60d 100644 ---- a/pcs/cli/resource/test/test_parse_args.py -+++ b/pcs/cli/resource/test/test_parse_args.py -@@ -215,7 +215,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - [], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {}, - "port_map": [], -@@ -229,9 +229,9 @@ class ParseBundleCreateOptions(TestCase): - - def test_container_type(self): - self.assert_produce( -- ["container", "lxc"], -+ ["container", "docker"], - { -- "container_type": "lxc", -+ "container_type": "docker", - "container": {}, - "network": {}, - "port_map": [], -@@ -244,7 +244,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["container", "a=b", "c=d"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {"a": "b", "c": "d"}, - "network": {}, - "port_map": [], -@@ -255,9 +255,9 @@ class ParseBundleCreateOptions(TestCase): - - def test_container_type_and_options(self): - self.assert_produce( -- ["container", "lxc", "a=b", "c=d"], -+ ["container", "docker", "a=b", "c=d"], - { -- "container_type": "lxc", -+ "container_type": "docker", - "container": {"a": "b", "c": "d"}, - "network": {}, - "port_map": [], -@@ -279,7 +279,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["network", "a=b", "c=d"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {"a": "b", "c": "d"}, - "port_map": [], -@@ -309,7 +309,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["port-map", "a=b", "c=d"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {}, - "port_map": [{"a": "b", "c": "d"}], -@@ -322,7 +322,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["port-map", "a=b", "c=d", "port-map", "e=f"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {}, - "port_map": [{"a": "b", "c": "d"}, {"e": "f"}], -@@ -349,7 +349,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["storage-map", "a=b", "c=d"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {}, - "port_map": [], -@@ -362,7 +362,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["storage-map", "a=b", "c=d", "storage-map", "e=f"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {}, - "port_map": [], -@@ -381,7 +381,7 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - ["meta", "a=b", "c=d"], - { -- "container_type": "docker", -+ "container_type": "", - "container": {}, - "network": {}, - "port_map": [], -@@ -402,7 +402,7 @@ class ParseBundleCreateOptions(TestCase): - def test_all(self): - self.assert_produce( - [ -- "container", "lxc", "a=b", "c=d", -+ "container", "docker", "a=b", "c=d", - "network", "e=f", "g=h", - "port-map", "i=j", "k=l", - "port-map", "m=n", "o=p", -@@ -411,7 +411,7 @@ class ParseBundleCreateOptions(TestCase): - "meta", "y=z", "A=B", - ], - { -- "container_type": "lxc", -+ "container_type": "docker", - "container": {"a": "b", "c": "d"}, - "network": {"e": "f", "g": "h"}, - "port_map": [{"i": "j", "k": "l"}, {"m": "n", "o": "p"}], -@@ -427,7 +427,7 @@ class ParseBundleCreateOptions(TestCase): - "meta", "y=z", - "port-map", "i=j", "k=l", - "network", "e=f", -- "container", "lxc", "a=b", -+ "container", "docker", "a=b", - "storage-map", "u=v", "w=x", - "port-map", "m=n", "o=p", - "meta", "A=B", -@@ -435,7 +435,7 @@ class ParseBundleCreateOptions(TestCase): - "container", "c=d", - ], - { -- "container_type": "lxc", -+ "container_type": "docker", - "container": {"a": "b", "c": "d"}, - "network": {"e": "f", "g": "h"}, - "port_map": [{"i": "j", "k": "l"}, {"m": "n", "o": "p"}], -diff --git a/pcs/pcs.8 b/pcs/pcs.8 -index 20b5c2e..27298a7 100644 ---- a/pcs/pcs.8 -+++ b/pcs/pcs.8 -@@ -162,8 +162,8 @@ Remove the clone which contains the specified group or resource (the resource or - master [<master/slave id>] <resource id | group id> [options] [\fB\-\-wait\fR[=n]] - Configure a resource or group as a multi\-state (master/slave) resource. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the operation to finish (including starting and promoting resource instances if appropriate) and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. Note: to remove a master you must remove the resource/group it contains. - .TP --bundle create <bundle id> [container [<container type>] <container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [meta <meta options>] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]] --Create a new bundle encapsulating no resources. The bundle can be used either as it is or a resource may be put into it at any time. If the container type is not specified, it defaults to 'docker'. If \fB\-\-disabled\fR is specified, the bundle is not started automatically. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the bundle to start and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. -+bundle create <bundle id> container <container type> [<container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [meta <meta options>] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]] -+Create a new bundle encapsulating no resources. The bundle can be used either as it is or a resource may be put into it at any time. If \fB\-\-disabled\fR is specified, the bundle is not started automatically. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the bundle to start and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. - .TP - bundle update <bundle id> [container <container options>] [network <network options>] [port\-map (add <port options>) | (remove <id>...)]... [storage\-map (add <storage options>) | (remove <id>...)]... [meta <meta options>] [\fB\-\-wait\fR[=n]] - Add, remove or change options to specified bundle. If you wish to update a resource encapsulated in the bundle, use the 'pcs resource update' command instead and specify the resource id. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the operation to finish (including moving resources if appropriate) and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. -diff --git a/pcs/test/cib_resource/test_bundle.py b/pcs/test/cib_resource/test_bundle.py -index 29e4339..50ea1df 100644 ---- a/pcs/test/cib_resource/test_bundle.py -+++ b/pcs/test/cib_resource/test_bundle.py -@@ -41,7 +41,7 @@ class BundleCreateUpgradeCib(BundleCreateCommon): - - def test_success(self): - self.assert_effect( -- "resource bundle create B1 container image=pcs:test", -+ "resource bundle create B1 container docker image=pcs:test", - """ - <resources> - <bundle id="B1"> -@@ -59,7 +59,7 @@ class BundleCreate(BundleCreateCommon): - - def test_minimal(self): - self.assert_effect( -- "resource bundle create B1 container image=pcs:test", -+ "resource bundle create B1 container docker image=pcs:test", - """ - <resources> - <bundle id="B1"> -@@ -73,7 +73,8 @@ class BundleCreate(BundleCreateCommon): - self.assert_effect( - """ - resource bundle create B1 -- container replicas=4 replicas-per-host=2 run-command=/bin/true -+ container docker replicas=4 replicas-per-host=2 -+ run-command=/bin/true - port-map port=1001 - meta target-role=Stopped - network control-port=12345 host-interface=eth0 host-netmask=24 -@@ -171,15 +172,24 @@ class BundleCreate(BundleCreateCommon): - stdout_start="\nUsage: pcs resource bundle create...\n" - ) - -- def test_fail_when_missing_required(self): -+ def test_fail_when_missing_container_type(self): - self.assert_pcs_fail_regardless_of_force( - "resource bundle create B1", -+ "Error: '' is not a valid container type value, use docker\n" -+ ) -+ -+ def test_fail_when_missing_required(self): -+ self.assert_pcs_fail_regardless_of_force( -+ "resource bundle create B1 container docker", - "Error: required container option 'image' is missing\n" - ) - - def test_fail_on_unknown_option(self): - self.assert_pcs_fail( -- "resource bundle create B1 container image=pcs:test extra=option", -+ """ -+ resource bundle create B1 container docker image=pcs:test -+ extra=option -+ """, - "Error: invalid container option 'extra', allowed options are: " - "image, masters, network, options, replicas, replicas-per-host," - " run-command, use --force to override\n" -@@ -192,8 +202,8 @@ class BundleCreate(BundleCreateCommon): - # supported by pacemaker and so the command fails. - self.assert_pcs_fail( - """ -- resource bundle create B1 container image=pcs:test extra=option -- --force -+ resource bundle create B1 container docker image=pcs:test -+ extra=option --force - """ - , - stdout_start="Error: Unable to update cib\n" -@@ -201,7 +211,7 @@ class BundleCreate(BundleCreateCommon): - - def test_more_errors(self): - self.assert_pcs_fail_regardless_of_force( -- "resource bundle create B#1 container replicas=x", -+ "resource bundle create B#1 container docker replicas=x", - outdent( - """\ - Error: invalid bundle name 'B#1', '#' is not a valid character for a bundle name -@@ -239,25 +249,25 @@ class BundleUpdate(BundleCreateCommon): - - def fixture_bundle(self, name): - self.assert_pcs_success( -- "resource bundle create {0} container image=pcs:test".format( -+ "resource bundle create {0} container docker image=pcs:test".format( - name - ) - ) - - def fixture_bundle_complex(self, name): - self.assert_pcs_success( -- ( -- "resource bundle create {0} " -- "container image=pcs:test replicas=4 masters=2 " -- "network control-port=12345 host-interface=eth0 host-netmask=24 " -- "port-map internal-port=1000 port=2000 " -- "port-map internal-port=1001 port=2001 " -- "port-map internal-port=1002 port=2002 " -- "storage-map source-dir=/tmp/docker1a target-dir=/tmp/docker1b " -- "storage-map source-dir=/tmp/docker2a target-dir=/tmp/docker2b " -- "storage-map source-dir=/tmp/docker3a target-dir=/tmp/docker3b " -- "meta priority=15 resource-stickiness=100 is-managed=false " -- ).format(name) -+ (""" -+ resource bundle create {0} -+ container docker image=pcs:test replicas=4 masters=2 -+ network control-port=12345 host-interface=eth0 host-netmask=24 -+ port-map internal-port=1000 port=2000 -+ port-map internal-port=1001 port=2001 -+ port-map internal-port=1002 port=2002 -+ storage-map source-dir=/tmp/docker1a target-dir=/tmp/docker1b -+ storage-map source-dir=/tmp/docker2a target-dir=/tmp/docker2b -+ storage-map source-dir=/tmp/docker3a target-dir=/tmp/docker3b -+ meta priority=15 resource-stickiness=100 is-managed=false -+ """).format(name) - ) - - def test_fail_when_missing_args_1(self): -@@ -415,7 +425,7 @@ class BundleShow(TestCase, AssertPcsMixin): - - def test_minimal(self): - self.assert_pcs_success( -- "resource bundle create B1 container image=pcs:test" -+ "resource bundle create B1 container docker image=pcs:test" - ) - self.assert_pcs_success("resource show B1", outdent( - """\ -@@ -428,7 +438,8 @@ class BundleShow(TestCase, AssertPcsMixin): - self.assert_pcs_success( - """ - resource bundle create B1 -- container image=pcs:test masters=2 replicas=4 options='a b c' -+ container docker image=pcs:test masters=2 replicas=4 -+ options='a b c' - """ - ) - self.assert_pcs_success("resource show B1", outdent( -@@ -442,7 +453,7 @@ class BundleShow(TestCase, AssertPcsMixin): - self.assert_pcs_success( - """ - resource bundle create B1 -- container image=pcs:test -+ container docker image=pcs:test - network host-interface=eth0 host-netmask=24 control-port=12345 - """ - ) -@@ -458,7 +469,7 @@ class BundleShow(TestCase, AssertPcsMixin): - self.assert_pcs_success( - """ - resource bundle create B1 -- container image=pcs:test -+ container docker image=pcs:test - port-map id=B1-port-map-1001 internal-port=2002 port=2000 - port-map range=3000-3300 - """ -@@ -477,7 +488,7 @@ class BundleShow(TestCase, AssertPcsMixin): - self.assert_pcs_success( - """ - resource bundle create B1 -- container image=pcs:test -+ container docker image=pcs:test - storage-map source-dir=/tmp/docker1a target-dir=/tmp/docker1b - storage-map id=my-storage-map source-dir=/tmp/docker2a - target-dir=/tmp/docker2b -@@ -494,9 +505,10 @@ class BundleShow(TestCase, AssertPcsMixin): - )) - - def test_meta(self): -- self.assert_pcs_success( -- "resource bundle create B1 container image=pcs:test --disabled" -- ) -+ self.assert_pcs_success(""" -+ resource bundle create B1 container docker image=pcs:test -+ --disabled -+ """) - self.assert_pcs_success("resource show B1", outdent( - # pylint:disable=trailing-whitespace - """\ -@@ -508,7 +520,7 @@ class BundleShow(TestCase, AssertPcsMixin): - - def test_resource(self): - self.assert_pcs_success( -- "resource bundle create B1 container image=pcs:test" -+ "resource bundle create B1 container docker image=pcs:test" - ) - self.assert_pcs_success( - "resource create A ocf:pacemaker:Dummy bundle B1 --no-default-ops" -@@ -526,7 +538,8 @@ class BundleShow(TestCase, AssertPcsMixin): - self.assert_pcs_success( - """ - resource bundle create B1 -- container image=pcs:test masters=2 replicas=4 options='a b c' -+ container docker image=pcs:test masters=2 replicas=4 -+ options='a b c' - network host-interface=eth0 host-netmask=24 control-port=12345 - port-map id=B1-port-map-1001 internal-port=2002 port=2000 - port-map range=3000-3300 -diff --git a/pcs/test/cib_resource/test_create.py b/pcs/test/cib_resource/test_create.py -index 2adef5a..2492ba9 100644 ---- a/pcs/test/cib_resource/test_create.py -+++ b/pcs/test/cib_resource/test_create.py -@@ -888,7 +888,7 @@ class Bundle(ResourceTest): - - def fixture_bundle(self, name): - self.assert_pcs_success( -- "resource bundle create {0} container image=pcs:test".format( -+ "resource bundle create {0} container docker image=pcs:test".format( - name - ) - ) -diff --git a/pcs/test/test_constraints.py b/pcs/test/test_constraints.py -index 69d955d..4160b01 100644 ---- a/pcs/test/test_constraints.py -+++ b/pcs/test/test_constraints.py -@@ -3246,7 +3246,7 @@ class Bundle(ConstraintEffect): - - def fixture_bundle(self, name): - self.assert_pcs_success( -- "resource bundle create {0} container image=pcs:test".format( -+ "resource bundle create {0} container docker image=pcs:test".format( - name - ) - ) -diff --git a/pcs/test/test_resource.py b/pcs/test/test_resource.py -index 4bdc194..c015fa4 100644 ---- a/pcs/test/test_resource.py -+++ b/pcs/test/test_resource.py -@@ -4710,7 +4710,7 @@ class BundleCommon( - - def fixture_bundle(self, name): - self.assert_pcs_success( -- "resource bundle create {0} container image=pcs:test".format( -+ "resource bundle create {0} container docker image=pcs:test".format( - name - ) - ) -diff --git a/pcs/usage.py b/pcs/usage.py -index 75cb118..9cbf7de 100644 ---- a/pcs/usage.py -+++ b/pcs/usage.py -@@ -428,13 +428,12 @@ Commands: - If 'n' is not specified it defaults to 60 minutes. - Note: to remove a master you must remove the resource/group it contains. - -- bundle create <bundle id> [container [<container type>] <container options>] -+ bundle create <bundle id> container <container type> [<container options>] - [network <network options>] [port-map <port options>]... - [storage-map <storage options>]... [meta <meta options>] - [--disabled] [--wait[=n]] - Create a new bundle encapsulating no resources. The bundle can be used - either as it is or a resource may be put into it at any time. -- If the container type is not specified, it defaults to 'docker'. - If --disabled is specified, the bundle is not started automatically. - If --wait is specified, pcs will wait up to 'n' seconds for the bundle - to start and then return 0 on success or 1 on error. If 'n' is not --- -1.8.3.1 - diff --git a/SOURCES/bz1447910-01-bundle-resources-are-missing-meta-attributes.patch b/SOURCES/bz1447910-01-bundle-resources-are-missing-meta-attributes.patch deleted file mode 100644 index 7bf8e65..0000000 --- a/SOURCES/bz1447910-01-bundle-resources-are-missing-meta-attributes.patch +++ /dev/null @@ -1,2488 +0,0 @@ -From db1a118ed0d36633c67513961b479f8fae3cc2b9 Mon Sep 17 00:00:00 2001 -From: Ivan Devat <idevat@redhat.com> -Date: Thu, 15 Jun 2017 11:46:12 +0200 -Subject: [PATCH] squash bz1447910 bundle resources are missing meta - -d21dd0e6b4d3 make resource enable | disable work with bundles - -27d46c115210 make resource manage | unmanage work with bundles - -c963cdcd321b show bundles' meta attributes in resources listing - -f1923af76d73 support meta attributes in 'resource bundle create' - -e09015ee868a support meta attributes in 'resource bundle update' - -c6e70a38346a stop bundles when deleting them ---- - pcs/cli/resource/parse_args.py | 4 +- - pcs/cli/resource/test/test_parse_args.py | 70 ++++++++ - pcs/lib/cib/nvpair.py | 12 +- - pcs/lib/cib/resource/bundle.py | 17 +- - pcs/lib/cib/resource/common.py | 40 +++-- - pcs/lib/cib/test/test_nvpair.py | 42 +++++ - pcs/lib/cib/test/test_resource_common.py | 16 +- - pcs/lib/cib/tools.py | 10 +- - pcs/lib/commands/resource.py | 86 +++++++--- - pcs/lib/commands/test/resource/fixture.py | 2 +- - .../commands/test/resource/test_bundle_create.py | 179 +++++++++++++++---- - .../commands/test/resource/test_bundle_update.py | 102 ++++++++++- - .../test/resource/test_resource_enable_disable.py | 93 ++++++++-- - .../test/resource/test_resource_manage_unmanage.py | 189 +++++++++++++++++++-- - pcs/lib/pacemaker/state.py | 40 ++++- - pcs/lib/pacemaker/test/test_state.py | 108 +++++++++++- - pcs/pcs.8 | 6 +- - pcs/resource.py | 99 ++++++++--- - pcs/test/cib_resource/test_bundle.py | 67 ++++++++ - pcs/test/cib_resource/test_manage_unmanage.py | 5 +- - pcs/test/test_resource.py | 40 +++-- - pcs/usage.py | 9 +- - 22 files changed, 1055 insertions(+), 181 deletions(-) - -diff --git a/pcs/cli/resource/parse_args.py b/pcs/cli/resource/parse_args.py -index 19ee8f9..366acac 100644 ---- a/pcs/cli/resource/parse_args.py -+++ b/pcs/cli/resource/parse_args.py -@@ -58,7 +58,7 @@ def parse_create(arg_list): - - def _parse_bundle_groups(arg_list): - repeatable_keyword_list = ["port-map", "storage-map"] -- keyword_list = ["container", "network"] + repeatable_keyword_list -+ keyword_list = ["meta", "container", "network"] + repeatable_keyword_list - groups = group_by_keywords( - arg_list, - set(keyword_list), -@@ -99,6 +99,7 @@ def parse_bundle_create_options(arg_list): - prepare_options(storage_map) - for storage_map in groups.get("storage-map", []) - ], -+ "meta": prepare_options(groups.get("meta", [])) - } - if not parts["container_type"]: - parts["container_type"] = "docker" -@@ -144,6 +145,7 @@ def parse_bundle_update_options(arg_list): - "port_map_remove": port_map["remove"], - "storage_map_add": storage_map["add"], - "storage_map_remove": storage_map["remove"], -+ "meta": prepare_options(groups.get("meta", [])) - } - return parts - -diff --git a/pcs/cli/resource/test/test_parse_args.py b/pcs/cli/resource/test/test_parse_args.py -index 5033ec7..0c936cc 100644 ---- a/pcs/cli/resource/test/test_parse_args.py -+++ b/pcs/cli/resource/test/test_parse_args.py -@@ -220,6 +220,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -235,6 +236,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -247,6 +249,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -259,6 +262,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -280,6 +284,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {"a": "b", "c": "d"}, - "port_map": [], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -309,6 +314,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [{"a": "b", "c": "d"}], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -321,6 +327,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [{"a": "b", "c": "d"}, {"e": "f"}], - "storage_map": [], -+ "meta": {}, - } - ) - -@@ -347,6 +354,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [], - "storage_map": [{"a": "b", "c": "d"}], -+ "meta": {}, - } - ) - -@@ -359,6 +367,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {}, - "port_map": [], - "storage_map": [{"a": "b", "c": "d"}, {"e": "f"}], -+ "meta": {}, - } - ) - -@@ -368,6 +377,28 @@ class ParseBundleCreateOptions(TestCase): - def test_storage_map_missing_key(self): - self.assert_raises_cmdline(["storage-map", "=b", "c=d"]) - -+ def test_meta(self): -+ self.assert_produce( -+ ["meta", "a=b", "c=d"], -+ { -+ "container_type": "docker", -+ "container": {}, -+ "network": {}, -+ "port_map": [], -+ "storage_map": [], -+ "meta": {"a": "b", "c": "d"}, -+ } -+ ) -+ -+ def test_meta_empty(self): -+ self.assert_raises_cmdline(["meta"]) -+ -+ def test_meta_missing_value(self): -+ self.assert_raises_cmdline(["meta", "a", "c=d"]) -+ -+ def test_meta_missing_key(self): -+ self.assert_raises_cmdline(["meta", "=b", "c=d"]) -+ - def test_all(self): - self.assert_produce( - [ -@@ -377,6 +408,7 @@ class ParseBundleCreateOptions(TestCase): - "port-map", "m=n", "o=p", - "storage-map", "q=r", "s=t", - "storage-map", "u=v", "w=x", -+ "meta", "y=z", "A=B", - ], - { - "container_type": "lxc", -@@ -384,6 +416,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {"e": "f", "g": "h"}, - "port_map": [{"i": "j", "k": "l"}, {"m": "n", "o": "p"}], - "storage_map": [{"q": "r", "s": "t"}, {"u": "v", "w": "x"}], -+ "meta": {"y": "z", "A": "B"}, - } - ) - -@@ -391,11 +424,13 @@ class ParseBundleCreateOptions(TestCase): - self.assert_produce( - [ - "storage-map", "q=r", "s=t", -+ "meta", "y=z", - "port-map", "i=j", "k=l", - "network", "e=f", - "container", "lxc", "a=b", - "storage-map", "u=v", "w=x", - "port-map", "m=n", "o=p", -+ "meta", "A=B", - "network", "g=h", - "container", "c=d", - ], -@@ -405,6 +440,7 @@ class ParseBundleCreateOptions(TestCase): - "network": {"e": "f", "g": "h"}, - "port_map": [{"i": "j", "k": "l"}, {"m": "n", "o": "p"}], - "storage_map": [{"q": "r", "s": "t"}, {"u": "v", "w": "x"}], -+ "meta": {"y": "z", "A": "B"}, - } - ) - -@@ -432,6 +468,7 @@ class ParseBundleUpdateOptions(TestCase): - "port_map_remove": [], - "storage_map_add": [], - "storage_map_remove": [], -+ "meta": {}, - } - ) - -@@ -445,6 +482,7 @@ class ParseBundleUpdateOptions(TestCase): - "port_map_remove": [], - "storage_map_add": [], - "storage_map_remove": [], -+ "meta": {}, - } - ) - -@@ -467,6 +505,7 @@ class ParseBundleUpdateOptions(TestCase): - "port_map_remove": [], - "storage_map_add": [], - "storage_map_remove": [], -+ "meta": {}, - } - ) - -@@ -519,6 +558,7 @@ class ParseBundleUpdateOptions(TestCase): - "port_map_remove": ["c", "d", "i"], - "storage_map_add": [], - "storage_map_remove": [], -+ "meta": {}, - } - ) - -@@ -562,9 +602,34 @@ class ParseBundleUpdateOptions(TestCase): - {"e": "f", "g": "h",}, - ], - "storage_map_remove": ["c", "d", "i"], -+ "meta": {}, -+ } -+ ) -+ -+ def test_meta(self): -+ self.assert_produce( -+ ["meta", "a=b", "c=d"], -+ { -+ "container": {}, -+ "network": {}, -+ "port_map_add": [], -+ "port_map_remove": [], -+ "storage_map_add": [], -+ "storage_map_remove": [], -+ "meta": {"a": "b", "c": "d"}, - } - ) - -+ def test_meta_empty(self): -+ self.assert_raises_cmdline(["meta"]) -+ -+ def test_meta_missing_value(self): -+ self.assert_raises_cmdline(["meta", "a", "c=d"]) -+ -+ def test_meta_missing_key(self): -+ self.assert_raises_cmdline(["meta", "=b", "c=d"]) -+ -+ - def test_all(self): - self.assert_produce( - [ -@@ -578,6 +643,7 @@ class ParseBundleUpdateOptions(TestCase): - "storage-map", "add", "v=w", - "storage-map", "remove", "x", "y", - "storage-map", "remove", "z", -+ "meta", "A=B", "C=D", - ], - { - "container": {"a": "b", "c": "d"}, -@@ -592,6 +658,7 @@ class ParseBundleUpdateOptions(TestCase): - {"v": "w"}, - ], - "storage_map_remove": ["x", "y", "z"], -+ "meta": {"A": "B", "C": "D"}, - } - ) - -@@ -599,11 +666,13 @@ class ParseBundleUpdateOptions(TestCase): - self.assert_produce( - [ - "storage-map", "remove", "x", "y", -+ "meta", "A=B", - "port-map", "remove", "o", "p", - "network", "e=f", "g=h", - "storage-map", "add", "r=s", "t=u", - "port-map", "add", "i=j", "k=l", - "container", "a=b", "c=d", -+ "meta", "C=D", - "port-map", "remove", "q", - "storage-map", "remove", "z", - "storage-map", "add", "v=w", -@@ -622,6 +691,7 @@ class ParseBundleUpdateOptions(TestCase): - {"v": "w"}, - ], - "storage_map_remove": ["x", "y", "z"], -+ "meta": {"A": "B", "C": "D"}, - } - ) - -diff --git a/pcs/lib/cib/nvpair.py b/pcs/lib/cib/nvpair.py -index 261d17c..d3f5a5c 100644 ---- a/pcs/lib/cib/nvpair.py -+++ b/pcs/lib/cib/nvpair.py -@@ -11,18 +11,19 @@ from functools import partial - from pcs.lib.cib.tools import create_subelement_id - from pcs.lib.xml_tools import get_sub_element - --def _append_new_nvpair(nvset_element, name, value): -+def _append_new_nvpair(nvset_element, name, value, id_provider=None): - """ - Create nvpair with name and value as subelement of nvset_element. - - etree.Element nvset_element is context of new nvpair - string name is name attribute of new nvpair - string value is value attribute of new nvpair -+ IdProvider id_provider -- elements' ids generator - """ - etree.SubElement( - nvset_element, - "nvpair", -- id=create_subelement_id(nvset_element, name), -+ id=create_subelement_id(nvset_element, name, id_provider), - name=name, - value=value - ) -@@ -73,7 +74,7 @@ def arrange_first_nvset(tag_name, context_element, nvpair_dict): - - update_nvset(nvset_element, nvpair_dict) - --def append_new_nvset(tag_name, context_element, nvpair_dict): -+def append_new_nvset(tag_name, context_element, nvpair_dict, id_provider=None): - """ - Append new nvset_element comprising nvpairs children (corresponding - nvpair_dict) to the context_element -@@ -81,12 +82,13 @@ def append_new_nvset(tag_name, context_element, nvpair_dict): - string tag_name should be "instance_attributes" or "meta_attributes" - etree.Element context_element is element where new nvset will be appended - dict nvpair_dict contains source for nvpair children -+ IdProvider id_provider -- elements' ids generator - """ - nvset_element = etree.SubElement(context_element, tag_name, { -- "id": create_subelement_id(context_element, tag_name) -+ "id": create_subelement_id(context_element, tag_name, id_provider) - }) - for name, value in sorted(nvpair_dict.items()): -- _append_new_nvpair(nvset_element, name, value) -+ _append_new_nvpair(nvset_element, name, value, id_provider) - - append_new_instance_attributes = partial( - append_new_nvset, -diff --git a/pcs/lib/cib/resource/bundle.py b/pcs/lib/cib/resource/bundle.py -index 0fe16f3..8a49c28 100644 ---- a/pcs/lib/cib/resource/bundle.py -+++ b/pcs/lib/cib/resource/bundle.py -@@ -9,6 +9,10 @@ from lxml import etree - - from pcs.common import report_codes - from pcs.lib import reports, validate -+from pcs.lib.cib.nvpair import ( -+ append_new_meta_attributes, -+ arrange_first_meta_attributes, -+) - from pcs.lib.cib.resource.primitive import TAG as TAG_PRIMITIVE - from pcs.lib.cib.tools import find_element_by_tag_and_id - from pcs.lib.errors import ( -@@ -96,7 +100,7 @@ def validate_new( - - def append_new( - parent_element, id_provider, bundle_id, container_type, container_options, -- network_options, port_map, storage_map -+ network_options, port_map, storage_map, meta_attributes - ): - """ - Create new bundle and add it to the CIB -@@ -109,6 +113,7 @@ def append_new( - dict network_options -- network options - list of dict port_map -- list of port mapping options - list of dict storage_map -- list of storage mapping options -+ dict meta_attributes -- meta attributes - """ - bundle_element = etree.SubElement(parent_element, TAG, {"id": bundle_id}) - # TODO create the proper element once more container_types are supported -@@ -132,6 +137,8 @@ def append_new( - _append_storage_map( - storage_element, id_provider, bundle_id, storage_map_options - ) -+ if meta_attributes: -+ append_new_meta_attributes(bundle_element, meta_attributes, id_provider) - return bundle_element - - def validate_update( -@@ -203,7 +210,8 @@ def validate_update( - - def update( - id_provider, bundle_el, container_options, network_options, -- port_map_add, port_map_remove, storage_map_add, storage_map_remove -+ port_map_add, port_map_remove, storage_map_add, storage_map_remove, -+ meta_attributes - ): - """ - Modify an existing bundle (does not touch encapsulated resources) -@@ -216,6 +224,7 @@ def update( - list of string port_map_remove -- list of port mapping ids to remove - list of dict storage_map_add -- list of storage mapping options to add - list of string storage_map_remove -- list of storage mapping ids to remove -+ dict meta_attributes -- meta attributes to update - """ - bundle_id = bundle_el.get("id") - update_attributes_remove_empty( -@@ -253,7 +262,11 @@ def update( - storage_element, id_provider, bundle_id, storage_map_options - ) - -+ if meta_attributes: -+ arrange_first_meta_attributes(bundle_el, meta_attributes) -+ - # remove empty elements with no attributes -+ # meta attributes are handled in their own function - for element in (network_element, storage_element): - if len(element) < 1 and not element.attrib: - element.getparent().remove(element) -diff --git a/pcs/lib/cib/resource/common.py b/pcs/lib/cib/resource/common.py -index f9028ff..0e52b4c 100644 ---- a/pcs/lib/cib/resource/common.py -+++ b/pcs/lib/cib/resource/common.py -@@ -58,16 +58,18 @@ def find_resources_to_enable(resource_el): - etree resource_el -- resource element - """ - if is_bundle(resource_el): -- # bundles currently cannot be disabled - pcmk does not support that -- # inner resources are supposed to be managed separately -- return [] -+ to_enable = [resource_el] -+ in_bundle = get_bundle_inner_resource(resource_el) -+ if in_bundle is not None: -+ to_enable.append(in_bundle) -+ return to_enable - - if is_any_clone(resource_el): - return [resource_el, get_clone_inner_resource(resource_el)] - - to_enable = [resource_el] - parent = resource_el.getparent() -- if is_any_clone(parent): -+ if is_any_clone(parent) or is_bundle(parent): - to_enable.append(parent) - return to_enable - -@@ -109,20 +111,25 @@ def find_resources_to_manage(resource_el): - # put there manually. If we didn't do it, the resource may stay unmanaged, - # as a managed primitive in an unmanaged clone / group is still unmanaged - # and vice versa. -- # Bundle resources cannot be set as unmanaged - pcmk currently doesn't -- # support that. Resources in a bundle are supposed to be treated separately. -- if is_bundle(resource_el): -- return [] - res_id = resource_el.attrib["id"] - return ( - [resource_el] # the resource itself - + - # its parents - find_parent(resource_el, "resources").xpath( -+ # a master or a clone which contains a group, a primitve, or a -+ # grouped primitive with the specified id -+ # OR -+ # a group (in a clone, master, etc. - hence //) which contains a -+ # primitive with the specified id -+ # OR -+ # a bundle which contains a primitive with the specified id - """ - (./master|./clone)[(group|group/primitive|primitive)[@id='{r}']] - | - //group[primitive[@id='{r}']] -+ | -+ ./bundle[primitive[@id='{r}']] - """ - .format(r=res_id) - ) -@@ -164,10 +171,19 @@ def find_resources_to_unmanage(resource_el): - # See clone notes above - # - # a bundled primitive - the primitive - the primitive -- # a bundled primitive - the bundle - nothing -- # bundles currently cannot be set as unmanaged - pcmk does not support that -- # an empty bundle - the bundle - nothing -- # bundles currently cannot be set as unmanaged - pcmk does not support that -+ # a bundled primitive - the bundle - the bundle and the primitive -+ # We need to unmanage implicit resources create by pacemaker and there is -+ # no other way to do it than unmanage the bundle itself. -+ # Since it is not possible to unbundle a resource, the concers described -+ # at unclone don't apply here. However to prevent future bugs, in case -+ # unbundling becomes possible, we unmanage the primitive as well. -+ # an empty bundle - the bundle - the bundle -+ # There is nothing else to unmanage. -+ if is_bundle(resource_el): -+ in_bundle = get_bundle_inner_resource(resource_el) -+ return ( -+ [resource_el, in_bundle] if in_bundle is not None else [resource_el] -+ ) - if is_any_clone(resource_el): - resource_el = get_clone_inner_resource(resource_el) - if is_group(resource_el): -diff --git a/pcs/lib/cib/test/test_nvpair.py b/pcs/lib/cib/test/test_nvpair.py -index 9b9d9b9..0f6d8f8 100644 ---- a/pcs/lib/cib/test/test_nvpair.py -+++ b/pcs/lib/cib/test/test_nvpair.py -@@ -8,6 +8,7 @@ from __future__ import ( - from lxml import etree - - from pcs.lib.cib import nvpair -+from pcs.lib.cib.tools import IdProvider - from pcs.test.tools.assertions import assert_xml_equal - from pcs.test.tools.pcs_unittest import TestCase, mock - from pcs.test.tools.xml import etree_to_str -@@ -25,6 +26,21 @@ class AppendNewNvpair(TestCase): - """ - ) - -+ def test_with_id_provider(self): -+ nvset_element = etree.fromstring('<nvset id="a"/>') -+ provider = IdProvider(nvset_element) -+ provider.book_ids("a-b") -+ nvpair._append_new_nvpair(nvset_element, "b", "c", provider) -+ assert_xml_equal( -+ etree_to_str(nvset_element), -+ """ -+ <nvset id="a"> -+ <nvpair id="a-b-1" name="b" value="c"></nvpair> -+ </nvset> -+ """ -+ ) -+ -+ - class UpdateNvsetTest(TestCase): - @mock.patch( - "pcs.lib.cib.nvpair.create_subelement_id", -@@ -167,6 +183,32 @@ class AppendNewNvsetTest(TestCase): - etree_to_str(context_element) - ) - -+ def test_with_id_provider(self): -+ context_element = etree.fromstring('<context id="a"/>') -+ provider = IdProvider(context_element) -+ provider.book_ids("a-instance_attributes", "a-instance_attributes-1-a") -+ nvpair.append_new_nvset( -+ "instance_attributes", -+ context_element, -+ { -+ "a": "b", -+ "c": "d", -+ }, -+ provider -+ ) -+ assert_xml_equal( -+ """ -+ <context id="a"> -+ <instance_attributes id="a-instance_attributes-1"> -+ <nvpair id="a-instance_attributes-1-a-1" name="a" value="b"/> -+ <nvpair id="a-instance_attributes-1-c" name="c" value="d"/> -+ </instance_attributes> -+ </context> -+ """, -+ etree_to_str(context_element) -+ ) -+ -+ - class ArrangeFirstNvsetTest(TestCase): - def setUp(self): - self.root = etree.Element("root", id="root") -diff --git a/pcs/lib/cib/test/test_resource_common.py b/pcs/lib/cib/test/test_resource_common.py -index 52c2329..6b485f7 100644 ---- a/pcs/lib/cib/test/test_resource_common.py -+++ b/pcs/lib/cib/test/test_resource_common.py -@@ -180,7 +180,7 @@ class FindResourcesToEnable(TestCase): - self.assert_find_resources("F2", ["F2"]) - - def test_primitive_in_bundle(self): -- self.assert_find_resources("H", ["H"]) -+ self.assert_find_resources("H", ["H", "H-bundle"]) - - def test_group(self): - self.assert_find_resources("D", ["D"]) -@@ -204,10 +204,10 @@ class FindResourcesToEnable(TestCase): - self.assert_find_resources("F-master", ["F-master", "F"]) - - def test_bundle_empty(self): -- self.assert_find_resources("G-bundle", []) -+ self.assert_find_resources("G-bundle", ["G-bundle"]) - - def test_bundle_with_primitive(self): -- self.assert_find_resources("H-bundle", []) -+ self.assert_find_resources("H-bundle", ["H-bundle", "H"]) - - - class Enable(TestCase): -@@ -360,7 +360,7 @@ class FindResourcesToManage(TestCase): - self.assert_find_resources("F2", ["F2", "F-master", "F"]) - - def test_primitive_in_bundle(self): -- self.assert_find_resources("H", ["H"]) -+ self.assert_find_resources("H", ["H", "H-bundle"]) - - def test_group(self): - self.assert_find_resources("D", ["D", "D1", "D2"]) -@@ -384,10 +384,10 @@ class FindResourcesToManage(TestCase): - self.assert_find_resources("F-master", ["F-master", "F", "F1", "F2"]) - - def test_bundle_empty(self): -- self.assert_find_resources("G-bundle", []) -+ self.assert_find_resources("G-bundle", ["G-bundle"]) - - def test_bundle_with_primitive(self): -- self.assert_find_resources("H-bundle", []) -+ self.assert_find_resources("H-bundle", ["H-bundle", "H"]) - - - class FindResourcesToUnmanage(TestCase): -@@ -447,10 +447,10 @@ class FindResourcesToUnmanage(TestCase): - self.assert_find_resources("F-master", ["F1", "F2"]) - - def test_bundle_empty(self): -- self.assert_find_resources("G-bundle", []) -+ self.assert_find_resources("G-bundle", ["G-bundle"]) - - def test_bundle_with_primitive(self): -- self.assert_find_resources("H-bundle", []) -+ self.assert_find_resources("H-bundle", ["H-bundle", "H"]) - - - class Manage(TestCase): -diff --git a/pcs/lib/cib/tools.py b/pcs/lib/cib/tools.py -index 2308a42..cf91125 100644 ---- a/pcs/lib/cib/tools.py -+++ b/pcs/lib/cib/tools.py -@@ -177,11 +177,11 @@ def find_element_by_tag_and_id( - ) - ) - --def create_subelement_id(context_element, suffix): -- return find_unique_id( -- context_element, -- "{0}-{1}".format(context_element.get("id"), suffix) -- ) -+def create_subelement_id(context_element, suffix, id_provider=None): -+ proposed_id = "{0}-{1}".format(context_element.get("id"), suffix) -+ if id_provider: -+ return id_provider.allocate_id(proposed_id) -+ return find_unique_id(context_element, proposed_id) - - def check_new_id_applicable(tree, description, id): - validate_id(id, description) -diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py -index 3a060b8..0c5f682 100644 ---- a/pcs/lib/commands/resource.py -+++ b/pcs/lib/commands/resource.py -@@ -22,6 +22,7 @@ from pcs.lib.errors import LibraryError - from pcs.lib.pacemaker.values import validate_id - from pcs.lib.pacemaker.state import ( - ensure_resource_state, -+ info_resource_state, - is_resource_managed, - ResourceNotFound, - ) -@@ -31,7 +32,10 @@ from pcs.lib.resource_agent import( - - @contextmanager - def resource_environment( -- env, wait=False, wait_for_resource_ids=None, disabled_after_wait=False, -+ env, -+ wait=False, -+ wait_for_resource_ids=None, -+ resource_state_reporter=info_resource_state, - required_cib_version=None - ): - env.ensure_wait_satisfiable(wait) -@@ -41,10 +45,19 @@ def resource_environment( - if wait is not False and wait_for_resource_ids: - state = env.get_cluster_state() - env.report_processor.process_list([ -- ensure_resource_state(not disabled_after_wait, state, res_id) -+ resource_state_reporter(state, res_id) - for res_id in wait_for_resource_ids - ]) - -+def _ensure_disabled_after_wait(disabled_after_wait): -+ def inner(state, resource_id): -+ return ensure_resource_state( -+ not disabled_after_wait, -+ state, -+ resource_id -+ ) -+ return inner -+ - def _validate_remote_connection( - resource_agent, nodes_to_validate_against, resource_id, instance_attributes, - allow_not_suitable_command -@@ -195,7 +208,11 @@ def create( - env, - wait, - [resource_id], -- ensure_disabled or resource.common.are_meta_disabled(meta_attributes), -+ _ensure_disabled_after_wait( -+ ensure_disabled -+ or -+ resource.common.are_meta_disabled(meta_attributes) -+ ) - ) as resources_section: - _check_special_cases( - env, -@@ -269,7 +286,7 @@ def _create_as_clone_common( - env, - wait, - [resource_id], -- ( -+ _ensure_disabled_after_wait( - ensure_disabled - or - resource.common.are_meta_disabled(meta_attributes) -@@ -353,7 +370,11 @@ def create_in_group( - env, - wait, - [resource_id], -- ensure_disabled or resource.common.are_meta_disabled(meta_attributes), -+ _ensure_disabled_after_wait( -+ ensure_disabled -+ or -+ resource.common.are_meta_disabled(meta_attributes) -+ ) - ) as resources_section: - _check_special_cases( - env, -@@ -433,7 +454,11 @@ def create_into_bundle( - env, - wait, - [resource_id], -- disabled_after_wait=ensure_disabled, -+ _ensure_disabled_after_wait( -+ ensure_disabled -+ or -+ resource.common.are_meta_disabled(meta_attributes) -+ ), - required_cib_version=(2, 8, 0) - ) as resources_section: - _check_special_cases( -@@ -465,8 +490,9 @@ def create_into_bundle( - - def bundle_create( - env, bundle_id, container_type, container_options=None, -- network_options=None, port_map=None, storage_map=None, -+ network_options=None, port_map=None, storage_map=None, meta_attributes=None, - force_options=False, -+ ensure_disabled=False, - wait=False, - ): - """ -@@ -477,24 +503,32 @@ def bundle_create( - string container_type -- container engine name (docker, lxc...) - dict container_options -- container options - dict network_options -- network options -- list of dict port_map -- list of port mapping options -- list of dict storage_map -- list of storage mapping options -+ list of dict port_map -- a list of port mapping options -+ list of dict storage_map -- a list of storage mapping options -+ dict meta_attributes -- bundle's meta attributes - bool force_options -- return warnings instead of forceable errors -+ bool ensure_disabled -- set the bundle's target-role to "Stopped" - mixed wait -- False: no wait, None: wait default timeout, int: wait timeout - """ - container_options = container_options or {} - network_options = network_options or {} - port_map = port_map or [] - storage_map = storage_map or [] -+ meta_attributes = meta_attributes or {} - - with resource_environment( - env, - wait, - [bundle_id], -- # bundles are always enabled, currently there is no way to disable them -- disabled_after_wait=False, -+ _ensure_disabled_after_wait( -+ ensure_disabled -+ or -+ resource.common.are_meta_disabled(meta_attributes) -+ ), - required_cib_version=(2, 8, 0) - ) as resources_section: -+ # no need to run validations related to remote and guest nodes as those -+ # nodes can only be created from primitive resources - id_provider = IdProvider(resources_section) - env.report_processor.process_list( - resource.bundle.validate_new( -@@ -505,10 +539,11 @@ def bundle_create( - network_options, - port_map, - storage_map, -+ # TODO meta attributes - there is no validation for now - force_options - ) - ) -- resource.bundle.append_new( -+ bundle_element = resource.bundle.append_new( - resources_section, - id_provider, - bundle_id, -@@ -516,13 +551,16 @@ def bundle_create( - container_options, - network_options, - port_map, -- storage_map -+ storage_map, -+ meta_attributes - ) -+ if ensure_disabled: -+ resource.common.disable(bundle_element) - - def bundle_update( - env, bundle_id, container_options=None, network_options=None, - port_map_add=None, port_map_remove=None, storage_map_add=None, -- storage_map_remove=None, -+ storage_map_remove=None, meta_attributes=None, - force_options=False, - wait=False, - ): -@@ -537,6 +575,7 @@ def bundle_update( - list of string port_map_remove -- list of port mapping ids to remove - list of dict storage_map_add -- list of storage mapping options to add - list of string storage_map_remove -- list of storage mapping ids to remove -+ dict meta_attributes -- meta attributes to update - bool force_options -- return warnings instead of forceable errors - mixed wait -- False: no wait, None: wait default timeout, int: wait timeout - """ -@@ -546,15 +585,16 @@ def bundle_update( - port_map_remove = port_map_remove or [] - storage_map_add = storage_map_add or [] - storage_map_remove = storage_map_remove or [] -+ meta_attributes = meta_attributes or {} - - with resource_environment( - env, - wait, - [bundle_id], -- # bundles are always enabled, currently there is no way to disable them -- disabled_after_wait=False, - required_cib_version=(2, 8, 0) - ) as resources_section: -+ # no need to run validations related to remote and guest nodes as those -+ # nodes can only be created from primitive resources - id_provider = IdProvider(resources_section) - bundle_element = find_element_by_tag_and_id( - resource.bundle.TAG, -@@ -571,6 +611,7 @@ def bundle_update( - port_map_remove, - storage_map_add, - storage_map_remove, -+ # TODO meta attributes - there is no validation for now - force_options - ) - ) -@@ -582,7 +623,8 @@ def bundle_update( - port_map_add, - port_map_remove, - storage_map_add, -- storage_map_remove -+ storage_map_remove, -+ meta_attributes - ) - - def disable(env, resource_ids, wait): -@@ -593,7 +635,7 @@ def disable(env, resource_ids, wait): - mixed wait -- False: no wait, None: wait default timeout, int: wait timeout - """ - with resource_environment( -- env, wait, resource_ids, True -+ env, wait, resource_ids, _ensure_disabled_after_wait(True) - ) as resources_section: - resource_el_list = _find_resources_or_raise( - resources_section, -@@ -615,7 +657,7 @@ def enable(env, resource_ids, wait): - mixed wait -- False: no wait, None: wait default timeout, int: wait timeout - """ - with resource_environment( -- env, wait, resource_ids, False -+ env, wait, resource_ids, _ensure_disabled_after_wait(False) - ) as resources_section: - resource_el_list = _find_resources_or_raise( - resources_section, -@@ -642,7 +684,7 @@ def _resource_list_enable_disable(resource_el_list, func, cluster_state): - report_list.append( - reports.id_not_found( - res_id, -- id_description="resource/clone/master/group" -+ id_description="resource/clone/master/group/bundle" - ) - ) - return report_list -@@ -735,7 +777,7 @@ def _find_resources_or_raise( - resource_tags = ( - resource.clone.ALL_TAGS - + -- [resource.group.TAG, resource.primitive.TAG] -+ [resource.group.TAG, resource.primitive.TAG, resource.bundle.TAG] - ) - for res_id in resource_ids: - try: -@@ -745,7 +787,7 @@ def _find_resources_or_raise( - resource_tags, - resources_section, - res_id, -- id_description="resource/clone/master/group" -+ id_description="resource/clone/master/group/bundle" - ) - ) - ) -diff --git a/pcs/lib/commands/test/resource/fixture.py b/pcs/lib/commands/test/resource/fixture.py -index f1fe09b..8d96dc9 100644 ---- a/pcs/lib/commands/test/resource/fixture.py -+++ b/pcs/lib/commands/test/resource/fixture.py -@@ -145,7 +145,7 @@ def report_not_found(res_id, context_type=""): - "context_type": context_type, - "context_id": "", - "id": res_id, -- "id_description": "resource/clone/master/group", -+ "id_description": "resource/clone/master/group/bundle", - }, - None - ) -diff --git a/pcs/lib/commands/test/resource/test_bundle_create.py b/pcs/lib/commands/test/resource/test_bundle_create.py -index b9922d8..3bdeee9 100644 ---- a/pcs/lib/commands/test/resource/test_bundle_create.py -+++ b/pcs/lib/commands/test/resource/test_bundle_create.py -@@ -40,7 +40,7 @@ class MinimalCreate(CommonTest): - self.fixture_cib_pre, - lambda: resource.bundle_create( - self.env, "B1", "docker", -- {"image": "pcs:test", } -+ container_options={"image": "pcs:test", } - ), - self.fixture_resources_bundle_simple - ) -@@ -90,7 +90,7 @@ class MinimalCreate(CommonTest): - - resource.bundle_create( - self.env, "B1", "docker", -- {"image": "pcs:test", } -+ container_options={"image": "pcs:test", } - ) - - self.env.report_processor.assert_reports([ -@@ -122,7 +122,7 @@ class CreateDocker(CommonTest): - self.fixture_cib_pre, - lambda: resource.bundle_create( - self.env, "B1", "docker", -- {"image": "pcs:test", } -+ container_options={"image": "pcs:test", } - ), - self.fixture_resources_bundle_simple - ) -@@ -132,7 +132,7 @@ class CreateDocker(CommonTest): - self.fixture_cib_pre, - lambda: resource.bundle_create( - self.env, "B1", "docker", -- { -+ container_options={ - "image": "pcs:test", - "masters": "0", - "network": "extra network settings", -@@ -168,7 +168,7 @@ class CreateDocker(CommonTest): - assert_raise_library_error( - lambda: resource.bundle_create( - self.env, "B1", "docker", -- { -+ container_options={ - "replicas-per-host": "0", - "replicas": "0", - "masters": "-1", -@@ -226,7 +226,7 @@ class CreateDocker(CommonTest): - assert_raise_library_error( - lambda: resource.bundle_create( - self.env, "B1", "docker", -- { -+ container_options={ - "image": "", - }, - force_options=True -@@ -253,7 +253,7 @@ class CreateDocker(CommonTest): - assert_raise_library_error( - lambda: resource.bundle_create( - self.env, "B1", "docker", -- { -+ container_options={ - "image": "pcs:test", - "extra": "option", - } -@@ -276,7 +276,7 @@ class CreateDocker(CommonTest): - self.fixture_cib_pre, - lambda: resource.bundle_create( - self.env, "B1", "docker", -- { -+ container_options={ - "image": "pcs:test", - "extra": "option", - }, -@@ -932,13 +932,61 @@ class CreateWithStorageMap(CommonTest): - ) - - -+class CreateWithMeta(CommonTest): -+ def test_success(self): -+ self.assert_command_effect( -+ self.fixture_cib_pre, -+ lambda: resource.bundle_create( -+ self.env, "B1", "docker", -+ container_options={"image": "pcs:test", }, -+ meta_attributes={ -+ "target-role": "Stopped", -+ "is-managed": "false", -+ } -+ ), -+ """ -+ <resources> -+ <bundle id="B1"> -+ <docker image="pcs:test" /> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair id="B1-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ <nvpair id="B1-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ </bundle> -+ </resources> -+ """ -+ ) -+ -+ def test_disabled(self): -+ self.assert_command_effect( -+ self.fixture_cib_pre, -+ lambda: resource.bundle_create( -+ self.env, "B1", "docker", -+ container_options={"image": "pcs:test", }, -+ ensure_disabled=True -+ ), -+ """ -+ <resources> -+ <bundle id="B1"> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair id="B1-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ </bundle> -+ </resources> -+ """ -+ ) -+ - class CreateWithAllOptions(CommonTest): - def test_success(self): - self.assert_command_effect( - self.fixture_cib_pre, - lambda: resource.bundle_create( - self.env, "B1", "docker", -- { -+ container_options={ - "image": "pcs:test", - "masters": "0", - "network": "extra network settings", -@@ -947,13 +995,13 @@ class CreateWithAllOptions(CommonTest): - "replicas": "4", - "replicas-per-host": "2", - }, -- { -+ network_options={ - "control-port": "12345", - "host-interface": "eth0", - "host-netmask": "24", - "ip-range-start": "192.168.100.200", - }, -- [ -+ port_map=[ - { - "port": "1001", - }, -@@ -967,7 +1015,7 @@ class CreateWithAllOptions(CommonTest): - "range": "3000-3300", - }, - ], -- [ -+ storage_map=[ - { - "source-dir": "/tmp/docker1a", - "target-dir": "/tmp/docker1b", -@@ -1082,21 +1130,26 @@ class Wait(CommonTest): - </resources> - """ - -- timeout = 10 -+ fixture_resources_bundle_simple_disabled = """ -+ <resources> -+ <bundle id="B1"> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair id="B1-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ </bundle> -+ </resources> -+ """ - -- def fixture_calls_initial(self): -- return ( -- fixture.call_wait_supported() + -- fixture.calls_cib( -- self.fixture_cib_pre, -- self.fixture_resources_bundle_simple, -- cib_base_file=self.cib_base_file, -- ) -- ) -+ timeout = 10 - -- def simple_bundle_create(self, wait=False): -+ def simple_bundle_create(self, wait=False, disabled=False): - return resource.bundle_create( -- self.env, "B1", "docker", {"image": "pcs:test"}, wait=wait, -+ self.env, "B1", "docker", -+ container_options={"image": "pcs:test"}, -+ ensure_disabled=disabled, -+ wait=wait, - ) - - def test_wait_fail(self): -@@ -1108,7 +1161,14 @@ class Wait(CommonTest): - """ - ) - self.runner.set_runs( -- self.fixture_calls_initial() + -+ fixture.call_wait_supported() -+ + -+ fixture.calls_cib( -+ self.fixture_cib_pre, -+ self.fixture_resources_bundle_simple, -+ cib_base_file=self.cib_base_file, -+ ) -+ + - fixture.call_wait(self.timeout, 62, fixture_wait_timeout_error) - ) - assert_raise_library_error( -@@ -1122,8 +1182,16 @@ class Wait(CommonTest): - @skip_unless_pacemaker_supports_bundle - def test_wait_ok_run_ok(self): - self.runner.set_runs( -- self.fixture_calls_initial() + -- fixture.call_wait(self.timeout) + -+ fixture.call_wait_supported() -+ + -+ fixture.calls_cib( -+ self.fixture_cib_pre, -+ self.fixture_resources_bundle_simple, -+ cib_base_file=self.cib_base_file, -+ ) -+ + -+ fixture.call_wait(self.timeout) -+ + - fixture.call_status(fixture.state_complete( - self.fixture_status_running - )) -@@ -1139,8 +1207,16 @@ class Wait(CommonTest): - @skip_unless_pacemaker_supports_bundle - def test_wait_ok_run_fail(self): - self.runner.set_runs( -- self.fixture_calls_initial() + -- fixture.call_wait(self.timeout) + -+ fixture.call_wait_supported() -+ + -+ fixture.calls_cib( -+ self.fixture_cib_pre, -+ self.fixture_resources_bundle_simple, -+ cib_base_file=self.cib_base_file, -+ ) -+ + -+ fixture.call_wait(self.timeout) -+ + - fixture.call_status(fixture.state_complete( - self.fixture_status_not_running - )) -@@ -1150,3 +1226,48 @@ class Wait(CommonTest): - fixture.report_resource_not_running("B1", severities.ERROR), - ) - self.runner.assert_everything_launched() -+ -+ @skip_unless_pacemaker_supports_bundle -+ def test_disabled_wait_ok_run_ok(self): -+ self.runner.set_runs( -+ fixture.call_wait_supported() -+ + -+ fixture.calls_cib( -+ self.fixture_cib_pre, -+ self.fixture_resources_bundle_simple_disabled, -+ cib_base_file=self.cib_base_file, -+ ) -+ + -+ fixture.call_wait(self.timeout) -+ + -+ fixture.call_status(fixture.state_complete( -+ self.fixture_status_not_running -+ )) -+ ) -+ self.simple_bundle_create(self.timeout, disabled=True) -+ self.runner.assert_everything_launched() -+ -+ @skip_unless_pacemaker_supports_bundle -+ def test_disabled_wait_ok_run_fail(self): -+ self.runner.set_runs( -+ fixture.call_wait_supported() -+ + -+ fixture.calls_cib( -+ self.fixture_cib_pre, -+ self.fixture_resources_bundle_simple_disabled, -+ cib_base_file=self.cib_base_file, -+ ) -+ + -+ fixture.call_wait(self.timeout) -+ + -+ fixture.call_status(fixture.state_complete( -+ self.fixture_status_running -+ )) -+ ) -+ assert_raise_library_error( -+ lambda: self.simple_bundle_create(self.timeout, disabled=True), -+ fixture.report_resource_running( -+ "B1", {"Started": ["node1", "node2"]}, severities.ERROR -+ ) -+ ) -+ self.runner.assert_everything_launched() -diff --git a/pcs/lib/commands/test/resource/test_bundle_update.py b/pcs/lib/commands/test/resource/test_bundle_update.py -index 55cfa7b..7a1ee49 100644 ---- a/pcs/lib/commands/test/resource/test_bundle_update.py -+++ b/pcs/lib/commands/test/resource/test_bundle_update.py -@@ -709,6 +709,96 @@ class StorageMap(CommonTest): - self.runner.assert_everything_launched() - - -+class Meta(CommonTest): -+ fixture_no_meta = """ -+ <resources> -+ <bundle id="B1"> -+ <docker image="pcs:test" masters="3" replicas="6"/> -+ </bundle> -+ </resources> -+ """ -+ -+ fixture_meta_stopped = """ -+ <resources> -+ <bundle id="B1"> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair id="B1-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ <docker image="pcs:test" masters="3" replicas="6"/> -+ </bundle> -+ </resources> -+ """ -+ -+ def test_add_meta_element(self): -+ self.assert_command_effect( -+ self.fixture_no_meta, -+ lambda: resource.bundle_update( -+ self.env, "B1", -+ meta_attributes={ -+ "target-role": "Stopped", -+ } -+ ), -+ self.fixture_meta_stopped -+ ) -+ -+ def test_remove_meta_element(self): -+ self.assert_command_effect( -+ self.fixture_meta_stopped, -+ lambda: resource.bundle_update( -+ self.env, "B1", -+ meta_attributes={ -+ "target-role": "", -+ } -+ ), -+ self.fixture_no_meta -+ ) -+ -+ def test_change_meta(self): -+ fixture_cib_pre = """ -+ <resources> -+ <bundle id="B1"> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair id="B1-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ <nvpair id="B1-meta_attributes-priority" -+ name="priority" value="15" /> -+ <nvpair id="B1-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <docker image="pcs:test" masters="3" replicas="6"/> -+ </bundle> -+ </resources> -+ """ -+ fixture_cib_post = """ -+ <resources> -+ <bundle id="B1"> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair id="B1-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ <nvpair id="B1-meta_attributes-priority" -+ name="priority" value="10" /> -+ <nvpair id="B1-meta_attributes-resource-stickiness" -+ name="resource-stickiness" value="100" /> -+ </meta_attributes> -+ <docker image="pcs:test" masters="3" replicas="6"/> -+ </bundle> -+ </resources> -+ """ -+ self.assert_command_effect( -+ fixture_cib_pre, -+ lambda: resource.bundle_update( -+ self.env, "B1", -+ meta_attributes={ -+ "priority": "10", -+ "resource-stickiness": "100", -+ "is-managed": "", -+ } -+ ), -+ fixture_cib_post -+ ) -+ -+ - class Wait(CommonTest): - fixture_status_running = """ - <resources> -@@ -794,7 +884,7 @@ class Wait(CommonTest): - self.runner.assert_everything_launched() - - @skip_unless_pacemaker_supports_bundle -- def test_wait_ok_run_ok(self): -+ def test_wait_ok_running(self): - self.runner.set_runs( - self.fixture_calls_initial() + - fixture.call_wait(self.timeout) + -@@ -811,7 +901,7 @@ class Wait(CommonTest): - self.runner.assert_everything_launched() - - @skip_unless_pacemaker_supports_bundle -- def test_wait_ok_run_fail(self): -+ def test_wait_ok_not_running(self): - self.runner.set_runs( - self.fixture_calls_initial() + - fixture.call_wait(self.timeout) + -@@ -819,8 +909,8 @@ class Wait(CommonTest): - self.fixture_status_not_running - )) - ) -- assert_raise_library_error( -- lambda: self.simple_bundle_update(self.timeout), -- fixture.report_resource_not_running("B1", severities.ERROR), -- ) -+ self.simple_bundle_update(self.timeout) -+ self.env.report_processor.assert_reports([ -+ fixture.report_resource_not_running("B1", severities.INFO), -+ ]) - self.runner.assert_everything_launched() -diff --git a/pcs/lib/commands/test/resource/test_resource_enable_disable.py b/pcs/lib/commands/test/resource/test_resource_enable_disable.py -index 91ac068..b03740b 100644 ---- a/pcs/lib/commands/test/resource/test_resource_enable_disable.py -+++ b/pcs/lib/commands/test/resource/test_resource_enable_disable.py -@@ -469,6 +469,35 @@ fixture_bundle_cib_disabled_primitive = """ - </bundle> - </resources> - """ -+fixture_bundle_cib_disabled_bundle = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <meta_attributes id="A-bundle-meta_attributes"> -+ <nvpair id="A-bundle-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy" /> -+ </bundle> -+ </resources> -+""" -+fixture_bundle_cib_disabled_both = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <meta_attributes id="A-bundle-meta_attributes"> -+ <nvpair id="A-bundle-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy"> -+ <meta_attributes id="A-meta_attributes"> -+ <nvpair id="A-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> -+ </primitive> -+ </bundle> -+ </resources> -+""" - fixture_bundle_status_managed = """ - <resources> - <bundle id="A-bundle" type="docker" image="pcmktest:http" -@@ -486,7 +515,7 @@ fixture_bundle_status_managed = """ - fixture_bundle_status_unmanaged = """ - <resources> - <bundle id="A-bundle" type="docker" image="pcmktest:http" -- unique="false" managed="true" failed="false" -+ unique="false" managed="false" failed="false" - > - <replica id="0"> - <resource id="A" managed="false" /> -@@ -1460,17 +1489,12 @@ class DisableBundle(ResourceWithStateTest): - ) - - def test_bundle(self): -- self.runner.set_runs( -- fixture.call_cib_load( -- fixture.cib_resources(fixture_bundle_cib_enabled) -- ) -- ) -- -- assert_raise_library_error( -+ self.assert_command_effect( -+ fixture_bundle_cib_enabled, -+ fixture_bundle_status_managed, - lambda: resource.disable(self.env, ["A-bundle"], False), -- fixture.report_not_for_bundles("A-bundle") -+ fixture_bundle_cib_disabled_bundle - ) -- self.runner.assert_everything_launched() - - def test_primitive_unmanaged(self): - self.assert_command_effect( -@@ -1483,6 +1507,17 @@ class DisableBundle(ResourceWithStateTest): - ] - ) - -+ def test_bundle_unmanaged(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_enabled, -+ fixture_bundle_status_unmanaged, -+ lambda: resource.disable(self.env, ["A-bundle"], False), -+ fixture_bundle_cib_disabled_bundle, -+ reports=[ -+ fixture_report_unmanaged("A-bundle"), -+ ] -+ ) -+ - - @skip_unless_pacemaker_supports_bundle - class EnableBundle(ResourceWithStateTest): -@@ -1494,18 +1529,29 @@ class EnableBundle(ResourceWithStateTest): - fixture_bundle_cib_enabled - ) - -+ def test_primitive_disabled_both(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_disabled_both, -+ fixture_bundle_status_managed, -+ lambda: resource.enable(self.env, ["A"], False), -+ fixture_bundle_cib_enabled -+ ) -+ - def test_bundle(self): -- self.runner.set_runs( -- fixture.call_cib_load( -- fixture.cib_resources(fixture_bundle_cib_enabled) -- ) -+ self.assert_command_effect( -+ fixture_bundle_cib_disabled_bundle, -+ fixture_bundle_status_managed, -+ lambda: resource.enable(self.env, ["A-bundle"], False), -+ fixture_bundle_cib_enabled - ) - -- assert_raise_library_error( -+ def test_bundle_disabled_both(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_disabled_both, -+ fixture_bundle_status_managed, - lambda: resource.enable(self.env, ["A-bundle"], False), -- fixture.report_not_for_bundles("A-bundle") -+ fixture_bundle_cib_enabled - ) -- self.runner.assert_everything_launched() - - def test_primitive_unmanaged(self): - self.assert_command_effect( -@@ -1515,5 +1561,18 @@ class EnableBundle(ResourceWithStateTest): - fixture_bundle_cib_enabled, - reports=[ - fixture_report_unmanaged("A"), -+ fixture_report_unmanaged("A-bundle"), -+ ] -+ ) -+ -+ def test_bundle_unmanaged(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_disabled_primitive, -+ fixture_bundle_status_unmanaged, -+ lambda: resource.enable(self.env, ["A-bundle"], False), -+ fixture_bundle_cib_enabled, -+ reports=[ -+ fixture_report_unmanaged("A-bundle"), -+ fixture_report_unmanaged("A"), - ] - ) -diff --git a/pcs/lib/commands/test/resource/test_resource_manage_unmanage.py b/pcs/lib/commands/test/resource/test_resource_manage_unmanage.py -index 6d8c787..95b44bc 100644 ---- a/pcs/lib/commands/test/resource/test_resource_manage_unmanage.py -+++ b/pcs/lib/commands/test/resource/test_resource_manage_unmanage.py -@@ -517,6 +517,26 @@ fixture_clone_group_cib_unmanaged_all_primitives_op_disabled = """ - </resources> - """ - -+ -+fixture_bundle_empty_cib_managed = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <docker image="pcs:test" /> -+ </bundle> -+ </resources> -+""" -+fixture_bundle_empty_cib_unmanaged_bundle = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <meta_attributes id="A-bundle-meta_attributes"> -+ <nvpair id="A-bundle-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ </bundle> -+ </resources> -+""" -+ - fixture_bundle_cib_managed = """ - <resources> - <bundle id="A-bundle"> -@@ -526,7 +546,19 @@ fixture_bundle_cib_managed = """ - </bundle> - </resources> - """ -- -+fixture_bundle_cib_unmanaged_bundle = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <meta_attributes id="A-bundle-meta_attributes"> -+ <nvpair id="A-bundle-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy"> -+ </primitive> -+ </bundle> -+ </resources> -+""" - fixture_bundle_cib_unmanaged_primitive = """ - <resources> - <bundle id="A-bundle"> -@@ -540,6 +572,78 @@ fixture_bundle_cib_unmanaged_primitive = """ - </bundle> - </resources> - """ -+fixture_bundle_cib_unmanaged_both = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <meta_attributes id="A-bundle-meta_attributes"> -+ <nvpair id="A-bundle-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy"> -+ <meta_attributes id="A-meta_attributes"> -+ <nvpair id="A-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ </primitive> -+ </bundle> -+ </resources> -+""" -+ -+fixture_bundle_cib_managed_op_enabled = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy"> -+ <operations> -+ <op id="A-start" name="start" /> -+ <op id="A-stop" name="stop" /> -+ <op id="A-monitor" name="monitor"/> -+ </operations> -+ </primitive> -+ </bundle> -+ </resources> -+""" -+fixture_bundle_cib_unmanaged_primitive_op_disabled = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy"> -+ <meta_attributes id="A-meta_attributes"> -+ <nvpair id="A-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <operations> -+ <op id="A-start" name="start" /> -+ <op id="A-stop" name="stop" /> -+ <op id="A-monitor" name="monitor" enabled="false"/> -+ </operations> -+ </primitive> -+ </bundle> -+ </resources> -+""" -+fixture_bundle_cib_unmanaged_both_op_disabled = """ -+ <resources> -+ <bundle id="A-bundle"> -+ <meta_attributes id="A-bundle-meta_attributes"> -+ <nvpair id="A-bundle-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <docker image="pcs:test" /> -+ <primitive id="A" class="ocf" provider="heartbeat" type="Dummy"> -+ <meta_attributes id="A-meta_attributes"> -+ <nvpair id="A-meta_attributes-is-managed" -+ name="is-managed" value="false" /> -+ </meta_attributes> -+ <operations> -+ <op id="A-start" name="start" /> -+ <op id="A-stop" name="stop" /> -+ <op id="A-monitor" name="monitor" enabled="false"/> -+ </operations> -+ </primitive> -+ </bundle> -+ </resources> -+""" - - def fixture_report_no_monitors(resource): - return ( -@@ -852,17 +956,18 @@ class UnmanageBundle(ResourceWithoutStateTest): - ) - - def test_bundle(self): -- self.runner.set_runs( -- fixture.call_cib_load( -- fixture.cib_resources(fixture_bundle_cib_managed) -- ) -+ self.assert_command_effect( -+ fixture_bundle_cib_managed, -+ lambda: resource.unmanage(self.env, ["A-bundle"]), -+ fixture_bundle_cib_unmanaged_both - ) - -- assert_raise_library_error( -- lambda: resource.unmanage(self.env, ["A-bundle"], False), -- fixture.report_not_for_bundles("A-bundle") -+ def test_bundle_empty(self): -+ self.assert_command_effect( -+ fixture_bundle_empty_cib_managed, -+ lambda: resource.unmanage(self.env, ["A-bundle"]), -+ fixture_bundle_empty_cib_unmanaged_bundle - ) -- self.runner.assert_everything_launched() - - - class ManageBundle(ResourceWithoutStateTest): -@@ -873,18 +978,47 @@ class ManageBundle(ResourceWithoutStateTest): - fixture_bundle_cib_managed, - ) - -+ def test_primitive_unmanaged_bundle(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_unmanaged_bundle, -+ lambda: resource.manage(self.env, ["A"]), -+ fixture_bundle_cib_managed, -+ ) -+ -+ def test_primitive_unmanaged_both(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_unmanaged_both, -+ lambda: resource.manage(self.env, ["A"]), -+ fixture_bundle_cib_managed, -+ ) -+ - def test_bundle(self): -- self.runner.set_runs( -- fixture.call_cib_load( -- fixture.cib_resources(fixture_bundle_cib_unmanaged_primitive) -- ) -+ self.assert_command_effect( -+ fixture_bundle_cib_unmanaged_bundle, -+ lambda: resource.manage(self.env, ["A-bundle"]), -+ fixture_bundle_cib_managed, - ) - -- assert_raise_library_error( -- lambda: resource.manage(self.env, ["A-bundle"], False), -- fixture.report_not_for_bundles("A-bundle") -+ def test_bundle_unmanaged_primitive(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_unmanaged_primitive, -+ lambda: resource.manage(self.env, ["A-bundle"]), -+ fixture_bundle_cib_managed, -+ ) -+ -+ def test_bundle_unmanaged_both(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_unmanaged_both, -+ lambda: resource.manage(self.env, ["A-bundle"]), -+ fixture_bundle_cib_managed, -+ ) -+ -+ def test_bundle_empty(self): -+ self.assert_command_effect( -+ fixture_bundle_empty_cib_unmanaged_bundle, -+ lambda: resource.manage(self.env, ["A-bundle"]), -+ fixture_bundle_empty_cib_managed - ) -- self.runner.assert_everything_launched() - - - class MoreResources(ResourceWithoutStateTest): -@@ -1090,3 +1224,24 @@ class WithMonitor(ResourceWithoutStateTest): - lambda: resource.unmanage(self.env, ["A1"], True), - fixture_clone_group_cib_unmanaged_primitive_op_disabled - ) -+ -+ def test_unmanage_bundle(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_managed_op_enabled, -+ lambda: resource.unmanage(self.env, ["A-bundle"], True), -+ fixture_bundle_cib_unmanaged_both_op_disabled -+ ) -+ -+ def test_unmanage_in_bundle(self): -+ self.assert_command_effect( -+ fixture_bundle_cib_managed_op_enabled, -+ lambda: resource.unmanage(self.env, ["A"], True), -+ fixture_bundle_cib_unmanaged_primitive_op_disabled -+ ) -+ -+ def test_unmanage_bundle_empty(self): -+ self.assert_command_effect( -+ fixture_bundle_empty_cib_managed, -+ lambda: resource.unmanage(self.env, ["A-bundle"], True), -+ fixture_bundle_empty_cib_unmanaged_bundle -+ ) -diff --git a/pcs/lib/pacemaker/state.py b/pcs/lib/pacemaker/state.py -index 71809db..be3e7ad 100644 ---- a/pcs/lib/pacemaker/state.py -+++ b/pcs/lib/pacemaker/state.py -@@ -201,6 +201,25 @@ def _get_primitive_roles_with_nodes(primitive_el_list): - for role, nodes in roles_with_nodes.items() - ]) - -+def info_resource_state(cluster_state, resource_id): -+ roles_with_nodes = _get_primitive_roles_with_nodes( -+ _get_primitives_for_state_check( -+ cluster_state, -+ resource_id, -+ expected_running=True -+ ) -+ ) -+ if not roles_with_nodes: -+ return reports.resource_does_not_run( -+ resource_id, -+ severities.INFO -+ ) -+ return reports.resource_running_on_nodes( -+ resource_id, -+ roles_with_nodes, -+ severities.INFO -+ ) -+ - def ensure_resource_state(expected_running, cluster_state, resource_id): - roles_with_nodes = _get_primitive_roles_with_nodes( - _get_primitives_for_state_check( -@@ -244,18 +263,25 @@ def is_resource_managed(cluster_state, resource_id): - for primitive in primitive_list: - if is_false(primitive.attrib.get("managed", "")): - return False -- clone = find_parent(primitive, ["clone"]) -- if clone is not None and is_false(clone.attrib.get("managed", "")): -+ parent = find_parent(primitive, ["clone", "bundle"]) -+ if ( -+ parent is not None -+ and -+ is_false(parent.attrib.get("managed", "")) -+ ): - return False - return True - -- clone_list = cluster_state.xpath( -- """.//clone[@id="{0}"]""".format(resource_id) -+ parent_list = cluster_state.xpath(""" -+ .//clone[@id="{0}"] -+ | -+ .//bundle[@id="{0}"] -+ """.format(resource_id) - ) -- for clone in clone_list: -- if is_false(clone.attrib.get("managed", "")): -+ for parent in parent_list: -+ if is_false(parent.attrib.get("managed", "")): - return False -- for primitive in clone.xpath(".//resource"): -+ for primitive in parent.xpath(".//resource"): - if is_false(primitive.attrib.get("managed", "")): - return False - return True -diff --git a/pcs/lib/pacemaker/test/test_state.py b/pcs/lib/pacemaker/test/test_state.py -index a29eddf..5de9426 100644 ---- a/pcs/lib/pacemaker/test/test_state.py -+++ b/pcs/lib/pacemaker/test/test_state.py -@@ -491,7 +491,7 @@ class GetPrimitivesForStateCheck(TestCase): - self.assert_primitives("B2-R2", ["B2-R2", "B2-R2"], False) - - --class EnsureResourceState(TestCase): -+class CommonResourceState(TestCase): - resource_id = "R" - def setUp(self): - self.cluster_state = "state" -@@ -526,6 +526,8 @@ class EnsureResourceState(TestCase): - "resource_id": self.resource_id - }) - -+ -+class EnsureResourceState(CommonResourceState): - def assert_running_info_transform(self, run_info, report, expected_running): - self.get_primitives_for_state_check.return_value = ["elem1", "elem2"] - self.get_primitive_roles_with_nodes.return_value = run_info -@@ -575,6 +577,35 @@ class EnsureResourceState(TestCase): - ) - - -+class InfoResourceState(CommonResourceState): -+ def assert_running_info_transform(self, run_info, report): -+ self.get_primitives_for_state_check.return_value = ["elem1", "elem2"] -+ self.get_primitive_roles_with_nodes.return_value = run_info -+ assert_report_item_equal( -+ state.info_resource_state(self.cluster_state, self.resource_id), -+ report -+ ) -+ self.get_primitives_for_state_check.assert_called_once_with( -+ self.cluster_state, -+ self.resource_id, -+ expected_running=True -+ ) -+ self.get_primitive_roles_with_nodes.assert_called_once_with( -+ ["elem1", "elem2"] -+ ) -+ -+ def test_report_info_running(self): -+ self.assert_running_info_transform( -+ self.fixture_running_state_info(), -+ self.fixture_running_report(severities.INFO) -+ ) -+ def test_report_info_not_running(self): -+ self.assert_running_info_transform( -+ [], -+ self.fixture_not_running_report(severities.INFO) -+ ) -+ -+ - class IsResourceManaged(TestCase): - status_xml = etree.fromstring(""" - <resources> -@@ -733,6 +764,60 @@ class IsResourceManaged(TestCase): - <resource id="R38:1" managed="false" /> - </group> - </clone> -+ -+ <bundle id="B1" managed="true" /> -+ <bundle id="B2" managed="false" /> -+ -+ <bundle id="B3" managed="true"> -+ <replica id="0"> -+ <resource id="R39" managed="true" /> -+ <resource id="R40" managed="true" /> -+ </replica> -+ <replica id="1"> -+ <resource id="R39" managed="true" /> -+ <resource id="R40" managed="true" /> -+ </replica> -+ </bundle> -+ <bundle id="B4" managed="false"> -+ <replica id="0"> -+ <resource id="R41" managed="true" /> -+ <resource id="R42" managed="true" /> -+ </replica> -+ <replica id="1"> -+ <resource id="R41" managed="true" /> -+ <resource id="R42" managed="true" /> -+ </replica> -+ </bundle> -+ <bundle id="B5" managed="true"> -+ <replica id="0"> -+ <resource id="R43" managed="false" /> -+ <resource id="R44" managed="true" /> -+ </replica> -+ <replica id="1"> -+ <resource id="R43" managed="false" /> -+ <resource id="R44" managed="true" /> -+ </replica> -+ </bundle> -+ <bundle id="B6" managed="true"> -+ <replica id="0"> -+ <resource id="R45" managed="true" /> -+ <resource id="R46" managed="false" /> -+ </replica> -+ <replica id="1"> -+ <resource id="R45" managed="true" /> -+ <resource id="R46" managed="false" /> -+ </replica> -+ </bundle> -+ <bundle id="B7" managed="false"> -+ <replica id="0"> -+ <resource id="R47" managed="false" /> -+ <resource id="R48" managed="false" /> -+ </replica> -+ <replica id="1"> -+ <resource id="R47" managed="false" /> -+ <resource id="R48" managed="false" /> -+ </replica> -+ </bundle> - </resources> - """) - -@@ -856,3 +941,24 @@ class IsResourceManaged(TestCase): - self.assert_managed("R36", False) - self.assert_managed("R37", False) - self.assert_managed("R38", False) -+ -+ def test_bundle(self): -+ self.assert_managed("B1", True) -+ self.assert_managed("B2", False) -+ self.assert_managed("B3", True) -+ self.assert_managed("B4", False) -+ self.assert_managed("B5", False) -+ self.assert_managed("B6", False) -+ self.assert_managed("B7", False) -+ -+ def test_primitive_in_bundle(self): -+ self.assert_managed("R39", True) -+ self.assert_managed("R40", True) -+ self.assert_managed("R41", False) -+ self.assert_managed("R42", False) -+ self.assert_managed("R43", False) -+ self.assert_managed("R44", True) -+ self.assert_managed("R45", True) -+ self.assert_managed("R46", False) -+ self.assert_managed("R47", False) -+ self.assert_managed("R48", False) -diff --git a/pcs/pcs.8 b/pcs/pcs.8 -index 446e7b3..20b5c2e 100644 ---- a/pcs/pcs.8 -+++ b/pcs/pcs.8 -@@ -162,10 +162,10 @@ Remove the clone which contains the specified group or resource (the resource or - master [<master/slave id>] <resource id | group id> [options] [\fB\-\-wait\fR[=n]] - Configure a resource or group as a multi\-state (master/slave) resource. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the operation to finish (including starting and promoting resource instances if appropriate) and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. Note: to remove a master you must remove the resource/group it contains. - .TP --bundle create <bundle id> [container [<container type>] <container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [\fB\-\-wait\fR[=n]] --Create a new bundle encapsulating no resources. The bundle can be used either as it is or a resource may be put into it at any time. If the container type is not specified, it defaults to 'docker'. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the bundle to start and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. -+bundle create <bundle id> [container [<container type>] <container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [meta <meta options>] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]] -+Create a new bundle encapsulating no resources. The bundle can be used either as it is or a resource may be put into it at any time. If the container type is not specified, it defaults to 'docker'. If \fB\-\-disabled\fR is specified, the bundle is not started automatically. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the bundle to start and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. - .TP --bundle update <bundle id> [container <container options>] [network <network options>] [port\-map (add <port options>) | (remove <id>...)]... [storage\-map (add <storage options>) | (remove <id>...)]... [\fB\-\-wait\fR[=n]] -+bundle update <bundle id> [container <container options>] [network <network options>] [port\-map (add <port options>) | (remove <id>...)]... [storage\-map (add <storage options>) | (remove <id>...)]... [meta <meta options>] [\fB\-\-wait\fR[=n]] - Add, remove or change options to specified bundle. If you wish to update a resource encapsulated in the bundle, use the 'pcs resource update' command instead and specify the resource id. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the operation to finish (including moving resources if appropriate) and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes. - .TP - manage <resource id>... [\fB\-\-monitor\fR] -diff --git a/pcs/resource.py b/pcs/resource.py -index dc6da13..467faa5 100644 ---- a/pcs/resource.py -+++ b/pcs/resource.py -@@ -20,7 +20,7 @@ from pcs import ( - ) - from pcs.settings import pacemaker_wait_timeout_status as \ - PACEMAKER_WAIT_TIMEOUT_STATUS --import pcs.lib.cib.acl as lib_acl -+from pcs.cli.common.console_report import error, warn - from pcs.cli.common.errors import CmdLineInputError - from pcs.cli.common.parse_args import prepare_options - from pcs.cli.resource.parse_args import ( -@@ -28,16 +28,21 @@ from pcs.cli.resource.parse_args import ( - parse_bundle_update_options, - parse_create as parse_create_args, - ) --from pcs.lib.errors import LibraryError -+import pcs.lib.cib.acl as lib_acl - from pcs.lib.cib.resource import guest_node --import pcs.lib.pacemaker.live as lib_pacemaker --from pcs.lib.pacemaker.values import timeout_to_seconds --import pcs.lib.resource_agent as lib_ra --from pcs.cli.common.console_report import error, warn - from pcs.lib.commands.resource import( - _validate_guest_change, - _get_nodes_to_validate_against, - ) -+from pcs.lib.errors import LibraryError -+import pcs.lib.pacemaker.live as lib_pacemaker -+from pcs.lib.pacemaker.state import ( -+ get_cluster_state_dom, -+ _get_primitive_roles_with_nodes, -+ _get_primitives_for_state_check, -+) -+from pcs.lib.pacemaker.values import timeout_to_seconds -+import pcs.lib.resource_agent as lib_ra - - - RESOURCE_RELOCATE_CONSTRAINT_PREFIX = "pcs-relocate-" -@@ -1432,6 +1437,18 @@ def resource_master_create(dom, argv, update=False, master_id=None): - return dom, master_element.getAttribute("id") - - def resource_remove(resource_id, output=True, is_remove_remote_context=False): -+ def is_bundle_running(bundle_id): -+ roles_with_nodes = _get_primitive_roles_with_nodes( -+ _get_primitives_for_state_check( -+ get_cluster_state_dom( -+ lib_pacemaker.get_cluster_status_xml(utils.cmd_runner()) -+ ), -+ bundle_id, -+ expected_running=True -+ ) -+ ) -+ return True if roles_with_nodes else False -+ - dom = utils.get_cib_dom() - # if resource is a clone or a master, work with its child instead - cloned_resource = utils.dom_get_clone_ms_resource(dom, resource_id) -@@ -1441,6 +1458,40 @@ def resource_remove(resource_id, output=True, is_remove_remote_context=False): - bundle = utils.dom_get_bundle(dom, resource_id) - if bundle is not None: - primitive_el = utils.dom_get_resource_bundle(bundle) -+ if primitive_el is None: -+ print("Deleting bundle '{0}'".format(resource_id)) -+ else: -+ print( -+ "Deleting bundle '{0}' and its inner resource '{1}'".format( -+ resource_id, -+ primitive_el.getAttribute("id") -+ ) -+ ) -+ -+ if ( -+ "--force" not in utils.pcs_options -+ and -+ not utils.usefile -+ and -+ is_bundle_running(resource_id) -+ ): -+ sys.stdout.write("Stopping bundle '{0}'... ".format(resource_id)) -+ sys.stdout.flush() -+ lib = utils.get_library_wrapper() -+ lib.resource.disable([resource_id], False) -+ output, retval = utils.run(["crm_resource", "--wait"]) -+ # pacemaker which supports bundles supports --wait as well -+ if is_bundle_running(resource_id): -+ msg = [ -+ "Unable to stop: %s before deleting " -+ "(re-run with --force to force deletion)" -+ % resource_id -+ ] -+ if retval != 0 and output: -+ msg.append("\n" + output) -+ utils.err("\n".join(msg).strip()) -+ print("Stopped") -+ - if primitive_el is not None: - resource_remove(primitive_el.getAttribute("id")) - utils.replace_cib_configuration( -@@ -1498,7 +1549,7 @@ def resource_remove(resource_id, output=True, is_remove_remote_context=False): - resource_remove(res.getAttribute("id")) - sys.exit(0) - -- # now we know resource is not a group, a clone nor a master -+ # now we know resource is not a group, a clone, a master nor a bundle - # because of the conditions above - if not utils.does_exist('//resources/descendant::primitive[@id="'+resource_id+'"]'): - utils.err("Resource '{0}' does not exist.".format(resource_id)) -@@ -1517,7 +1568,7 @@ def resource_remove(resource_id, output=True, is_remove_remote_context=False): - and - utils.resource_running_on(resource_id)["is_running"] - ): -- sys.stdout.write("Attempting to stop: "+ resource_id + "...") -+ sys.stdout.write("Attempting to stop: "+ resource_id + "... ") - sys.stdout.flush() - lib = utils.get_library_wrapper() - # we are not using wait from disable command, because if wait is not -@@ -2246,6 +2297,7 @@ def print_node(node, tab = 0): - node.findall("storage/storage-mapping"), - spaces + " " - ) -+ print_meta_vars_string(node, spaces) - for child in node: - print_node(child, tab + 1) - return -@@ -2675,12 +2727,14 @@ def resource_bundle_create_cmd(lib, argv, modifiers): - lib.resource.bundle_create( - bundle_id, - parts["container_type"], -- parts["container"], -- parts["network"], -- parts["port_map"], -- parts["storage_map"], -- modifiers["force"], -- modifiers["wait"] -+ container_options=parts["container"], -+ network_options=parts["network"], -+ port_map=parts["port_map"], -+ storage_map=parts["storage_map"], -+ meta_attributes=parts["meta"], -+ force_options=modifiers["force"], -+ ensure_disabled=modifiers["disabled"], -+ wait=modifiers["wait"] - ) - - def resource_bundle_update_cmd(lib, argv, modifiers): -@@ -2691,12 +2745,13 @@ def resource_bundle_update_cmd(lib, argv, modifiers): - parts = parse_bundle_update_options(argv[1:]) - lib.resource.bundle_update( - bundle_id, -- parts["container"], -- parts["network"], -- parts["port_map_add"], -- parts["port_map_remove"], -- parts["storage_map_add"], -- parts["storage_map_remove"], -- modifiers["force"], -- modifiers["wait"] -+ container_options=parts["container"], -+ network_options=parts["network"], -+ port_map_add=parts["port_map_add"], -+ port_map_remove=parts["port_map_remove"], -+ storage_map_add=parts["storage_map_add"], -+ storage_map_remove=parts["storage_map_remove"], -+ meta_attributes=parts["meta"], -+ force_options=modifiers["force"], -+ wait=modifiers["wait"] - ) -diff --git a/pcs/test/cib_resource/test_bundle.py b/pcs/test/cib_resource/test_bundle.py -index d8c97c6..29e4339 100644 ---- a/pcs/test/cib_resource/test_bundle.py -+++ b/pcs/test/cib_resource/test_bundle.py -@@ -75,6 +75,7 @@ class BundleCreate(BundleCreateCommon): - resource bundle create B1 - container replicas=4 replicas-per-host=2 run-command=/bin/true - port-map port=1001 -+ meta target-role=Stopped - network control-port=12345 host-interface=eth0 host-netmask=24 - port-map id=B1-port-map-1001 internal-port=2002 port=2000 - port-map range=3000-3300 -@@ -83,6 +84,7 @@ class BundleCreate(BundleCreateCommon): - storage-map id=B1-storage-map source-dir=/tmp/docker2a - target-dir=/tmp/docker2b - container image=pcs:test masters=0 -+ meta is-managed=false - storage-map source-dir-root=/tmp/docker3a - target-dir=/tmp/docker3b - storage-map id=B1-port-map-1001-1 source-dir-root=/tmp/docker4a -@@ -140,6 +142,18 @@ class BundleCreate(BundleCreateCommon): - target-dir="/tmp/docker4b" - /> - </storage> -+ <meta_attributes id="B1-meta_attributes"> -+ <nvpair -+ id="B1-meta_attributes-is-managed" -+ name="is-managed" -+ value="false" -+ /> -+ <nvpair -+ id="B1-meta_attributes-target-role" -+ name="target-role" -+ value="Stopped" -+ /> -+ </meta_attributes> - </bundle> - </resources> - """ -@@ -215,6 +229,9 @@ class BundleCreate(BundleCreateCommon): - def test_empty_port_map(self): - self.assert_no_options("port-map") - -+ def test_empty_meta(self): -+ self.assert_no_options("meta") -+ - - @skip_unless_pacemaker_supports_bundle - class BundleUpdate(BundleCreateCommon): -@@ -239,6 +256,7 @@ class BundleUpdate(BundleCreateCommon): - "storage-map source-dir=/tmp/docker1a target-dir=/tmp/docker1b " - "storage-map source-dir=/tmp/docker2a target-dir=/tmp/docker2b " - "storage-map source-dir=/tmp/docker3a target-dir=/tmp/docker3b " -+ "meta priority=15 resource-stickiness=100 is-managed=false " - ).format(name) - ) - -@@ -282,6 +300,7 @@ class BundleUpdate(BundleCreateCommon): - port-map add internal-port=1003 port=2003 - storage-map remove B-storage-map B-storage-map-2 - storage-map add source-dir=/tmp/docker4a target-dir=/tmp/docker4b -+ meta priority=10 is-managed= target-role=Stopped - """, - """ - <resources> -@@ -319,6 +338,14 @@ class BundleUpdate(BundleCreateCommon): - target-dir="/tmp/docker4b" - /> - </storage> -+ <meta_attributes id="B-meta_attributes"> -+ <nvpair id="B-meta_attributes-priority" -+ name="priority" value="10" /> -+ <nvpair id="B-meta_attributes-resource-stickiness" -+ name="resource-stickiness" value="100" /> -+ <nvpair id="B-meta_attributes-target-role" -+ name="target-role" value="Stopped" /> -+ </meta_attributes> - </bundle> - </resources> - """ -@@ -373,6 +400,9 @@ class BundleUpdate(BundleCreateCommon): - def test_empty_port_map(self): - self.assert_no_options("port-map") - -+ def test_empty_meta(self): -+ self.assert_no_options("meta") -+ - - @skip_unless_pacemaker_supports_bundle - class BundleShow(TestCase, AssertPcsMixin): -@@ -463,6 +493,35 @@ class BundleShow(TestCase, AssertPcsMixin): - """ - )) - -+ def test_meta(self): -+ self.assert_pcs_success( -+ "resource bundle create B1 container image=pcs:test --disabled" -+ ) -+ self.assert_pcs_success("resource show B1", outdent( -+ # pylint:disable=trailing-whitespace -+ """\ -+ Bundle: B1 -+ Docker: image=pcs:test -+ Meta Attrs: target-role=Stopped -+ """ -+ )) -+ -+ def test_resource(self): -+ self.assert_pcs_success( -+ "resource bundle create B1 container image=pcs:test" -+ ) -+ self.assert_pcs_success( -+ "resource create A ocf:pacemaker:Dummy bundle B1 --no-default-ops" -+ ) -+ self.assert_pcs_success("resource show B1", outdent( -+ """\ -+ Bundle: B1 -+ Docker: image=pcs:test -+ Resource: A (class=ocf provider=pacemaker type=Dummy) -+ Operations: monitor interval=10 timeout=20 (A-monitor-interval-10) -+ """ -+ )) -+ - def test_all(self): - self.assert_pcs_success( - """ -@@ -474,9 +533,14 @@ class BundleShow(TestCase, AssertPcsMixin): - storage-map source-dir=/tmp/docker1a target-dir=/tmp/docker1b - storage-map id=my-storage-map source-dir=/tmp/docker2a - target-dir=/tmp/docker2b -+ meta target-role=Stopped is-managed=false - """ - ) -+ self.assert_pcs_success( -+ "resource create A ocf:pacemaker:Dummy bundle B1 --no-default-ops" -+ ) - self.assert_pcs_success("resource show B1", outdent( -+ # pylint:disable=trailing-whitespace - """\ - Bundle: B1 - Docker: image=pcs:test masters=2 options="a b c" replicas=4 -@@ -487,5 +551,8 @@ class BundleShow(TestCase, AssertPcsMixin): - Storage Mapping: - source-dir=/tmp/docker1a target-dir=/tmp/docker1b (B1-storage-map) - source-dir=/tmp/docker2a target-dir=/tmp/docker2b (my-storage-map) -+ Meta Attrs: is-managed=false target-role=Stopped -+ Resource: A (class=ocf provider=pacemaker type=Dummy) -+ Operations: monitor interval=10 timeout=20 (A-monitor-interval-10) - """ - )) -diff --git a/pcs/test/cib_resource/test_manage_unmanage.py b/pcs/test/cib_resource/test_manage_unmanage.py -index 5b78646..2a87cd3 100644 ---- a/pcs/test/cib_resource/test_manage_unmanage.py -+++ b/pcs/test/cib_resource/test_manage_unmanage.py -@@ -18,6 +18,7 @@ class ManageUnmanage( - TestCase, - get_assert_pcs_effect_mixin( - lambda cib: etree.tostring( -+ # pylint:disable=undefined-variable - etree.parse(cib).findall(".//resources")[0] - ) - ) -@@ -234,7 +235,7 @@ class ManageUnmanage( - - self.assert_pcs_fail( - "resource unmanage A B", -- "Error: resource/clone/master/group 'B' does not exist\n" -+ "Error: resource/clone/master/group/bundle 'B' does not exist\n" - ) - self.assert_resources_xml_in_cib( - """ -@@ -255,7 +256,7 @@ class ManageUnmanage( - - self.assert_pcs_fail( - "resource manage A B", -- "Error: resource/clone/master/group 'B' does not exist\n" -+ "Error: resource/clone/master/group/bundle 'B' does not exist\n" - ) - self.assert_resources_xml_in_cib( - """ -diff --git a/pcs/test/test_resource.py b/pcs/test/test_resource.py -index 96eae8f..4bdc194 100644 ---- a/pcs/test/test_resource.py -+++ b/pcs/test/test_resource.py -@@ -8,6 +8,7 @@ from __future__ import ( - from lxml import etree - import re - import shutil -+from textwrap import dedent - - from pcs.test.tools import pcs_unittest as unittest - from pcs.test.tools.assertions import AssertPcsMixin -@@ -3321,11 +3322,11 @@ Error: Cannot remove more than one resource from cloned group - - # bad resource name - o,r = pcs(temp_cib, "resource enable NoExist") -- ac(o,"Error: resource/clone/master/group 'NoExist' does not exist\n") -+ ac(o,"Error: resource/clone/master/group/bundle 'NoExist' does not exist\n") - assert r == 1 - - o,r = pcs(temp_cib, "resource disable NoExist") -- ac(o,"Error: resource/clone/master/group 'NoExist' does not exist\n") -+ ac(o,"Error: resource/clone/master/group/bundle 'NoExist' does not exist\n") - assert r == 1 - - # cloned group -@@ -3829,7 +3830,7 @@ Error: Cannot remove more than one resource from cloned group - - self.assert_pcs_fail_regardless_of_force( - "resource enable dummy3 dummyX", -- "Error: resource/clone/master/group 'dummyX' does not exist\n" -+ "Error: resource/clone/master/group/bundle 'dummyX' does not exist\n" - ) - self.assert_pcs_success( - "resource show --full", -@@ -3849,7 +3850,7 @@ Error: Cannot remove more than one resource from cloned group - - self.assert_pcs_fail_regardless_of_force( - "resource disable dummy1 dummyX", -- "Error: resource/clone/master/group 'dummyX' does not exist\n" -+ "Error: resource/clone/master/group/bundle 'dummyX' does not exist\n" - ) - self.assert_pcs_success( - "resource show --full", -@@ -4719,7 +4720,11 @@ class BundleCommon( - class BundleDeleteTest(BundleCommon): - def test_without_primitive(self): - self.fixture_bundle("B") -- self.assert_effect("resource delete B", "<resources/>") -+ self.assert_effect( -+ "resource delete B", -+ "<resources/>", -+ "Deleting bundle 'B'\n" -+ ) - - def test_with_primitive(self): - self.fixture_bundle("B") -@@ -4727,7 +4732,10 @@ class BundleDeleteTest(BundleCommon): - self.assert_effect( - "resource delete B", - "<resources/>", -- "Deleting Resource - R\n", -+ dedent("""\ -+ Deleting bundle 'B' and its inner resource 'R' -+ Deleting Resource - R -+ """), - ) - - def test_remove_primitive(self): -@@ -4823,30 +4831,26 @@ class BundleCloneMaster(BundleCommon): - class BundleMiscCommands(BundleCommon): - def test_resource_enable_bundle(self): - self.fixture_bundle("B") -- self.assert_pcs_fail_regardless_of_force( -- "resource enable B", -- "Error: 'B' is not clone/master/a group/primitive\n" -+ self.assert_pcs_success( -+ "resource enable B" - ) - - def test_resource_disable_bundle(self): - self.fixture_bundle("B") -- self.assert_pcs_fail_regardless_of_force( -- "resource disable B", -- "Error: 'B' is not clone/master/a group/primitive\n" -+ self.assert_pcs_success( -+ "resource disable B" - ) - - def test_resource_manage_bundle(self): - self.fixture_bundle("B") -- self.assert_pcs_fail_regardless_of_force( -- "resource manage B", -- "Error: 'B' is not clone/master/a group/primitive\n" -+ self.assert_pcs_success( -+ "resource manage B" - ) - - def test_resource_unmanage_bundle(self): - self.fixture_bundle("B") -- self.assert_pcs_fail_regardless_of_force( -- "resource unmanage B", -- "Error: 'B' is not clone/master/a group/primitive\n" -+ self.assert_pcs_success( -+ "resource unmanage B" - ) - - def test_op_add(self): -diff --git a/pcs/usage.py b/pcs/usage.py -index d2262a6..75cb118 100644 ---- a/pcs/usage.py -+++ b/pcs/usage.py -@@ -430,10 +430,12 @@ Commands: - - bundle create <bundle id> [container [<container type>] <container options>] - [network <network options>] [port-map <port options>]... -- [storage-map <storage options>]... [--wait[=n]] -+ [storage-map <storage options>]... [meta <meta options>] -+ [--disabled] [--wait[=n]] - Create a new bundle encapsulating no resources. The bundle can be used - either as it is or a resource may be put into it at any time. - If the container type is not specified, it defaults to 'docker'. -+ If --disabled is specified, the bundle is not started automatically. - If --wait is specified, pcs will wait up to 'n' seconds for the bundle - to start and then return 0 on success or 1 on error. If 'n' is not - specified it defaults to 60 minutes. -@@ -442,13 +444,14 @@ Commands: - [network <network options>] - [port-map (add <port options>) | (remove <id>...)]... - [storage-map (add <storage options>) | (remove <id>...)]... -+ [meta <meta options>] - [--wait[=n]] - Add, remove or change options to specified bundle. If you wish to update - a resource encapsulated in the bundle, use the 'pcs resource update' -- command instead and specify the resource id. If --wait is specified, -+ command instead and specify the resource id. If --wait is specified, - pcs will wait up to 'n' seconds for the operation to finish (including - moving resources if appropriate) and then return 0 on success or 1 on -- error. If 'n' is not specified it defaults to 60 minutes. -+ error. If 'n' is not specified it defaults to 60 minutes. - - manage <resource id>... [--monitor] - Set resources listed to managed mode (default). If --monitor is --- -1.8.3.1 - diff --git a/SOURCES/bz1458153-01-give-back-orig.-master-behav.-resource-create.patch b/SOURCES/bz1458153-01-give-back-orig.-master-behav.-resource-create.patch index 590c8a0..b7f98cd 100644 --- a/SOURCES/bz1458153-01-give-back-orig.-master-behav.-resource-create.patch +++ b/SOURCES/bz1458153-01-give-back-orig.-master-behav.-resource-create.patch @@ -1,4 +1,4 @@ -From 9fadaf3189f3c19bf6c2802f7a4a2b9c8c6638c1 Mon Sep 17 00:00:00 2001 +From fe7151898fed1b6383a49db26426c3f23c5ff7f2 Mon Sep 17 00:00:00 2001 From: Ivan Devat <idevat@redhat.com> Date: Mon, 5 Jun 2017 17:13:41 +0200 Subject: [PATCH] give back orig. --master behav. (resource create) @@ -14,10 +14,10 @@ Subject: [PATCH] give back orig. --master behav. (resource create) 7 files changed, 228 insertions(+), 59 deletions(-) diff --git a/pcs/cli/common/parse_args.py b/pcs/cli/common/parse_args.py -index d72a6d4..3cd96c9 100644 +index 70b926c0..d3151043 100644 --- a/pcs/cli/common/parse_args.py +++ b/pcs/cli/common/parse_args.py -@@ -300,7 +300,13 @@ def upgrade_args(arg_list): +@@ -299,7 +299,13 @@ def upgrade_args(arg_list): and args_without_options[:2] == ["resource", "create"] ): @@ -33,10 +33,10 @@ index d72a6d4..3cd96c9 100644 upgraded_args.append(arg) return upgraded_args diff --git a/pcs/cli/common/test/test_parse_args.py b/pcs/cli/common/test/test_parse_args.py -index 5b79b85..1ce03b4 100644 +index efe38d0e..900094c9 100644 --- a/pcs/cli/common/test/test_parse_args.py +++ b/pcs/cli/common/test/test_parse_args.py -@@ -487,9 +487,21 @@ class UpgradeArgs(TestCase): +@@ -486,9 +486,21 @@ class UpgradeArgs(TestCase): upgrade_args(["first", "--cloneopt=1", "second"]) ) @@ -61,7 +61,7 @@ index 5b79b85..1ce03b4 100644 upgrade_args(["resource", "create", "--master", "second"]) ) -@@ -499,10 +511,22 @@ class UpgradeArgs(TestCase): +@@ -498,10 +510,22 @@ class UpgradeArgs(TestCase): upgrade_args(["first", "--master", "second"]) ) @@ -87,10 +87,10 @@ index 5b79b85..1ce03b4 100644 ], upgrade_args([ diff --git a/pcs/resource.py b/pcs/resource.py -index 467faa5..818cb5b 100644 +index 082bd9d1..6637f806 100644 --- a/pcs/resource.py +++ b/pcs/resource.py -@@ -375,6 +375,25 @@ def resource_create(lib, argv, modifiers): +@@ -384,6 +384,25 @@ def resource_create(lib, argv, modifiers): ra_type = argv[1] parts = parse_create_args(argv[2:]) @@ -117,10 +117,10 @@ index 467faa5..818cb5b 100644 defined_options = [opt for opt in parts_sections if opt in parts] if modifiers["group"]: diff --git a/pcs/test/cib_resource/test_create.py b/pcs/test/cib_resource/test_create.py -index 2492ba9..045ce68 100644 +index cfb2e645..c554ec24 100644 --- a/pcs/test/cib_resource/test_create.py +++ b/pcs/test/cib_resource/test_create.py -@@ -223,7 +223,7 @@ class Success(ResourceTest): +@@ -233,7 +233,7 @@ class Success(ResourceTest): def test_with_master(self): self.assert_effect( [ @@ -129,7 +129,7 @@ index 2492ba9..045ce68 100644 "resource create R ocf:heartbeat:Dummy --no-default-ops master", ], """<resources> -@@ -609,7 +609,7 @@ class SuccessGroup(ResourceTest): +@@ -654,7 +654,7 @@ class SuccessGroup(ResourceTest): class SuccessMaster(ResourceTest): def test_disable_is_on_master_element(self): self.assert_effect( @@ -138,7 +138,7 @@ index 2492ba9..045ce68 100644 """<resources> <master id="R-master"> <meta_attributes id="R-master-meta_attributes"> -@@ -630,13 +630,55 @@ class SuccessMaster(ResourceTest): +@@ -675,13 +675,55 @@ class SuccessMaster(ResourceTest): </resources>""" ) @@ -197,7 +197,7 @@ index 2492ba9..045ce68 100644 , """<resources> <master id="R-master"> -@@ -644,6 +686,9 @@ class SuccessMaster(ResourceTest): +@@ -689,6 +731,9 @@ class SuccessMaster(ResourceTest): type="Dummy" > <instance_attributes id="R-instance_attributes"> @@ -207,7 +207,7 @@ index 2492ba9..045ce68 100644 <nvpair id="R-instance_attributes-state" name="state" value="a" /> -@@ -660,22 +705,58 @@ class SuccessMaster(ResourceTest): +@@ -714,22 +759,58 @@ class SuccessMaster(ResourceTest): /> </operations> </primitive> @@ -276,7 +276,7 @@ index 2492ba9..045ce68 100644 " --no-default-ops" , """<resources> -@@ -690,22 +771,53 @@ class SuccessMaster(ResourceTest): +@@ -744,22 +825,53 @@ class SuccessMaster(ResourceTest): </instance_attributes> <operations> <op id="R-monitor-interval-10s" interval="10s" @@ -340,7 +340,7 @@ index 2492ba9..045ce68 100644 """ self.assert_effect( "resource create R ocf:heartbeat:Dummy meta a=b --master b=c" -@@ -716,11 +828,6 @@ class SuccessMaster(ResourceTest): +@@ -770,11 +882,6 @@ class SuccessMaster(ResourceTest): <primitive class="ocf" id="R" provider="heartbeat" type="Dummy" > @@ -352,7 +352,7 @@ index 2492ba9..045ce68 100644 <operations> <op id="R-monitor-interval-10" interval="10" name="monitor" timeout="20" -@@ -728,18 +835,24 @@ class SuccessMaster(ResourceTest): +@@ -782,18 +889,24 @@ class SuccessMaster(ResourceTest): </operations> </primitive> <meta_attributes id="R-master-meta_attributes"> @@ -378,7 +378,7 @@ index 2492ba9..045ce68 100644 , """<resources> <master id="R-master"> -@@ -960,7 +1073,7 @@ class FailOrWarn(ResourceTest): +@@ -1041,7 +1154,7 @@ class FailOrWarn(ResourceTest): def test_error_master_clone_combination(self): self.assert_pcs_fail( "resource create R ocf:heartbeat:Dummy --no-default-ops --clone" @@ -387,7 +387,7 @@ index 2492ba9..045ce68 100644 , "Error: you can specify only one of clone, master, bundle or" " --group\n" -@@ -968,7 +1081,7 @@ class FailOrWarn(ResourceTest): +@@ -1049,7 +1162,7 @@ class FailOrWarn(ResourceTest): def test_error_master_group_combination(self): self.assert_pcs_fail( @@ -396,7 +396,7 @@ index 2492ba9..045ce68 100644 " --group G" , "Error: you can specify only one of clone, master, bundle or" -@@ -986,7 +1099,7 @@ class FailOrWarn(ResourceTest): +@@ -1067,7 +1180,7 @@ class FailOrWarn(ResourceTest): def test_error_bundle_master_combination(self): self.assert_pcs_fail( @@ -406,10 +406,10 @@ index 2492ba9..045ce68 100644 , "Error: you can specify only one of clone, master, bundle or" diff --git a/pcs/test/test_constraints.py b/pcs/test/test_constraints.py -index 4160b01..152218e 100644 +index 9f0bc5d6..f5973410 100644 --- a/pcs/test/test_constraints.py +++ b/pcs/test/test_constraints.py -@@ -341,43 +341,43 @@ Ticket Constraints: +@@ -346,43 +346,43 @@ Ticket Constraints: def testColocationConstraints(self): # see also BundleColocation @@ -500,10 +500,10 @@ index 4160b01..152218e 100644 ac(output, """\ Warning: changing a monitor operation interval from 10 to 11 to make the operation unique diff --git a/pcs/test/test_resource.py b/pcs/test/test_resource.py -index c015fa4..5bc9517 100644 +index bd596f64..d8f68c12 100644 --- a/pcs/test/test_resource.py +++ b/pcs/test/test_resource.py -@@ -2729,7 +2729,7 @@ Ticket Constraints: +@@ -2826,7 +2826,7 @@ Ticket Constraints: output, returnVal = pcs( temp_cib, @@ -512,7 +512,7 @@ index c015fa4..5bc9517 100644 ) assert returnVal == 0 assert output == "", [output] -@@ -2797,7 +2797,7 @@ Warning: changing a monitor operation interval from 10 to 11 to make the operati +@@ -2919,7 +2919,7 @@ Warning: changing a monitor operation interval from 10 to 11 to make the operati ac(o,"") assert r == 0 @@ -521,7 +521,7 @@ index c015fa4..5bc9517 100644 ac(o,"") assert r == 0 -@@ -3011,7 +3011,7 @@ Warning: changing a monitor operation interval from 10 to 11 to make the operati +@@ -3133,7 +3133,7 @@ Warning: changing a monitor operation interval from 10 to 11 to make the operati output, returnVal = pcs( temp_cib, @@ -530,7 +530,7 @@ index c015fa4..5bc9517 100644 ) ac(output, "") self.assertEqual(0, returnVal) -@@ -3602,7 +3602,7 @@ Error: Cannot remove more than one resource from cloned group +@@ -3727,7 +3727,7 @@ Error: Cannot remove more than one resource from cloned group # However those test the pcs library. I'm leaving these tests here to # test the cli part for now. self.assert_pcs_success( @@ -539,7 +539,7 @@ index c015fa4..5bc9517 100644 "Warning: changing a monitor operation interval from 10 to 11 to make the operation unique\n" ) -@@ -4630,7 +4630,7 @@ class CloneMasterUpdate(unittest.TestCase, AssertPcsMixin): +@@ -4761,7 +4761,7 @@ class CloneMasterUpdate(unittest.TestCase, AssertPcsMixin): def test_no_op_allowed_in_master_update(self): self.assert_pcs_success( @@ -549,10 +549,10 @@ index c015fa4..5bc9517 100644 self.assert_pcs_success("resource show dummy-master", outdent( """\ diff --git a/pcs/utils.py b/pcs/utils.py -index d6aabf4..255fcf8 100644 +index 5b608239..9b46810f 100644 --- a/pcs/utils.py +++ b/pcs/utils.py -@@ -2890,6 +2890,13 @@ def get_modificators(): +@@ -2861,6 +2861,13 @@ def get_modificators(): "start": "--start" in pcs_options, "wait": pcs_options.get("--wait", False), "watchdog": pcs_options.get("--watchdog", []), @@ -567,5 +567,5 @@ index d6aabf4..255fcf8 100644 def exit_on_cmdline_input_errror(error, main_name, usage_name): -- -1.8.3.1 +2.13.6 diff --git a/SOURCES/bz1459503-01-OSP-workarounds-not-compatible-wi.patch b/SOURCES/bz1459503-01-OSP-workarounds-not-compatible-wi.patch index 133846c..9a7a010 100644 --- a/SOURCES/bz1459503-01-OSP-workarounds-not-compatible-wi.patch +++ b/SOURCES/bz1459503-01-OSP-workarounds-not-compatible-wi.patch @@ -1,4 +1,4 @@ -From 764f74bb363613c63c3757637267bf37ee2381a0 Mon Sep 17 00:00:00 2001 +From b5f98fed9903bc6c4e30056aa99f3be6fcb68917 Mon Sep 17 00:00:00 2001 From: Ivan Devat <idevat@redhat.com> Date: Wed, 7 Jun 2017 14:36:05 +0200 Subject: [PATCH] squash bz1459503 OSP workarounds not compatible wi @@ -14,10 +14,10 @@ show only warn if `resource create` creates remote 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/pcs/cluster.py b/pcs/cluster.py -index d896b0c..16eb0c5 100644 +index b66bec78..a3cc74c6 100644 --- a/pcs/cluster.py +++ b/pcs/cluster.py -@@ -452,13 +452,21 @@ def cluster_setup(argv): +@@ -501,13 +501,21 @@ def cluster_setup(argv): print("Destroying cluster on nodes: {0}...".format( ", ".join(primary_addr_list) )) @@ -41,10 +41,10 @@ index d896b0c..16eb0c5 100644 if modifiers["encryption"] == "1": file_definitions.update( diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py -index 0c5f682..0c4d6fc 100644 +index 8cda3102..cb45771f 100644 --- a/pcs/lib/commands/resource.py +++ b/pcs/lib/commands/resource.py -@@ -69,7 +69,8 @@ def _validate_remote_connection( +@@ -67,7 +67,8 @@ def _validate_remote_connection( report_list.append( reports.get_problem_creator( report_codes.FORCE_NOT_SUITABLE_COMMAND, @@ -54,7 +54,7 @@ index 0c5f682..0c4d6fc 100644 )(reports.use_command_node_add_remote) ) -@@ -99,7 +100,8 @@ def _validate_guest_change( +@@ -97,7 +98,8 @@ def _validate_guest_change( report_list.append( reports.get_problem_creator( report_codes.FORCE_NOT_SUITABLE_COMMAND, @@ -65,10 +65,10 @@ index 0c5f682..0c4d6fc 100644 ) diff --git a/pcs/test/cib_resource/test_create.py b/pcs/test/cib_resource/test_create.py -index 045ce68..afd7d0a 100644 +index c554ec24..282ce1c5 100644 --- a/pcs/test/cib_resource/test_create.py +++ b/pcs/test/cib_resource/test_create.py -@@ -1463,11 +1463,10 @@ class FailOrWarnGroup(ResourceTest): +@@ -1581,11 +1581,10 @@ class FailOrWarnGroup(ResourceTest): ) def test_fail_when_on_pacemaker_remote_attempt(self): @@ -83,7 +83,7 @@ index 045ce68..afd7d0a 100644 ) def test_warn_when_on_pacemaker_remote_attempt(self): -@@ -1567,10 +1566,10 @@ class FailOrWarnGroup(ResourceTest): +@@ -1685,10 +1684,10 @@ class FailOrWarnGroup(ResourceTest): ) def test_fail_when_on_pacemaker_remote_guest_attempt(self): @@ -98,10 +98,10 @@ index 045ce68..afd7d0a 100644 def test_warn_when_on_pacemaker_remote_guest_attempt(self): diff --git a/pcs/test/test_resource.py b/pcs/test/test_resource.py -index 5bc9517..9ab1dd5 100644 +index d8f68c12..eac7eb04 100644 --- a/pcs/test/test_resource.py +++ b/pcs/test/test_resource.py -@@ -4973,10 +4973,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): +@@ -5110,10 +5110,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): self.assert_pcs_success( "resource create R ocf:heartbeat:Dummy", ) @@ -115,7 +115,7 @@ index 5bc9517..9ab1dd5 100644 ) def test_update_warn_on_pacemaker_guest_attempt(self): self.assert_pcs_success( -@@ -4995,10 +4995,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): +@@ -5132,10 +5132,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): "Warning: this command is not sufficient for creating a guest node," " use 'pcs cluster node add-guest'\n" ) @@ -129,7 +129,7 @@ index 5bc9517..9ab1dd5 100644 ) def test_update_warn_on_pacemaker_guest_attempt_remove(self): -@@ -5019,10 +5019,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): +@@ -5156,10 +5156,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): self.assert_pcs_success( "resource create R ocf:heartbeat:Dummy", ) @@ -143,7 +143,7 @@ index 5bc9517..9ab1dd5 100644 ) def test_meta_warn_on_pacemaker_guest_attempt(self): -@@ -5043,10 +5043,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): +@@ -5180,10 +5180,10 @@ class ResourceUpdateSpcialChecks(unittest.TestCase, AssertPcsMixin): "Warning: this command is not sufficient for creating a guest node," " use 'pcs cluster node add-guest'\n" ) @@ -158,5 +158,5 @@ index 5bc9517..9ab1dd5 100644 def test_meta_warn_on_pacemaker_guest_attempt_remove(self): -- -1.8.3.1 +2.13.6 diff --git a/SOURCES/bz1464781-01-fix-exit-code-when-adding-a-remote-or-guest-node.patch b/SOURCES/bz1464781-01-fix-exit-code-when-adding-a-remote-or-guest-node.patch new file mode 100644 index 0000000..a964969 --- /dev/null +++ b/SOURCES/bz1464781-01-fix-exit-code-when-adding-a-remote-or-guest-node.patch @@ -0,0 +1,39 @@ +From f45f7cd9feffcdba68c880b0a28fb7ebde94ad58 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Wed, 3 Jan 2018 17:21:29 +0100 +Subject: [PATCH] fix exit code when adding a remote or guest node + +--- + pcs/cli/common/reports.py | 2 +- + pcs/cluster.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/pcs/cli/common/reports.py b/pcs/cli/common/reports.py +index 5fd39cb..f96db73 100644 +--- a/pcs/cli/common/reports.py ++++ b/pcs/cli/common/reports.py +@@ -130,7 +130,7 @@ def process_library_reports(report_item_list): + report_item_list list of ReportItem + """ + if not report_item_list: +- error("Errors have occurred, therefore pcs is unable to continue") ++ raise error("Errors have occurred, therefore pcs is unable to continue") + + critical_error = False + for report_item in report_item_list: +diff --git a/pcs/cluster.py b/pcs/cluster.py +index a330164..b66bec7 100644 +--- a/pcs/cluster.py ++++ b/pcs/cluster.py +@@ -209,7 +209,7 @@ def cluster_cmd(argv): + utils.get_modificators() + ) + except LibraryError as e: +- utils.process_library_reports(e.args) ++ process_library_reports(e.args) + except CmdLineInputError as e: + utils.exit_on_cmdline_input_errror( + e, "cluster", "node " + argv[0] +-- +1.8.3.1 + diff --git a/SOURCES/bz1507399-01-Make-resource-update-idempotent-with-remote-node.patch b/SOURCES/bz1507399-01-Make-resource-update-idempotent-with-remote-node.patch deleted file mode 100644 index 501c6f8..0000000 --- a/SOURCES/bz1507399-01-Make-resource-update-idempotent-with-remote-node.patch +++ /dev/null @@ -1,71 +0,0 @@ -From a2d864535fc819c8b9c599aeccf1128716d56b15 Mon Sep 17 00:00:00 2001 -From: Bruno Travouillon <devel@travouillon.fr> -Date: Fri, 29 Sep 2017 20:22:16 +0200 -Subject: [PATCH] Make resource update idempotent with remote-node - -If the remote-node name argument is already defined for the resource, -do not deal with guest change. - -Fix issue #145. ---- - pcs/resource.py | 15 ++++++++++----- - pcs/test/test_cluster_pcmk_remote.py | 10 ++++++++++ - 2 files changed, 20 insertions(+), 5 deletions(-) - -diff --git a/pcs/resource.py b/pcs/resource.py -index 818cb5b..39f6fc2 100644 ---- a/pcs/resource.py -+++ b/pcs/resource.py -@@ -723,11 +723,6 @@ def resource_update(res_id,args, deal_with_guest_change=True): - - # Extract operation arguments - ra_values, op_values, meta_values = parse_resource_options(args) -- if deal_with_guest_change: -- _detect_guest_change( -- prepare_options(meta_values), -- "--force" in utils.pcs_options, -- ) - - wait = False - wait_timeout = None -@@ -811,6 +806,16 @@ def resource_update(res_id,args, deal_with_guest_change=True): - instance_attributes.appendChild(ia) - - remote_node_name = utils.dom_get_resource_remote_node_name(resource) -+ -+ if remote_node_name == guest_node.get_guest_option_value(prepare_options(meta_values)): -+ deal_with_guest_change = False -+ -+ if deal_with_guest_change: -+ _detect_guest_change( -+ prepare_options(meta_values), -+ "--force" in utils.pcs_options, -+ ) -+ - utils.dom_update_meta_attr( - resource, - utils.convert_args_to_tuples(meta_values) -diff --git a/pcs/test/test_cluster_pcmk_remote.py b/pcs/test/test_cluster_pcmk_remote.py -index 0db4a5c..aa6a215 100644 ---- a/pcs/test/test_cluster_pcmk_remote.py -+++ b/pcs/test/test_cluster_pcmk_remote.py -@@ -291,6 +291,16 @@ class NodeAddGuest(ResourceTest): - output=fixture_nolive_add_report - ) - -+ def test_success_when_guest_node_matches_with_existing_guest(self): -+ self.create_resource() -+ self.assert_pcs_success( -+ "cluster node add-guest node-host G", -+ fixture_nolive_add_report -+ ) -+ self.assert_pcs_success( -+ "resource update G meta remote-node=node-host", -+ ) -+ - def test_success_with_options(self): - self.create_resource() - self.assert_effect( --- -1.8.3.1 - diff --git a/SOURCES/bz1522813-01-fix-a-crash-when-wait-is-used-in-stonith-create.patch b/SOURCES/bz1522813-01-fix-a-crash-when-wait-is-used-in-stonith-create.patch new file mode 100644 index 0000000..43e4784 --- /dev/null +++ b/SOURCES/bz1522813-01-fix-a-crash-when-wait-is-used-in-stonith-create.patch @@ -0,0 +1,524 @@ +From 663143e3abbf2798a3c780c691242511b64046a2 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Wed, 6 Dec 2017 17:38:15 +0100 +Subject: [PATCH] fix a crash when --wait is used in stonith create + +--- + pcs/lib/commands/stonith.py | 19 ++- + pcs/lib/commands/test/test_stonith.py | 188 ++++++++++++++++++++++ + pcs/lib/resource_agent.py | 4 + + pcs/test/resources/stonith_agent_fence_simple.xml | 33 ++++ + pcs/test/resources/stonithd_metadata.xml | 156 ++++++++++++++++++ + pcs/test/tools/command_env/config_runner_pcmk.py | 36 +++++ + 6 files changed, 430 insertions(+), 6 deletions(-) + create mode 100644 pcs/lib/commands/test/test_stonith.py + create mode 100644 pcs/test/resources/stonith_agent_fence_simple.xml + create mode 100644 pcs/test/resources/stonithd_metadata.xml + +diff --git a/pcs/lib/commands/stonith.py b/pcs/lib/commands/stonith.py +index bb9fb98..584e1b2 100644 +--- a/pcs/lib/commands/stonith.py ++++ b/pcs/lib/commands/stonith.py +@@ -4,11 +4,14 @@ from __future__ import ( + print_function, + ) + +-from pcs.lib.resource_agent import find_valid_stonith_agent_by_name as get_agent + from pcs.lib.cib import resource + from pcs.lib.cib.resource.common import are_meta_disabled ++from pcs.lib.commands.resource import ( ++ _ensure_disabled_after_wait, ++ resource_environment ++) + from pcs.lib.pacemaker.values import validate_id +-from pcs.lib.commands.resource import resource_environment ++from pcs.lib.resource_agent import find_valid_stonith_agent_by_name as get_agent + + def create( + env, stonith_id, stonith_agent_name, +@@ -55,8 +58,10 @@ def create( + with resource_environment( + env, + wait, +- stonith_id, +- ensure_disabled or are_meta_disabled(meta_attributes), ++ [stonith_id], ++ _ensure_disabled_after_wait( ++ ensure_disabled or are_meta_disabled(meta_attributes), ++ ) + ) as resources_section: + stonith_element = resource.primitive.create( + env.report_processor, +@@ -125,8 +130,10 @@ def create_in_group( + with resource_environment( + env, + wait, +- stonith_id, +- ensure_disabled or are_meta_disabled(meta_attributes), ++ [stonith_id], ++ _ensure_disabled_after_wait( ++ ensure_disabled or are_meta_disabled(meta_attributes), ++ ) + ) as resources_section: + stonith_element = resource.primitive.create( + env.report_processor, resources_section, +diff --git a/pcs/lib/commands/test/test_stonith.py b/pcs/lib/commands/test/test_stonith.py +new file mode 100644 +index 0000000..912742f +--- /dev/null ++++ b/pcs/lib/commands/test/test_stonith.py +@@ -0,0 +1,188 @@ ++from __future__ import ( ++ absolute_import, ++ division, ++ print_function, ++) ++ ++from pcs.common import report_codes ++from pcs.lib.commands import stonith ++from pcs.lib.resource_agent import StonithAgent ++from pcs.test.tools import fixture ++from pcs.test.tools.command_env import get_env_tools ++from pcs.test.tools.pcs_unittest import TestCase ++ ++ ++class Create(TestCase): ++ def setUp(self): ++ self.env_assist, self.config = get_env_tools(test_case=self) ++ self.agent_name = "test_simple" ++ self.instance_name = "stonith-test" ++ self.timeout = 10 ++ self.expected_cib = """ ++ <resources> ++ <primitive class="stonith" id="stonith-test" type="test_simple"> ++ <instance_attributes id="stonith-test-instance_attributes"> ++ <nvpair id="stonith-test-instance_attributes-must-set" ++ name="must-set" value="value" ++ /> ++ </instance_attributes> ++ <operations> ++ <op id="stonith-test-monitor-interval-60s" ++ interval="60s" name="monitor" ++ /> ++ </operations> ++ </primitive> ++ </resources> ++ """ ++ self.expected_status = """ ++ <resources> ++ <resource ++ id="{id}" ++ resource_agent="stonith:{agent}" ++ role="Started" ++ active="true" ++ failed="false" ++ nodes_running_on="1" ++ > ++ <node name="node1" id="1" cached="false"/> ++ </resource> ++ </resources> ++ """.format(id=self.instance_name, agent=self.agent_name) ++ (self.config ++ .runner.pcmk.load_agent( ++ agent_name="stonith:{0}".format(self.agent_name), ++ agent_filename="stonith_agent_fence_simple.xml" ++ ) ++ .runner.cib.load() ++ .runner.pcmk.load_stonithd_metadata() ++ ) ++ ++ def tearDown(self): ++ StonithAgent.clear_stonithd_metadata_cache() ++ ++ def test_minimal_success(self): ++ self.config.env.push_cib(resources=self.expected_cib) ++ stonith.create( ++ self.env_assist.get_env(), ++ self.instance_name, ++ self.agent_name, ++ operations=[], ++ meta_attributes={}, ++ instance_attributes={"must-set": "value"} ++ ) ++ ++ def test_minimal_wait_ok_run_ok(self): ++ (self.config ++ .runner.pcmk.can_wait(before="runner.cib.load") ++ .env.push_cib( ++ resources=self.expected_cib, ++ wait=self.timeout ++ ) ++ .runner.pcmk.load_state(resources=self.expected_status) ++ ) ++ stonith.create( ++ self.env_assist.get_env(), ++ self.instance_name, ++ self.agent_name, ++ operations=[], ++ meta_attributes={}, ++ instance_attributes={"must-set": "value"}, ++ wait=self.timeout ++ ) ++ self.env_assist.assert_reports([ ++ fixture.info( ++ report_codes.RESOURCE_RUNNING_ON_NODES, ++ roles_with_nodes={"Started": ["node1"]}, ++ resource_id=self.instance_name, ++ ), ++ ]) ++ ++ ++class CreateInGroup(TestCase): ++ def setUp(self): ++ self.env_assist, self.config = get_env_tools(test_case=self) ++ self.agent_name = "test_simple" ++ self.instance_name = "stonith-test" ++ self.timeout = 10 ++ self.expected_cib = """ ++ <resources> ++ <group id="my-group"> ++ <primitive class="stonith" id="stonith-test" type="test_simple"> ++ <instance_attributes id="stonith-test-instance_attributes"> ++ <nvpair id="stonith-test-instance_attributes-must-set" ++ name="must-set" value="value" ++ /> ++ </instance_attributes> ++ <operations> ++ <op id="stonith-test-monitor-interval-60s" ++ interval="60s" name="monitor" ++ /> ++ </operations> ++ </primitive> ++ </group> ++ </resources> ++ """ ++ self.expected_status = """ ++ <resources> ++ <resource ++ id="{id}" ++ resource_agent="stonith:{agent}" ++ role="Started" ++ active="true" ++ failed="false" ++ nodes_running_on="1" ++ > ++ <node name="node1" id="1" cached="false"/> ++ </resource> ++ </resources> ++ """.format(id=self.instance_name, agent=self.agent_name) ++ (self.config ++ .runner.pcmk.load_agent( ++ agent_name="stonith:{0}".format(self.agent_name), ++ agent_filename="stonith_agent_fence_simple.xml" ++ ) ++ .runner.cib.load() ++ .runner.pcmk.load_stonithd_metadata() ++ ) ++ ++ def tearDown(self): ++ StonithAgent.clear_stonithd_metadata_cache() ++ ++ def test_minimal_success(self): ++ self.config.env.push_cib(resources=self.expected_cib) ++ stonith.create_in_group( ++ self.env_assist.get_env(), ++ self.instance_name, ++ self.agent_name, ++ "my-group", ++ operations=[], ++ meta_attributes={}, ++ instance_attributes={"must-set": "value"} ++ ) ++ ++ def test_minimal_wait_ok_run_ok(self): ++ (self.config ++ .runner.pcmk.can_wait(before="runner.cib.load") ++ .env.push_cib( ++ resources=self.expected_cib, ++ wait=self.timeout ++ ) ++ .runner.pcmk.load_state(resources=self.expected_status) ++ ) ++ stonith.create_in_group( ++ self.env_assist.get_env(), ++ self.instance_name, ++ self.agent_name, ++ "my-group", ++ operations=[], ++ meta_attributes={}, ++ instance_attributes={"must-set": "value"}, ++ wait=self.timeout ++ ) ++ self.env_assist.assert_reports([ ++ fixture.info( ++ report_codes.RESOURCE_RUNNING_ON_NODES, ++ roles_with_nodes={"Started": ["node1"]}, ++ resource_id=self.instance_name, ++ ), ++ ]) +diff --git a/pcs/lib/resource_agent.py b/pcs/lib/resource_agent.py +index 4639477..2f2686d 100644 +--- a/pcs/lib/resource_agent.py ++++ b/pcs/lib/resource_agent.py +@@ -836,6 +836,10 @@ class StonithAgent(CrmAgent): + """ + _stonithd_metadata = None + ++ @classmethod ++ def clear_stonithd_metadata_cache(cls): ++ cls._stonithd_metadata = None ++ + def _prepare_name_parts(self, name): + # pacemaker doesn't support stonith (nor resource) agents with : in type + if ":" in name: +diff --git a/pcs/test/resources/stonith_agent_fence_simple.xml b/pcs/test/resources/stonith_agent_fence_simple.xml +new file mode 100644 +index 0000000..bb86af2 +--- /dev/null ++++ b/pcs/test/resources/stonith_agent_fence_simple.xml +@@ -0,0 +1,33 @@ ++<?xml version="1.0" ?> ++<resource-agent ++ name="fence_simple" ++ shortdesc="Basic fence agent for pcs tests" ++> ++ <longdesc> ++ This is a testing fence agent. Its purpose is to provide a mock of a fence ++ agent which is always available no matter what is the configuration of a ++ system pcs test suite runs on. ++ </longdesc> ++ <vendor-url>https://github.com/ClusterLabs/pcs</vendor-url> ++ <parameters> ++ <parameter name="must-set" unique="0" required="1"> ++ <content type="string" /> ++ <shortdesc lang="en">An example of a required attribute</shortdesc> ++ </parameter> ++ <parameter name="may-set" unique="0" required="0"> ++ <content type="string" /> ++ <shortdesc lang="en">An example of an optional attribute</shortdesc> ++ </parameter> ++ </parameters> ++ <actions> ++ <action name="on" automatic="0"/> ++ <action name="off" /> ++ <action name="reboot" /> ++ <action name="status" /> ++ <action name="list" /> ++ <action name="list-status" /> ++ <action name="monitor" /> ++ <action name="metadata" /> ++ <action name="validate-all" /> ++ </actions> ++</resource-agent> +diff --git a/pcs/test/resources/stonithd_metadata.xml b/pcs/test/resources/stonithd_metadata.xml +new file mode 100644 +index 0000000..fc638a2 +--- /dev/null ++++ b/pcs/test/resources/stonithd_metadata.xml +@@ -0,0 +1,156 @@ ++<?xml version="1.0"?><!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> ++<resource-agent name="stonithd"> ++ <version>1.0</version> ++ <longdesc lang="en">This is a fake resource that details the instance attributes handled by stonithd.</longdesc> ++ <shortdesc lang="en">Options available for all stonith resources</shortdesc> ++ <parameters> ++ <parameter name="priority" unique="0"> ++ <shortdesc lang="en">The priority of the stonith resource. Devices are tried in order of highest priority to lowest.</shortdesc> ++ <content type="integer" default="0"/> ++ </parameter> ++ <parameter name="pcmk_host_argument" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate parameter to supply instead of 'port'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard 'port' parameter or may provide additional ones. ++Use this to specify an alternate, device-specific, parameter that should indicate the machine to be fenced. ++A value of 'none' can be used to tell the cluster not to supply any additional parameters. ++ </longdesc> ++ <content type="string" default="port"/> ++ </parameter> ++ <parameter name="pcmk_host_map" unique="0"> ++ <shortdesc lang="en">A mapping of host names to ports numbers for devices that do not support host names.</shortdesc> ++ <longdesc lang="en">Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3 for node2</longdesc> ++ <content type="string" default=""/> ++ </parameter> ++ <parameter name="pcmk_host_list" unique="0"> ++ <shortdesc lang="en">A list of machines controlled by this device (Optional unless pcmk_host_check=static-list).</shortdesc> ++ <content type="string" default=""/> ++ </parameter> ++ <parameter name="pcmk_host_check" unique="0"> ++ <shortdesc lang="en">How to determine which machines are controlled by the device.</shortdesc> ++ <longdesc lang="en">Allowed values: dynamic-list (query the device), static-list (check the pcmk_host_list attribute), none (assume every device can fence every machine)</longdesc> ++ <content type="string" default="dynamic-list"/> ++ </parameter> ++ <parameter name="pcmk_delay_max" unique="0"> ++ <shortdesc lang="en">Enable a random delay for stonith actions and specify the maximum of random delay.</shortdesc> ++ <longdesc lang="en">This prevents double fencing when using slow devices such as sbd. ++Use this to enable a random delay for stonith actions. ++The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay.</longdesc> ++ <content type="time" default="0s"/> ++ </parameter> ++ <parameter name="pcmk_delay_base" unique="0"> ++ <shortdesc lang="en">Enable a base delay for stonith actions and specify base delay value.</shortdesc> ++ <longdesc lang="en">This prevents double fencing when different delays are configured on the nodes. ++Use this to enable a static delay for stonith actions. ++The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay.</longdesc> ++ <content type="time" default="0s"/> ++ </parameter> ++ <parameter name="pcmk_action_limit" unique="0"> ++ <shortdesc lang="en">The maximum number of actions can be performed in parallel on this device</shortdesc> ++ <longdesc lang="en">Pengine property concurrent-fencing=true needs to be configured first. ++Then use this to specify the maximum number of actions can be performed in parallel on this device. -1 is unlimited.</longdesc> ++ <content type="integer" default="1"/> ++ </parameter> ++ <parameter name="pcmk_reboot_action" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate command to run instead of 'reboot'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard commands or may provide additional ones. ++Use this to specify an alternate, device-specific, command that implements the 'reboot' action.</longdesc> ++ <content type="string" default="reboot"/> ++ </parameter> ++ <parameter name="pcmk_reboot_timeout" unique="0"> ++ <shortdesc lang="en">Advanced use only: Specify an alternate timeout to use for reboot actions instead of stonith-timeout</shortdesc> ++ <longdesc lang="en">Some devices need much more/less time to complete than normal. ++Use this to specify an alternate, device-specific, timeout for 'reboot' actions.</longdesc> ++ <content type="time" default="60s"/> ++ </parameter> ++ <parameter name="pcmk_reboot_retries" unique="0"> ++ <shortdesc lang="en">Advanced use only: The maximum number of times to retry the 'reboot' command within the timeout period</shortdesc> ++ <longdesc lang="en">Some devices do not support multiple connections. Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining. Use this option to alter the number of times Pacemaker retries 'reboot' actions before giving up.</longdesc> ++ <content type="integer" default="2"/> ++ </parameter> ++ <parameter name="pcmk_off_action" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate command to run instead of 'off'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard commands or may provide additional ones. ++Use this to specify an alternate, device-specific, command that implements the 'off' action.</longdesc> ++ <content type="string" default="off"/> ++ </parameter> ++ <parameter name="pcmk_off_timeout" unique="0"> ++ <shortdesc lang="en">Advanced use only: Specify an alternate timeout to use for off actions instead of stonith-timeout</shortdesc> ++ <longdesc lang="en">Some devices need much more/less time to complete than normal. ++Use this to specify an alternate, device-specific, timeout for 'off' actions.</longdesc> ++ <content type="time" default="60s"/> ++ </parameter> ++ <parameter name="pcmk_off_retries" unique="0"> ++ <shortdesc lang="en">Advanced use only: The maximum number of times to retry the 'off' command within the timeout period</shortdesc> ++ <longdesc lang="en">Some devices do not support multiple connections. Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining. Use this option to alter the number of times Pacemaker retries 'off' actions before giving up.</longdesc> ++ <content type="integer" default="2"/> ++ </parameter> ++ <parameter name="pcmk_on_action" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate command to run instead of 'on'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard commands or may provide additional ones. ++Use this to specify an alternate, device-specific, command that implements the 'on' action.</longdesc> ++ <content type="string" default="on"/> ++ </parameter> ++ <parameter name="pcmk_on_timeout" unique="0"> ++ <shortdesc lang="en">Advanced use only: Specify an alternate timeout to use for on actions instead of stonith-timeout</shortdesc> ++ <longdesc lang="en">Some devices need much more/less time to complete than normal. ++Use this to specify an alternate, device-specific, timeout for 'on' actions.</longdesc> ++ <content type="time" default="60s"/> ++ </parameter> ++ <parameter name="pcmk_on_retries" unique="0"> ++ <shortdesc lang="en">Advanced use only: The maximum number of times to retry the 'on' command within the timeout period</shortdesc> ++ <longdesc lang="en">Some devices do not support multiple connections. Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining. Use this option to alter the number of times Pacemaker retries 'on' actions before giving up.</longdesc> ++ <content type="integer" default="2"/> ++ </parameter> ++ <parameter name="pcmk_list_action" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate command to run instead of 'list'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard commands or may provide additional ones. ++Use this to specify an alternate, device-specific, command that implements the 'list' action.</longdesc> ++ <content type="string" default="list"/> ++ </parameter> ++ <parameter name="pcmk_list_timeout" unique="0"> ++ <shortdesc lang="en">Advanced use only: Specify an alternate timeout to use for list actions instead of stonith-timeout</shortdesc> ++ <longdesc lang="en">Some devices need much more/less time to complete than normal. ++Use this to specify an alternate, device-specific, timeout for 'list' actions.</longdesc> ++ <content type="time" default="60s"/> ++ </parameter> ++ <parameter name="pcmk_list_retries" unique="0"> ++ <shortdesc lang="en">Advanced use only: The maximum number of times to retry the 'list' command within the timeout period</shortdesc> ++ <longdesc lang="en">Some devices do not support multiple connections. Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining. Use this option to alter the number of times Pacemaker retries 'list' actions before giving up.</longdesc> ++ <content type="integer" default="2"/> ++ </parameter> ++ <parameter name="pcmk_monitor_action" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate command to run instead of 'monitor'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard commands or may provide additional ones. ++Use this to specify an alternate, device-specific, command that implements the 'monitor' action.</longdesc> ++ <content type="string" default="monitor"/> ++ </parameter> ++ <parameter name="pcmk_monitor_timeout" unique="0"> ++ <shortdesc lang="en">Advanced use only: Specify an alternate timeout to use for monitor actions instead of stonith-timeout</shortdesc> ++ <longdesc lang="en">Some devices need much more/less time to complete than normal. ++Use this to specify an alternate, device-specific, timeout for 'monitor' actions.</longdesc> ++ <content type="time" default="60s"/> ++ </parameter> ++ <parameter name="pcmk_monitor_retries" unique="0"> ++ <shortdesc lang="en">Advanced use only: The maximum number of times to retry the 'monitor' command within the timeout period</shortdesc> ++ <longdesc lang="en">Some devices do not support multiple connections. Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining. Use this option to alter the number of times Pacemaker retries 'monitor' actions before giving up.</longdesc> ++ <content type="integer" default="2"/> ++ </parameter> ++ <parameter name="pcmk_status_action" unique="0"> ++ <shortdesc lang="en">Advanced use only: An alternate command to run instead of 'status'</shortdesc> ++ <longdesc lang="en">Some devices do not support the standard commands or may provide additional ones. ++Use this to specify an alternate, device-specific, command that implements the 'status' action.</longdesc> ++ <content type="string" default="status"/> ++ </parameter> ++ <parameter name="pcmk_status_timeout" unique="0"> ++ <shortdesc lang="en">Advanced use only: Specify an alternate timeout to use for status actions instead of stonith-timeout</shortdesc> ++ <longdesc lang="en">Some devices need much more/less time to complete than normal. ++Use this to specify an alternate, device-specific, timeout for 'status' actions.</longdesc> ++ <content type="time" default="60s"/> ++ </parameter> ++ <parameter name="pcmk_status_retries" unique="0"> ++ <shortdesc lang="en">Advanced use only: The maximum number of times to retry the 'status' command within the timeout period</shortdesc> ++ <longdesc lang="en">Some devices do not support multiple connections. Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining. Use this option to alter the number of times Pacemaker retries 'status' actions before giving up.</longdesc> ++ <content type="integer" default="2"/> ++ </parameter> ++ </parameters> ++</resource-agent> +diff --git a/pcs/test/tools/command_env/config_runner_pcmk.py b/pcs/test/tools/command_env/config_runner_pcmk.py +index 6499ef8..059f3eb 100644 +--- a/pcs/test/tools/command_env/config_runner_pcmk.py ++++ b/pcs/test/tools/command_env/config_runner_pcmk.py +@@ -70,6 +70,42 @@ class PcmkShortcuts(object): + instead=instead, + ) + ++ def load_stonithd_metadata( ++ self, ++ name="runner.pcmk.load_stonithd_metadata", ++ stdout=None, ++ stderr="", ++ returncode=0, ++ instead=None, ++ before=None, ++ ): ++ """ ++ Create a call for loading stonithd metadata - additional fence options ++ ++ string name -- the key of this call ++ string stdout -- stonithd stdout, default metadata if None ++ string stderr -- stonithd stderr ++ int returncode -- stonithd returncode ++ string instead -- the key of a call instead of which this new call is to ++ be placed ++ string before -- the key of a call before which this new call is to be ++ placed ++ """ ++ self.__calls.place( ++ name, ++ RunnerCall( ++ "/usr/libexec/pacemaker/stonithd metadata", ++ stdout=( ++ stdout if stdout is not None ++ else open(rc("stonithd_metadata.xml")).read() ++ ), ++ stderr=stderr, ++ returncode=returncode ++ ), ++ before=before, ++ instead=instead, ++ ) ++ + def resource_cleanup( + self, + name="runner.pcmk.cleanup", +-- +1.8.3.1 + diff --git a/SOURCES/bz1523378-01-warn-when-a-stonith-device-has-method-cycle-set.patch b/SOURCES/bz1523378-01-warn-when-a-stonith-device-has-method-cycle-set.patch new file mode 100644 index 0000000..0da5ae3 --- /dev/null +++ b/SOURCES/bz1523378-01-warn-when-a-stonith-device-has-method-cycle-set.patch @@ -0,0 +1,261 @@ +From b3b7bf416724f424a280c63f94121fb47e7397e6 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Mon, 11 Dec 2017 08:54:26 +0100 +Subject: [PATCH] warn when a stonith device has method=cycle set + +--- + pcs/status.py | 21 ++++++++++++++-- + pcs/test/test_status.py | 47 ++++++++++++++++++++++++----------- + pcsd/cluster_entity.rb | 22 +++++++++++++++++ + pcsd/test/cib1.xml | 2 ++ + pcsd/test/test_cluster_entity.rb | 53 ++++++++++++++++++++++++++++++++++++++++ + 5 files changed, 129 insertions(+), 16 deletions(-) + +diff --git a/pcs/status.py b/pcs/status.py +index ec10d61..8a517be 100644 +--- a/pcs/status.py ++++ b/pcs/status.py +@@ -125,6 +125,7 @@ def status_stonith_check(): + stonith_enabled = True + stonith_devices = [] + stonith_devices_id_action = [] ++ stonith_devices_id_method_cycle = [] + sbd_running = False + + cib = utils.get_cib_dom() +@@ -155,6 +156,14 @@ def status_stonith_check(): + stonith_devices_id_action.append( + resource.getAttribute("id") + ) ++ if ( ++ nvpair.getAttribute("name") == "method" ++ and ++ nvpair.getAttribute("value") == "cycle" ++ ): ++ stonith_devices_id_method_cycle.append( ++ resource.getAttribute("id") ++ ) + + if not utils.usefile: + # check if SBD daemon is running +@@ -171,14 +180,22 @@ def status_stonith_check(): + + if stonith_devices_id_action: + print( +- "WARNING: following stonith devices have the 'action' attribute" +- " set, it is recommended to set {0} instead: {1}".format( ++ "WARNING: following stonith devices have the 'action' option set, " ++ "it is recommended to set {0} instead: {1}".format( + ", ".join( + ["'{0}'".format(x) for x in _STONITH_ACTION_REPLACED_BY] + ), + ", ".join(sorted(stonith_devices_id_action)) + ) + ) ++ if stonith_devices_id_method_cycle: ++ print( ++ "WARNING: following stonith devices have the 'method' option set " ++ "to 'cycle' which is potentially dangerous, please consider using " ++ "'onoff': {0}".format( ++ ", ".join(sorted(stonith_devices_id_method_cycle)) ++ ) ++ ) + + # Parse crm_mon for status + def nodes_status(argv): +diff --git a/pcs/test/test_status.py b/pcs/test/test_status.py +index b412b91..1a4fb70 100644 +--- a/pcs/test/test_status.py ++++ b/pcs/test/test_status.py +@@ -21,45 +21,64 @@ class StonithWarningTest(TestCase, AssertPcsMixin): + shutil.copy(self.empty_cib, self.temp_cib) + self.pcs_runner = PcsRunner(self.temp_cib) + +- def fixture_stonith(self, action=False): ++ def fixture_stonith_action(self): + self.assert_pcs_success( +- "stonith create S fence_apc ipaddr=i login=l {0} --force".format( +- "action=reboot" if action else "" +- ), ++ "stonith create Sa fence_apc ipaddr=i login=l action=reboot --force", + "Warning: stonith option 'action' is deprecated and should not be" + " used, use pcmk_off_action, pcmk_reboot_action instead\n" +- if action +- else "" ++ ) ++ ++ def fixture_stonith_cycle(self): ++ self.assert_pcs_success( ++ "stonith create Sc fence_ipmilan method=cycle" + ) + + def fixture_resource(self): + self.assert_pcs_success( +- "resource create dummy ocf:pacemaker:Dummy action=reboot --force", +- "Warning: invalid resource option 'action', allowed options are: " +- "envfile, fail_start_on, fake, op_sleep, passwd, state," +- " trace_file, trace_ra\n" ++ "resource create dummy ocf:pacemaker:Dummy action=reboot " ++ "method=cycle --force" ++ , ++ "Warning: invalid resource options: 'action', 'method', allowed " ++ "options are: envfile, fail_start_on, fake, op_sleep, passwd, " ++ "state, trace_file, trace_ra\n" + ) + + def test_warning_stonith_action(self): +- self.fixture_stonith(action=True) ++ self.fixture_stonith_action() ++ self.fixture_resource() + self.assert_pcs_success( + "status", + stdout_start=dedent("""\ + Cluster name: test99 +- WARNING: following stonith devices have the 'action' attribute set, it is recommended to set 'pcmk_off_action', 'pcmk_reboot_action' instead: S ++ WARNING: following stonith devices have the 'action' option set, it is recommended to set 'pcmk_off_action', 'pcmk_reboot_action' instead: Sa + Stack: unknown + Current DC: NONE + """) + ) + +- def test_action_ignored_for_non_stonith_resources(self): +- self.fixture_stonith(action=False) ++ def test_warning_stonith_method_cycle(self): ++ self.fixture_stonith_cycle() + self.fixture_resource() ++ self.assert_pcs_success( ++ "status", ++ stdout_start=dedent("""\ ++ Cluster name: test99 ++ WARNING: following stonith devices have the 'method' option set to 'cycle' which is potentially dangerous, please consider using 'onoff': Sc ++ Stack: unknown ++ Current DC: NONE ++ """) ++ ) + ++ def test_stonith_warnings(self): ++ self.fixture_stonith_action() ++ self.fixture_stonith_cycle() ++ self.fixture_resource() + self.assert_pcs_success( + "status", + stdout_start=dedent("""\ + Cluster name: test99 ++ WARNING: following stonith devices have the 'action' option set, it is recommended to set 'pcmk_off_action', 'pcmk_reboot_action' instead: Sa ++ WARNING: following stonith devices have the 'method' option set to 'cycle' which is potentially dangerous, please consider using 'onoff': Sc + Stack: unknown + Current DC: NONE + """) +diff --git a/pcsd/cluster_entity.rb b/pcsd/cluster_entity.rb +index 21092c5..3675719 100644 +--- a/pcsd/cluster_entity.rb ++++ b/pcsd/cluster_entity.rb +@@ -516,6 +516,28 @@ module ClusterEntity + @utilization << ClusterEntity::NvPair.from_dom(e) + } + @stonith = @_class == 'stonith' ++ if @stonith ++ @instance_attr.each{ |attr| ++ if attr.name == 'action' ++ @warning_list << { ++ :message => ( ++ 'This fence-device has the "action" option set, it is ' + ++ 'recommended to set "pcmk_off_action", "pcmk_reboot_action" ' + ++ 'instead' ++ ) ++ } ++ end ++ if attr.name == 'method' and attr.value == 'cycle' ++ @warning_list << { ++ :message => ( ++ 'This fence-device has the "method" option set to "cycle" ' + ++ 'which is potentially dangerous, please consider using ' + ++ '"onoff"' ++ ) ++ } ++ end ++ } ++ end + if @id and rsc_status + @crm_status = rsc_status[@id] || [] + end +diff --git a/pcsd/test/cib1.xml b/pcsd/test/cib1.xml +index f603f24..03749ab 100644 +--- a/pcsd/test/cib1.xml ++++ b/pcsd/test/cib1.xml +@@ -28,6 +28,8 @@ + <primitive class="stonith" id="node2-stonith" type="fence_xvm"> + <instance_attributes id="node2-stonith-instance_attributes"> + <nvpair id="node2-stonith-instance_attributes-domain" name="domain" value="node2"/> ++ <nvpair id="node2-stonith-instance_attributes-action" name="action" value="monitor"/> ++ <nvpair id="node2-stonith-instance_attributes-method" name="method" value="cycle"/> + </instance_attributes> + <operations> + <op id="node2-stonith-monitor-interval-60s" interval="60s" name="monitor"/> +diff --git a/pcsd/test/test_cluster_entity.rb b/pcsd/test/test_cluster_entity.rb +index 2b67e19..60719ef 100644 +--- a/pcsd/test/test_cluster_entity.rb ++++ b/pcsd/test/test_cluster_entity.rb +@@ -719,6 +719,59 @@ class TestPrimitive < Test::Unit::TestCase + assert(obj.operations.empty?) + end + ++ def test_init_stonith_with_warnings ++ obj = ClusterEntity::Primitive.new( ++ @cib.elements["//primitive[@id='node2-stonith']"] ++ ) ++ assert_nil(obj.parent) ++ assert_nil(obj.get_master) ++ assert_nil(obj.get_clone) ++ assert_nil(obj.get_group) ++ assert(obj.meta_attr.empty?) ++ assert_equal('node2-stonith', obj.id) ++ assert(obj.error_list.empty?) ++ assert_equal( ++ obj.warning_list, ++ [ ++ { ++ :message => ( ++ 'This fence-device has the "action" option set, it is ' + ++ 'recommended to set "pcmk_off_action", "pcmk_reboot_action" instead' ++ ) ++ }, ++ { ++ :message => ( ++ 'This fence-device has the "method" option set to "cycle" which ' + ++ 'is potentially dangerous, please consider using "onoff"' ++ ) ++ } ++ ] ++ ) ++ assert_equal('stonith:fence_xvm', obj.agentname) ++ assert_equal('stonith', obj._class) ++ assert_nil(obj.provider) ++ assert_equal('fence_xvm', obj.type) ++ assert(obj.stonith) ++ instance_attr = ClusterEntity::NvSet.new << ClusterEntity::NvPair.new( ++ 'node2-stonith-instance_attributes-domain', ++ 'domain', ++ 'node2' ++ ) ++ instance_attr << ClusterEntity::NvPair.new( ++ 'node2-stonith-instance_attributes-action', ++ 'action', ++ 'monitor' ++ ) ++ instance_attr << ClusterEntity::NvPair.new( ++ 'node2-stonith-instance_attributes-method', ++ 'method', ++ 'cycle' ++ ) ++ assert_equal_NvSet(instance_attr, obj.instance_attr) ++ assert(obj.crm_status.empty?) ++ assert(obj.operations.empty?) ++ end ++ + def test_init_stonith_with_crm + obj = ClusterEntity::Primitive.new( + @cib.elements["//primitive[@id='node1-stonith']"], +-- +1.8.3.1 + diff --git a/SOURCES/bz1527530-01-fix-a-crash-in-pcs-booth-sync.patch b/SOURCES/bz1527530-01-fix-a-crash-in-pcs-booth-sync.patch new file mode 100644 index 0000000..0bd88e7 --- /dev/null +++ b/SOURCES/bz1527530-01-fix-a-crash-in-pcs-booth-sync.patch @@ -0,0 +1,39 @@ +From 772b48493eb19f2d9e25579740edd6216c9f8ed0 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Thu, 4 Jan 2018 13:22:03 +0100 +Subject: [PATCH] fix a crash in 'pcs booth sync' + +--- + pcs/lib/communication/booth.py | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/pcs/lib/communication/booth.py b/pcs/lib/communication/booth.py +index 240738a..a3aa918 100644 +--- a/pcs/lib/communication/booth.py ++++ b/pcs/lib/communication/booth.py +@@ -10,6 +10,7 @@ import os + + from pcs.common.node_communicator import RequestData + from pcs.lib import reports ++from pcs.lib.booth import reports as reports_booth + from pcs.lib.communication.tools import ( + AllAtOnceStrategyMixin, + AllSameDataMixin, +@@ -51,12 +52,12 @@ class BoothSendConfig( + ) + + def _get_success_report(self, node_label): +- return reports.booth_config_accepted_by_node( ++ return reports_booth.booth_config_accepted_by_node( + node_label, [self._booth_name] + ) + + def before(self): +- self._report(reports.booth_config_distribution_started()) ++ self._report(reports_booth.booth_config_distribution_started()) + + + class ProcessJsonDataMixin(object): +-- +1.8.3.1 + diff --git a/SOURCES/change-cman-to-rhel6-in-messages.patch b/SOURCES/change-cman-to-rhel6-in-messages.patch index 3315132..96a6019 100644 --- a/SOURCES/change-cman-to-rhel6-in-messages.patch +++ b/SOURCES/change-cman-to-rhel6-in-messages.patch @@ -1,4 +1,4 @@ -From 25b32eed71eb0d22330867f962c34ec5f515e3c9 Mon Sep 17 00:00:00 2001 +From 0f462859f1a923093f211ea9813b8147f00be431 Mon Sep 17 00:00:00 2001 From: Ivan Devat <idevat@redhat.com> Date: Mon, 23 May 2016 17:00:13 +0200 Subject: [PATCH] change cman to rhel6 in messages @@ -17,10 +17,10 @@ Subject: [PATCH] change cman to rhel6 in messages 10 files changed, 47 insertions(+), 47 deletions(-) diff --git a/pcs/cli/common/console_report.py b/pcs/cli/common/console_report.py -index 793ff8d..57b2d64 100644 +index 406532dd..75fcf562 100644 --- a/pcs/cli/common/console_report.py +++ b/pcs/cli/common/console_report.py -@@ -583,7 +583,7 @@ CODE_TO_MESSAGE_BUILDER_MAP = { +@@ -642,7 +642,7 @@ CODE_TO_MESSAGE_BUILDER_MAP = { , codes.CMAN_UNSUPPORTED_COMMAND: @@ -29,7 +29,7 @@ index 793ff8d..57b2d64 100644 , codes.ID_ALREADY_EXISTS: lambda info: -@@ -813,7 +813,7 @@ CODE_TO_MESSAGE_BUILDER_MAP = { +@@ -889,7 +889,7 @@ CODE_TO_MESSAGE_BUILDER_MAP = { , codes.IGNORED_CMAN_UNSUPPORTED_OPTION: lambda info: @@ -38,7 +38,7 @@ index 793ff8d..57b2d64 100644 .format(**info) , -@@ -822,12 +822,12 @@ CODE_TO_MESSAGE_BUILDER_MAP = { +@@ -898,12 +898,12 @@ CODE_TO_MESSAGE_BUILDER_MAP = { , codes.CMAN_UDPU_RESTART_REQUIRED: ( @@ -54,10 +54,10 @@ index 793ff8d..57b2d64 100644 ), diff --git a/pcs/cluster.py b/pcs/cluster.py -index 16eb0c5..7720771 100644 +index a3cc74c6..ecafa756 100644 --- a/pcs/cluster.py +++ b/pcs/cluster.py -@@ -1862,7 +1862,7 @@ def node_add(lib_env, node0, node1, modifiers): +@@ -1938,7 +1938,7 @@ def node_add(lib_env, node0, node1, modifiers): else: utils.err("Unable to update any nodes") if utils.is_cman_with_udpu_transport(): @@ -66,7 +66,7 @@ index 16eb0c5..7720771 100644 + "cluster restart is required to apply node addition") if wait: print() -@@ -1938,7 +1938,7 @@ def node_remove(lib_env, node0, modifiers): +@@ -2014,7 +2014,7 @@ def node_remove(lib_env, node0, modifiers): output, retval = utils.reloadCorosync() output, retval = utils.run(["crm_node", "--force", "-R", node0]) if utils.is_cman_with_udpu_transport(): @@ -75,7 +75,7 @@ index 16eb0c5..7720771 100644 + "cluster restart is required to apply node removal") def cluster_localnode(argv): -@@ -2106,7 +2106,7 @@ def cluster_uidgid(argv, silent_list = False): +@@ -2182,7 +2182,7 @@ def cluster_uidgid(argv, silent_list = False): def cluster_get_corosync_conf(argv): if utils.is_rhel6(): @@ -85,10 +85,10 @@ index 16eb0c5..7720771 100644 if len(argv) > 1: usage.cluster() diff --git a/pcs/config.py b/pcs/config.py -index 5526eb5..389122b 100644 +index ac32b669..450403c0 100644 --- a/pcs/config.py +++ b/pcs/config.py -@@ -614,7 +614,7 @@ def config_checkpoint_restore(argv): +@@ -613,7 +613,7 @@ def config_checkpoint_restore(argv): def config_import_cman(argv): if no_clufter: @@ -98,10 +98,10 @@ index 5526eb5..389122b 100644 cluster_conf = settings.cluster_conf_file dry_run_output = None diff --git a/pcs/pcs.8 b/pcs/pcs.8 -index 27298a7..4acddb8 100644 +index 15454c7f..02eab591 100644 --- a/pcs/pcs.8 +++ b/pcs/pcs.8 -@@ -206,13 +206,13 @@ auth [node] [...] [\fB\-u\fR username] [\fB\-p\fR password] [\fB\-\-force\fR] [\ +@@ -209,13 +209,13 @@ auth [<node>[:<port>]] [...] [\fB\-u\fR <username>] [\fB\-p\fR <password>] [\fB\ Authenticate pcs to pcsd on nodes specified, or on all nodes configured in the local cluster if no nodes are specified (authorization tokens are stored in ~/.pcs/tokens or /var/lib/pcsd/tokens for root). By default all nodes are also authenticated to each other, using \fB\-\-local\fR only authenticates the local node (and does not authenticate the remote nodes with each other). Using \fB\-\-force\fR forces re\-authentication to occur. .TP setup [\fB\-\-start\fR [\fB\-\-wait\fR[=<n>]]] [\fB\-\-local\fR] [\fB\-\-enable\fR] \fB\-\-name\fR <cluster name> <node1[,node1\-altaddr]> [<node2[,node2\-altaddr]>] [...] [\fB\-\-transport\fR udpu|udp] [\fB\-\-rrpmode\fR active|passive] [\fB\-\-addr0\fR <addr/net> [[[\fB\-\-mcast0\fR <address>] [\fB\-\-mcastport0\fR <port>] [\fB\-\-ttl0\fR <ttl>]] | [\fB\-\-broadcast0\fR]] [\fB\-\-addr1\fR <addr/net> [[[\fB\-\-mcast1\fR <address>] [\fB\-\-mcastport1\fR <port>] [\fB\-\-ttl1\fR <ttl>]] | [\fB\-\-broadcast1\fR]]]] [\fB\-\-wait_for_all\fR=<0|1>] [\fB\-\-auto_tie_breaker\fR=<0|1>] [\fB\-\-last_man_standing\fR=<0|1> [\fB\-\-last_man_standing_window\fR=<time in ms>]] [\fB\-\-ipv6\fR] [\fB\-\-token\fR <timeout>] [\fB\-\-token_coefficient\fR <timeout>] [\fB\-\-join\fR <timeout>] [\fB\-\-consensus\fR <timeout>] [\fB\-\-miss_count_const\fR <count>] [\fB\-\-fail_recv_const\fR <failures>] [\fB\-\-encryption\fR 0|1] @@ -118,7 +118,7 @@ index 27298a7..4acddb8 100644 \fB\-\-join\fR <timeout> sets time in milliseconds to wait for join messages (default 50 ms) -@@ -729,10 +729,10 @@ checkpoint restore <checkpoint_number> +@@ -738,10 +738,10 @@ checkpoint restore <checkpoint_number> Restore cluster configuration to specified checkpoint. .TP import\-cman output=<filename> [input=<filename>] [\fB\-\-interactive\fR] [output\-format=corosync.conf|cluster.conf] [dist=<dist>] @@ -132,10 +132,10 @@ index 27298a7..4acddb8 100644 export pcs\-commands|pcs\-commands\-verbose [output=<filename>] [dist=<dist>] Creates a list of pcs commands which upon execution recreates the current cluster running on this node. Commands will be saved to 'output' file or written to stdout if 'output' is not specified. Use pcs\-commands to get a simple list of commands, whereas pcs\-commands\-verbose creates a list including comments and debug messages. Optionally specify output version by setting 'dist' option e. g. rhel,6.8 or redhat,7.3 or debian,7 or ubuntu,trusty. You can get the list of supported dist values by running the "clufter \fB\-\-list\-dists\fR" command. If 'dist' is not specified, it defaults to this node's version. diff --git a/pcs/quorum.py b/pcs/quorum.py -index 937b057..8e8431c 100644 +index 10c27602..7140efdf 100644 --- a/pcs/quorum.py +++ b/pcs/quorum.py -@@ -196,7 +196,7 @@ def quorum_unblock_cmd(argv): +@@ -245,7 +245,7 @@ def quorum_unblock_cmd(argv): sys.exit(1) if utils.is_rhel6(): @@ -145,10 +145,10 @@ index 937b057..8e8431c 100644 output, retval = utils.run( ["corosync-cmapctl", "-g", "runtime.votequorum.wait_for_all_status"] diff --git a/pcs/test/test_cluster.py b/pcs/test/test_cluster.py -index 5c7a4a1..a3836be 100644 +index 01998f0f..194f544e 100644 --- a/pcs/test/test_cluster.py +++ b/pcs/test/test_cluster.py -@@ -1228,7 +1228,7 @@ logging { +@@ -1335,7 +1335,7 @@ logging { .format(cluster_conf_tmp) ) ac(output, """\ @@ -157,7 +157,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 0) with open(cluster_conf_tmp) as f: -@@ -1320,7 +1320,7 @@ logging { +@@ -1427,7 +1427,7 @@ logging { .format(cluster_conf_tmp) ) ac(output, """\ @@ -166,7 +166,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 0) with open(cluster_conf_tmp) as f: -@@ -1914,7 +1914,7 @@ logging { +@@ -2021,7 +2021,7 @@ logging { ) ac(output, """\ Error: 'blah' is not a valid RRP mode value, use active, passive, use --force to override @@ -175,7 +175,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 1) -@@ -2193,7 +2193,7 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in +@@ -2300,7 +2300,7 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in ) ac(output, """\ Error: using a RRP mode of 'active' is not supported or tested, use --force to override @@ -184,7 +184,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 1) -@@ -2203,7 +2203,7 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in +@@ -2310,7 +2310,7 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in .format(cluster_conf_tmp) ) ac(output, """\ @@ -193,7 +193,7 @@ index 5c7a4a1..a3836be 100644 Warning: using a RRP mode of 'active' is not supported or tested """) self.assertEqual(returnVal, 0) -@@ -2272,7 +2272,7 @@ Error: if one node is configured for RRP, all nodes must be configured for RRP +@@ -2379,7 +2379,7 @@ Error: if one node is configured for RRP, all nodes must be configured for RRP ) ac(output, """\ Error: --addr0 and --addr1 can only be used with --transport=udp @@ -202,7 +202,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 1) -@@ -2362,7 +2362,7 @@ Warning: Using udpu transport on a CMAN cluster, cluster restart is required aft +@@ -2469,7 +2469,7 @@ Warning: Using udpu transport on a CMAN cluster, cluster restart is required aft .format(cluster_conf_tmp) ) ac(output, """\ @@ -211,7 +211,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 0) with open(cluster_conf_tmp) as f: -@@ -2377,7 +2377,7 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in +@@ -2484,7 +2484,7 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in .format(cluster_conf_tmp) ) ac(output, """\ @@ -220,7 +220,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 0) with open(cluster_conf_tmp) as f: -@@ -2395,10 +2395,10 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in +@@ -2502,10 +2502,10 @@ Warning: Enabling broadcast for all rings as CMAN does not support broadcast in .format(cluster_conf_tmp) ) ac(output, """\ @@ -235,7 +235,7 @@ index 5c7a4a1..a3836be 100644 """) self.assertEqual(returnVal, 0) with open(cluster_conf_tmp) as f: -@@ -2494,7 +2494,7 @@ logging { +@@ -2601,7 +2601,7 @@ logging { .format(cluster_conf_tmp) ) ac(output, """\ @@ -245,10 +245,10 @@ index 5c7a4a1..a3836be 100644 self.assertEqual(returnVal, 0) with open(cluster_conf_tmp) as f: diff --git a/pcs/usage.py b/pcs/usage.py -index 9cbf7de..4a9f2f2 100644 +index 090a150f..3a83f777 100644 --- a/pcs/usage.py +++ b/pcs/usage.py -@@ -585,23 +585,23 @@ Commands: +@@ -603,23 +603,23 @@ Commands: --wait will wait up to 'n' seconds for the nodes to start, --enable will enable corosync and pacemaker on node startup, --transport allows specification of corosync transport (default: udpu; @@ -276,7 +276,7 @@ index 9cbf7de..4a9f2f2 100644 --join <timeout> sets time in milliseconds to wait for join messages (default 50 ms) --consensus <timeout> sets time in milliseconds to wait for consensus -@@ -1325,9 +1325,9 @@ Commands: +@@ -1358,9 +1358,9 @@ Commands: import-cman output=<filename> [input=<filename>] [--interactive] [output-format=corosync.conf|cluster.conf] [dist=<dist>] @@ -289,7 +289,7 @@ index 9cbf7de..4a9f2f2 100644 command can be used. If --interactive is specified you will be prompted to solve incompatibilities manually. If no input is specified /etc/cluster/cluster.conf will be used. You can force to create output -@@ -1341,9 +1341,9 @@ Commands: +@@ -1374,9 +1374,9 @@ Commands: import-cman output=<filename> [input=<filename>] [--interactive] output-format=pcs-commands|pcs-commands-verbose [dist=<dist>] @@ -303,7 +303,7 @@ index 9cbf7de..4a9f2f2 100644 export pcs-commands|pcs-commands-verbose [output=<filename>] [dist=<dist>] Creates a list of pcs commands which upon execution recreates diff --git a/pcsd/views/_dialogs.erb b/pcsd/views/_dialogs.erb -index d18ac71..21be443 100644 +index d18ac71f..21be443f 100644 --- a/pcsd/views/_dialogs.erb +++ b/pcsd/views/_dialogs.erb @@ -40,7 +40,7 @@ @@ -316,10 +316,10 @@ index d18ac71..21be443 100644 </div> diff --git a/pcsd/views/manage.erb b/pcsd/views/manage.erb -index 2b12aaa..4129de5 100644 +index 0f87263e..b4e79225 100644 --- a/pcsd/views/manage.erb +++ b/pcsd/views/manage.erb -@@ -213,9 +213,9 @@ +@@ -214,9 +214,9 @@ <tr><td align=center style="color: red" colspan=2"><span id="at_least_one_node_error_msg" style="display:none;">At least one valid node must be entered.</span></td></tr> <tr><td align=center style="color: red" colspan=2"><span id="bad_cluster_name_error_msg" style="display:none;">You may not leave the cluster name field blank</span></td></tr> <tr><td align=center style="color: red" colspan=2"><span id="addr0_addr1_mismatch_error_msg" style="display:none;">Ring 1 addresses do not match to Ring 0 addresses</span></td></tr> @@ -331,7 +331,7 @@ index 2b12aaa..4129de5 100644 </table> <span onclick='$(".advanced_open").toggle();$("#advanced_cluster_create_options").toggle();'><span class="advanced_open rightarrow sprites"></span><span class="advanced_open downarrow sprites" style="display:none;"></span>Advanced Options:</span> <div id="advanced_cluster_create_options" style="display:none;"> -@@ -248,7 +248,7 @@ remaining 3 nodes will be fenced. +@@ -249,7 +249,7 @@ remaining 3 nodes will be fenced. It is very useful when combined with Last Man Standing. @@ -340,7 +340,7 @@ index 2b12aaa..4129de5 100644 <% auto_tie_desc = "\ Enables Auto Tie Breaker (ATB) feature (default: off). -@@ -261,7 +261,7 @@ partition, or the set of nodes that are still in contact with the \ +@@ -262,7 +262,7 @@ partition, or the set of nodes that are still in contact with the \ node that has the lowest nodeid will remain quorate. The other nodes \ will be inquorate. @@ -349,7 +349,7 @@ index 2b12aaa..4129de5 100644 <% last_man_desc = "\ Enables Last Man Standing (LMS) feature (default: off). -@@ -282,18 +282,18 @@ Using the above 8 node cluster example, with LMS enabled the cluster \ +@@ -283,18 +283,18 @@ Using the above 8 node cluster example, with LMS enabled the cluster \ can retain quorum and continue operating by losing, in a cascade \ fashion, up to 6 nodes with only 2 remaining active. @@ -371,7 +371,7 @@ index 2b12aaa..4129de5 100644 <% token_timeout = "\ Sets time in milliseconds until a token loss is declared after not receiving \ a token (default: 1000 ms)" %> -@@ -302,7 +302,7 @@ Sets time in milliseconds used for clusters with at least 3 nodes \ +@@ -303,7 +303,7 @@ Sets time in milliseconds used for clusters with at least 3 nodes \ as a coefficient for real token timeout calculation \ (token + (number_of_nodes - 2) * token_coefficient) (default: 650 ms) @@ -381,10 +381,10 @@ index 2b12aaa..4129de5 100644 Sets time in milliseconds to wait for join messages (default: 50 ms)" %> <% consensus_timeout = "\ diff --git a/pcsd/views/nodes.erb b/pcsd/views/nodes.erb -index 3c3aeed..4b03f06 100644 +index 5849b75d..4c7278a6 100644 --- a/pcsd/views/nodes.erb +++ b/pcsd/views/nodes.erb -@@ -359,7 +359,7 @@ +@@ -363,7 +363,7 @@ {{/if}} {{#if Pcs.is_cman_with_udpu_transport}} <tr> @@ -394,5 +394,5 @@ index 3c3aeed..4b03f06 100644 {{/if}} </table> -- -1.8.3.1 +2.13.6 diff --git a/SOURCES/fix-skip-offline-in-pcs-quorum-device-remove.patch b/SOURCES/fix-skip-offline-in-pcs-quorum-device-remove.patch new file mode 100644 index 0000000..95dc4fe --- /dev/null +++ b/SOURCES/fix-skip-offline-in-pcs-quorum-device-remove.patch @@ -0,0 +1,25 @@ +From 51485a618da52af2c6bb114e70a52a51b68e6a09 Mon Sep 17 00:00:00 2001 +From: Tomas Jelinek <tojeline@redhat.com> +Date: Tue, 21 Nov 2017 17:05:35 +0100 +Subject: [PATCH] fix --skip-offline in 'pcs quorum device remove' + +--- + pcs/lib/commands/quorum.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pcs/lib/commands/quorum.py b/pcs/lib/commands/quorum.py +index 3e9db0e..6b869ed 100644 +--- a/pcs/lib/commands/quorum.py ++++ b/pcs/lib/commands/quorum.py +@@ -325,7 +325,7 @@ def _remove_device_model_net(lib_env, cluster_nodes, skip_offline_nodes): + reporter.process( + reports.qdevice_certificate_removal_started() + ) +- com_cmd = qdevice_net_com.ClientDestroy(reporter) ++ com_cmd = qdevice_net_com.ClientDestroy(reporter, skip_offline_nodes) + com_cmd.set_targets( + lib_env.get_node_target_factory().get_target_list(cluster_nodes) + ) +-- +1.8.3.1 + diff --git a/SOURCES/rhel7.patch b/SOURCES/rhel7.patch index 706a433..f5074da 100644 --- a/SOURCES/rhel7.patch +++ b/SOURCES/rhel7.patch @@ -1,4 +1,4 @@ -From cff71cf607ae7d0b42f61fbc0159e2bccd559ee8 Mon Sep 17 00:00:00 2001 +From 39c0da04f3f9087ab9115e08e45cc7c8b7d12a95 Mon Sep 17 00:00:00 2001 From: Ivan Devat <idevat@redhat.com> Date: Tue, 24 May 2016 07:26:15 +0200 Subject: [PATCH] adapt working with gems to rhel 7 @@ -10,7 +10,7 @@ Subject: [PATCH] adapt working with gems to rhel 7 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/pcsd/Gemfile b/pcsd/Gemfile -index e01b31c..02134fd 100644 +index e01b31ca..02134fd4 100644 --- a/pcsd/Gemfile +++ b/pcsd/Gemfile @@ -1,9 +1,5 @@ @@ -34,7 +34,7 @@ index e01b31c..02134fd 100644 gem 'open4' gem 'orderedhash' diff --git a/pcsd/Gemfile.lock b/pcsd/Gemfile.lock -index a3fab96..36853bd 100644 +index dcb0d053..fd5a8dcc 100644 --- a/pcsd/Gemfile.lock +++ b/pcsd/Gemfile.lock @@ -1,11 +1,9 @@ @@ -42,11 +42,11 @@ index a3fab96..36853bd 100644 remote: https://rubygems.org/ - remote: https://tojeline.fedorapeople.org/rubygems/ specs: - backports (3.6.8) + backports (3.9.1) ethon (0.10.1) - ffi (1.9.17) -- json (2.0.3) - multi_json (1.12.1) + ffi (1.9.18) +- json (2.1.0) + multi_json (1.12.2) open4 (1.3.4) orderedhash (0.0.6) @@ -35,7 +33,6 @@ DEPENDENCIES @@ -58,11 +58,11 @@ index a3fab96..36853bd 100644 open4 orderedhash diff --git a/pcsd/Makefile b/pcsd/Makefile -index 2ecd4de..10719e1 100644 +index d452ac06..f8452c5c 100644 --- a/pcsd/Makefile +++ b/pcsd/Makefile @@ -1,7 +1,7 @@ - FFI_VERSION="1.9.17" + FFI_VERSION="1.9.18" FFI_C_DIR=vendor/bundle/ruby/gems/ffi-${FFI_VERSION}/ext/ffi_c -build_gems: get_gems @@ -80,5 +80,5 @@ index 2ecd4de..10719e1 100644 clean: rm -rfv vendor/ -- -1.8.3.1 +2.13.6 diff --git a/SOURCES/show-only-warning-when-crm_mon-xml-is-invalid.patch b/SOURCES/show-only-warning-when-crm_mon-xml-is-invalid.patch index f31bd23..80ef12c 100644 --- a/SOURCES/show-only-warning-when-crm_mon-xml-is-invalid.patch +++ b/SOURCES/show-only-warning-when-crm_mon-xml-is-invalid.patch @@ -1,4 +1,4 @@ -From 07ea4bec19958563e82bd8e863f64ba4f36850c0 Mon Sep 17 00:00:00 2001 +From d290c211b3e4f6b56173a02fb0ef6f93b0192f17 Mon Sep 17 00:00:00 2001 From: Ivan Devat <idevat@redhat.com> Date: Mon, 29 Aug 2016 18:16:41 +0200 Subject: [PATCH] show only warning when crm_mon xml is invalid @@ -9,10 +9,10 @@ Subject: [PATCH] show only warning when crm_mon xml is invalid 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/pcs/lib/pacemaker/state.py b/pcs/lib/pacemaker/state.py -index be3e7ad..cab72c8 100644 +index 6b87d8b9..6b71612d 100644 --- a/pcs/lib/pacemaker/state.py +++ b/pcs/lib/pacemaker/state.py -@@ -145,10 +145,17 @@ class _NodeSection(_Element): +@@ -144,10 +144,17 @@ class _NodeSection(_Element): def get_cluster_state_dom(xml): try: dom = xml_fromstring(xml) @@ -34,11 +34,11 @@ index be3e7ad..cab72c8 100644 class ClusterState(_Element): diff --git a/pcs/lib/pacemaker/test/test_state.py b/pcs/lib/pacemaker/test/test_state.py -index 5de9426..70bd886 100644 +index 13628f44..5ea20d98 100644 --- a/pcs/lib/pacemaker/test/test_state.py +++ b/pcs/lib/pacemaker/test/test_state.py -@@ -5,6 +5,14 @@ from __future__ import ( - unicode_literals, +@@ -4,6 +4,14 @@ from __future__ import ( + print_function, ) +import sys @@ -52,7 +52,7 @@ index 5de9426..70bd886 100644 from pcs.test.tools.pcs_unittest import TestCase, mock from lxml import etree -@@ -87,16 +95,24 @@ class ClusterStatusTest(TestBase): +@@ -86,16 +94,24 @@ class ClusterStatusTest(TestBase): ) def test_refuse_invalid_document(self): @@ -82,5 +82,5 @@ index 5de9426..70bd886 100644 class WorkWithClusterStatusNodesTest(TestBase): def fixture_node_string(self, **kwargs): -- -1.8.3.1 +2.13.6 diff --git a/SPECS/pcs.spec b/SPECS/pcs.spec index 56e97d7..f455b64 100644 --- a/SPECS/pcs.spec +++ b/SPECS/pcs.spec @@ -1,6 +1,6 @@ Name: pcs -Version: 0.9.158 -Release: 6%{?dist}.1 +Version: 0.9.162 +Release: 5%{?dist} License: GPLv2 URL: https://github.com/ClusterLabs/pcs Group: System Environment/Base @@ -8,51 +8,55 @@ Summary: Pacemaker Configuration System #building only for architectures with pacemaker and corosync available ExclusiveArch: i686 x86_64 s390x ppc64le +%global pcs_snmp_pkg_name pcs-snmp +%global pyagentx_version 0.4.pcs.1 +%global bundled_lib_dir pcs/bundled +%global pyagentx_dir %{bundled_lib_dir}/pyagentx + #part after last slash is recognized as filename in look-aside repository #desired name is achived by trick with hash anchor Source0: %{url}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz Source1: HAM-logo.png Source2: pcsd-bundle-config-1 -Source11: https://rubygems.org/downloads/backports-3.6.8.gem -Source12: https://rubygems.org/downloads/multi_json-1.12.1.gem +Source11: https://rubygems.org/downloads/backports-3.9.1.gem +Source12: https://rubygems.org/downloads/multi_json-1.12.2.gem Source13: https://rubygems.org/downloads/open4-1.3.4.gem Source14: https://rubygems.org/downloads/orderedhash-0.0.6.gem Source15: https://rubygems.org/downloads/rack-protection-1.5.3.gem -Source16: https://rubygems.org/downloads/rack-test-0.6.3.gem +Source16: https://rubygems.org/downloads/rack-test-0.7.0.gem Source17: https://rubygems.org/downloads/rack-1.6.4.gem Source18: https://rubygems.org/downloads/rpam-ruby19-1.2.1.gem Source19: https://rubygems.org/downloads/sinatra-contrib-1.4.7.gem Source20: https://rubygems.org/downloads/sinatra-1.4.8.gem -Source21: https://rubygems.org/downloads/tilt-2.0.6.gem +Source21: https://rubygems.org/downloads/tilt-2.0.8.gem Source22: https://rubygems.org/downloads/ethon-0.10.1.gem -Source23: https://rubygems.org/downloads/ffi-1.9.17.gem +Source23: https://rubygems.org/downloads/ffi-1.9.18.gem Source31: https://github.com/testing-cabal/mock/archive/1.0.1.tar.gz#/mock-1.0.1.tar.gz -Source99: favicon.ico - -Patch0: bz1176018-01-remote-guest-nodes-crashes-fixed.patch -Patch1: bz1373614-01-return-1-when-pcsd-is-unable-to-bind.patch -Patch2: bz1386114-01-fix-a-crash-in-adding-a-remote-node.patch -Patch3: bz1284404-01-web-UI-fix-creating-a-new-cluster.patch -Patch4: bz1165821-01-pcs-CLI-GUI-should-be-capable-of.patch -Patch5: bz1176018-02-pcs-pcsd-should-be-able-to-config.patch -Patch6: bz1386114-02-deal-with-f-corosync_conf-if-create-remote-res.patch -Patch7: bz1176018-03-don-t-call-remove-guest-node-when-f-is-used.patch -Patch8: bz1165821-02-pcs-CLI-GUI-should-be-capable-of.patch -Patch9: bz1447910-01-bundle-resources-are-missing-meta-attributes.patch -Patch10: bz1433016-02-make-container-type-mandatory-in-bundle-create.patch -Patch11: bz1284404-02-web-ui-fix-timeout-when-cluster-setup-takes-long.patch -Patch12: bz1458153-01-give-back-orig.-master-behav.-resource-create.patch -Patch13: bz1459503-01-OSP-workarounds-not-compatible-wi.patch -Patch14: bz1507399-01-Make-resource-update-idempotent-with-remote-node.patch - +Source41: https://github.com/ondrejmular/pyagentx/archive/v%{pyagentx_version}.tar.gz#/pyagentx-%{pyagentx_version}.tar.gz +Patch1: fix-skip-offline-in-pcs-quorum-device-remove.patch +Patch2: bz1367808-01-fix-formating-of-assertion-error-in-snmp.patch +Patch3: bz1367808-02-change-snmp-agent-logfile-path.patch +Patch4: bz1421702-01-gui-allow-forcing-resource-stonith-create-update.patch +Patch5: bz1522813-01-fix-a-crash-when-wait-is-used-in-stonith-create.patch +Patch6: bz1523378-01-warn-when-a-stonith-device-has-method-cycle-set.patch +Patch7: bz1464781-01-fix-exit-code-when-adding-a-remote-or-guest-node.patch +Patch8: bz1527530-01-fix-a-crash-in-pcs-booth-sync.patch +Patch9: bz1415197-01-fix-pcs-cluster-auth.patch +Patch10: bz1415197-02-fix-pcs-cluster-auth.patch +Patch98: bz1458153-01-give-back-orig.-master-behav.-resource-create.patch +Patch99: bz1459503-01-OSP-workarounds-not-compatible-wi.patch Patch100: rhel7.patch +#next patch is needed for situation when the rhel6 cluster is controlled from +#rhel7 gui Patch101: change-cman-to-rhel6-in-messages.patch Patch102: show-only-warning-when-crm_mon-xml-is-invalid.patch # git for patches BuildRequires: git +#printf from coreutils is used in makefile +BuildRequires: coreutils # python for pcs BuildRequires: python BuildRequires: python-devel @@ -130,6 +134,25 @@ Provides: bundled(rubygem-ffi) = 1.9.17 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 +License: GPLv2, 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) + %define PCS_PREFIX /usr %prep %autosetup -p1 -S git @@ -147,7 +170,6 @@ UpdateTimestamps() { touch -r $PatchFile $f done } -UpdateTimestamps -p1 %{PATCH0} UpdateTimestamps -p1 %{PATCH1} UpdateTimestamps -p1 %{PATCH2} UpdateTimestamps -p1 %{PATCH3} @@ -158,10 +180,8 @@ UpdateTimestamps -p1 %{PATCH7} UpdateTimestamps -p1 %{PATCH8} UpdateTimestamps -p1 %{PATCH9} UpdateTimestamps -p1 %{PATCH10} -UpdateTimestamps -p1 %{PATCH11} -UpdateTimestamps -p1 %{PATCH12} -UpdateTimestamps -p1 %{PATCH13} -UpdateTimestamps -p1 %{PATCH14} +UpdateTimestamps -p1 %{PATCH98} +UpdateTimestamps -p1 %{PATCH99} UpdateTimestamps -p1 %{PATCH100} UpdateTimestamps -p1 %{PATCH101} UpdateTimestamps -p1 %{PATCH102} @@ -188,7 +208,12 @@ cp -f %SOURCE22 pcsd/vendor/cache cp -f %SOURCE23 pcsd/vendor/cache #ruby gems copied -cp -f %SOURCE99 pcsd/public +mkdir -p %{bundled_lib_dir} +tar -xzf %SOURCE41 -C %{bundled_lib_dir} +mv %{bundled_lib_dir}/pyagentx-%{pyagentx_version} %{pyagentx_dir} +cp %{pyagentx_dir}/LICENSE.txt pyagentx_LICENSE.txt +cp %{pyagentx_dir}/CONTRIBUTORS.txt pyagentx_CONTRIBUTORS.txt +cp %{pyagentx_dir}/README.md pyagentx_README.md %build @@ -199,14 +224,19 @@ make install \ DESTDIR=$RPM_BUILD_ROOT \ PYTHON_SITELIB=%{python_sitelib} \ PREFIX=%{PCS_PREFIX} \ - BASH_COMPLETION_DIR=$RPM_BUILD_ROOT/usr/share/bash-completion/completions + BASH_COMPLETION_DIR=$RPM_BUILD_ROOT/usr/share/bash-completion/completions \ + PYAGENTX_DIR=`readlink -f %{pyagentx_dir}` \ + SYSTEMCTL_OVERRIDE=true + +#SYSTEMCTL_OVERRIDE enforces systemd to be used and skip autodetection make install_pcsd \ DESTDIR=$RPM_BUILD_ROOT \ PYTHON_SITELIB=%{python_sitelib} \ hdrdir="%{_includedir}" \ rubyhdrdir="%{_includedir}" \ includedir="%{_includedir}" \ - PREFIX=%{PCS_PREFIX} + PREFIX=%{PCS_PREFIX} \ + SYSTEMCTL_OVERRIDE=true #after the ruby gem compilation we do not need ruby gems in the cache rm -r -v $RPM_BUILD_ROOT%{PCS_PREFIX}/lib/pcsd/vendor/cache @@ -249,6 +279,40 @@ run_all_tests(){ #Red Hat Enterprise Linux Server release 7.4 Beta (Maipo) ## crm_resource --show-metadata systemd:nonexistent@some:thingxxx #error: crm_abort: systemd_unit_exec: Triggered fatal assert at systemd.c:676 : systemd_init() + # + #************************************************************** + # + #pcs.lib.commands.test.test_stonith.Create.test_minimal_success + #-------------------------------------------------------------- + #This tests fails only with live build on i686. Live build was tried twice. + #Architectures x86_64 and s390x succeeded, ppc64le was skipped with live + #build. Test succeeded with scratch build on i686. + # + #====================================================================== + #ERROR: test_minimal_success (pcs.lib.commands.test.test_stonith.Create) + #---------------------------------------------------------------------- + #Traceback (most recent call last): + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/commands/test/test_stonith.py", line 71, in test_minimal_success + # instance_attributes={"must-set": "value"} + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/commands/stonith.py", line 77, in create + # resource_type="stonith" + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/cib/resource/primitive.py", line 78, in create + # allow_invalid=allow_invalid_instance_attributes, + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 871, in validate_parameters + # update=update + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 486, in validate_parameters + # parameters + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 518, in validate_parameters_values + # agent_params = self.get_parameters() + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 858, in get_parameters + # self._get_stonithd_metadata().get_parameters() + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 437, in get_parameters + # params_element = self._get_metadata().find("parameters") + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 596, in _get_metadata + # self._metadata = self._parse_metadata(self._load_metadata()) + # File "/builddir/build/BUILDROOT/pcs-0.9.162-3.el7.i386/usr/lib/python2.7/site-packages/pcs/lib/resource_agent.py", line 668, in _load_metadata + # [settings.stonithd_binary, "metadata"] + #ValueError: need more than 0 values to unpack export PYTHONPATH="${PYTHONPATH}:${sitelib}" easy_install -d ${sitelib} %SOURCE31 @@ -256,6 +320,10 @@ run_all_tests(){ pcs.test.test_cluster.ClusterTest.testUIDGID \ pcs.test.test_stonith.StonithTest.test_stonith_create_provides_unfencing \ pcs.test.cib_resource.test_create.Success.test_base_create_with_agent_name_including_systemd_instance \ + pcs.lib.commands.test.test_stonith.Create.test_minimal_success \ + pcs.lib.commands.test.test_stonith.Create.test_minimal_wait_ok_run_ok \ + pcs.lib.commands.test.test_stonith.CreateInGroup.test_minimal_success \ + pcs.lib.commands.test.test_stonith.CreateInGroup.test_minimal_wait_ok_run_ok \ test_result_python=$? @@ -290,12 +358,21 @@ run_all_tests %post %systemd_post pcsd.service +%post -n %{pcs_snmp_pkg_name} +%systemd_post pcs_snmp_agent.service + %preun %systemd_preun pcsd.service +%preun -n %{pcs_snmp_pkg_name} +%systemd_preun pcs_snmp_agent.service + %postun %systemd_postun_with_restart pcsd.service +%postun -n %{pcs_snmp_pkg_name} +%systemd_postun_with_restart pcs_snmp_agent.service + %files %{python_sitelib}/pcs %{python_sitelib}/pcs-%{version}-py2.*.egg-info @@ -306,8 +383,8 @@ run_all_tests /usr/share/bash-completion/completions/pcs /var/lib/pcsd /etc/pam.d/pcsd -/etc/logrotate.d/pcsd %dir /var/log/pcsd +%config(noreplace) /etc/logrotate.d/pcsd %config(noreplace) /etc/sysconfig/pcsd %ghost %config(noreplace) /var/lib/pcsd/cfgsync_ctl %ghost %config(noreplace) /var/lib/pcsd/pcsd.cookiesecret @@ -326,16 +403,51 @@ run_all_tests %exclude %{python_sitelib}/pcs/pcs %doc COPYING -%doc README %doc CHANGELOG.md +%files -n %{pcs_snmp_pkg_name} +/usr/lib/pcs/pcs_snmp_agent +/usr/lib/pcs/bundled/packages/pyagentx* +/usr/lib/systemd/system/pcs_snmp_agent.service +/usr/share/snmp/mibs/PCMK-PCS*-MIB.txt +%{_mandir}/man8/pcs_snmp_agent.* +%config(noreplace) /etc/sysconfig/pcs_snmp_agent +%dir /var/log/pcs +%doc COPYING +%doc CHANGELOG.md +%doc pyagentx_LICENSE.txt +%doc pyagentx_CONTRIBUTORS.txt +%doc pyagentx_README.md + %changelog -* Thu Nov 30 2017 Johnny Hughes <johnny@centos.org> 0.9.158-6.el7_4.1 -- Manually Debreand PCS +* Mon Feb 05 2018 Ondrej Mular <omular@redhat.com> - 0.9.162-5 +- Fixed `pcs cluster auth` in a cluster when not authenticated and using a non-default port +- Fixed `pcs cluster auth` in a cluster when previously authenticated using a non-default port and reauthenticating using an implicit default port +- Resolves: rhbz#1415197 + +* Fri Jan 05 2018 Ivan Devat <idevat@redhat.com> - 0.9.162-3 +- Pcs now properly exits with code 1 when an error occurs in pcs cluster node add-remote and pcs cluster node add-guest commands +- Fixed a crash in the pcs booth sync command +- Resolves: rhbz#1464781 rhbz#1527530 + +* Mon Dec 11 2017 Ivan Devat <idevat@redhat.com> - 0.9.162-2 +- Changed snmp agent logfile path +- It is now possible to set the `action` option of stonith devices in GUI by using force +- Do not crash when `--wait` is used in `pcs stonith create` +- A warning is displayed in `pcs status` and a stonith device detail in web UI when a stonith device has its `method` option set to `cycle` +- Resolves: rhbz#1367808 rhbz#1421702 rhbz#1522813 rhbz#1523378 + +* Wed Nov 15 2017 Ondrej Mular <omular@redhat.com> - 0.9.162-1 +- Rebased to latest upstream sources (see CHANGELOG.md) +- Resolves: rhbz#1389943 rhbz#1389209 rhbz#1506220 rhbz#1508351 rhbz#1415197 rhbz#1506864 rhbz#1367808 rhbz#1499749 -* Mon Oct 30 2017 Ivan Devat <idevat@redhat.com> - 0.9.158-6.el7_4.1 -- `resurce update` no longer exits with an error when the `remote-node` meta attribute is set to the same value that it already has -- Resolves: rhbz#1507399 +* Thu Nov 02 2017 Ivan Devat <idevat@redhat.com> - 0.9.161-1 +- Rebased to latest upstream sources (see CHANGELOG.md) +- Resolves: rhbz#1499749 rhbz#1415197 rhbz#1501274 rhbz#1502715 rhbz#1230919 rhbz#1503110 + +* Wed Oct 11 2017 Ivan Devat <idevat@redhat.com> - 0.9.160-1 +- Rebased to latest upstream sources (see CHANGELOG.md) +- Resolves: rhbz#1499749 rhbz#1443647 rhbz#1432283 rhbz#1421702 rhbz#1443418 rhbz#1464781 rhbz#1435697 rhbz#1441673 rhbz#1420437 rhbz#1388783 rhbz#1463327 rhbz#1418199 rhbz#1341582 rhbz#1489682 rhbz#1491631 rhbz#1213946 * Thu Jun 15 2017 Ivan Devat <idevat@redhat.com> - 0.9.158-6 - It is now possible to disable, enable, unmanage and manage bundle resources and set their meta attributes