Blob Blame History Raw
From 62b970d5e9edbdd68dc006193b0e606fb7ae7cdd Mon Sep 17 00:00:00 2001
From: Tomas Jelinek <tojeline@redhat.com>
Date: Thu, 2 Jul 2020 15:18:29 +0200
Subject: [PATCH 1/3] upgrade CIB schema for on-fail=demote

---
 pcs/lib/cib/resource/operations.py            |   9 +-
 pcs/lib/commands/remote_node.py               |  73 +++--
 pcs/lib/commands/resource.py                  | 269 ++++++++++--------
 pcs/resource.py                               |  51 +++-
 pcs_test/resources/cib-empty-3.3.xml          |   2 +-
 pcs_test/resources/cib-empty-3.4.xml          |   2 +-
 .../tier0/lib/cib/test_resource_operations.py |   9 +-
 .../remote_node/test_node_add_remote.py       |  54 +++-
 .../commands/resource/test_resource_create.py | 237 ++++++++++++++-
 pcs_test/tier1/cib_resource/test_create.py    |   2 +-
 pcs_test/tier1/legacy/test_resource.py        |  23 ++
 pcs_test/tools/misc.py                        |   6 +
 pcsd/capabilities.xml                         |  42 +++
 13 files changed, 594 insertions(+), 185 deletions(-)

diff --git a/pcs/lib/cib/resource/operations.py b/pcs/lib/cib/resource/operations.py
index 131e0a49..79d00685 100644
--- a/pcs/lib/cib/resource/operations.py
+++ b/pcs/lib/cib/resource/operations.py
@@ -39,13 +39,14 @@ ATTRIBUTES = [
 ]
 
 ON_FAIL_VALUES = [
-    "ignore",
     "block",
-    "stop",
-    "restart",
-    "standby",
+    "demote",
     "fence",
+    "ignore",
+    "restart",
     "restart-container",
+    "standby",
+    "stop",
 ]
 
 BOOLEAN_VALUES = [
diff --git a/pcs/lib/commands/remote_node.py b/pcs/lib/commands/remote_node.py
index 6a2656a5..575e8044 100644
--- a/pcs/lib/commands/remote_node.py
+++ b/pcs/lib/commands/remote_node.py
@@ -1,3 +1,10 @@
+from typing import (
+    Iterable,
+    Mapping,
+    Optional,
+    Union,
+)
+
 from pcs import settings
 from pcs.common import reports
 from pcs.common.file import RawFileError
@@ -13,6 +20,10 @@ from pcs.lib.cib.tools import (
     ElementSearcher,
     get_resources,
 )
+
+# TODO lib.commands should never import each other. This is to be removed when
+# the 'resource create' commands are overhauled.
+from pcs.lib.commands.resource import get_required_cib_version_for_primitive
 from pcs.lib.communication.nodes import (
     DistributeFiles,
     GetHostInfo,
@@ -24,6 +35,7 @@ from pcs.lib.communication.tools import (
     run as run_com,
     run_and_raise,
 )
+from pcs.lib.corosync.config_facade import ConfigFacade as CorosyncConfigFacade
 from pcs.lib.env import LibraryEnvironment
 from pcs.lib.errors import LibraryError
 from pcs.lib.file.instance import FileInstance
@@ -33,6 +45,9 @@ from pcs.lib.pacemaker import state
 from pcs.lib.pacemaker.live import remove_node
 
 
+WaitType = Union[None, bool, int]
+
+
 def _reports_skip_new_node(new_node_name, reason_type):
     assert reason_type in {"unreachable", "not_live_cib"}
     return [
@@ -220,19 +235,19 @@ def _ensure_resource_running(env: LibraryEnvironment, resource_id):
 
 
 def node_add_remote(
-    env,
-    node_name,
-    node_addr,
-    operations,
-    meta_attributes,
-    instance_attributes,
-    skip_offline_nodes=False,
-    allow_incomplete_distribution=False,
-    allow_pacemaker_remote_service_fail=False,
-    allow_invalid_operation=False,
-    allow_invalid_instance_attributes=False,
-    use_default_operations=True,
-    wait=False,
+    env: LibraryEnvironment,
+    node_name: str,
+    node_addr: Optional[str],
+    operations: Iterable[Mapping[str, str]],
+    meta_attributes: Mapping[str, str],
+    instance_attributes: Mapping[str, str],
+    skip_offline_nodes: bool = False,
+    allow_incomplete_distribution: bool = False,
+    allow_pacemaker_remote_service_fail: bool = False,
+    allow_invalid_operation: bool = False,
+    allow_invalid_instance_attributes: bool = False,
+    use_default_operations: bool = True,
+    wait: WaitType = False,
 ):
     # pylint: disable=too-many-arguments
     # pylint: disable=too-many-branches
@@ -241,34 +256,36 @@ def node_add_remote(
     """
     create an ocf:pacemaker:remote resource and use it as a remote node
 
-    LibraryEnvironment env -- provides all for communication with externals
-    string node_name -- the name of the new node
-    mixed node_addr -- the address of the new node or None for default
-    list of dict operations -- attributes for each entered operation
-    dict meta_attributes -- attributes for primitive/meta_attributes
-    dict instance_attributes -- attributes for primitive/instance_attributes
-    bool skip_offline_nodes -- if True, ignore when some nodes are offline
-    bool allow_incomplete_distribution -- if True, allow this command to
+    env -- provides all for communication with externals
+    node_name -- the name of the new node
+    node_addr -- the address of the new node or None for default
+    operations -- attributes for each entered operation
+    meta_attributes -- attributes for primitive/meta_attributes
+    instance_attributes -- attributes for primitive/instance_attributes
+    skip_offline_nodes -- if True, ignore when some nodes are offline
+    allow_incomplete_distribution -- if True, allow this command to
         finish successfully even if file distribution did not succeed
-    bool allow_pacemaker_remote_service_fail -- if True, allow this command to
+    allow_pacemaker_remote_service_fail -- if True, allow this command to
         finish successfully even if starting/enabling pacemaker_remote did not
         succeed
-    bool allow_invalid_operation -- if True, allow to use operations that
+    allow_invalid_operation -- if True, allow to use operations that
         are not listed in a resource agent metadata
-    bool allow_invalid_instance_attributes -- if True, allow to use instance
+    allow_invalid_instance_attributes -- if True, allow to use instance
         attributes that are not listed in a resource agent metadata and allow to
         omit required instance_attributes
-    bool use_default_operations -- if True, add operations specified in
+    use_default_operations -- if True, add operations specified in
         a resource agent metadata to the resource
-    mixed wait -- a flag for controlling waiting for pacemaker idle mechanism
+    wait -- a flag for controlling waiting for pacemaker idle mechanism
     """
     env.ensure_wait_satisfiable(wait)
 
     report_processor = env.report_processor
-    cib = env.get_cib()
+    cib = env.get_cib(
+        minimal_version=get_required_cib_version_for_primitive(operations)
+    )
     id_provider = IdProvider(cib)
     if env.is_cib_live:
-        corosync_conf = env.get_corosync_conf()
+        corosync_conf: Optional[CorosyncConfigFacade] = env.get_corosync_conf()
     else:
         corosync_conf = None
         report_processor.report(
diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py
index 75826c9d..db4b7bb3 100644
--- a/pcs/lib/commands/resource.py
+++ b/pcs/lib/commands/resource.py
@@ -61,6 +61,9 @@ from pcs.lib.resource_agent import (
 from pcs.lib.validate import ValueTimeInterval
 
 
+WaitType = Union[None, bool, int]
+
+
 @contextmanager
 def resource_environment(
     env,
@@ -262,44 +265,43 @@ def _get_required_cib_version_for_container(
 
 
 def create(
-    env,
-    resource_id,
-    resource_agent_name,
-    operation_list,
-    meta_attributes,
-    instance_attributes,
-    allow_absent_agent=False,
-    allow_invalid_operation=False,
-    allow_invalid_instance_attributes=False,
-    use_default_operations=True,
-    ensure_disabled=False,
-    wait=False,
-    allow_not_suitable_command=False,
+    env: LibraryEnvironment,
+    resource_id: str,
+    resource_agent_name: str,
+    operation_list: Iterable[Mapping[str, str]],
+    meta_attributes: Mapping[str, str],
+    instance_attributes: Mapping[str, str],
+    allow_absent_agent: bool = False,
+    allow_invalid_operation: bool = False,
+    allow_invalid_instance_attributes: bool = False,
+    use_default_operations: bool = True,
+    ensure_disabled: bool = False,
+    wait: WaitType = False,
+    allow_not_suitable_command: bool = False,
 ):
     # pylint: disable=too-many-arguments, too-many-locals
     """
-    Create resource in a cib.
+    Create a primitive resource in a cib.
 
-    LibraryEnvironment env provides all for communication with externals
-    string resource_id is identifier of resource
-    string resource_agent_name contains name for the identification of agent
-    list of dict operation_list contains attributes for each entered operation
-    dict meta_attributes contains attributes for primitive/meta_attributes
-    dict instance_attributes contains attributes for
-        primitive/instance_attributes
-    bool allow_absent_agent is a flag for allowing agent that is not installed
+    env -- provides all for communication with externals
+    resource_id -- is identifier of resource
+    resource_agent_name -- contains name for the identification of agent
+    operation_list -- contains attributes for each entered operation
+    meta_attributes -- contains attributes for primitive/meta_attributes
+    instance_attributes -- contains attributes for primitive/instance_attributes
+    allow_absent_agent -- is a flag for allowing agent that is not installed
         in a system
-    bool allow_invalid_operation is a flag for allowing to use operations that
+    allow_invalid_operation -- is a flag for allowing to use operations that
         are not listed in a resource agent metadata
-    bool allow_invalid_instance_attributes is a flag for allowing to use
+    allow_invalid_instance_attributes -- is a flag for allowing to use
         instance attributes that are not listed in a resource agent metadata
         or for allowing to not use the instance_attributes that are required in
         resource agent metadata
-    bool use_default_operations is a flag for stopping stopping of adding
+    use_default_operations -- is a flag for stopping stopping of adding
         default cib operations (specified in a resource agent)
-    bool ensure_disabled is flag that keeps resource in target-role "Stopped"
-    mixed wait is flag for controlling waiting for pacemaker idle mechanism
-    bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
+    ensure_disabled -- is flag that keeps resource in target-role "Stopped"
+    wait -- is flag for controlling waiting for pacemaker idle mechanism
+    allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
     """
     resource_agent = get_agent(
         env.report_processor,
@@ -315,6 +317,9 @@ def create(
             ensure_disabled
             or resource.common.are_meta_disabled(meta_attributes)
         ),
+        required_cib_version=get_required_cib_version_for_primitive(
+            operation_list
+        ),
     ) as resources_section:
         id_provider = IdProvider(resources_section)
         _check_special_cases(
@@ -345,46 +350,45 @@ def create(
 
 
 def create_as_clone(
-    env,
-    resource_id,
-    resource_agent_name,
-    operation_list,
-    meta_attributes,
-    instance_attributes,
-    clone_meta_options,
-    allow_absent_agent=False,
-    allow_invalid_operation=False,
-    allow_invalid_instance_attributes=False,
-    use_default_operations=True,
-    ensure_disabled=False,
-    wait=False,
-    allow_not_suitable_command=False,
+    env: LibraryEnvironment,
+    resource_id: str,
+    resource_agent_name: str,
+    operation_list: Iterable[Mapping[str, str]],
+    meta_attributes: Mapping[str, str],
+    instance_attributes: Mapping[str, str],
+    clone_meta_options: Mapping[str, str],
+    allow_absent_agent: bool = False,
+    allow_invalid_operation: bool = False,
+    allow_invalid_instance_attributes: bool = False,
+    use_default_operations: bool = True,
+    ensure_disabled: bool = False,
+    wait: WaitType = False,
+    allow_not_suitable_command: bool = False,
 ):
     # pylint: disable=too-many-arguments, too-many-locals
     """
-    Create resource in a clone
+    Create a primitive resource in a clone
 
-    LibraryEnvironment env provides all for communication with externals
-    string resource_id is identifier of resource
-    string resource_agent_name contains name for the identification of agent
-    list of dict operation_list contains attributes for each entered operation
-    dict meta_attributes contains attributes for primitive/meta_attributes
-    dict instance_attributes contains attributes for
-        primitive/instance_attributes
-    dict clone_meta_options contains attributes for clone/meta_attributes
-    bool allow_absent_agent is a flag for allowing agent that is not installed
+    env -- provides all for communication with externals
+    resource_id -- is identifier of resource
+    resource_agent_name -- contains name for the identification of agent
+    operation_list -- contains attributes for each entered operation
+    meta_attributes -- contains attributes for primitive/meta_attributes
+    instance_attributes -- contains attributes for primitive/instance_attributes
+    clone_meta_options -- contains attributes for clone/meta_attributes
+    allow_absent_agent -- is a flag for allowing agent that is not installed
         in a system
-    bool allow_invalid_operation is a flag for allowing to use operations that
+    allow_invalid_operation -- is a flag for allowing to use operations that
         are not listed in a resource agent metadata
-    bool allow_invalid_instance_attributes is a flag for allowing to use
+    allow_invalid_instance_attributes -- is a flag for allowing to use
         instance attributes that are not listed in a resource agent metadata
         or for allowing to not use the instance_attributes that are required in
         resource agent metadata
-    bool use_default_operations is a flag for stopping stopping of adding
+    use_default_operations -- is a flag for stopping stopping of adding
         default cib operations (specified in a resource agent)
-    bool ensure_disabled is flag that keeps resource in target-role "Stopped"
-    mixed wait is flag for controlling waiting for pacemaker idle mechanism
-    bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
+    ensure_disabled -- is flag that keeps resource in target-role "Stopped"
+    wait -- is flag for controlling waiting for pacemaker idle mechanism
+    allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
     """
     resource_agent = get_agent(
         env.report_processor,
@@ -401,6 +405,9 @@ def create_as_clone(
             or resource.common.are_meta_disabled(meta_attributes)
             or resource.common.is_clone_deactivated_by_meta(clone_meta_options)
         ),
+        required_cib_version=get_required_cib_version_for_primitive(
+            operation_list
+        ),
     ) as resources_section:
         id_provider = IdProvider(resources_section)
         _check_special_cases(
@@ -437,49 +444,50 @@ def create_as_clone(
 
 
 def create_in_group(
-    env,
-    resource_id,
-    resource_agent_name,
-    group_id,
-    operation_list,
-    meta_attributes,
-    instance_attributes,
-    allow_absent_agent=False,
-    allow_invalid_operation=False,
-    allow_invalid_instance_attributes=False,
-    use_default_operations=True,
-    ensure_disabled=False,
-    adjacent_resource_id=None,
-    put_after_adjacent=False,
-    wait=False,
-    allow_not_suitable_command=False,
+    env: LibraryEnvironment,
+    resource_id: str,
+    resource_agent_name: str,
+    group_id: str,
+    operation_list: Iterable[Mapping[str, str]],
+    meta_attributes: Mapping[str, str],
+    instance_attributes: Mapping[str, str],
+    allow_absent_agent: bool = False,
+    allow_invalid_operation: bool = False,
+    allow_invalid_instance_attributes: bool = False,
+    use_default_operations: bool = True,
+    ensure_disabled: bool = False,
+    adjacent_resource_id: Optional[str] = None,
+    put_after_adjacent: bool = False,
+    wait: WaitType = False,
+    allow_not_suitable_command: bool = False,
 ):
     # pylint: disable=too-many-arguments, too-many-locals
     """
     Create resource in a cib and put it into defined group
 
-    LibraryEnvironment env provides all for communication with externals
-    string resource_id is identifier of resource
-    string resource_agent_name contains name for the identification of agent
-    string group_id is identificator for group to put primitive resource inside
-    list of dict operation_list contains attributes for each entered operation
-    dict meta_attributes contains attributes for primitive/meta_attributes
-    bool allow_absent_agent is a flag for allowing agent that is not installed
+    env -- provides all for communication with externals
+    resource_id -- is identifier of resource
+    resource_agent_name -- contains name for the identification of agent
+    group_id -- is identificator for group to put primitive resource inside
+    operation_list -- contains attributes for each entered operation
+    meta_attributes -- contains attributes for primitive/meta_attributes
+    instance_attributes -- contains attributes for primitive/instance_attributes
+    allow_absent_agent -- is a flag for allowing agent that is not installed
         in a system
-    bool allow_invalid_operation is a flag for allowing to use operations that
+    allow_invalid_operation -- is a flag for allowing to use operations that
         are not listed in a resource agent metadata
-    bool allow_invalid_instance_attributes is a flag for allowing to use
+    allow_invalid_instance_attributes -- is a flag for allowing to use
         instance attributes that are not listed in a resource agent metadata
         or for allowing to not use the instance_attributes that are required in
         resource agent metadata
-    bool use_default_operations is a flag for stopping stopping of adding
+    use_default_operations -- is a flag for stopping stopping of adding
         default cib operations (specified in a resource agent)
-    bool ensure_disabled is flag that keeps resource in target-role "Stopped"
-    string adjacent_resource_id identify neighbor of a newly created resource
-    bool put_after_adjacent is flag to put a newly create resource befor/after
+    ensure_disabled -- is flag that keeps resource in target-role "Stopped"
+    adjacent_resource_id -- identify neighbor of a newly created resource
+    put_after_adjacent -- is flag to put a newly create resource befor/after
         adjacent resource
-    mixed wait is flag for controlling waiting for pacemaker idle mechanism
-    bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
+    wait -- is flag for controlling waiting for pacemaker idle mechanism
+    allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
     """
     resource_agent = get_agent(
         env.report_processor,
@@ -495,6 +503,9 @@ def create_in_group(
             ensure_disabled
             or resource.common.are_meta_disabled(meta_attributes)
         ),
+        required_cib_version=get_required_cib_version_for_primitive(
+            operation_list
+        ),
     ) as resources_section:
         id_provider = IdProvider(resources_section)
         _check_special_cases(
@@ -532,48 +543,48 @@ def create_in_group(
 
 
 def create_into_bundle(
-    env,
-    resource_id,
-    resource_agent_name,
-    operation_list,
-    meta_attributes,
-    instance_attributes,
-    bundle_id,
-    allow_absent_agent=False,
-    allow_invalid_operation=False,
-    allow_invalid_instance_attributes=False,
-    use_default_operations=True,
-    ensure_disabled=False,
-    wait=False,
-    allow_not_suitable_command=False,
-    allow_not_accessible_resource=False,
+    env: LibraryEnvironment,
+    resource_id: str,
+    resource_agent_name: str,
+    operation_list: Iterable[Mapping[str, str]],
+    meta_attributes: Mapping[str, str],
+    instance_attributes: Mapping[str, str],
+    bundle_id: str,
+    allow_absent_agent: bool = False,
+    allow_invalid_operation: bool = False,
+    allow_invalid_instance_attributes: bool = False,
+    use_default_operations: bool = True,
+    ensure_disabled: bool = False,
+    wait: WaitType = False,
+    allow_not_suitable_command: bool = False,
+    allow_not_accessible_resource: bool = False,
 ):
     # pylint: disable=too-many-arguments, too-many-locals
     """
     Create a new resource in a cib and put it into an existing bundle
 
-    LibraryEnvironment env provides all for communication with externals
-    string resource_id is identifier of resource
-    string resource_agent_name contains name for the identification of agent
-    list of dict operation_list contains attributes for each entered operation
-    dict meta_attributes contains attributes for primitive/meta_attributes
-    dict instance_attributes contains attributes for
+    env -- provides all for communication with externals
+    resource_id -- is identifier of resource
+    resource_agent_name -- contains name for the identification of agent
+    operation_list -- contains attributes for each entered operation
+    meta_attributes -- contains attributes for primitive/meta_attributes
+    instance_attributes -- contains attributes for
         primitive/instance_attributes
-    string bundle_id is id of an existing bundle to put the created resource in
-    bool allow_absent_agent is a flag for allowing agent that is not installed
+    bundle_id -- is id of an existing bundle to put the created resource in
+    allow_absent_agent -- is a flag for allowing agent that is not installed
         in a system
-    bool allow_invalid_operation is a flag for allowing to use operations that
+    allow_invalid_operation -- is a flag for allowing to use operations that
         are not listed in a resource agent metadata
-    bool allow_invalid_instance_attributes is a flag for allowing to use
+    allow_invalid_instance_attributes -- is a flag for allowing to use
         instance attributes that are not listed in a resource agent metadata
         or for allowing to not use the instance_attributes that are required in
         resource agent metadata
-    bool use_default_operations is a flag for stopping stopping of adding
+    use_default_operations -- is a flag for stopping stopping of adding
         default cib operations (specified in a resource agent)
-    bool ensure_disabled is flag that keeps resource in target-role "Stopped"
-    mixed wait is flag for controlling waiting for pacemaker idle mechanism
-    bool allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
-    bool allow_not_accessible_resource -- flag for
+    ensure_disabled -- is flag that keeps resource in target-role "Stopped"
+    wait -- is flag for controlling waiting for pacemaker idle mechanism
+    allow_not_suitable_command -- flag for FORCE_NOT_SUITABLE_COMMAND
+    allow_not_accessible_resource -- flag for
         FORCE_RESOURCE_IN_BUNDLE_NOT_ACCESSIBLE
     """
     resource_agent = get_agent(
@@ -582,6 +593,11 @@ def create_into_bundle(
         resource_agent_name,
         allow_absent_agent,
     )
+    required_cib_version = get_required_cib_version_for_primitive(
+        operation_list
+    )
+    if not required_cib_version:
+        required_cib_version = Version(2, 8, 0)
     with resource_environment(
         env,
         wait,
@@ -590,7 +606,7 @@ def create_into_bundle(
             ensure_disabled
             or resource.common.are_meta_disabled(meta_attributes)
         ),
-        required_cib_version=Version(2, 8, 0),
+        required_cib_version=required_cib_version,
     ) as resources_section:
         id_provider = IdProvider(resources_section)
         _check_special_cases(
@@ -1070,9 +1086,7 @@ def disable_simulate(
 
 
 def enable(
-    env: LibraryEnvironment,
-    resource_or_tag_ids: Iterable[str],
-    wait: Optional[Union[bool, int]],
+    env: LibraryEnvironment, resource_or_tag_ids: Iterable[str], wait: WaitType,
 ):
     """
     Allow specified resources to be started by the cluster
@@ -1689,3 +1703,12 @@ def _find_resources_expand_tags_or_raise(
     return resource.common.expand_tags_to_resources(
         resources_section, resource_or_tag_el_list,
     )
+
+
+def get_required_cib_version_for_primitive(
+    op_list: Iterable[Mapping[str, str]]
+) -> Optional[Version]:
+    for op in op_list:
+        if op.get("on-fail", "") == "demote":
+            return Version(3, 4, 0)
+    return None
diff --git a/pcs/resource.py b/pcs/resource.py
index e835fc99..9a3bd0ee 100644
--- a/pcs/resource.py
+++ b/pcs/resource.py
@@ -355,6 +355,21 @@ def resource_op_add_cmd(lib, argv, modifiers):
     if not argv:
         raise CmdLineInputError()
     res_id = argv.pop(0)
+
+    # Check if we need to upgrade cib schema.
+    # To do that, argv must be parsed, which is duplication of parsing in
+    # resource_operation_add. But we need to upgrade the cib first before
+    # calling that function. Hopefully, this will be fixed in the new pcs
+    # architecture.
+
+    # argv[0] is an operation name
+    op_properties = utils.convert_args_to_tuples(argv[1:])
+    for key, value in op_properties:
+        if key == "on-fail" and value == "demote":
+            utils.checkAndUpgradeCIB(3, 4, 0)
+            break
+
+    # add the requested operation
     utils.replace_cib_configuration(
         resource_operation_add(utils.get_cib_dom(), res_id, argv)
     )
@@ -895,8 +910,6 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True):
     if len(args) < 2:
         raise CmdLineInputError()
     res_id = args.pop(0)
-    cib_xml = utils.get_cib()
-    dom = utils.get_cib_dom(cib_xml=cib_xml)
 
     # Extract operation arguments
     ra_values, op_values, meta_values = parse_resource_options(args)
@@ -907,6 +920,28 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True):
         wait_timeout = utils.validate_wait_get_timeout()
         wait = True
 
+    # Check if we need to upgrade cib schema.
+    # To do that, argv must be parsed, which is duplication of parsing below.
+    # But we need to upgrade the cib first before calling that function.
+    # Hopefully, this will be fixed in the new pcs architecture.
+
+    cib_upgraded = False
+    for op_argv in op_values:
+        if cib_upgraded:
+            break
+        if len(op_argv) < 2:
+            continue
+        # argv[0] is an operation name
+        op_vars = utils.convert_args_to_tuples(op_argv[1:])
+        for k, v in op_vars:
+            if k == "on-fail" and v == "demote":
+                utils.checkAndUpgradeCIB(3, 4, 0)
+                cib_upgraded = True
+                break
+
+    cib_xml = utils.get_cib()
+    dom = utils.get_cib_dom(cib_xml=cib_xml)
+
     resource = utils.dom_get_resource(dom, res_id)
     if not resource:
         clone = utils.dom_get_clone(dom, res_id)
@@ -994,21 +1029,21 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True):
     else:
         operations = operations[0]
 
-    for element in op_values:
-        if not element:
+    for op_argv in op_values:
+        if not op_argv:
             continue
 
-        op_name = element[0]
+        op_name = op_argv[0]
         if op_name.find("=") != -1:
             utils.err(
                 "%s does not appear to be a valid operation action" % op_name
             )
 
-        if len(element) < 2:
+        if len(op_argv) < 2:
             continue
 
         op_role = ""
-        op_vars = utils.convert_args_to_tuples(element[1:])
+        op_vars = utils.convert_args_to_tuples(op_argv[1:])
 
         for k, v in op_vars:
             if k == "role":
@@ -1032,7 +1067,7 @@ def resource_update(lib, args, modifiers, deal_with_guest_change=True):
         dom = resource_operation_add(
             dom,
             res_id,
-            element,
+            op_argv,
             validate_strict=False,
             before_op=updating_op_before,
         )
diff --git a/pcs_test/resources/cib-empty-3.3.xml b/pcs_test/resources/cib-empty-3.3.xml
index 3a44fe08..4de94b6e 100644
--- a/pcs_test/resources/cib-empty-3.3.xml
+++ b/pcs_test/resources/cib-empty-3.3.xml
@@ -1,4 +1,4 @@
-<cib epoch="557" num_updates="122" admin_epoch="0" validate-with="pacemaker-3.3" crm_feature_set="3.1.0" update-origin="rh7-3" update-client="crmd" cib-last-written="Thu Aug 23 16:49:17 2012" have-quorum="0" dc-uuid="2">
+<cib epoch="557" num_updates="122" admin_epoch="0" validate-with="pacemaker-3.3" crm_feature_set="3.4.0" update-origin="rh7-3" update-client="crmd" cib-last-written="Thu Aug 23 16:49:17 2012" have-quorum="0" dc-uuid="2">
   <configuration>
     <crm_config/>
     <nodes>
diff --git a/pcs_test/resources/cib-empty-3.4.xml b/pcs_test/resources/cib-empty-3.4.xml
index dcd4ff44..e677462d 100644
--- a/pcs_test/resources/cib-empty-3.4.xml
+++ b/pcs_test/resources/cib-empty-3.4.xml
@@ -1,4 +1,4 @@
-<cib epoch="557" num_updates="122" admin_epoch="0" validate-with="pacemaker-3.4" crm_feature_set="3.1.0" update-origin="rh7-3" update-client="crmd" cib-last-written="Thu Aug 23 16:49:17 2012" have-quorum="0" dc-uuid="2">
+<cib epoch="557" num_updates="122" admin_epoch="0" validate-with="pacemaker-3.4" crm_feature_set="3.4.0" update-origin="rh7-3" update-client="crmd" cib-last-written="Thu Aug 23 16:49:17 2012" have-quorum="0" dc-uuid="2">
   <configuration>
     <crm_config/>
     <nodes>
diff --git a/pcs_test/tier0/lib/cib/test_resource_operations.py b/pcs_test/tier0/lib/cib/test_resource_operations.py
index 5e556cf4..e5be7a54 100644
--- a/pcs_test/tier0/lib/cib/test_resource_operations.py
+++ b/pcs_test/tier0/lib/cib/test_resource_operations.py
@@ -316,13 +316,14 @@ class ValidateOperation(TestCase):
                     option_value="b",
                     option_name="on-fail",
                     allowed_values=[
-                        "ignore",
                         "block",
-                        "stop",
-                        "restart",
-                        "standby",
+                        "demote",
                         "fence",
+                        "ignore",
+                        "restart",
                         "restart-container",
+                        "standby",
+                        "stop",
                     ],
                     cannot_be_empty=False,
                     forbidden_characters=None,
diff --git a/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py b/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py
index 725ec68a..4da3fa0a 100644
--- a/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py
+++ b/pcs_test/tier0/lib/commands/remote_node/test_node_add_remote.py
@@ -118,7 +118,7 @@ FIXTURE_RESOURCES_TEMPLATE = """
                     interval="0s" name="migrate_to" timeout="60"
                 />
                 <op id="node-name-monitor-interval-60s"
-                    interval="60s" name="monitor" timeout="30"
+                    interval="60s" name="monitor" timeout="30" {onfail}
                 />
                 <op id="node-name-reload-interval-0s"
                   interval="0s" name="reload" timeout="60"
@@ -133,7 +133,9 @@ FIXTURE_RESOURCES_TEMPLATE = """
         </primitive>
     </resources>
 """
-FIXTURE_RESOURCES = FIXTURE_RESOURCES_TEMPLATE.format(server="remote-host")
+FIXTURE_RESOURCES = FIXTURE_RESOURCES_TEMPLATE.format(
+    server="remote-host", onfail=""
+)
 
 
 class AddRemote(TestCase):
@@ -178,12 +180,48 @@ class AddRemote(TestCase):
             .local.push_existing_authkey_to_remote(NODE_NAME, NODE_DEST_LIST)
             .local.run_pacemaker_remote(NODE_NAME, NODE_DEST_LIST)
             .env.push_cib(
-                resources=FIXTURE_RESOURCES_TEMPLATE.format(server=NODE_NAME)
+                resources=FIXTURE_RESOURCES_TEMPLATE.format(
+                    server=NODE_NAME, onfail=""
+                )
             )
         )
         node_add_remote(self.env_assist.get_env(), node_addr=NODE_NAME)
         self.env_assist.assert_reports(REPORTS)
 
+    def test_cib_upgrade_on_onfail_demote(self):
+        self._config_success_base()
+        self.config.runner.cib.load(
+            filename="cib-empty-3.4.xml", instead="runner.cib.load",
+        )
+        self.config.runner.cib.upgrade(before="runner.cib.load")
+        self.config.runner.cib.load(
+            filename="cib-empty-3.3.xml",
+            name="load_cib_old_version",
+            before="runner.cib.upgrade",
+        )
+        self.config.env.push_cib(
+            resources=FIXTURE_RESOURCES_TEMPLATE.format(
+                server="remote-host", onfail='on-fail="demote"'
+            ),
+            instead="env.push_cib",
+        )
+        node_add_remote(
+            self.env_assist.get_env(),
+            operations=[
+                {
+                    "name": "monitor",
+                    "timeout": "30",
+                    "interval": "60s",
+                    "on-fail": "demote",
+                }
+            ],
+        )
+        self.env_assist.assert_reports(
+            REPORTS.info(
+                "cib_upgrade_successful", reports.codes.CIB_UPGRADE_SUCCESSFUL
+            )
+        )
+
     def test_node_name_conflict_report_is_unique(self):
         (
             self.config.runner.cib.load(
@@ -623,7 +661,7 @@ class NotLive(TestCase):
             .runner.pcmk.load_agent(agent_name="ocf:pacemaker:remote")
             .env.push_cib(
                 resources=FIXTURE_RESOURCES_TEMPLATE.format(
-                    server=NODE_ADDR_PCSD
+                    server=NODE_ADDR_PCSD, onfail=""
                 )
             )
         )
@@ -648,7 +686,9 @@ class NotLive(TestCase):
             self.config.runner.cib.load()
             .runner.pcmk.load_agent(agent_name="ocf:pacemaker:remote")
             .env.push_cib(
-                resources=FIXTURE_RESOURCES_TEMPLATE.format(server=NODE_NAME)
+                resources=FIXTURE_RESOURCES_TEMPLATE.format(
+                    server=NODE_NAME, onfail=""
+                )
             )
         )
         node_add_remote(self.env_assist.get_env(), no_node_addr=True)
@@ -672,7 +712,9 @@ class NotLive(TestCase):
             self.config.runner.cib.load()
             .runner.pcmk.load_agent(agent_name="ocf:pacemaker:remote")
             .env.push_cib(
-                resources=FIXTURE_RESOURCES_TEMPLATE.format(server="addr")
+                resources=FIXTURE_RESOURCES_TEMPLATE.format(
+                    server="addr", onfail=""
+                )
             )
         )
         node_add_remote(self.env_assist.get_env(), node_addr="addr")
diff --git a/pcs_test/tier0/lib/commands/resource/test_resource_create.py b/pcs_test/tier0/lib/commands/resource/test_resource_create.py
index dc70ce22..a040a9d9 100644
--- a/pcs_test/tier0/lib/commands/resource/test_resource_create.py
+++ b/pcs_test/tier0/lib/commands/resource/test_resource_create.py
@@ -35,13 +35,19 @@ def create(
     )
 
 
-def create_group(env, wait=TIMEOUT, disabled=False, meta_attributes=None):
+def create_group(
+    env,
+    wait=TIMEOUT,
+    disabled=False,
+    meta_attributes=None,
+    operation_list=None,
+):
     return resource.create_in_group(
         env,
         "A",
         "ocf:heartbeat:Dummy",
         "G",
-        operation_list=[],
+        operation_list=operation_list if operation_list else [],
         meta_attributes=meta_attributes if meta_attributes else {},
         instance_attributes={},
         wait=wait,
@@ -50,13 +56,18 @@ def create_group(env, wait=TIMEOUT, disabled=False, meta_attributes=None):
 
 
 def create_clone(
-    env, wait=TIMEOUT, disabled=False, meta_attributes=None, clone_options=None
+    env,
+    wait=TIMEOUT,
+    disabled=False,
+    meta_attributes=None,
+    clone_options=None,
+    operation_list=None,
 ):
     return resource.create_as_clone(
         env,
         "A",
         "ocf:heartbeat:Dummy",
-        operation_list=[],
+        operation_list=operation_list if operation_list else [],
         meta_attributes=meta_attributes if meta_attributes else {},
         instance_attributes={},
         clone_meta_options=clone_options if clone_options else {},
@@ -71,12 +82,13 @@ def create_bundle(
     disabled=False,
     meta_attributes=None,
     allow_not_accessible_resource=False,
+    operation_list=None,
 ):
     return resource.create_into_bundle(
         env,
         "A",
         "ocf:heartbeat:Dummy",
-        operation_list=[],
+        operation_list=operation_list if operation_list else [],
         meta_attributes=meta_attributes if meta_attributes else {},
         instance_attributes={},
         bundle_id="B",
@@ -576,6 +588,60 @@ class Create(TestCase):
             ]
         )
 
+    def test_cib_upgrade_on_onfail_demote(self):
+        self.config.runner.cib.load(
+            filename="cib-empty-3.3.xml",
+            instead="runner.cib.load",
+            name="load_cib_old_version",
+        )
+        self.config.runner.cib.upgrade()
+        self.config.runner.cib.load(filename="cib-empty-3.4.xml")
+        self.config.env.push_cib(
+            resources="""
+                <resources>
+                    <primitive class="ocf" id="A" provider="heartbeat"
+                        type="Dummy"
+                    >
+                        <operations>
+                            <op id="A-migrate_from-interval-0s" interval="0s"
+                                name="migrate_from" timeout="20"
+                            />
+                            <op id="A-migrate_to-interval-0s" interval="0s"
+                                name="migrate_to" timeout="20"
+                            />
+                            <op id="A-monitor-interval-10" interval="10"
+                                name="monitor" timeout="10" on-fail="demote"
+                            />
+                            <op id="A-reload-interval-0s" interval="0s"
+                                name="reload" timeout="20"
+                            />
+                            <op id="A-start-interval-0s" interval="0s"
+                                name="start" timeout="20"
+                            />
+                            <op id="A-stop-interval-0s" interval="0s"
+                                name="stop" timeout="20"
+                            />
+                        </operations>
+                    </primitive>
+                </resources>
+            """
+        )
+
+        create(
+            self.env_assist.get_env(),
+            operation_list=[
+                {
+                    "name": "monitor",
+                    "timeout": "10",
+                    "interval": "10",
+                    "on-fail": "demote",
+                }
+            ],
+        )
+        self.env_assist.assert_reports(
+            [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)]
+        )
+
 
 class CreateWait(TestCase):
     def setUp(self):
@@ -746,6 +812,66 @@ class CreateInGroup(TestCase):
 
         create_group(self.env_assist.get_env(), wait=False)
 
+    def test_cib_upgrade_on_onfail_demote(self):
+        self.config.remove(name="runner.pcmk.can_wait")
+        self.config.runner.cib.load(
+            filename="cib-empty-3.3.xml",
+            instead="runner.cib.load",
+            name="load_cib_old_version",
+        )
+        self.config.runner.cib.upgrade()
+        self.config.runner.cib.load(filename="cib-empty-3.4.xml")
+        self.config.env.push_cib(
+            resources="""
+                <resources>
+                    <group id="G">
+                        <primitive class="ocf" id="A" provider="heartbeat"
+                            type="Dummy"
+                        >
+                            <operations>
+                                <op id="A-migrate_from-interval-0s"
+                                    interval="0s" name="migrate_from"
+                                    timeout="20"
+                                />
+                                <op id="A-migrate_to-interval-0s"
+                                    interval="0s" name="migrate_to"
+                                    timeout="20"
+                                />
+                                <op id="A-monitor-interval-10" interval="10"
+                                    name="monitor" timeout="10" on-fail="demote"
+                                />
+                                <op id="A-reload-interval-0s" interval="0s"
+                                    name="reload" timeout="20"
+                                />
+                                <op id="A-start-interval-0s" interval="0s"
+                                    name="start" timeout="20"
+                                />
+                                <op id="A-stop-interval-0s" interval="0s"
+                                    name="stop" timeout="20"
+                                />
+                            </operations>
+                        </primitive>
+                    </group>
+                </resources>
+            """
+        )
+
+        create_group(
+            self.env_assist.get_env(),
+            operation_list=[
+                {
+                    "name": "monitor",
+                    "timeout": "10",
+                    "interval": "10",
+                    "on-fail": "demote",
+                }
+            ],
+            wait=False,
+        )
+        self.env_assist.assert_reports(
+            [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)]
+        )
+
     def test_fail_wait(self):
         self.config.env.push_cib(
             resources=fixture_cib_resources_xml_group_simplest,
@@ -859,6 +985,62 @@ class CreateAsClone(TestCase):
         )
         create_clone(self.env_assist.get_env(), wait=False)
 
+    def test_cib_upgrade_on_onfail_demote(self):
+        self.config.remove(name="runner.pcmk.can_wait")
+        self.config.runner.cib.load(
+            filename="cib-empty-3.3.xml",
+            instead="runner.cib.load",
+            name="load_cib_old_version",
+        )
+        self.config.runner.cib.upgrade()
+        self.config.runner.cib.load(filename="cib-empty-3.4.xml")
+        self.config.env.push_cib(
+            resources="""<resources>
+                <clone id="A-clone">
+                    <primitive class="ocf" id="A" provider="heartbeat"
+                        type="Dummy"
+                    >
+                        <operations>
+                            <op id="A-migrate_from-interval-0s" interval="0s"
+                                name="migrate_from" timeout="20"
+                            />
+                            <op id="A-migrate_to-interval-0s" interval="0s"
+                                name="migrate_to" timeout="20"
+                            />
+                            <op id="A-monitor-interval-10" interval="10"
+                                name="monitor" timeout="10" on-fail="demote"
+                            />
+                            <op id="A-reload-interval-0s" interval="0s"
+                                name="reload" timeout="20"
+                            />
+                            <op id="A-start-interval-0s" interval="0s"
+                                name="start" timeout="20"
+                            />
+                            <op id="A-stop-interval-0s" interval="0s"
+                                name="stop" timeout="20"
+                            />
+                        </operations>
+                    </primitive>
+                </clone>
+            </resources>"""
+        )
+
+        create_clone(
+            self.env_assist.get_env(),
+            operation_list=[
+                {
+                    "name": "monitor",
+                    "timeout": "10",
+                    "interval": "10",
+                    "on-fail": "demote",
+                }
+            ],
+            wait=False,
+        )
+        self.env_assist.assert_reports(
+            [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)]
+        )
+
     def test_fail_wait(self):
         self.config.env.push_cib(
             resources=fixture_cib_resources_xml_clone_simplest,
@@ -1168,7 +1350,7 @@ class CreateInToBundle(TestCase):
                             name="migrate_to" timeout="20"
                         />
                         <op id="A-monitor-interval-10" interval="10"
-                            name="monitor" timeout="20"
+                            name="monitor" timeout="20" {onfail}
                         />
                         <op id="A-reload-interval-0s" interval="0s" name="reload"
                             timeout="20"
@@ -1190,7 +1372,8 @@ class CreateInToBundle(TestCase):
         fixture_resource_post_simple_without_network.format(
             network="""
                 <network control-port="12345" ip-range-start="192.168.100.200"/>
-            """
+            """,
+            onfail=""
         )
     )
     # fmt: on
@@ -1290,6 +1473,42 @@ class CreateInToBundle(TestCase):
             [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)]
         )
 
+    def test_cib_upgrade_on_onfail_demote(self):
+        self.config.runner.pcmk.load_agent()
+        self.config.runner.cib.load(
+            filename="cib-empty-3.3.xml", name="load_cib_old_version",
+        )
+        self.config.runner.cib.upgrade()
+        self.config.runner.cib.load(
+            filename="cib-empty-3.4.xml", resources=self.fixture_resources_pre
+        )
+        self.config.env.push_cib(
+            resources=self.fixture_resource_post_simple_without_network.format(
+                network="""
+                    <network
+                        control-port="12345" ip-range-start="192.168.100.200"
+                    />
+                """,
+                onfail='on-fail="demote"',
+            )
+        )
+
+        create_bundle(
+            self.env_assist.get_env(),
+            operation_list=[
+                {
+                    "name": "monitor",
+                    "timeout": "20",
+                    "interval": "10",
+                    "on-fail": "demote",
+                }
+            ],
+            wait=False,
+        )
+        self.env_assist.assert_reports(
+            [fixture.info(report_codes.CIB_UPGRADE_SUCCESSFUL)]
+        )
+
     def test_simplest_resource(self):
         (
             self.config.runner.pcmk.load_agent()
@@ -1504,7 +1723,7 @@ class CreateInToBundle(TestCase):
             .env.push_cib(
                 resources=(
                     self.fixture_resource_post_simple_without_network.format(
-                        network=""
+                        network="", onfail=""
                     )
                 )
             )
@@ -1540,7 +1759,7 @@ class CreateInToBundle(TestCase):
             .env.push_cib(
                 resources=(
                     self.fixture_resource_post_simple_without_network.format(
-                        network=network
+                        network=network, onfail=""
                     )
                 )
             )
diff --git a/pcs_test/tier1/cib_resource/test_create.py b/pcs_test/tier1/cib_resource/test_create.py
index 977d627a..5ff83b71 100644
--- a/pcs_test/tier1/cib_resource/test_create.py
+++ b/pcs_test/tier1/cib_resource/test_create.py
@@ -1143,7 +1143,7 @@ class FailOrWarnOp(ResourceTest):
             " monitor on-fail=Abc",
             (
                 "Error: 'Abc' is not a valid on-fail value, use 'block', "
-                "'fence', 'ignore', 'restart', 'restart-container', "
+                "'demote', 'fence', 'ignore', 'restart', 'restart-container', "
                 "'standby', 'stop'\n" + ERRORS_HAVE_OCURRED
             ),
         )
diff --git a/pcs_test/tier1/legacy/test_resource.py b/pcs_test/tier1/legacy/test_resource.py
index 7ffcc83b..107ff406 100644
--- a/pcs_test/tier1/legacy/test_resource.py
+++ b/pcs_test/tier1/legacy/test_resource.py
@@ -22,6 +22,7 @@ from pcs_test.tools.misc import (
     is_minimum_pacemaker_version,
     outdent,
     skip_unless_pacemaker_supports_bundle,
+    skip_unless_pacemaker_supports_op_onfail_demote,
     skip_unless_crm_rule,
     write_data_to_tmpfile,
     write_file_to_tmpfile,
@@ -723,6 +724,28 @@ monitor interval=60s OCF_CHECK_LEVEL=1 (OPTest7-monitor-interval-60s)
             ),
         )
 
+    @skip_unless_pacemaker_supports_op_onfail_demote()
+    def test_add_operation_onfail_demote_upgrade_cib(self):
+        write_file_to_tmpfile(rc("cib-empty-3.3.xml"), self.temp_cib)
+        self.assert_pcs_success(
+            "resource create --no-default-ops R ocf:pacemaker:Dummy"
+        )
+        self.assert_pcs_success(
+            "resource op add R start on-fail=demote",
+            stdout_full="Cluster CIB has been upgraded to latest version\n",
+        )
+
+    @skip_unless_pacemaker_supports_op_onfail_demote()
+    def test_update_add_operation_onfail_demote_upgrade_cib(self):
+        write_file_to_tmpfile(rc("cib-empty-3.3.xml"), self.temp_cib)
+        self.assert_pcs_success(
+            "resource create --no-default-ops R ocf:pacemaker:Dummy"
+        )
+        self.assert_pcs_success(
+            "resource update R op start on-fail=demote",
+            stdout_full="Cluster CIB has been upgraded to latest version\n",
+        )
+
     def _test_delete_remove_operation(self, command):
         assert command in {"delete", "remove"}
 
diff --git a/pcs_test/tools/misc.py b/pcs_test/tools/misc.py
index 33d78002..820f1e79 100644
--- a/pcs_test/tools/misc.py
+++ b/pcs_test/tools/misc.py
@@ -253,6 +253,12 @@ def skip_unless_pacemaker_supports_rsc_and_op_rules():
     )
 
 
+def skip_unless_pacemaker_supports_op_onfail_demote():
+    return skip_unless_cib_schema_version(
+        (3, 4, 0), "resource operations with 'on-fail' option set to 'demote'"
+    )
+
+
 def skip_if_service_enabled(service_name):
     return skipUnless(
         not is_service_enabled(runner, service_name),
diff --git a/pcsd/capabilities.xml b/pcsd/capabilities.xml
index 6e1886cb..09983354 100644
--- a/pcsd/capabilities.xml
+++ b/pcsd/capabilities.xml
@@ -465,6 +465,13 @@
         pcs commands: cluster node ( add-remote | delete-remote | remove-remote )
       </description>
     </capability>
+    <capability id="node.remote.onfail-demote" in-pcs="1" in-pcsd="0">
+      <description>
+        Support for "demote" value of resource operation's "on-fail" option
+
+        pcs commands: cluster node add-remote
+      </description>
+    </capability>
 
 
 
@@ -1056,6 +1063,13 @@
         pcs commands: resource create ... op
       </description>
     </capability>
+    <capability id="pcmk.resource.create.operations.onfail-demote" in-pcs="1" in-pcsd="0">
+      <description>
+        Support for "demote" value of resource operation's "on-fail" option
+
+        pcs commands: resource create ... op
+      </description>
+    </capability>
     <capability id="pcmk.resource.create.wait" in-pcs="1" in-pcsd="0">
       <description>
         Wait for the created resource to start.
@@ -1105,6 +1119,13 @@
         pcs commands: resource update ... op
       </description>
     </capability>
+    <capability id="pcmk.resource.update.operations.onfail-demote" in-pcs="1" in-pcsd="0">
+      <description>
+        Support for "demote" value of resource operation's "on-fail" option
+
+        pcs commands: resource update ... op
+      </description>
+    </capability>
     <capability id="pcmk.resource.update.wait" in-pcs="1" in-pcsd="0">
       <description>
         Wait for the changes to take effect.
@@ -1143,6 +1164,13 @@
         pcs commands: resource op ( add | delete | remove )
       </description>
     </capability>
+    <capability id="pcmk.resource.update-operations.onfail-demote" in-pcs="1" in-pcsd="0">
+      <description>
+        Support for "demote" value of resource operation's "on-fail" option
+
+        pcs commands: resource op add
+      </description>
+    </capability>
 
     <capability id="pcmk.resource.group" in-pcs="1" in-pcsd="1">
       <description>
@@ -1555,6 +1583,20 @@
         pcs commands: stonith create ... op
       </description>
     </capability>
+    <capability id="pcmk.stonith.create.operations" in-pcs="1" in-pcsd="0">
+      <description>
+        Set resource operations when creating a stonith resource.
+
+        pcs commands: stonith create ... op
+      </description>
+    </capability>
+    <capability id="pcmk.stonith.create.operations.onfail-demote" in-pcs="1" in-pcsd="0">
+      <description>
+        Support for "demote" value of resource operation's "on-fail" option
+
+        pcs commands: stonith create ... op
+      </description>
+    </capability>
     <capability id="pcmk.stonith.create.wait" in-pcs="1" in-pcsd="0">
       <description>
         Wait for the created resource to start.
-- 
2.25.4