diff --git a/cts/cli/regression.crm_shadow.exp b/cts/cli/regression.crm_shadow.exp
index d6ab93deba..148723c531 100644
--- a/cts/cli/regression.crm_shadow.exp
+++ b/cts/cli/regression.crm_shadow.exp
@@ -1,1630 +1,1591 @@
=#=#=#= Begin test: Get active shadow instance (no active instance) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance (no active instance) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance (no active instance)
=#=#=#= Begin test: Get active shadow instance (no active instance) (XML) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance (no active instance) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance (no active instance) (XML)
=#=#=#= Begin test: Get active shadow instance's file name (no active instance) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance's file name (no active instance) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's file name (no active instance)
=#=#=#= Begin test: Get active shadow instance's file name (no active instance) (XML) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance's file name (no active instance) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's file name (no active instance) (XML)
=#=#=#= Begin test: Get active shadow instance's contents (no active instance) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance's contents (no active instance) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's contents (no active instance)
=#=#=#= Begin test: Get active shadow instance's contents (no active instance) (XML) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance's contents (no active instance) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's contents (no active instance) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (no active instance) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance's diff (no active instance) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (no active instance)
=#=#=#= Begin test: Get active shadow instance's diff (no active instance) (XML) =#=#=#=
crm_shadow: No active shadow configuration defined
=#=#=#= End test: Get active shadow instance's diff (no active instance) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (no active instance) (XML)
=#=#=#= Begin test: Create copied shadow instance =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance
=#=#=#= Begin test: Create copied shadow instance (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (XML)
=#=#=#= Begin test: Get active shadow instance (copied) =#=#=#=
cts-cli
=#=#=#= End test: Get active shadow instance (copied) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance (copied)
=#=#=#= Begin test: Get active shadow instance (copied) (XML) =#=#=#=
=#=#=#= End test: Get active shadow instance (copied) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance (copied) (XML)
=#=#=#= Begin test: Get active shadow instance's file name (copied) =#=#=#=
/tmp/cts-cli.shadow/shadow.cts-cli
=#=#=#= End test: Get active shadow instance's file name (copied) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's file name (copied)
=#=#=#= Begin test: Get active shadow instance's file name (copied) (XML) =#=#=#=
=#=#=#= End test: Get active shadow instance's file name (copied) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's file name (copied) (XML)
=#=#=#= Begin test: Get active shadow instance's contents (copied) =#=#=#=
=#=#=#= End test: Get active shadow instance's contents (copied) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's contents (copied)
=#=#=#= Begin test: Get active shadow instance's contents (copied) (XML) =#=#=#=
]]>
=#=#=#= End test: Get active shadow instance's contents (copied) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's contents (copied) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (copied) =#=#=#=
=#=#=#= End test: Get active shadow instance's diff (copied) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (copied)
=#=#=#= Begin test: Get active shadow instance's diff (copied) (XML) =#=#=#=
=#=#=#= End test: Get active shadow instance's diff (copied) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (copied) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (after changes) =#=#=#=
Diff: --- 1.1.173 2
Diff: +++ 1.4.1 (null)
-- /cib/configuration/op_defaults
+ /cib: @epoch=4, @num_updates=1
+ /cib/configuration/resources/primitive[@id='dummy']: @description=desc
++ /cib/configuration/resources:
++ /cib/status:
=#=#=#= End test: Get active shadow instance's diff (after changes) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (after changes)
=#=#=#= Begin test: Get active shadow instance's diff (after changes) (XML) =#=#=#=
]]>
=#=#=#= End test: Get active shadow instance's diff (after changes) (XML) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (after changes) (XML)
=#=#=#= Begin test: Commit shadow instance =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance
=#=#=#= Begin test: Commit shadow instance (force) =#=#=#=
=#=#=#= End test: Commit shadow instance (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (force)
=#=#=#= Begin test: Get active shadow instance's diff (after commit) =#=#=#=
Diff: --- 1.2.0 2
Diff: +++ 1.4.1 (null)
+ /cib: @epoch=4, @num_updates=1
++ /cib/status:
=#=#=#= End test: Get active shadow instance's diff (after commit) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (after commit)
=#=#=#= Begin test: Commit shadow instance (force) (all) =#=#=#=
=#=#=#= End test: Commit shadow instance (force) (all) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (force) (all)
=#=#=#= Begin test: Get active shadow instance's diff (after commit all) =#=#=#=
Diff: --- 1.4.2 2
Diff: +++ 1.4.1 (null)
+ /cib: @num_updates=1
=#=#=#= End test: Get active shadow instance's diff (after commit all) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (after commit all)
=#=#=#= Begin test: Commit shadow instance (XML) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (XML)
=#=#=#= Begin test: Commit shadow instance (force) (XML) =#=#=#=
=#=#=#= End test: Commit shadow instance (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (force) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (after commit) (XML) =#=#=#=
]]>
=#=#=#= End test: Get active shadow instance's diff (after commit) (XML) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (after commit) (XML)
=#=#=#= Begin test: Commit shadow instance (force) (all) (XML) =#=#=#=
=#=#=#= End test: Commit shadow instance (force) (all) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (force) (all) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (after commit all) (XML) =#=#=#=
]]>
=#=#=#= End test: Get active shadow instance's diff (after commit all) (XML) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (after commit all) (XML)
=#=#=#= Begin test: Commit shadow instance (no active instance) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (no active instance) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (no active instance)
=#=#=#= Begin test: Commit shadow instance (no active instance) (XML) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (no active instance) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (no active instance) (XML)
=#=#=#= Begin test: Commit shadow instance (no active instance) (force) =#=#=#=
=#=#=#= End test: Commit shadow instance (no active instance) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (no active instance) (force)
=#=#=#= Begin test: Commit shadow instance (no active instance) (force) (XML) =#=#=#=
=#=#=#= End test: Commit shadow instance (no active instance) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (no active instance) (force) (XML)
=#=#=#= Begin test: Commit shadow instance (mismatch) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
Additionally, the supplied shadow instance (cts-cli) is not the same as the active one (nonexistent_shadow).
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (mismatch) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (mismatch)
=#=#=#= Begin test: Commit shadow instance (mismatch) (XML) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
Additionally, the supplied shadow instance (cts-cli) is not the same as the active one (nonexistent_shadow).
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (mismatch) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (mismatch) (XML)
=#=#=#= Begin test: Commit shadow instance (mismatch) (force) =#=#=#=
=#=#=#= End test: Commit shadow instance (mismatch) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (mismatch) (force)
=#=#=#= Begin test: Commit shadow instance (mismatch) (force) (XML) =#=#=#=
=#=#=#= End test: Commit shadow instance (mismatch) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (mismatch) (force) (XML)
=#=#=#= Begin test: Commit shadow instance (nonexistent shadow file) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (nonexistent shadow file) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent shadow file)
=#=#=#= Begin test: Commit shadow instance (nonexistent shadow file) (XML) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (nonexistent shadow file) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent shadow file) (XML)
=#=#=#= Begin test: Commit shadow instance (nonexistent shadow file) (force) =#=#=#=
crm_shadow: Could not access shadow instance 'nonexistent_shadow': No such file or directory
=#=#=#= End test: Commit shadow instance (nonexistent shadow file) (force) - No such object (105) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent shadow file) (force)
=#=#=#= Begin test: Commit shadow instance (nonexistent shadow file) (force) (XML) =#=#=#=
crm_shadow: Could not access shadow instance 'nonexistent_shadow': No such file or directory
=#=#=#= End test: Commit shadow instance (nonexistent shadow file) (force) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent shadow file) (force) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (nonexistent shadow file) =#=#=#=
crm_shadow: Could not access shadow instance 'nonexistent_shadow': No such file or directory
=#=#=#= End test: Get active shadow instance's diff (nonexistent shadow file) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (nonexistent shadow file)
=#=#=#= Begin test: Get active shadow instance's diff (nonexistent shadow file) (XML) =#=#=#=
crm_shadow: Could not access shadow instance 'nonexistent_shadow': No such file or directory
=#=#=#= End test: Get active shadow instance's diff (nonexistent shadow file) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (nonexistent shadow file) (XML)
=#=#=#= Begin test: Commit shadow instance (nonexistent CIB file) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (nonexistent CIB file) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent CIB file)
=#=#=#= Begin test: Commit shadow instance (nonexistent CIB file) (XML) =#=#=#=
crm_shadow: The commit command overwrites the active cluster configuration.
To prevent accidental destruction of the cluster, the --force flag is required in order to proceed.
=#=#=#= End test: Commit shadow instance (nonexistent CIB file) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent CIB file) (XML)
=#=#=#= Begin test: Commit shadow instance (nonexistent CIB file) (force) =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Commit shadow instance (nonexistent CIB file) (force) - No such object (105) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent CIB file) (force)
=#=#=#= Begin test: Commit shadow instance (nonexistent CIB file) (force) (XML) =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Commit shadow instance (nonexistent CIB file) (force) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Commit shadow instance (nonexistent CIB file) (force) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (nonexistent CIB file) =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Get active shadow instance's diff (nonexistent CIB file) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (nonexistent CIB file)
=#=#=#= Begin test: Get active shadow instance's diff (nonexistent CIB file) (XML) =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Get active shadow instance's diff (nonexistent CIB file) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (nonexistent CIB file) (XML)
=#=#=#= Begin test: Delete shadow instance =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance
=#=#=#= Begin test: Delete shadow instance (force) =#=#=#=
Remember to unset the CIB_shadow variable by entering the following into your shell:
unset CIB_shadow
=#=#=#= End test: Delete shadow instance (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (force)
=#=#=#= Begin test: Delete shadow instance (XML) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (XML)
=#=#=#= Begin test: Delete shadow instance (force) (XML) =#=#=#=
Remember to unset the CIB_shadow variable by entering the following into your shell:
unset CIB_shadow
=#=#=#= End test: Delete shadow instance (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (force) (XML)
=#=#=#= Begin test: Delete shadow instance (no active instance) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (no active instance) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (no active instance)
=#=#=#= Begin test: Delete shadow instance (no active instance) (force) =#=#=#=
=#=#=#= End test: Delete shadow instance (no active instance) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (no active instance) (force)
=#=#=#= Begin test: Delete shadow instance (no active instance) (XML) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (no active instance) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (no active instance) (XML)
=#=#=#= Begin test: Delete shadow instance (no active instance) (force) (XML) =#=#=#=
=#=#=#= End test: Delete shadow instance (no active instance) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (no active instance) (force) (XML)
=#=#=#= Begin test: Delete shadow instance (mismatch) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
Additionally, the supplied shadow instance (cts-cli) is not the same as the active one (nonexistent_shadow).
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (mismatch) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (mismatch)
=#=#=#= Begin test: Delete shadow instance (mismatch) (force) =#=#=#=
=#=#=#= End test: Delete shadow instance (mismatch) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (mismatch) (force)
=#=#=#= Begin test: Delete shadow instance (mismatch) (XML) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
Additionally, the supplied shadow instance (cts-cli) is not the same as the active one (nonexistent_shadow).
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (mismatch) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (mismatch) (XML)
=#=#=#= Begin test: Delete shadow instance (mismatch) (force) (XML) =#=#=#=
=#=#=#= End test: Delete shadow instance (mismatch) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (mismatch) (force) (XML)
=#=#=#= Begin test: Delete shadow instance (nonexistent shadow file) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (nonexistent shadow file) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent shadow file)
=#=#=#= Begin test: Delete shadow instance (nonexistent shadow file) (force) =#=#=#=
Remember to unset the CIB_shadow variable by entering the following into your shell:
unset CIB_shadow
=#=#=#= End test: Delete shadow instance (nonexistent shadow file) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent shadow file) (force)
=#=#=#= Begin test: Delete shadow instance (nonexistent shadow file) (XML) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (nonexistent shadow file) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent shadow file) (XML)
=#=#=#= Begin test: Delete shadow instance (nonexistent shadow file) (force) (XML) =#=#=#=
Remember to unset the CIB_shadow variable by entering the following into your shell:
unset CIB_shadow
=#=#=#= End test: Delete shadow instance (nonexistent shadow file) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent shadow file) (force) (XML)
=#=#=#= Begin test: Delete shadow instance (nonexistent CIB file) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (nonexistent CIB file) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent CIB file)
=#=#=#= Begin test: Delete shadow instance (nonexistent CIB file) (force) =#=#=#=
Remember to unset the CIB_shadow variable by entering the following into your shell:
unset CIB_shadow
=#=#=#= End test: Delete shadow instance (nonexistent CIB file) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent CIB file) (force)
=#=#=#= Begin test: Delete shadow instance (nonexistent CIB file) (XML) =#=#=#=
crm_shadow: The delete command removes the specified shadow file.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Delete shadow instance (nonexistent CIB file) (XML) - Incorrect usage (64) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent CIB file) (XML)
=#=#=#= Begin test: Delete shadow instance (nonexistent CIB file) (force) (XML) =#=#=#=
Remember to unset the CIB_shadow variable by entering the following into your shell:
unset CIB_shadow
=#=#=#= End test: Delete shadow instance (nonexistent CIB file) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Delete shadow instance (nonexistent CIB file) (force) (XML)
=#=#=#= Begin test: Create copied shadow instance (no active instance) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (no active instance) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (no active instance)
=#=#=#= Begin test: Create copied shadow instance (no active instance) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (no active instance) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (no active instance) (XML)
=#=#=#= Begin test: Create copied shadow instance (mismatch) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (mismatch) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (mismatch)
=#=#=#= Begin test: Create copied shadow instance (mismatch) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (mismatch) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (mismatch) (XML)
=#=#=#= Begin test: Create copied shadow instance (file already exists) =#=#=#=
crm_shadow: A shadow instance 'cts-cli' already exists.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Create copied shadow instance (file already exists) - Cannot create output file (73) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (file already exists)
=#=#=#= Begin test: Create copied shadow instance (file already exists) (XML) =#=#=#=
crm_shadow: A shadow instance 'cts-cli' already exists.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Create copied shadow instance (file already exists) (XML) - Cannot create output file (73) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (file already exists) (XML)
=#=#=#= Begin test: Create copied shadow instance (file already exists) (force) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (file already exists) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (file already exists) (force)
=#=#=#= Begin test: Create copied shadow instance (file already exists) (force) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create copied shadow instance (file already exists) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (file already exists) (force) (XML)
=#=#=#= Begin test: Create copied shadow instance (nonexistent CIB file) (force) =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Create copied shadow instance (nonexistent CIB file) (force) - No such object (105) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (nonexistent CIB file) (force)
=#=#=#= Begin test: Create copied shadow instance (nonexistent CIB file) (force) (XML) =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Create copied shadow instance (nonexistent CIB file) (force) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Create copied shadow instance (nonexistent CIB file) (force) (XML)
=#=#=#= Begin test: Create empty shadow instance =#=#=#=
Created new pacemaker configuration
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance
=#=#=#= Begin test: Create empty shadow instance (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (XML)
=#=#=#= Begin test: Create empty shadow instance (no active instance) =#=#=#=
Created new pacemaker configuration
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (no active instance) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (no active instance)
=#=#=#= Begin test: Create empty shadow instance (no active instance) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (no active instance) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (no active instance) (XML)
=#=#=#= Begin test: Create empty shadow instance (mismatch) =#=#=#=
Created new pacemaker configuration
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (mismatch) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (mismatch)
=#=#=#= Begin test: Create empty shadow instance (mismatch) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (mismatch) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (mismatch) (XML)
=#=#=#= Begin test: Create empty shadow instance (nonexistent CIB file) =#=#=#=
Created new pacemaker configuration
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (nonexistent CIB file) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (nonexistent CIB file)
=#=#=#= Begin test: Create empty shadow instance (nonexistent CIB file) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (nonexistent CIB file) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (nonexistent CIB file) (XML)
=#=#=#= Begin test: Create empty shadow instance (file already exists) =#=#=#=
crm_shadow: A shadow instance 'cts-cli' already exists.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Create empty shadow instance (file already exists) - Cannot create output file (73) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (file already exists)
=#=#=#= Begin test: Create empty shadow instance (file already exists) (XML) =#=#=#=
crm_shadow: A shadow instance 'cts-cli' already exists.
To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
=#=#=#= End test: Create empty shadow instance (file already exists) (XML) - Cannot create output file (73) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (file already exists) (XML)
=#=#=#= Begin test: Create empty shadow instance (file already exists) (force) =#=#=#=
Created new pacemaker configuration
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (file already exists) (force) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (file already exists) (force)
=#=#=#= Begin test: Create empty shadow instance (file already exists) (force) (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Create empty shadow instance (file already exists) (force) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Create empty shadow instance (file already exists) (force) (XML)
=#=#=#= Begin test: Get active shadow instance's contents (empty CIB) =#=#=#=
=#=#=#= End test: Get active shadow instance's contents (empty CIB) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's contents (empty CIB)
=#=#=#= Begin test: Get active shadow instance's contents (empty CIB) (XML) =#=#=#=
]]>
=#=#=#= End test: Get active shadow instance's contents (empty CIB) (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's contents (empty CIB) (XML)
=#=#=#= Begin test: Get active shadow instance's diff (empty CIB) =#=#=#=
Diff: --- 1.1.173 2
Diff: +++ 0.1.0 (null)
-- /cib/configuration/crm_config/cluster_property_set[@id='cib-bootstrap-options']
-- /cib/configuration/nodes/node[@id='1']
-- /cib/configuration/nodes/node[@id='2']
-- /cib/configuration/resources/clone[@id='ping-clone']
-- /cib/configuration/resources/primitive[@id='Fencing']
-- /cib/configuration/resources/primitive[@id='dummy']
-- /cib/configuration/resources/clone[@id='inactive-clone']
-- /cib/configuration/resources/group[@id='inactive-group']
-- /cib/configuration/resources/bundle[@id='httpd-bundle']
-- /cib/configuration/resources/group[@id='exim-group']
-- /cib/configuration/resources/clone[@id='mysql-clone-group']
-- /cib/configuration/resources/clone[@id='promotable-clone']
-- /cib/configuration/constraints/rsc_location[@id='not-on-cluster1']
-- /cib/configuration/constraints/rsc_location[@id='loc-promotable-clone']
-- /cib/configuration/tags
-- /cib/configuration/op_defaults
-- /cib/status/node_state[@id='2']
-- /cib/status/node_state[@id='1']
-- /cib/status/node_state[@id='httpd-bundle-0']
-- /cib/status/node_state[@id='httpd-bundle-1']
+ /cib: @validate-with=pacemaker-X, @num_updates=0, @admin_epoch=0
-- /cib: @cib-last-written, @update-origin, @update-client, @update-user, @have-quorum, @dc-uuid
=#=#=#= End test: Get active shadow instance's diff (empty CIB) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (empty CIB)
=#=#=#= Begin test: Get active shadow instance's diff (empty CIB) (XML) =#=#=#=
]]>
=#=#=#= End test: Get active shadow instance's diff (empty CIB) (XML) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Get active shadow instance's diff (empty CIB) (XML)
+=#=#=#= Begin test: Resetting active shadow instance to active CIB requires force =#=#=#=
+crm_shadow: The reset command overwrites the active shadow configuration.
+To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
+=#=#=#= End test: Resetting active shadow instance to active CIB requires force - Incorrect usage (64) =#=#=#=
+* Passed: crm_shadow - Resetting active shadow instance to active CIB requires force
=#=#=#= Begin test: Reset active shadow instance to active CIB =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Reset active shadow instance to active CIB - OK (0) =#=#=#=
* Passed: crm_shadow - Reset active shadow instance to active CIB
=#=#=#= Begin test: Active shadow instance no different from active CIB after reset =#=#=#=
=#=#=#= End test: Active shadow instance no different from active CIB after reset - OK (0) =#=#=#=
* Passed: crm_shadow - Active shadow instance no different from active CIB after reset
=#=#=#= Begin test: Active shadow instance differs from active CIB after change =#=#=#=
Diff: --- 1.1.173 2
Diff: +++ 1.2.0 (null)
+ /cib: @epoch=2, @num_updates=0
++ /cib/configuration/crm_config/cluster_property_set[@id='cib-bootstrap-options']:
=#=#=#= End test: Active shadow instance differs from active CIB after change - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Active shadow instance differs from active CIB after change
=#=#=#= Begin test: Reset active shadow instance to active CIB (XML) =#=#=#=
-
+A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Reset active shadow instance to active CIB (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Reset active shadow instance to active CIB (XML)
=#=#=#= Begin test: Active shadow instance no different from active CIB after reset (XML) =#=#=#=
=#=#=#= End test: Active shadow instance no different from active CIB after reset (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Active shadow instance no different from active CIB after reset (XML)
=#=#=#= Begin test: Active shadow instance differs from active CIB after change (XML) =#=#=#=
]]>
=#=#=#= End test: Active shadow instance differs from active CIB after change (XML) - Error occurred (1) =#=#=#=
* Passed: crm_shadow - Active shadow instance differs from active CIB after change (XML)
=#=#=#= Begin test: Reset shadow instance to active CIB with nonexistent shadow file =#=#=#=
-crm_shadow: Could not access shadow instance 'cts-cli': No such file or directory
-=#=#=#= End test: Reset shadow instance to active CIB with nonexistent shadow file - No such object (105) =#=#=#=
-* Passed: crm_shadow - Reset shadow instance to active CIB with nonexistent shadow file
-=#=#=#= Begin test: Reset shadow instance to active CIB with nonexistent shadow file (XML) =#=#=#=
-
-
-
- crm_shadow: Could not access shadow instance 'cts-cli': No such file or directory
-
-
-
-=#=#=#= End test: Reset shadow instance to active CIB with nonexistent shadow file (XML) - No such object (105) =#=#=#=
-* Passed: crm_shadow - Reset shadow instance to active CIB with nonexistent shadow file (XML)
-=#=#=#= Begin test: Force-reset shadow instance to active CIB with nonexistent shadow file =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
-=#=#=#= End test: Force-reset shadow instance to active CIB with nonexistent shadow file - OK (0) =#=#=#=
-* Passed: crm_shadow - Force-reset shadow instance to active CIB with nonexistent shadow file
-=#=#=#= Begin test: Force-reset shadow instance to active CIB with nonexistent shadow file (XML) =#=#=#=
+=#=#=#= End test: Reset shadow instance to active CIB with nonexistent shadow file - OK (0) =#=#=#=
+* Passed: crm_shadow - Reset shadow instance to active CIB with nonexistent shadow file
+=#=#=#= Begin test: Reset shadow instance to active CIB with nonexistent shadow file (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
-=#=#=#= End test: Force-reset shadow instance to active CIB with nonexistent shadow file (XML) - OK (0) =#=#=#=
-* Passed: crm_shadow - Force-reset shadow instance to active CIB with nonexistent shadow file (XML)
+=#=#=#= End test: Reset shadow instance to active CIB with nonexistent shadow file (XML) - OK (0) =#=#=#=
+* Passed: crm_shadow - Reset shadow instance to active CIB with nonexistent shadow file (XML)
=#=#=#= Begin test: Active shadow instance no different from active CIB after force-reset =#=#=#=
=#=#=#= End test: Active shadow instance no different from active CIB after force-reset - OK (0) =#=#=#=
* Passed: crm_shadow - Active shadow instance no different from active CIB after force-reset
=#=#=#= Begin test: Reset inactive shadow instance (none active) to active CIB =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Reset inactive shadow instance (none active) to active CIB - OK (0) =#=#=#=
* Passed: crm_shadow - Reset inactive shadow instance (none active) to active CIB
=#=#=#= Begin test: Reset inactive shadow instance (none active) to active CIB (XML) =#=#=#=
-
+A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Reset inactive shadow instance (none active) to active CIB (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Reset inactive shadow instance (none active) to active CIB (XML)
-=#=#=#= Begin test: Resetting inactive shadow instance while another instance active requires --force =#=#=#=
-crm_shadow: The supplied shadow instance (cts-cli) is not the same as the active one (nonexistent_shadow).
-To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
-=#=#=#= End test: Resetting inactive shadow instance while another instance active requires --force - Incorrect usage (64) =#=#=#=
-* Passed: crm_shadow - Resetting inactive shadow instance while another instance active requires --force
-=#=#=#= Begin test: Resetting inactive shadow instance while another instance active requires --force (XML) =#=#=#=
-
-
-
- crm_shadow: The supplied shadow instance (cts-cli) is not the same as the active one (nonexistent_shadow).
-To prevent accidental destruction of the shadow file, the --force flag is required in order to proceed.
-
-
-
-=#=#=#= End test: Resetting inactive shadow instance while another instance active requires --force (XML) - Incorrect usage (64) =#=#=#=
-* Passed: crm_shadow - Resetting inactive shadow instance while another instance active requires --force (XML)
=#=#=#= Begin test: Reset inactive shadow instance while another instance active =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Reset inactive shadow instance while another instance active - OK (0) =#=#=#=
* Passed: crm_shadow - Reset inactive shadow instance while another instance active
=#=#=#= Begin test: Reset inactive shadow instance while another instance active (XML) =#=#=#=
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Reset inactive shadow instance while another instance active (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Reset inactive shadow instance while another instance active (XML)
=#=#=#= Begin test: Reset shadow instance with nonexistent CIB =#=#=#=
crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Reset shadow instance with nonexistent CIB - No such object (105) =#=#=#=
* Passed: crm_shadow - Reset shadow instance with nonexistent CIB
=#=#=#= Begin test: Reset shadow instance with nonexistent CIB (XML) =#=#=#=
-
+crm_shadow: Could not connect to CIB: No such device or address
=#=#=#= End test: Reset shadow instance with nonexistent CIB (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Reset shadow instance with nonexistent CIB (XML)
-=#=#=#= Begin test: Force-reset shadow instance with nonexistent CIB =#=#=#=
-crm_shadow: Could not connect to CIB: No such device or address
-=#=#=#= End test: Force-reset shadow instance with nonexistent CIB - No such object (105) =#=#=#=
-* Passed: crm_shadow - Force-reset shadow instance with nonexistent CIB
-=#=#=#= Begin test: Force-reset shadow instance with nonexistent CIB (XML) =#=#=#=
-
-
-
- crm_shadow: Could not connect to CIB: No such device or address
-
-
-
-=#=#=#= End test: Force-reset shadow instance with nonexistent CIB (XML) - No such object (105) =#=#=#=
-* Passed: crm_shadow - Force-reset shadow instance with nonexistent CIB (XML)
=#=#=#= Begin test: Switch to new shadow instance =#=#=#=
To switch to the named shadow instance, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Switch to new shadow instance - OK (0) =#=#=#=
* Passed: crm_shadow - Switch to new shadow instance
=#=#=#= Begin test: Switch to new shadow instance (XML) =#=#=#=
To switch to the named shadow instance, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= End test: Switch to new shadow instance (XML) - OK (0) =#=#=#=
* Passed: crm_shadow - Switch to new shadow instance (XML)
=#=#=#= Begin test: Switch to nonexistent shadow instance =#=#=#=
crm_shadow: Could not access shadow instance 'cts-cli': No such file or directory
=#=#=#= End test: Switch to nonexistent shadow instance - No such object (105) =#=#=#=
* Passed: crm_shadow - Switch to nonexistent shadow instance
=#=#=#= Begin test: Switch to nonexistent shadow instance (XML) =#=#=#=
crm_shadow: Could not access shadow instance 'cts-cli': No such file or directory
=#=#=#= End test: Switch to nonexistent shadow instance (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Switch to nonexistent shadow instance (XML)
=#=#=#= Begin test: Switch to nonexistent shadow instance (force) =#=#=#=
crm_shadow: Could not access shadow instance 'cts-cli': No such file or directory
=#=#=#= End test: Switch to nonexistent shadow instance (force) - No such object (105) =#=#=#=
* Passed: crm_shadow - Switch to nonexistent shadow instance (force)
=#=#=#= Begin test: Switch to nonexistent shadow instance (force) (XML) =#=#=#=
crm_shadow: Could not access shadow instance 'cts-cli': No such file or directory
=#=#=#= End test: Switch to nonexistent shadow instance (force) (XML) - No such object (105) =#=#=#=
* Passed: crm_shadow - Switch to nonexistent shadow instance (force) (XML)
diff --git a/cts/cts-cli.in b/cts/cts-cli.in
index 4a07688c87..f7fd04ea69 100644
--- a/cts/cts-cli.in
+++ b/cts/cts-cli.in
@@ -1,3415 +1,3405 @@
#!@PYTHON@
"""Regression tests for Pacemaker's command line tools."""
# pylint doesn't like the module name "cts-cli" which is an invalid complaint for this file
# but probably something we want to continue warning about elsewhere
# pylint: disable=invalid-name
# pacemaker imports need to come after we modify sys.path, which pylint will complain about.
# pylint: disable=wrong-import-position
# We know this is a very long file.
# pylint: disable=too-many-lines
__copyright__ = "Copyright 2024 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
import argparse
from contextlib import contextmanager
from datetime import datetime, timedelta
import fileinput
from functools import partial
from gettext import ngettext
from multiprocessing import Pool, cpu_count
import os
import pathlib
import re
from shutil import copyfile
import signal
from string import Formatter
import subprocess
import sys
from tempfile import NamedTemporaryFile, TemporaryDirectory, mkstemp
import types
# These imports allow running from a source checkout after running `make`.
if os.path.exists("@abs_top_srcdir@/python"):
sys.path.insert(0, "@abs_top_srcdir@/python")
# pylint: disable=comparison-of-constants,comparison-with-itself,condition-evals-to-constant
if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
sys.path.insert(0, "@abs_top_builddir@/python")
from pacemaker._cts.errors import XmlValidationError
from pacemaker._cts.validate import validate
from pacemaker.buildoptions import BuildOptions
from pacemaker.exitstatus import ExitStatus
# Individual tool tests are split out, but can also be accessed as a group with "tools"
tools_tests = ["cibadmin", "crm_attribute", "crm_standby", "crm_resource",
"crm_ticket", "crmadmin", "crm_shadow", "crm_verify"]
# The default list of tests to run, in the order they should be run
default_tests = ["access_render", "daemons", "dates", "error_codes"] + tools_tests + \
["crm_mon", "acls", "validity", "upgrade", "rules", "feature_set"]
other_tests = ["agents"]
# The directory containing this program
test_home = os.path.dirname(os.path.realpath(__file__))
# The name of the shadow CIB
SHADOW_NAME = "cts-cli"
# Arguments to pass to valgrind
VALGRIND_ARGS = ["-q", "--gen-suppressions=all", "--show-reachable=no", "--leak-check=full",
"--trace-children=no", "--time-stamp=yes", "--num-callers=20",
"--suppressions=%s/valgrind-pcmk.suppressions" % test_home]
class PluralFormatter(Formatter):
"""
Special string formatting class for selecting singular vs. plurals.
Use like so:
fmt = PluralFormatter()
print(fmt.format("{0} {0}:plural,test,tests} succeeded", n_tests))
"""
def format_field(self, value, format_spec):
"""Convert a value to a formatted representation."""
if format_spec.startswith("plural,"):
eles = format_spec.split(',')
if len(eles) == 2:
singular = eles[1]
plural = singular + "s"
else:
singular = eles[1]
plural = eles[2]
return ngettext(singular, plural, value)
return super().format_field(value, format_spec)
def apply_substitutions(s, extra=None):
"""Apply text substitutions to an input string and return it."""
substitutions = {
"cts_cli_data": "%s/cli" % test_home,
"shadow": SHADOW_NAME,
"test_home": test_home,
}
if extra is not None:
substitutions.update(extra)
return s.format(**substitutions)
def cleanup_shadow_dir():
"""Remove any previously created shadow CIB directory."""
subprocess.run(["crm_shadow", "--force", "--delete", SHADOW_NAME],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True)
def copy_existing_cib(existing):
"""
Generate a CIB by copying an existing one to a temporary location.
This is suitable for use with the cib_gen= parameter to the TestGroup class.
"""
(fp, new) = mkstemp(prefix="cts-cli.cib.xml.")
os.close(fp)
copyfile(apply_substitutions(existing), new)
return new
def current_cib():
"""Return the complete current CIB."""
with environ({"CIB_user": "root"}):
return subprocess.check_output(["cibadmin", "-Q"], encoding="utf-8")
def make_test_group(desc, cmd, classes, **kwargs):
"""
Create a TestGroup that replicates the same test for multiple classes.
The given description, cmd, and kwargs will be passed as arguments to each
Test subclass in the classes parameter. The resulting objects will then be
added to a TestGroup and returned.
The main purpose of this function is to be able to run the same test for
both text and XML formats without having to duplicate everything. Thus, the
cmd string may contain "{fmt}", which will have any --output-as= class
variable substituted in.
"""
tests = []
for c in classes:
obj = c(desc, apply_substitutions(cmd, extra={"fmt": c.format_args}),
**kwargs)
tests.append(obj)
return TestGroup(tests)
def create_shadow_cib(shadow_dir, create_empty=True, validate_with=None,
valgrind=False):
"""
Create a shadow CIB file.
Keyword arguments:
create_empty -- If True, the shadow CIB will be empty. Otherwise, the
shadow CIB will be a copy of the currently active
cluster configuration.
validate_with -- If not None, the schema version to validate the CIB
against
valgrind -- If True, run the create operation under valgrind
"""
args = ["crm_shadow", "--batch", "--force"]
if create_empty:
args += ["--create-empty", SHADOW_NAME]
else:
args += ["--create", SHADOW_NAME]
if validate_with is not None:
args += ["--validate-with", validate_with]
if valgrind:
args = ["valgrind"] + VALGRIND_ARGS + args
os.environ["CIB_shadow_dir"] = shadow_dir
os.environ["CIB_shadow"] = SHADOW_NAME
subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True)
delete_shadow_resource_defaults()
def delete_shadow_resource_defaults():
"""Clear out the rsc_defaults section from a shadow CIB file."""
# A newly created empty CIB might or might not have a rsc_defaults section
# depending on whether the --with-resource-stickiness-default configure
# option was used. To ensure regression tests behave the same either way,
# delete any rsc_defaults after creating or erasing a CIB.
subprocess.run(["cibadmin", "--delete", "--xml-text", ""],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True)
# The above command might or might not bump the CIB version, so reset it
# to ensure future changes result in the same version for comparison.
reset_shadow_cib_version()
def reset_shadow_cib_version():
"""Set various version numbers in a shadow CIB file back to 0."""
with fileinput.input(files=[shadow_path()], inplace=True) as f:
for line in f:
line = re.sub('epoch="[0-9]*"', 'epoch="1"', line)
line = re.sub('num_updates="[0-9]*"', 'num_updates="0"', line)
line = re.sub('admin_epoch="[0-9]*"', 'admin_epoch="0"', line)
print(line, end='')
def run_cmd_list(cmds):
"""
Run one or more shell commands.
cmds can be:
* A string
* A Python function
* A list of the above
Raises subprocess.CalledProcessError on error.
"""
if cmds is None:
return
if isinstance(cmds, (str, types.FunctionType)):
cmds = [cmds]
for c in cmds:
if isinstance(c, types.FunctionType):
c()
else:
subprocess.run(apply_substitutions(c), stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True, universal_newlines=True, check=True)
def sanitize_output(s):
"""
Replace content in the output expected to change between test runs.
This is stuff like version numbers, timestamps, source line numbers,
build options, system names and messages, etc.
"""
# A list of tuples of regular expressions and their replacements.
replacements = [
(r'Created new pacemaker-.* configuration', r'Created new pacemaker configuration'),
(r'Device not configured', r'No such device or address'),
(r'^Entity: line [0-9]+: ', r''),
(r'Last change: .*', r'Last change:'),
(r'Last updated: .*', r'Last updated:'),
(r'^Migration will take effect until: .*', r'Migration will take effect until:'),
(r'(\* Possible values.*: .*)\(default: [^)]*\)', r'\1(default: )'),
(r"""-X '.*'""", r"""-X '...'"""),
(r' api-version="[^"]*"', r' api-version="X"'),
(r'\(apply_upgrade@.*\.c:[0-9]+\)', r'apply_upgrade'),
(r'\(invert_action@.*\.c:[0-9]+\)', r'invert_action'),
(r'\(pcmk__update_schema@.*\.c:[0-9]+\)', r'pcmk__update_schema'),
(r'(
"""
# Create a test CIB that has ACL roles
basic_tests = [
Test("Configure some ACLs", "cibadmin -M -o acls -p", update_cib=True,
stdin=acl_cib),
Test("Enable ACLs", "crm_attribute -n enable-acl -v true",
update_cib=True),
# Run cibadmin --show-access on the test CIB as an ACL-restricted user
Test("An instance of ACLs render (into color)",
"cibadmin --force --show-access=color -Q --user tony"),
Test("An instance of ACLs render (into namespacing)",
"cibadmin --force --show-access=namespace -Q --user tony"),
Test("An instance of ACLs render (into text)",
"cibadmin --force --show-access=text -Q --user tony"),
]
return [
ShadowTestGroup(basic_tests),
]
class DaemonsRegressionTest(RegressionTest):
"""A class for testing command line options of pacemaker daemons."""
@property
def name(self):
"""Return the name of this regression test."""
return "daemons"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
return [
Test("Get CIB manager metadata", "pacemaker-based metadata"),
Test("Get controller metadata", "pacemaker-controld metadata"),
Test("Get fencer metadata", "pacemaker-fenced metadata"),
Test("Get scheduler metadata", "pacemaker-schedulerd metadata"),
]
class DatesRegressionTest(RegressionTest):
"""A class for testing handling of ISO8601 dates."""
@property
def name(self):
"""Return the name of this regression test."""
return "dates"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
invalid_periods = [
"",
"2019-01-01 00:00:00Z", # Start with no end
"2019-01-01 00:00:00Z/", # Start with only a trailing slash
"PT2S/P1M", # Two durations
"2019-13-01 00:00:00Z/P1M", # Out-of-range month
"20191077T15/P1M", # Out-of-range day
"2019-10-01T25:00:00Z/P1M", # Out-of-range hour
"2019-10-01T24:00:01Z/P1M", # Hour 24 with anything but :00:00
"PT5H/20191001T007000Z", # Out-of-range minute
"2019-10-01 00:00:80Z/P1M", # Out-of-range second
"2019-10-01 00:00:10 +25:00/P1M", # Out-of-range offset hour
"20191001T000010 -00:61/P1M", # Out-of-range offset minute
"P1Y/2019-02-29 00:00:00Z", # Feb. 29 in non-leap-year
"2019-01-01 00:00:00Z/P", # Duration with no values
"P1Z/2019-02-20 00:00:00Z", # Invalid duration unit
"P1YM/2019-02-20 00:00:00Z", # No number for duration unit
]
# Ensure invalid period specifications are rejected
invalid_period_tests = []
for p in invalid_periods:
invalid_period_tests.append(Test("Invalid period - [%s]" % p,
"iso8601 -p '%s'" % p,
expected_rc=ExitStatus.INVALID_PARAM))
year_tests = []
for y in ["06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "40"]:
year_tests.extend([
Test("20%s-W01-7" % y,
"iso8601 -d '20%s-W01-7 00Z'" % y),
Test("20%s-W01-7 - round-trip" % y,
"iso8601 -d '20%s-W01-7 00Z' -W -E '20%s-W01-7 00:00:00Z'" % (y, y)),
Test("20%s-W01-1" % y,
"iso8601 -d '20%s-W01-1 00Z'" % y),
Test("20%s-W01-1 - round-trip" % y,
"iso8601 -d '20%s-W01-1 00Z' -W -E '20%s-W01-1 00:00:00Z'" % (y, y))
])
return invalid_period_tests + [
make_test_group("'2005-040/2005-043' period", "iso8601 {fmt} -p '2005-040/2005-043'",
[Test, ValidatingTest]),
Test("2014-01-01 00:30:00 - 1 Hour",
"iso8601 -d '2014-01-01 00:30:00Z' -D P-1H -E '2013-12-31 23:30:00Z'"),
Test("Valid date - Feb 29 in leap year",
"iso8601 -d '2020-02-29 00:00:00Z' -E '2020-02-29 00:00:00Z'"),
Test("Valid date - using 'T' and offset",
"iso8601 -d '20191201T131211 -05:00' -E '2019-12-01 18:12:11Z'"),
Test("24:00:00 equivalent to 00:00:00 of next day",
"iso8601 -d '2019-12-31 24:00:00Z' -E '2020-01-01 00:00:00Z'"),
] + year_tests + [
make_test_group("2009-W53-07",
"iso8601 {fmt} -d '2009-W53-7 00:00:00Z' -W -E '2009-W53-7 00:00:00Z'",
[Test, ValidatingTest]),
Test("epoch + 2 Years 5 Months 6 Minutes",
"iso8601 -d 'epoch' -D P2Y5MT6M -E '1972-06-01 00:06:00Z'"),
Test("2009-01-31 + 1 Month",
"iso8601 -d '20090131T000000Z' -D P1M -E '2009-02-28 00:00:00Z'"),
Test("2009-01-31 + 2 Months",
"iso8601 -d '2009-01-31 00:00:00Z' -D P2M -E '2009-03-31 00:00:00Z'"),
Test("2009-01-31 + 3 Months",
"iso8601 -d '2009-01-31 00:00:00Z' -D P3M -E '2009-04-30 00:00:00Z'"),
make_test_group("2009-03-31 - 1 Month",
"iso8601 {fmt} -d '2009-03-31 01:00:00 +01:00' -D P-1M -E '2009-02-28 00:00:00Z'",
[Test, ValidatingTest]),
make_test_group("2038-01-01 + 3 Months",
"iso8601 {fmt} -d '2038-01-01 00:00:00Z' -D P3M -E '2038-04-01 00:00:00Z'",
[Test, ValidatingTest]),
]
class ErrorCodeRegressionTest(RegressionTest):
"""A class for testing error code reporting."""
@property
def name(self):
"""Return the name of this regression test."""
return "error_codes"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
# Legacy return codes
#
# Don't test unknown legacy code. FreeBSD includes a colon in strerror(),
# while other distros do not.
legacy_tests = [
make_test_group("Get legacy return code", "crm_error {fmt} 201",
[Test, ValidatingTest]),
make_test_group("Get legacy return code (with name)", "crm_error -n {fmt} 201",
[Test, ValidatingTest]),
make_test_group("Get multiple legacy return codes", "crm_error {fmt} 201 202",
[Test, ValidatingTest]),
make_test_group("Get multiple legacy return codes (with names)",
"crm_error -n {fmt} 201 202",
[Test, ValidatingTest]),
# We can only rely on our custom codes, so we'll spot-check codes 201-209
Test("List legacy return codes (spot check)",
"crm_error -l | grep 20[1-9]"),
ValidatingTest("List legacy return codes (spot check)",
"crm_error -l --output-as=xml | grep -Ev '&1"),
Test("Require --force for CIB erasure", "cibadmin -E",
expected_rc=ExitStatus.UNSAFE, update_cib=True),
Test("Allow CIB erasure with --force", "cibadmin -E --force"),
# Verify the output after erasure
Test("Query CIB", "cibadmin -Q",
setup=delete_shadow_resource_defaults,
update_cib=True),
]
# Add some stuff to the empty CIB so we know that erasing it did something.
basic_tests_setup = [
"""cibadmin -C -o nodes --xml-text ''""",
"""cibadmin -C -o crm_config --xml-text ''""",
"""cibadmin -C -o resources --xml-text ''"""
]
return [
ShadowTestGroup(basic_tests, setup=basic_tests_setup),
]
class CrmAttributeRegressionTest(RegressionTest):
"""A class for testing crm_attribute."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_attribute"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
options_tests = [
make_test_group("List all available options (invalid type)",
"crm_attribute --list-options=asdf {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.USAGE),
make_test_group("List non-advanced cluster options",
"crm_attribute --list-options=cluster {fmt}",
[Test, ValidatingTest]),
make_test_group("List all available cluster options",
"crm_attribute --list-options=cluster --all {fmt}",
[Test, ValidatingTest]),
Test("Return usage error if both -p and OCF_RESOURCE_INSTANCE are empty strings",
"crm_attribute -N cluster01 -p '' -G",
expected_rc=ExitStatus.USAGE),
]
value_update_tests = [
Test("Query the value of an attribute that does not exist",
"crm_attribute -n ABCD --query --quiet",
expected_rc=ExitStatus.NOSUCH),
Test("Configure something before erasing",
"crm_attribute -n test_attr -v 5", update_cib=True),
Test("Test '++' XML attribute update syntax",
"""cibadmin -M --score --xml-text=''""",
update_cib=True),
Test("Test '+=' XML attribute update syntax",
"""cibadmin -M --score --xml-text=''""",
update_cib=True),
make_test_group("Test '++' nvpair value update syntax",
"crm_attribute -n test_attr -v 'value++' --score {fmt}",
[Test, ValidatingTest], update_cib=True),
make_test_group("Test '+=' nvpair value update syntax",
"crm_attribute -n test_attr -v 'value+=2' --score {fmt}",
[Test, ValidatingTest], update_cib=True),
Test("Test '++' XML attribute update syntax (--score not set)",
"""cibadmin -M --xml-text=''""",
update_cib=True),
Test("Test '+=' XML attribute update syntax (--score not set)",
"""cibadmin -M --xml-text=''""",
update_cib=True),
make_test_group("Test '++' nvpair value update syntax (--score not set)",
"crm_attribute -n test_attr -v 'value++' {fmt}",
[Test, ValidatingTest], update_cib=True),
make_test_group("Test '+=' nvpair value update syntax (--score not set)",
"crm_attribute -n test_attr -v 'value+=2' {fmt}",
[Test, ValidatingTest], update_cib=True),
]
query_set_tests = [
Test("Set cluster option", "crm_attribute -n cluster-delay -v 60s",
update_cib=True),
Test("Query new cluster option",
"cibadmin -Q -o crm_config | grep cib-bootstrap-options-cluster-delay"),
Test("Set no-quorum policy",
"crm_attribute -n no-quorum-policy -v ignore", update_cib=True),
Test("Delete nvpair",
"""cibadmin -D -o crm_config --xml-text ''""",
update_cib=True),
Test("Create operation should fail",
"""cibadmin -C -o crm_config --xml-text ''""",
expected_rc=ExitStatus.EXISTS, update_cib=True),
Test("Modify cluster options section",
"""cibadmin -M -o crm_config --xml-text ''""",
update_cib=True),
Test("Query updated cluster option",
"cibadmin -Q -o crm_config | grep cib-bootstrap-options-cluster-delay",
update_cib=True),
Test("Set duplicate cluster option",
"crm_attribute -n cluster-delay -v 40s -s duplicate",
update_cib=True),
Test("Setting multiply defined cluster option should fail",
"crm_attribute -n cluster-delay -v 30s",
expected_rc=ExitStatus.MULTIPLE, update_cib=True),
Test("Set cluster option with -s",
"crm_attribute -n cluster-delay -v 30s -s duplicate",
update_cib=True),
Test("Delete cluster option with -i",
"crm_attribute -n cluster-delay -D -i cib-bootstrap-options-cluster-delay",
update_cib=True),
Test("Create node1 and bring it online",
"crm_simulate --live-check --in-place --node-up=node1",
update_cib=True),
Test("Create node attribute",
"crm_attribute -n ram -v 1024M -N node1 -t nodes",
update_cib=True),
Test("Query new node attribute",
"cibadmin -Q -o nodes | grep node1-ram",
update_cib=True),
Test("Create second node attribute",
"crm_attribute -n rattr -v XYZ -N node1 -t nodes",
update_cib=True),
Test("Query node attributes by pattern",
"crm_attribute -t nodes -P 'ra.*' -N node1 --query"),
Test("Update node attributes by pattern",
"crm_attribute -t nodes -P 'rat.*' -N node1 -v 10",
update_cib=True),
Test("Delete node attributes by pattern",
"crm_attribute -t nodes -P 'rat.*' -N node1 -D",
update_cib=True),
Test("Set a transient (fail-count) node attribute",
"crm_attribute -n fail-count-foo -v 3 -N node1 -t status",
update_cib=True),
Test("Query a fail count", "crm_failcount --query -r foo -N node1",
update_cib=True),
Test("Show node attributes with crm_simulate",
"crm_simulate --live-check --show-attrs"),
Test("Set a second transient node attribute",
"crm_attribute -n fail-count-bar -v 5 -N node1 -t status",
update_cib=True),
Test("Query transient node attributes by pattern",
"crm_attribute -t status -P fail-count -N node1 --query"),
Test("Update transient node attributes by pattern",
"crm_attribute -t status -P fail-count -N node1 -v 10",
update_cib=True),
Test("Delete transient node attributes by pattern",
"crm_attribute -t status -P fail-count -N node1 -D",
update_cib=True),
Test("crm_attribute given invalid delete usage",
"crm_attribute -t nodes -N node1 -D",
expected_rc=ExitStatus.USAGE),
Test("Set a utilization node attribute",
"crm_attribute -n cpu -v 1 -N node1 -z",
update_cib=True),
Test("Query utilization node attribute",
"crm_attribute --query -n cpu -N node1 -z"),
# This update will fail because it has version numbers
Test("Replace operation should fail",
"""cibadmin -Q | sed -e 's/epoch="[^"]*"/epoch="1"/' | cibadmin -R -p""",
expected_rc=ExitStatus.OLD),
]
promotable_tests = [
make_test_group("Query a nonexistent promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -G {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
make_test_group("Delete a nonexistent promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -D {fmt}",
[Test, ValidatingTest]),
make_test_group("Query after deleting a nonexistent promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -G {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
make_test_group("Update a nonexistent promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -v 1 {fmt}",
[Test, ValidatingTest]),
make_test_group("Query after updating a nonexistent promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -G {fmt}",
[Test, ValidatingTest]),
make_test_group("Update an existing promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -v 5 {fmt}",
[Test, ValidatingTest]),
make_test_group("Query after updating an existing promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -G {fmt}",
[Test, ValidatingTest]),
make_test_group("Delete an existing promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -D {fmt}",
[Test, ValidatingTest]),
make_test_group("Query after deleting an existing promotable score attribute",
"crm_attribute -N cluster01 -p promotable-rsc -G {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
]
# Test for an issue with legacy command line parsing when the resource is
# specified in the environment (CLBZ#5509)
ocf_rsc_instance_tests = [
make_test_group("Update a promotable score attribute to -INFINITY",
"crm_attribute -N cluster01 -p -v -INFINITY {fmt}",
[Test, ValidatingTest],
env={"OCF_RESOURCE_INSTANCE": "promotable-rsc"}),
make_test_group("Query after updating a promotable score attribute to -INFINITY",
"crm_attribute -N cluster01 -p -G {fmt}",
[Test, ValidatingTest],
env={"OCF_RESOURCE_INSTANCE": "promotable-rsc"}),
Test("Try OCF_RESOURCE_INSTANCE if -p is specified with an empty string",
"crm_attribute -N cluster01 -p '' -G",
env={"OCF_RESOURCE_INSTANCE": "promotable-rsc"}),
]
return options_tests + [
ShadowTestGroup(value_update_tests),
ShadowTestGroup(query_set_tests),
TestGroup(promotable_tests + ocf_rsc_instance_tests,
env={"OCF_RESOURCE_INSTANCE": "promotable-rsc"},
cib_gen=partial(copy_existing_cib, "{cts_cli_data}/crm_mon.xml")),
]
class CrmStandbyRegressionTest(RegressionTest):
"""A class for testing crm_standby."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_standby"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
basic_tests = [
Test("Default standby value", "crm_standby -N node1 -G"),
Test("Set standby status", "crm_standby -N node1 -v true",
update_cib=True),
Test("Query standby value", "crm_standby -N node1 -G"),
Test("Delete standby value", "crm_standby -N node1 -D",
update_cib=True),
]
return [
ShadowTestGroup(basic_tests,
setup="""cibadmin -C -o nodes --xml-text ''"""),
]
class CrmResourceRegressionTest(RegressionTest):
"""A class for testing crm_resource."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_resource"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
options_tests = [
Test("crm_resource run with extra arguments", "crm_resource foo bar",
expected_rc=ExitStatus.USAGE),
Test("List all available resource options (invalid type)",
"crm_resource --list-options=asdf",
expected_rc=ExitStatus.USAGE),
Test("List all available resource options (invalid type)",
"crm_resource --list-options=asdf --output-as=xml",
expected_rc=ExitStatus.USAGE),
make_test_group("List non-advanced primitive meta-attributes",
"crm_resource --list-options=primitive {fmt}",
[Test, ValidatingTest]),
make_test_group("List all available primitive meta-attributes",
"crm_resource --list-options=primitive --all {fmt}",
[Test, ValidatingTest]),
make_test_group("List non-advanced fencing parameters",
"crm_resource --list-options=fencing {fmt}",
[Test, ValidatingTest]),
make_test_group("List all available fencing parameters",
"crm_resource --list-options=fencing --all {fmt}",
[Test, ValidatingTest]),
]
basic_tests = [
Test("Create a resource",
"""cibadmin -C -o resources --xml-text ''""",
update_cib=True),
Test("crm_resource given both -r and resource config",
"crm_resource -r xyz --class ocf --provider pacemaker --agent Dummy",
expected_rc=ExitStatus.USAGE),
Test("crm_resource given resource config with invalid action",
"crm_resource --class ocf --provider pacemaker --agent Dummy -D",
expected_rc=ExitStatus.USAGE),
Test("Create a resource meta attribute",
"crm_resource -r dummy --meta -p is-managed -v false",
update_cib=True),
Test("Query a resource meta attribute",
"crm_resource -r dummy --meta -g is-managed",
update_cib=True),
Test("Remove a resource meta attribute",
"crm_resource -r dummy --meta -d is-managed",
update_cib=True),
ValidatingTest("Create another resource meta attribute",
"crm_resource -r dummy --meta -p target-role -v Stopped --output-as=xml"),
ValidatingTest("Show why a resource is not running",
"crm_resource -Y -r dummy --output-as=xml"),
ValidatingTest("Remove another resource meta attribute",
"crm_resource -r dummy --meta -d target-role --output-as=xml"),
ValidatingTest("Get a non-existent attribute from a resource element",
"crm_resource -r dummy --get-parameter nonexistent --element --output-as=xml"),
make_test_group("Get a non-existent attribute from a resource element",
"crm_resource -r dummy --get-parameter nonexistent --element {fmt}",
[Test, ValidatingTest], update_cib=True),
Test("Get an existent attribute from a resource element",
"crm_resource -r dummy --get-parameter class --element",
update_cib=True),
ValidatingTest("Set a non-existent attribute for a resource element",
"crm_resource -r dummy --set-parameter=description -v test_description --element --output-as=xml",
update_cib=True),
ValidatingTest("Set an existent attribute for a resource element",
"crm_resource -r dummy --set-parameter=description -v test_description --element --output-as=xml",
update_cib=True),
ValidatingTest("Delete an existent attribute for a resource element",
"crm_resource -r dummy -d description --element --output-as=xml",
update_cib=True),
ValidatingTest("Delete a non-existent attribute for a resource element",
"crm_resource -r dummy -d description --element --output-as=xml",
update_cib=True),
Test("Set a non-existent attribute for a resource element",
"crm_resource -r dummy --set-parameter=description -v test_description --element",
update_cib=True),
Test("Set an existent attribute for a resource element",
"crm_resource -r dummy --set-parameter=description -v test_description --element",
update_cib=True),
Test("Delete an existent attribute for a resource element",
"crm_resource -r dummy -d description --element",
update_cib=True),
Test("Delete a non-existent attribute for a resource element",
"crm_resource -r dummy -d description --element",
update_cib=True),
Test("Create a resource attribute", "crm_resource -r dummy -p delay -v 10s",
update_cib=True),
make_test_group("List the configured resources", "crm_resource -L {fmt}",
[Test, ValidatingTest], update_cib=True),
Test("Implicitly list the configured resources", "crm_resource"),
Test("List IDs of instantiated resources", "crm_resource -l"),
make_test_group("Show XML configuration of resource", "crm_resource -q -r dummy {fmt}",
[Test, ValidatingTest]),
Test("Require a destination when migrating a resource that is stopped",
"crm_resource -r dummy -M",
update_cib=True, expected_rc=ExitStatus.USAGE),
Test("Don't support migration to non-existent locations",
"crm_resource -r dummy -M -N i.do.not.exist",
update_cib=True, expected_rc=ExitStatus.NOSUCH),
Test("Create a fencing resource",
"""cibadmin -C -o resources --xml-text ''""",
update_cib=True),
Test("Bring resources online", "crm_simulate --live-check --in-place",
update_cib=True),
Test("Try to move a resource to its existing location",
"crm_resource -r dummy --move --node node1",
update_cib=True, expected_rc=ExitStatus.EXISTS),
Test("Try to move a resource that doesn't exist",
"crm_resource -r xyz --move --node node1",
expected_rc=ExitStatus.NOSUCH),
Test("Move a resource from its existing location",
"crm_resource -r dummy --move",
update_cib=True),
Test("Clear out constraints generated by --move",
"crm_resource -r dummy --clear",
update_cib=True),
Test("Ban a resource on unknown node",
"crm_resource -r dummy -B -N host1",
expected_rc=ExitStatus.NOSUCH),
Test("Create two more nodes and bring them online",
"crm_simulate --live-check --in-place --node-up=node2 --node-up=node3",
update_cib=True),
Test("Ban dummy from node1", "crm_resource -r dummy -B -N node1",
update_cib=True),
Test("Show where a resource is running", "crm_resource -r dummy -W"),
Test("Show constraints on a resource", "crm_resource -a -r dummy"),
ValidatingTest("Ban dummy from node2",
"crm_resource -r dummy -B -N node2 --output-as=xml",
update_cib=True),
Test("Relocate resources due to ban",
"crm_simulate --live-check --in-place -S",
update_cib=True),
ValidatingTest("Move dummy to node1",
"crm_resource -r dummy -M -N node1 --output-as=xml",
update_cib=True),
Test("Clear implicit constraints for dummy on node2",
"crm_resource -r dummy -U -N node2",
update_cib=True),
Test("Drop the status section",
"cibadmin -R -o status --xml-text ''"),
Test("Create a clone",
"""cibadmin -C -o resources --xml-text ''"""),
Test("Create a resource meta attribute",
"crm_resource -r test-primitive --meta -p is-managed -v false",
update_cib=True),
Test("Create a resource meta attribute in the primitive",
"crm_resource -r test-primitive --meta -p is-managed -v false --force",
update_cib=True),
Test("Update resource meta attribute with duplicates",
"crm_resource -r test-clone --meta -p is-managed -v true",
update_cib=True),
Test("Update resource meta attribute with duplicates (force clone)",
"crm_resource -r test-clone --meta -p is-managed -v true --force",
update_cib=True),
Test("Update child resource meta attribute with duplicates",
"crm_resource -r test-primitive --meta -p is-managed -v false",
update_cib=True),
Test("Delete resource meta attribute with duplicates",
"crm_resource -r test-clone --meta -d is-managed",
update_cib=True),
Test("Delete resource meta attribute in parent",
"crm_resource -r test-primitive --meta -d is-managed",
update_cib=True),
Test("Create a resource meta attribute in the primitive",
"crm_resource -r test-primitive --meta -p is-managed -v false --force",
update_cib=True),
Test("Update existing resource meta attribute",
"crm_resource -r test-clone --meta -p is-managed -v true",
update_cib=True),
Test("Create a resource meta attribute in the parent",
"crm_resource -r test-clone --meta -p is-managed -v true --force",
update_cib=True),
Test("Delete resource parent meta attribute (force)",
"crm_resource -r test-clone --meta -d is-managed --force",
update_cib=True),
# Restore meta-attributes before running this test
Test("Delete resource child meta attribute",
"crm_resource -r test-primitive --meta -d is-managed",
setup=["crm_resource -r test-primitive --meta -p is-managed -v true --force",
"crm_resource -r test-clone --meta -p is-managed -v true --force"],
update_cib=True),
Test("Create the dummy-group resource group",
"""cibadmin -C -o resources --xml-text '"""
""""""
""""""
"""'""",
update_cib=True),
Test("Create a resource meta attribute in dummy1",
"crm_resource -r dummy1 --meta -p is-managed -v true",
update_cib=True),
Test("Create a resource meta attribute in dummy-group",
"crm_resource -r dummy-group --meta -p is-managed -v false",
update_cib=True),
Test("Delete the dummy-group resource group",
"cibadmin -D -o resources --xml-text ''",
update_cib=True),
Test("Specify a lifetime when moving a resource",
"crm_resource -r dummy --move --node node2 --lifetime=PT1H",
update_cib=True),
Test("Try to move a resource previously moved with a lifetime",
"crm_resource -r dummy --move --node node1",
update_cib=True),
Test("Ban dummy from node1 for a short time",
"crm_resource -r dummy -B -N node1 --lifetime=PT1S",
update_cib=True),
Test("Remove expired constraints",
"sleep 2 && crm_resource --clear --expired",
update_cib=True),
# Clear has already been tested elsewhere, but we need to get rid of the
# constraints so testing delete works. It won't delete if there's still
# a reference to the resource somewhere.
Test("Clear all implicit constraints for dummy",
"crm_resource -r dummy -U",
update_cib=True),
Test("Set a node health strategy",
"crm_attribute -n node-health-strategy -v migrate-on-red",
update_cib=True),
Test("Set a node health attribute",
"crm_attribute -N node3 -n '#health-cts-cli' -v red",
update_cib=True),
ValidatingTest("Show why a resource is not running on an unhealthy node",
"crm_resource -N node3 -Y -r dummy --output-as=xml"),
Test("Delete a resource",
"crm_resource -D -r dummy -t primitive",
update_cib=True),
]
constraint_tests = []
for rsc in ["prim1", "prim2", "prim3", "prim4", "prim5", "prim6", "prim7",
"prim8", "prim9", "prim10", "prim11", "prim12", "prim13",
"group", "clone"]:
constraint_tests.extend([
make_test_group("Check locations and constraints for %s" % rsc,
"crm_resource -a -r %s {fmt}" % rsc,
[Test, ValidatingTest]),
make_test_group("Recursively check locations and constraints for %s" % rsc,
"crm_resource -A -r %s {fmt}" % rsc,
[Test, ValidatingTest]),
])
constraint_tests.extend([
Test("Check locations and constraints for group member (referring to group)",
"crm_resource -a -r gr2"),
Test("Check locations and constraints for group member (without referring to group)",
"crm_resource -a -r gr2 --force"),
])
colocation_tests = [
ValidatingTest("Set a meta-attribute for primitive and resources colocated with it",
"crm_resource -r prim5 --meta --set-parameter=target-role -v Stopped --recursive --output-as=xml"),
Test("Set a meta-attribute for group and resource colocated with it",
"crm_resource -r group --meta --set-parameter=target-role -v Stopped --recursive"),
ValidatingTest("Set a meta-attribute for clone and resource colocated with it",
"crm_resource -r clone --meta --set-parameter=target-role -v Stopped --recursive --output-as=xml"),
]
digest_tests = [
ValidatingTest("Show resource digests",
"crm_resource --digests -r rsc1 -N node1 --output-as=xml"),
Test("Show resource digests with overrides",
"crm_resource --digests -r rsc1 -N node1 --output-as=xml CRM_meta_interval=10000 CRM_meta_timeout=20000"),
make_test_group("Show resource operations", "crm_resource --list-operations {fmt}",
[Test, ValidatingTest]),
]
basic2_tests = [
make_test_group("List a promotable clone resource",
"crm_resource --locate -r promotable-clone {fmt}",
[Test, ValidatingTest]),
make_test_group("List the primitive of a promotable clone resource",
"crm_resource --locate -r promotable-rsc {fmt}",
[Test, ValidatingTest]),
make_test_group("List a single instance of a promotable clone resource",
"crm_resource --locate -r promotable-rsc:0 {fmt}",
[Test, ValidatingTest]),
make_test_group("List another instance of a promotable clone resource",
"crm_resource --locate -r promotable-rsc:1 {fmt}",
[Test, ValidatingTest]),
Test("Try to move an instance of a cloned resource",
"crm_resource -r promotable-rsc:0 --move --node node1",
expected_rc=ExitStatus.INVALID_PARAM),
]
basic_tests_setup = [
"crm_attribute -n no-quorum-policy -v ignore",
"crm_simulate --live-check --in-place --node-up=node1"
]
return options_tests + [
ShadowTestGroup(basic_tests, setup=basic_tests_setup),
TestGroup(constraint_tests, env={"CIB_file": "{cts_cli_data}/constraints.xml"}),
TestGroup(colocation_tests, cib_gen=partial(copy_existing_cib, "{cts_cli_data}/constraints.xml")),
TestGroup(digest_tests, env={"CIB_file": "{cts_cli_data}/crm_resource_digests.xml"}),
TestGroup(basic2_tests, env={"CIB_file": "{cts_cli_data}/crm_mon.xml"}),
ValidatingTest("Check that CIB_file=\"-\" works - crm_resource",
"crm_resource --digests -r rsc1 -N node1 --output-as=xml",
env={"CIB_file": "-"},
stdin=pathlib.Path(apply_substitutions("{cts_cli_data}/crm_resource_digests.xml"))),
]
class CrmTicketRegressionTest(RegressionTest):
"""A class for testing crm_ticket."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_ticket"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
basic_tests = [
Test("Default ticket granted state",
"crm_ticket -t ticketA -G granted -d false"),
Test("Set ticket granted state", "crm_ticket -t ticketA -r --force",
update_cib=True),
make_test_group("List ticket IDs", "crm_ticket -w {fmt}",
[Test, ValidatingTest]),
make_test_group("Query ticket state", "crm_ticket -t ticketA -q {fmt}",
[Test, ValidatingTest]),
make_test_group("Query ticket granted state",
"crm_ticket -t ticketA -G granted {fmt}",
[Test, ValidatingTest]),
Test("Delete ticket granted state",
"crm_ticket -t ticketA -D granted --force",
update_cib=True),
Test("Make a ticket standby", "crm_ticket -t ticketA -s",
update_cib=True),
Test("Query ticket standby state", "crm_ticket -t ticketA -G standby"),
Test("Activate a ticket", "crm_ticket -t ticketA -a",
update_cib=True),
make_test_group("List ticket details", "crm_ticket -L -t ticketA {fmt}",
[Test, ValidatingTest]),
Test("Add a second ticket", "crm_ticket -t ticketB -G granted -d false",
update_cib=True),
Test("Set second ticket granted state",
"crm_ticket -t ticketB -r --force",
update_cib=True),
make_test_group("List tickets", "crm_ticket -l {fmt}",
[Test, ValidatingTest]),
Test("Delete second ticket",
"""cibadmin --delete --xml-text ''""",
update_cib=True),
Test("Delete ticket standby state", "crm_ticket -t ticketA -D standby",
update_cib=True),
Test("Add a constraint to a ticket",
"""cibadmin -C -o constraints --xml-text ''""",
update_cib=True),
make_test_group("Query ticket constraints", "crm_ticket -t ticketA -c {fmt}",
[Test, ValidatingTest]),
Test("Delete ticket constraint",
"""cibadmin --delete --xml-text ''""",
update_cib=True),
]
basic_tests_setup = [
"""cibadmin -C -o crm_config --xml-text ''""",
"""cibadmin -C -o resources --xml-text ''"""
]
return [
ShadowTestGroup(basic_tests, setup=basic_tests_setup),
]
class CrmadminRegressionTest(RegressionTest):
"""A class for testing crmadmin."""
@property
def name(self):
"""Return the name of this regression test."""
return "crmadmin"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
basic_tests = [
make_test_group("List all nodes", "crmadmin -N {fmt}",
[Test, ValidatingTest]),
make_test_group("Minimally list all nodes", "crmadmin -N -q {fmt}",
[Test, ValidatingTest]),
Test("List all nodes as bash exports", "crmadmin -N -B"),
make_test_group("List cluster nodes",
"crmadmin -N cluster {fmt}",
[Test, ValidatingTest]),
make_test_group("List guest nodes",
"crmadmin -N guest {fmt}",
[Test, ValidatingTest]),
make_test_group("List remote nodes",
"crmadmin -N remote {fmt}",
[Test, ValidatingTest]),
make_test_group("List cluster,remote nodes",
"crmadmin -N cluster,remote {fmt}",
[Test, ValidatingTest]),
make_test_group("List guest,remote nodes",
"crmadmin -N guest,remote {fmt}",
[Test, ValidatingTest]),
]
return [
TestGroup(basic_tests,
env={"CIB_file": "{cts_cli_data}/crmadmin-cluster-remote-guest-nodes.xml"}),
Test("Check that CIB_file=\"-\" works", "crmadmin -N",
env={"CIB_file": "-"},
stdin=pathlib.Path(apply_substitutions("{cts_cli_data}/crmadmin-cluster-remote-guest-nodes.xml"))),
]
class CrmShadowRegressionTest(RegressionTest):
"""A class for testing crm_shadow."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_shadow"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
no_instance_tests = [
make_test_group("Get active shadow instance (no active instance)",
"crm_shadow --which {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
make_test_group("Get active shadow instance's file name (no active instance)",
"crm_shadow --file {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
make_test_group("Get active shadow instance's contents (no active instance)",
"crm_shadow --display {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
make_test_group("Get active shadow instance's diff (no active instance)",
"crm_shadow --diff {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
]
# Create new shadow instance based on active CIB
# Don't use create_shadow_cib() here; test explicitly
new_instance_tests = [
make_test_group("Create copied shadow instance",
"crm_shadow --create {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force"),
# Query shadow instance based on active CIB
make_test_group("Get active shadow instance (copied)",
"crm_shadow --which {fmt}",
[Test, ValidatingTest]),
make_test_group("Get active shadow instance's file name (copied)",
"crm_shadow --file {fmt}",
[Test, ValidatingTest]),
make_test_group("Get active shadow instance's contents (copied)",
"crm_shadow --display {fmt}",
[Test, ValidatingTest]),
make_test_group("Get active shadow instance's diff (copied)",
"crm_shadow --diff {fmt}",
[Test, ValidatingTest]),
]
# Make some changes to the shadow file
modify_cib = """export CIB_file=$(crm_shadow --file) && """ \
"""cibadmin --modify --xml-text '' && """ \
"""cibadmin --delete --xml-text '' && """ \
"""cibadmin --create -o resources --xml-text '' && """ \
"""cibadmin --create -o status --xml-text ''"""
more_tests = [
# We can't use make_test_group() here because we only want to run
# the modify_cib setup code once, and make_test_group will pass all
# kwargs to every instance it creates.
Test("Get active shadow instance's diff (after changes)",
"crm_shadow --diff",
setup=modify_cib, expected_rc=ExitStatus.ERROR),
ValidatingTest("Get active shadow instance's diff (after changes)",
"crm_shadow --diff --output-as=xml",
expected_rc=ExitStatus.ERROR),
TestGroup([
# Commit the modified shadow CIB to a temp active CIB file
Test("Commit shadow instance",
"crm_shadow --commit {shadow}",
expected_rc=ExitStatus.USAGE),
Test("Commit shadow instance (force)",
"crm_shadow --commit {shadow} --force"),
Test("Get active shadow instance's diff (after commit)",
"crm_shadow --diff",
expected_rc=ExitStatus.ERROR),
Test("Commit shadow instance (force) (all)",
"crm_shadow --commit {shadow} --force --all"),
Test("Get active shadow instance's diff (after commit all)",
"crm_shadow --diff",
expected_rc=ExitStatus.ERROR),
], cib_gen=partial(copy_existing_cib, "{cts_cli_data}/crm_mon.xml")),
TestGroup([
# Repeat sequence with XML output
ValidatingTest("Commit shadow instance",
"crm_shadow --commit {shadow} --output-as=xml",
expected_rc=ExitStatus.USAGE),
ValidatingTest("Commit shadow instance (force)",
"crm_shadow --commit {shadow} --force --output-as=xml"),
ValidatingTest("Get active shadow instance's diff (after commit)",
"crm_shadow --diff --output-as=xml",
expected_rc=ExitStatus.ERROR),
ValidatingTest("Commit shadow instance (force) (all)",
"crm_shadow --commit {shadow} --force --all --output-as=xml"),
ValidatingTest("Get active shadow instance's diff (after commit all)",
"crm_shadow --diff --output-as=xml",
expected_rc=ExitStatus.ERROR),
# Commit an inactive shadow instance with no active instance
make_test_group("Commit shadow instance (no active instance)",
"crm_shadow --commit {shadow} {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": None},
expected_rc=ExitStatus.USAGE),
make_test_group("Commit shadow instance (no active instance) (force)",
"crm_shadow --commit {shadow} --force {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": None}),
# Commit an inactive shadow instance with an active instance
make_test_group("Commit shadow instance (mismatch)",
"crm_shadow --commit {shadow} {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": "nonexistent_shadow"},
expected_rc=ExitStatus.USAGE),
make_test_group("Commit shadow instance (mismatch) (force)",
"crm_shadow --commit {shadow} --force {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": "nonexistent_shadow"}),
# Commit an active shadow instance whose shadow file is missing
make_test_group("Commit shadow instance (nonexistent shadow file)",
"crm_shadow --commit nonexistent_shadow {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": "nonexistent_shadow"},
expected_rc=ExitStatus.USAGE),
make_test_group("Commit shadow instance (nonexistent shadow file) (force)",
"crm_shadow --commit nonexistent_shadow --force {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": "nonexistent_shadow"},
expected_rc=ExitStatus.NOSUCH),
make_test_group("Get active shadow instance's diff (nonexistent shadow file)",
"crm_shadow --diff {fmt}",
[Test, ValidatingTest],
env={"CIB_shadow": "nonexistent_shadow"},
expected_rc=ExitStatus.NOSUCH),
# Commit an active shadow instance when the CIB file is missing
make_test_group("Commit shadow instance (nonexistent CIB file)",
"crm_shadow --commit {shadow} {fmt}",
[Test, ValidatingTest],
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"},
expected_rc=ExitStatus.USAGE),
make_test_group("Commit shadow instance (nonexistent CIB file) (force)",
"crm_shadow --commit {shadow} --force {fmt}",
[Test, ValidatingTest],
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"},
expected_rc=ExitStatus.NOSUCH),
make_test_group("Get active shadow instance's diff (nonexistent CIB file)",
"crm_shadow --diff {fmt}",
[Test, ValidatingTest],
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"},
expected_rc=ExitStatus.NOSUCH),
], cib_gen=partial(copy_existing_cib, "{cts_cli_data}/crm_mon.xml")),
]
delete_1_tests = [
# Delete an active shadow instance
Test("Delete shadow instance", "crm_shadow --delete {shadow}",
expected_rc=ExitStatus.USAGE),
Test("Delete shadow instance (force)", "crm_shadow --delete {shadow} --force"),
ShadowTestGroup([
ValidatingTest("Delete shadow instance",
"crm_shadow --delete {shadow} --output-as=xml",
expected_rc=ExitStatus.USAGE),
ValidatingTest("Delete shadow instance (force)",
"crm_shadow --delete {shadow} --force --output-as=xml"),
])
]
delete_2_tests = [
# Delete an inactive shadow instance with no active instance
Test("Delete shadow instance (no active instance)",
"crm_shadow --delete {shadow}",
expected_rc=ExitStatus.USAGE),
Test("Delete shadow instance (no active instance) (force)",
"crm_shadow --delete {shadow} --force"),
]
delete_3_tests = [
ValidatingTest("Delete shadow instance (no active instance)",
"crm_shadow --delete {shadow} --output-as=xml",
expected_rc=ExitStatus.USAGE),
ValidatingTest("Delete shadow instance (no active instance) (force)",
"crm_shadow --delete {shadow} --force --output-as=xml"),
]
delete_4_tests = [
# Delete an inactive shadow instance with an active instance
Test("Delete shadow instance (mismatch)",
"crm_shadow --delete {shadow}",
expected_rc=ExitStatus.USAGE),
Test("Delete shadow instance (mismatch) (force)",
"crm_shadow --delete {shadow} --force"),
]
delete_5_tests = [
ValidatingTest("Delete shadow instance (mismatch)",
"crm_shadow --delete {shadow} --output-as=xml",
expected_rc=ExitStatus.USAGE),
ValidatingTest("Delete shadow instance (mismatch) (force)",
"crm_shadow --delete {shadow} --force --output-as=xml"),
# Delete an active shadow instance whose shadow file is missing
Test("Delete shadow instance (nonexistent shadow file)",
"crm_shadow --delete nonexistent_shadow",
expected_rc=ExitStatus.USAGE),
Test("Delete shadow instance (nonexistent shadow file) (force)",
"crm_shadow --delete nonexistent_shadow --force"),
ValidatingTest("Delete shadow instance (nonexistent shadow file)",
"crm_shadow --delete nonexistent_shadow --output-as=xml",
expected_rc=ExitStatus.USAGE),
ValidatingTest("Delete shadow instance (nonexistent shadow file) (force)",
"crm_shadow --delete nonexistent_shadow --force --output-as=xml"),
]
delete_6_tests = [
# Delete an active shadow instance when the CIB file is missing
Test("Delete shadow instance (nonexistent CIB file)",
"crm_shadow --delete {shadow}",
expected_rc=ExitStatus.USAGE),
Test("Delete shadow instance (nonexistent CIB file) (force)",
"crm_shadow --delete {shadow} --force"),
]
delete_7_tests = [
ValidatingTest("Delete shadow instance (nonexistent CIB file)",
"crm_shadow --delete {shadow} --output-as=xml",
expected_rc=ExitStatus.USAGE),
ValidatingTest("Delete shadow instance (nonexistent CIB file) (force)",
"crm_shadow --delete {shadow} --force --output-as=xml"),
]
create_1_tests = [
# Create new shadow instance based on active CIB with no instance active
make_test_group("Create copied shadow instance (no active instance)",
"crm_shadow --create {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force",
env={"CIB_shadow": None}),
# Create new shadow instance based on active CIB with other instance active
make_test_group("Create copied shadow instance (mismatch)",
"crm_shadow --create {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force",
env={"CIB_shadow": "nonexistent_shadow"}),
# Create new shadow instance based on CIB (shadow file already exists)
make_test_group("Create copied shadow instance (file already exists)",
"crm_shadow --create {shadow} --batch {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.CANTCREAT),
make_test_group("Create copied shadow instance (file already exists) (force)",
"crm_shadow --create {shadow} --batch --force {fmt}",
[Test, ValidatingTest]),
# Create new shadow instance based on active CIB when the CIB file is missing
make_test_group("Create copied shadow instance (nonexistent CIB file) (force)",
"crm_shadow --create {shadow} --batch --force {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.NOSUCH,
setup="crm_shadow --delete {shadow} --force",
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"}),
]
create_2_tests = [
# Create new empty shadow instance
make_test_group("Create empty shadow instance",
"crm_shadow --create-empty {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force"),
# Create empty shadow instance with no active instance
make_test_group("Create empty shadow instance (no active instance)",
"crm_shadow --create-empty {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force",
env={"CIB_shadow": None}),
# Create empty shadow instance with other instance active
make_test_group("Create empty shadow instance (mismatch)",
"crm_shadow --create-empty {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force",
env={"CIB_shadow": "nonexistent_shadow"}),
# Create empty shadow instance when the CIB file is missing
make_test_group("Create empty shadow instance (nonexistent CIB file)",
"crm_shadow --create-empty {shadow} --batch {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force",
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"}),
# Create empty shadow instance (shadow file already exists)
make_test_group("Create empty shadow instance (file already exists)",
"crm_shadow --create-empty {shadow} --batch {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.CANTCREAT),
make_test_group("Create empty shadow instance (file already exists) (force)",
"crm_shadow --create-empty {shadow} --batch --force {fmt}",
[Test, ValidatingTest]),
# Query shadow instance with an empty CIB.
# --which and --file queries were done earlier.
TestGroup([
make_test_group("Get active shadow instance's contents (empty CIB)",
"crm_shadow --display {fmt}",
[Test, ValidatingTest]),
make_test_group("Get active shadow instance's diff (empty CIB)",
"crm_shadow --diff {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.ERROR),
], setup=delete_shadow_resource_defaults),
]
reset_1_tests = [
+ Test("Resetting active shadow instance to active CIB requires force",
+ "crm_shadow --reset {shadow} --batch",
+ expected_rc=ExitStatus.USAGE),
Test("Reset active shadow instance to active CIB",
- "crm_shadow --reset {shadow} --batch"),
+ "crm_shadow --reset {shadow} --batch --force"),
Test("Active shadow instance no different from active CIB after reset",
"crm_shadow --diff"),
Test("Active shadow instance differs from active CIB after change",
"crm_shadow --diff",
setup="crm_attribute -n admin_epoch -v 99",
expected_rc=ExitStatus.ERROR),
ValidatingTest("Reset active shadow instance to active CIB",
- "crm_shadow --reset {shadow} --batch --output-as=xml"),
+ "crm_shadow --reset {shadow} --batch --force --output-as=xml"),
ValidatingTest("Active shadow instance no different from active CIB after reset",
"crm_shadow --diff --output-as=xml"),
ValidatingTest("Active shadow instance differs from active CIB after change",
"crm_shadow --diff --output-as=xml",
setup="crm_attribute -n admin_epoch -v 199",
expected_rc=ExitStatus.ERROR),
make_test_group("Reset shadow instance to active CIB with nonexistent shadow file",
- "crm_shadow --reset {shadow} --batch {fmt}",
- [Test, ValidatingTest],
- setup="crm_shadow --delete {shadow} --force",
- expected_rc=ExitStatus.NOSUCH),
- make_test_group("Force-reset shadow instance to active CIB with nonexistent shadow file",
"crm_shadow --reset {shadow} --batch --force {fmt}",
[Test, ValidatingTest],
setup="crm_shadow --delete {shadow} --force"),
Test("Active shadow instance no different from active CIB after force-reset",
"crm_shadow --diff"),
]
reset_2_tests = [
make_test_group("Reset inactive shadow instance (none active) to active CIB",
- "crm_shadow --reset {shadow} --batch {fmt}",
+ "crm_shadow --reset {shadow} --force --batch {fmt}",
[Test, ValidatingTest]),
]
reset_3_tests = [
- make_test_group("Resetting inactive shadow instance while another instance active requires --force",
- "crm_shadow --reset {shadow} --batch {fmt}",
- [Test, ValidatingTest],
- expected_rc=ExitStatus.USAGE),
make_test_group("Reset inactive shadow instance while another instance active",
"crm_shadow --reset {shadow} --batch --force {fmt}",
[Test, ValidatingTest]),
]
reset_4_tests = [
make_test_group("Reset shadow instance with nonexistent CIB",
- "crm_shadow --reset {shadow} --batch {fmt}",
- [Test, ValidatingTest],
- expected_rc=ExitStatus.NOSUCH),
- make_test_group("Force-reset shadow instance with nonexistent CIB",
"crm_shadow --reset {shadow} --batch --force {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.NOSUCH),
]
# Switch shadow instances
switch_tests = [
make_test_group("Switch to new shadow instance",
"crm_shadow --switch {shadow} --batch {fmt}",
[Test, ValidatingTest]),
TestGroup([
make_test_group("Switch to nonexistent shadow instance",
"crm_shadow --switch {shadow} --batch {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.NOSUCH),
make_test_group("Switch to nonexistent shadow instance (force)",
"crm_shadow --switch {shadow} --batch --force {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.NOSUCH),
], setup="crm_shadow --delete {shadow} --force"),
]
return no_instance_tests + [
ShadowTestGroup(new_instance_tests + more_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml"},
create=False),
ShadowTestGroup(delete_1_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml"}),
ShadowTestGroup(delete_2_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml",
"CIB_shadow": None}),
ShadowTestGroup(delete_3_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml",
"CIB_shadow": None}),
ShadowTestGroup(delete_4_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml",
"CIB_shadow": "nonexistent_shadow"}),
ShadowTestGroup(delete_5_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml",
"CIB_shadow": "nonexistent_shadow"}),
ShadowTestGroup(delete_6_tests,
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"}),
ShadowTestGroup(delete_7_tests,
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"}),
ShadowTestGroup(create_1_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml"},
create=False),
ShadowTestGroup(create_2_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml"},
create=False),
ShadowTestGroup(reset_1_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml"}),
ShadowTestGroup(reset_2_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml",
"CIB_shadow": None}),
ShadowTestGroup(reset_3_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml",
"CIB_shadow": "nonexistent_shadow"}),
ShadowTestGroup(reset_4_tests,
env={"CIB_file": "{cts_cli_data}/nonexistent_cib.xml"}),
ShadowTestGroup(switch_tests,
env={"CIB_shadow": "nonexistent_shadow"},
create_empty=True),
]
class CrmVerifyRegressionTest(RegressionTest):
"""A class for testing crm_verify."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_verify"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
invalid_tests = [
Test("Verbosely verify a file-specified configuration with an unallowed fencing level ID",
"crm_verify --xml-file {cts_cli_data}/crm_verify_invalid_fencing_topology.xml --verbose",
expected_rc=ExitStatus.CONFIG),
make_test_group("Verify a file-specified invalid configuration",
"crm_verify --xml-file {cts_cli_data}/crm_verify_invalid_bz.xml {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.CONFIG),
make_test_group("Verify a file-specified invalid configuration (verbose)",
"crm_verify --xml-file {cts_cli_data}/crm_verify_invalid_bz.xml --verbose {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.CONFIG),
make_test_group("Verify a file-specified invalid configuration (quiet)",
"crm_verify --xml-file {cts_cli_data}/crm_verify_invalid_bz.xml --quiet {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.CONFIG),
ValidatingTest("Verify another file-specified invalid configuration",
"crm_verify --xml-file {cts_cli_data}/crm_verify_invalid_no_stonith.xml --output-as=xml",
expected_rc=ExitStatus.CONFIG),
]
with open("%s/cli/crm_mon.xml" % test_home, encoding="utf-8") as f:
cib_contents = f.read()
valid_tests = [
ValidatingTest("Verify a file-specified valid configuration",
"crm_verify --xml-file {cts_cli_data}/crm_mon.xml --output-as=xml"),
ValidatingTest("Verify a piped-in valid configuration",
"crm_verify -p --output-as=xml",
stdin=pathlib.Path(apply_substitutions("{cts_cli_data}/crm_mon.xml"))),
ValidatingTest("Verbosely verify a file-specified valid configuration",
"crm_verify --xml-file {cts_cli_data}/crm_mon.xml --output-as=xml --verbose"),
ValidatingTest("Verbosely verify a piped-in valid configuration",
"crm_verify -p --output-as=xml --verbose",
stdin=pathlib.Path(apply_substitutions("{cts_cli_data}/crm_mon.xml"))),
ValidatingTest("Verify a string-supplied valid configuration",
"crm_verify -X '%s' --output-as=xml" % cib_contents),
ValidatingTest("Verbosely verify a string-supplied valid configuration",
"crm_verify -X '%s' --output-as=xml --verbose" % cib_contents),
]
return invalid_tests + valid_tests
class CrmMonRegressionTest(RegressionTest):
"""A class for testing crm_mon."""
@property
def name(self):
"""Return the name of this regression test."""
return "crm_mon"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
basic_tests = [
make_test_group("Basic output", "crm_mon -1 {fmt}",
[Test, ValidatingTest]),
make_test_group("Output without node section",
"crm_mon -1 --exclude=nodes {fmt}",
[Test, ValidatingTest]),
# The next test doesn't need to be performed for other output formats. It's
# really just a test to make sure that blank lines are correct.
Test("Output with only the node section",
"crm_mon -1 --exclude=all --include=nodes"),
# XML includes everything already so there's no need for a complete test
Test("Complete text output", "crm_mon -1 --include=all"),
# XML includes detailed output already
Test("Complete text output with detail", "crm_mon -1R --include=all"),
Test("Complete brief text output", "crm_mon -1 --include=all --brief"),
Test("Complete text output grouped by node",
"crm_mon -1 --include=all --group-by-node"),
# XML does not have a brief output option
Test("Complete brief text output grouped by node",
"crm_mon -1 --include=all --group-by-node --brief"),
ValidatingTest("Output grouped by node",
"crm_mon --output-as=xml --group-by-node"),
make_test_group("Complete output filtered by node",
"crm_mon -1 --include=all --node=cluster01 {fmt}",
[Test, ValidatingTest]),
make_test_group("Complete output filtered by tag",
"crm_mon -1 --include=all --node=even-nodes {fmt}",
[Test, ValidatingTest]),
make_test_group("Complete output filtered by resource tag",
"crm_mon -1 --include=all --resource=fencing-rscs {fmt}",
[Test, ValidatingTest]),
make_test_group("Output filtered by node that doesn't exist",
"crm_mon -1 --node=blah {fmt}",
[Test, ValidatingTest]),
Test("Basic text output with inactive resources", "crm_mon -1 -r"),
# XML already includes inactive resources
Test("Basic text output with inactive resources, filtered by node",
"crm_mon -1 -r --node=cluster02"),
make_test_group("Complete output filtered by primitive resource",
"crm_mon -1 --include=all --resource=Fencing {fmt}",
[Test, ValidatingTest]),
make_test_group("Complete output filtered by group resource",
"crm_mon -1 --include=all --resource=exim-group {fmt}",
[Test, ValidatingTest]),
Test("Complete text output filtered by group resource member",
"crm_mon -1 --include=all --resource=Public-IP"),
ValidatingTest("Output filtered by group resource member",
"crm_mon --output-as=xml --resource=Email"),
make_test_group("Complete output filtered by clone resource",
"crm_mon -1 --include=all --resource=ping-clone {fmt}",
[Test, ValidatingTest]),
make_test_group("Complete output filtered by clone resource instance",
"crm_mon -1 --include=all --resource=ping {fmt}",
[Test, ValidatingTest]),
Test("Complete text output filtered by exact clone resource instance",
"crm_mon -1 --include=all --show-detail --resource=ping:0"),
ValidatingTest("Output filtered by exact clone resource instance",
"crm_mon --output-as=xml --resource=ping:1"),
make_test_group("Output filtered by resource that doesn't exist",
"crm_mon -1 --resource=blah {fmt}",
[Test, ValidatingTest]),
Test("Basic text output with inactive resources, filtered by tag",
"crm_mon -1 -r --resource=inactive-rscs"),
Test("Basic text output with inactive resources, filtered by bundle resource",
"crm_mon -1 -r --resource=httpd-bundle"),
ValidatingTest("Output filtered by inactive bundle resource",
"crm_mon --output-as=xml --resource=httpd-bundle"),
Test("Basic text output with inactive resources, filtered by bundled IP address resource",
"crm_mon -1 -r --resource=httpd-bundle-ip-192.168.122.131"),
ValidatingTest("Output filtered by bundled IP address resource",
"crm_mon --output-as=xml --resource=httpd-bundle-ip-192.168.122.132"),
Test("Basic text output with inactive resources, filtered by bundled container",
"crm_mon -1 -r --resource=httpd-bundle-docker-1"),
ValidatingTest("Output filtered by bundled container",
"crm_mon --output-as=xml --resource=httpd-bundle-docker-2"),
Test("Basic text output with inactive resources, filtered by bundle connection",
"crm_mon -1 -r --resource=httpd-bundle-0"),
ValidatingTest("Output filtered by bundle connection",
"crm_mon --output-as=xml --resource=httpd-bundle-0"),
Test("Basic text output with inactive resources, filtered by bundled primitive resource",
"crm_mon -1 -r --resource=httpd"),
ValidatingTest("Output filtered by bundled primitive resource",
"crm_mon --output-as=xml --resource=httpd"),
Test("Complete text output, filtered by clone name in cloned group",
"crm_mon -1 --include=all --show-detail --resource=mysql-clone-group"),
ValidatingTest("Output, filtered by clone name in cloned group",
"crm_mon --output-as=xml --resource=mysql-clone-group"),
Test("Complete text output, filtered by group name in cloned group",
"crm_mon -1 --include=all --show-detail --resource=mysql-group"),
ValidatingTest("Output, filtered by group name in cloned group",
"crm_mon --output-as=xml --resource=mysql-group"),
Test("Complete text output, filtered by exact group instance name in cloned group",
"crm_mon -1 --include=all --show-detail --resource=mysql-group:1"),
ValidatingTest("Output, filtered by exact group instance name in cloned group",
"crm_mon --output-as=xml --resource=mysql-group:1"),
Test("Complete text output, filtered by primitive name in cloned group",
"crm_mon -1 --include=all --show-detail --resource=mysql-proxy"),
ValidatingTest("Output, filtered by primitive name in cloned group",
"crm_mon --output-as=xml --resource=mysql-proxy"),
Test("Complete text output, filtered by exact primitive instance name in cloned group",
"crm_mon -1 --include=all --show-detail --resource=mysql-proxy:1"),
ValidatingTest("Output, filtered by exact primitive instance name in cloned group",
"crm_mon --output-as=xml --resource=mysql-proxy:1"),
]
partial_tests = [
Test("Output of partially active resources", "crm_mon -1 --show-detail"),
ValidatingTest("Output of partially active resources", "crm_mon --output-as=xml"),
Test("Output of partially active resources, with inactive resources",
"crm_mon -1 -r --show-detail"),
# XML already includes inactive resources
Test("Complete brief text output, with inactive resources",
"crm_mon -1 -r --include=all --brief --show-detail"),
# XML does not have a brief output option
Test("Text output of partially active group", "crm_mon -1 --resource=partially-active-group"),
Test("Text output of partially active group, with inactive resources",
"crm_mon -1 --resource=partially-active-group -r"),
Test("Text output of active member of partially active group",
"crm_mon -1 --resource=dummy-1"),
Test("Text output of inactive member of partially active group",
"crm_mon -1 --resource=dummy-2 --show-detail"),
Test("Complete brief text output grouped by node, with inactive resources",
"crm_mon -1 -r --include=all --group-by-node --brief --show-detail"),
Test("Text output of partially active resources, with inactive resources, filtered by node",
"crm_mon -1 -r --node=cluster01"),
ValidatingTest("Output of partially active resources, filtered by node",
"crm_mon --output-as=xml --node=cluster01"),
]
unmanaged_tests = [
make_test_group("Output of active unmanaged resource on offline node",
"crm_mon -1 {fmt}",
[Test, ValidatingTest]),
Test("Brief text output of active unmanaged resource on offline node",
"crm_mon -1 --brief"),
Test("Brief text output of active unmanaged resource on offline node, grouped by node",
"crm_mon -1 --brief --group-by-node"),
]
maint1_tests = [
make_test_group("Output of all resources with maintenance-mode enabled",
"crm_mon -1 -r {fmt}",
[Test, ValidatingTest],
setup="crm_attribute -n maintenance-mode -v true",
teardown="crm_attribute -n maintenance-mode -v false"),
make_test_group("Output of all resources with maintenance enabled for a node",
"crm_mon -1 -r {fmt}",
[Test, ValidatingTest],
setup="crm_attribute -n maintenance -N cluster02 -v true",
teardown="crm_attribute -n maintenance -N cluster02 -v false"),
]
maint2_tests = [
# The fence resource is excluded, for comparison
make_test_group("Output of all resources with maintenance meta attribute true",
"crm_mon -1 -r {fmt}",
[Test, ValidatingTest]),
]
t180_tests = [
Test("Text output of guest node's container on different node from its remote resource",
"crm_mon -1"),
Test("Complete text output of guest node's container on different node from its remote resource",
"crm_mon -1 --show-detail"),
]
return [
TestGroup(basic_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon.xml"}),
Test("Check that CIB_file=\"-\" works", "crm_mon -1",
env={"CIB_file": "-"},
stdin=pathlib.Path(apply_substitutions("{cts_cli_data}/crm_mon.xml"))),
TestGroup(partial_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon-partial.xml"}),
TestGroup(unmanaged_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon-unmanaged.xml"}),
TestGroup(maint1_tests,
cib_gen=partial(copy_existing_cib, "{cts_cli_data}/crm_mon.xml")),
TestGroup(maint2_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon-rsc-maint.xml"}),
TestGroup(t180_tests,
env={"CIB_file": "{cts_cli_data}/crm_mon-T180.xml"}),
]
class AclsRegressionTest(RegressionTest):
"""A class for testing access control lists."""
@property
def name(self):
"""Return the name of this regression test."""
return "acls"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
acl_cib = """
"""
basic_tests = [
Test("Configure some ACLs", "cibadmin -M -o acls -p",
update_cib=True, stdin=acl_cib),
Test("Enable ACLs", "crm_attribute -n enable-acl -v true",
update_cib=True),
Test("Set cluster option", "crm_attribute -n no-quorum-policy -v ignore",
update_cib=True),
Test("New ACL role",
"""cibadmin --create -o acls --xml-text ''""",
update_cib=True),
Test("New ACL target",
"""cibadmin --create -o acls --xml-text ''""",
update_cib=True),
Test("Another ACL role",
"""cibadmin --create -o acls --xml-text ''""",
update_cib=True),
Test("Another ACL target",
"""cibadmin --create -o acls --xml-text ''""",
update_cib=True),
Test("Updated ACL",
"""cibadmin --replace -o acls --xml-text ''""",
update_cib=True),
]
no_acl_tests = [
Test("unknownguy: Query configuration", "cibadmin -Q",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("unknownguy: Set enable-acl",
"crm_attribute -n enable-acl -v false",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("unknownguy: Set stonith-enabled",
"crm_attribute -n stonith-enabled -v false",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("unknownguy: Create a resource",
"""cibadmin -C -o resources --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
]
deny_cib_tests = [
Test("l33t-haxor: Query configuration",
"cibadmin -Q",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("l33t-haxor: Set enable-acl",
"crm_attribute -n enable-acl -v false",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("l33t-haxor: Set stonith-enabled",
"crm_attribute -n stonith-enabled -v false",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("l33t-haxor: Create a resource",
"""cibadmin -C -o resources --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
]
observer_tests = [
Test("niceguy: Query configuration", "cibadmin -Q"),
Test("niceguy: Set enable-acl",
"crm_attribute -n enable-acl -v false",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("niceguy: Set stonith-enabled",
"crm_attribute -n stonith-enabled -v false",
update_cib=True),
Test("niceguy: Create a resource",
"""cibadmin -C -o resources --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("root: Query configuration", "cibadmin -Q",
env={"CIB_user": "root"}),
Test("root: Set stonith-enabled", "crm_attribute -n stonith-enabled -v true",
update_cib=True, env={"CIB_user": "root"}),
Test("root: Create a resource",
"""cibadmin -C -o resources --xml-text ''""",
update_cib=True, env={"CIB_user": "root"}),
]
deny_cib_2_tests = [
Test("l33t-haxor: Create a resource meta attribute",
"crm_resource -r dummy --meta -p target-role -v Stopped",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("l33t-haxor: Query a resource meta attribute",
"crm_resource -r dummy --meta -g target-role",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
Test("l33t-haxor: Remove a resource meta attribute",
"crm_resource -r dummy --meta -d target-role",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
]
observer_2_tests = [
Test("niceguy: Create a resource meta attribute",
"crm_resource -r dummy --meta -p target-role -v Stopped",
update_cib=True),
Test("niceguy: Query a resource meta attribute",
"crm_resource -r dummy --meta -g target-role",
update_cib=True),
Test("niceguy: Remove a resource meta attribute",
"crm_resource -r dummy --meta -d target-role",
update_cib=True),
Test("niceguy: Create a resource meta attribute",
"crm_resource -r dummy --meta -p target-role -v Started",
update_cib=True),
]
read_meta_tests = [
Test("badidea: Query configuration - implied deny", "cibadmin -Q"),
]
deny_cib_3_tests = [
Test("betteridea: Query configuration - explicit deny", "cibadmin -Q"),
]
replace_tests = [
TestGroup([
AclTest("niceguy: Replace - remove acls",
"cibadmin --replace -p",
setup="cibadmin --delete --xml-text ''",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
AclTest("niceguy: Replace - create resource",
"cibadmin --replace -p",
setup="""cibadmin -C -o resources --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
AclTest("niceguy: Replace - modify attribute (deny)",
"cibadmin --replace -p",
setup="crm_attribute -n enable-acl -v false",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
AclTest("niceguy: Replace - delete attribute (deny)",
"cibadmin --replace -p",
setup="""cibadmin --replace --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
AclTest("niceguy: Replace - create attribute (deny)",
"cibadmin --replace -p",
setup="""cibadmin --modify --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
], env={"CIB_user": "niceguy"}),
# admin role
TestGroup([
AclTest("bob: Replace - create attribute (direct allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''"""),
AclTest("bob: Replace - modify attribute (direct allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''"""),
AclTest("bob: Replace - delete attribute (direct allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --replace -o resources --xml-text ''"""),
], env={"CIB_user": "bob"}),
# super_user role
TestGroup([
AclTest("joe: Replace - create attribute (inherited allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''"""),
AclTest("joe: Replace - modify attribute (inherited allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''"""),
AclTest("joe: Replace - delete attribute (inherited allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --replace -o resources --xml-text ''"""),
], env={"CIB_user": "joe"}),
# rsc_writer role
TestGroup([
AclTest("mike: Replace - create attribute (allow overrides deny)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''"""),
AclTest("mike: Replace - modify attribute (allow overrides deny)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''"""),
AclTest("mike: Replace - delete attribute (allow overrides deny)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --replace -o resources --xml-text ''"""),
# Create an additional resource for deny-overrides-allow testing
AclTest("mike: Create another resource",
"""cibadmin -C -o resources --xml-text ''""",
update_cib=True),
], env={"CIB_user": "mike"}),
# rsc_denied role
TestGroup([
AclTest("chris: Replace - create attribute (deny overrides allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
AclTest("chris: Replace - modify attribute (deny overrides allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --modify --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
AclTest("chris: Replace - delete attribute (deny overrides allow)",
"cibadmin --replace -o resources -p",
setup="""cibadmin --replace -o resources --xml-text ''""",
expected_rc=ExitStatus.INSUFFICIENT_PRIV),
], env={"CIB_user": "chris"}),
]
loop_tests = [
# no ACL
TestGroup(no_acl_tests, env={"CIB_user": "unknownguy"}),
# deny /cib permission
TestGroup(deny_cib_tests, env={"CIB_user": "l33t-haxor"}),
# observer role
TestGroup(observer_tests, env={"CIB_user": "niceguy"}),
# deny /cib permission
TestGroup(deny_cib_2_tests, env={"CIB_user": "l33t-haxor"}),
# observer role
TestGroup(observer_2_tests, env={"CIB_user": "niceguy"}),
# read //meta_attributes
TestGroup(read_meta_tests, env={"CIB_user": "badidea"}),
# deny /cib, read //meta_attributes
TestGroup(deny_cib_3_tests, env={"CIB_user": "betteridea"}),
] + replace_tests
return [
ShadowTestGroup(basic_tests + [
TestGroup(loop_tests,
env={"PCMK_trace_functions": "pcmk__check_acl,pcmk__apply_creation_acl"})]),
]
class ValidityRegressionTest(RegressionTest):
"""A class for testing CIB validity."""
@property
def name(self):
"""Return the name of this regression test."""
return "validity"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
basic_tests = [
# sanitize_output() strips out validate-with, so there's no point in
# outputting the CIB after tests that modify it
Test("Try to set unrecognized validate-with",
"cibadmin -M --xml-text ''",
expected_rc=ExitStatus.CONFIG),
Test("Try to remove validate-with attribute",
"cibadmin -R -p",
stdin=StdinCmd("""cibadmin -Q | sed 's#validate-with="[^"]*"##'"""),
expected_rc=ExitStatus.CONFIG),
Test("Try to use rsc_order first-action value disallowed by schema",
"cibadmin -M -o constraints --xml-text ''",
expected_rc=ExitStatus.CONFIG, update_cib=True),
Test("Try to use configuration legal only with schema after configured one",
"cibadmin -C -o configuration --xml-text ''",
expected_rc=ExitStatus.CONFIG, update_cib=True),
Test("Disable schema validation",
"cibadmin -M --xml-text ''",
expected_rc=ExitStatus.OK),
Test("Set invalid rsc_order first-action value (schema validation disabled)",
"cibadmin -M -o constraints --xml-text ''",
expected_rc=ExitStatus.OK, update_cib=True),
Test("Run crm_simulate with invalid rsc_order first-action "
"(schema validation disabled)",
"crm_simulate -SL",
expected_rc=ExitStatus.OK),
]
basic_tests_setup = [
"""cibadmin -C -o resources --xml-text ''""",
"""cibadmin -C -o resources --xml-text ''""",
"""cibadmin -C -o constraints --xml-text ''""",
]
return [
ShadowTestGroup(basic_tests, validate_with="pacemaker-1.2",
setup=basic_tests_setup,
env={"PCMK_trace_functions": "apply_upgrade,pcmk__update_schema,invert_action"}),
]
class UpgradeRegressionTest(RegressionTest):
"""A class for testing upgrading the CIB."""
@property
def name(self):
"""Return the name of this regression test."""
return "upgrade"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
resource_cib = """
"""
basic_tests = [
Test("Set stonith-enabled=false", "crm_attribute -n stonith-enabled -v false",
update_cib=True),
Test("Configure the initial resource", "cibadmin -M -o resources -p",
update_cib=True, stdin=resource_cib),
Test("Upgrade to latest CIB schema (trigger 2.10.xsl + the wrapping)",
"cibadmin --upgrade --force -V -V",
update_cib=True),
Test("Query a resource instance attribute (shall survive)",
"crm_resource -r mySmartFuse -g requires",
update_cib=True),
]
return [
ShadowTestGroup(basic_tests, validate_with="pacemaker-2.10",
env={"PCMK_trace_functions": "apply_upgrade,pcmk__update_schema"})
]
class RulesRegressionTest(RegressionTest):
"""A class for testing support for CIB rules."""
@property
def name(self):
"""Return the name of this regression test."""
return "rules"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
tomorrow = datetime.now() + timedelta(days=1)
rule_cib = """""" % tomorrow.strftime("%F %T %z")
usage_tests = [
make_test_group("crm_rule given no arguments", "crm_rule {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.USAGE),
make_test_group("crm_rule given no rule to check", "crm_rule -c {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.USAGE),
make_test_group("crm_rule given invalid input XML",
"crm_rule -c -r blahblah -X invalidxml {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.DATAERR),
make_test_group("crm_rule given invalid input XML on stdin",
"crm_rule -c -r blahblah -X - {fmt}",
[Test, ValidatingTest],
stdin=StdinCmd("echo invalidxml"),
expected_rc=ExitStatus.DATAERR),
]
basic_tests = [
make_test_group("Try to check a rule that doesn't exist",
"crm_rule -c -r blahblah {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.NOSUCH),
make_test_group("Try to check a rule that has too many date_expressions",
"crm_rule -c -r cli-rule-too-many-date-expressions {fmt}",
[Test, ValidatingTest], expected_rc=ExitStatus.UNIMPLEMENT_FEATURE),
make_test_group("Verify basic rule is expired",
"crm_rule -c -r cli-prefer-rule-dummy-expired {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.EXPIRED),
make_test_group("Verify basic rule worked in the past",
"crm_rule -c -r cli-prefer-rule-dummy-expired -d 20180101 {fmt}",
[Test, ValidatingTest]),
make_test_group("Verify basic rule is not yet in effect",
"crm_rule -c -r cli-prefer-rule-dummy-not-yet {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.NOT_YET_IN_EFFECT),
make_test_group("Verify date_spec rule with years has expired",
"crm_rule -c -r cli-prefer-rule-dummy-date_spec-only-years {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.EXPIRED),
make_test_group("Verify multiple rules at once",
"crm_rule -c -r cli-prefer-rule-dummy-not-yet -r cli-prefer-rule-dummy-date_spec-only-years {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.EXPIRED),
make_test_group("Verify date_spec rule with years is in effect",
"crm_rule -c -r cli-prefer-rule-dummy-date_spec-only-years -d 20190201 {fmt}",
[Test, ValidatingTest]),
make_test_group("Try to check a rule whose date_spec does not contain years=",
"crm_rule -c -r cli-prefer-rule-dummy-date_spec-without-years {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.UNIMPLEMENT_FEATURE),
make_test_group("Try to check a rule whose date_spec contains years= and moon=",
"crm_rule -c -r cli-prefer-rule-dummy-date_spec-years-moon {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.UNIMPLEMENT_FEATURE),
make_test_group("Try to check a rule with no date_expression",
"crm_rule -c -r cli-no-date_expression-rule {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.UNIMPLEMENT_FEATURE),
]
return usage_tests + [
TestGroup(basic_tests, cib_gen=partial(write_cib, rule_cib))
]
class FeatureSetRegressionTest(RegressionTest):
"""A class for testing support for version-specific features."""
@property
def name(self):
"""Return the name of this regression test."""
return "feature_set"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
basic_tests = [
# Import the test CIB
Test("Import the test CIB",
"cibadmin --replace --xml-file {cts_cli_data}/crm_mon-feature_set.xml",
update_cib=True),
Test("Complete text output, no mixed status",
"crm_mon -1 --show-detail"),
ValidatingTest("Output, no mixed status", "crm_mon --output-as=xml"),
# Modify the CIB to fake that the cluster has mixed versions
Test("Fake inconsistent feature set",
"crm_attribute --node=cluster02 --name=#feature-set --update=3.15.0 --lifetime=reboot",
update_cib=True),
Test("Complete text output, mixed status",
"crm_mon -1 --show-detail"),
ValidatingTest("Output, mixed status", "crm_mon --output-as=xml"),
]
return [
ShadowTestGroup(basic_tests),
]
# Tests that depend on resource agents and must be run in an installed
# environment
class AgentRegressionTest(RegressionTest):
"""A class for testing resource agents."""
@property
def name(self):
"""Return the name of this regression test."""
return "agents"
@property
def tests(self):
"""A list of Test instances to be run as part of this regression test."""
return [
make_test_group("Validate a valid resource configuration",
"crm_resource --validate --class ocf --provider pacemaker --agent Dummy {fmt}",
[Test, ValidatingTest]),
# Make the Dummy configuration invalid (op_sleep can't be a generic string)
make_test_group("Validate an invalid resource configuration",
"crm_resource --validate --class ocf --provider pacemaker --agent Dummy {fmt}",
[Test, ValidatingTest],
expected_rc=ExitStatus.NOT_CONFIGURED,
env={"OCF_RESKEY_op_sleep": "asdf"}),
]
def build_options():
"""Handle command line arguments."""
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
description="Command line tool regression tests",
epilog="Default tests: %s\n"
"Other tests: agents (must be run in an installed environment)" %
" ".join(default_tests))
parser.add_argument("-j", "--jobs", metavar="JOBS", default=cpu_count() - 1, type=int,
help="The number of tests to run simultaneously")
parser.add_argument("-p", "--path", metavar="DIR", action="append",
help="Look for executables in DIR (may be specified multiple times)")
parser.add_argument("-r", "--run-only", metavar="TEST", choices=default_tests + ["tools"] + other_tests,
action="append",
help="Run only specified tests (may be specified multiple times)")
parser.add_argument("-s", "--save", action="store_true",
help="Save actual output as expected output")
parser.add_argument("-v", "--valgrind", action="store_true",
help="Run all commands under valgrind")
parser.add_argument("-V", "--verbose", action="store_true",
help="Display any differences from expected output")
args = parser.parse_args()
if args.path is None:
args.path = []
return args
def setup_environment(valgrind):
"""Set various environment variables needed for operation."""
if valgrind:
os.environ["G_SLICE"] = "always-malloc"
# Ensure all command output is in portable locale for comparison
os.environ["LC_ALL"] = "C"
# Log test errors to stderr
os.environ["PCMK_stderr"] = "1"
# Because we will change the value of PCMK_trace_functions and then reset it
# back to some initial value at various points, it's easiest to assume it is
# defined but empty by default
if "PCMK_trace_functions" not in os.environ:
os.environ["PCMK_trace_functions"] = ""
def path_prepend(p):
"""Add another directory to the front of $PATH."""
old = os.environ["PATH"]
os.environ["PATH"] = "%s:%s" % (p, old)
def setup_path(opts_path):
"""Set the PATH environment variable appropriately for the tests."""
srcdir = os.path.dirname(test_home)
# Add any search paths given on the command line
for p in opts_path:
path_prepend(p)
if os.path.exists("%s/tools/crm_simulate" % srcdir):
print("Using local binaries from: %s" % srcdir)
path_prepend("%s/tools" % srcdir)
for daemon in ["based", "controld", "fenced", "schedulerd"]:
path_prepend("%s/daemons/%s" % (srcdir, daemon))
print("Using local schemas from: %s/xml" % srcdir)
os.environ["PCMK_schema_directory"] = "%s/xml" % srcdir
else:
path_prepend(BuildOptions.DAEMON_DIR)
os.environ["PCMK_schema_directory"] = BuildOptions.SCHEMA_DIR
def _run_one(valgrind, r):
"""Run and return a TestGroup object."""
# See comments in run_regression_tests.
r.run(valgrind=valgrind)
return r
def run_regression_tests(regs, jobs, valgrind=False):
"""Run the given tests and return the modified objects."""
executed = []
with Pool(processes=jobs) as pool:
# What we really want to do here is:
# pool.map(lambda r: r.run(),regs)
#
# However, multiprocessing uses pickle somehow in its operation, and python
# doesn't want to pickle a lambda (nor a nested function within this one).
# Thus, we need to use the _run_one wrapper at the file level just to call
# run(). Further, if we don't return the modified object from that and then
# return the list of modified objects here, it looks like the rest of the
# program will use the originals, before this was ever run.
executed = pool.map(partial(_run_one, valgrind), regs)
return executed
def results(regs, save, verbose):
"""Print the output from each regression test, returning the number whose output differs."""
output_differs = 0
if verbose:
print("\n\nResults")
for r in regs:
r.write()
if save:
dest = "%s/cli/regression.%s.exp" % (test_home, r.name)
copyfile(r.results_file, dest)
r.diff()
if not r.identical:
output_differs += 1
return output_differs
def summary(regs, output_differs, verbose):
"""Print the summary output for the entire test run."""
test_failures = 0
test_successes = 0
for r in regs:
test_failures += r.failures
test_successes += r.successes
print("\n\nSummary")
# First, print all the Passed/Failed lines from each Test run.
for r in regs:
print("\n".join(r.summary))
fmt = PluralFormatter()
# Then, print information specific to each result possibility. Basically,
# if there were failures then we print the output differences, leave the
# failed output files in place, and exit with an error. Otherwise, clean up
# anything that passed.
if test_failures > 0 and output_differs > 0:
print(fmt.format("{0} {0:plural,test} failed; see output in:",
test_failures))
for r in regs:
r.process_results(verbose)
return ExitStatus.ERROR
if test_failures > 0:
print(fmt.format("{0} {0:plural,test} failed", test_failures))
for r in regs:
r.process_results(verbose)
return ExitStatus.ERROR
if output_differs:
print(fmt.format("{0} {0:plural,test} passed but output was "
"unexpected; see output in:", test_successes))
for r in regs:
r.process_results(verbose)
return ExitStatus.DIGEST
print(fmt.format("{0} {0:plural,test} passed", test_successes))
for r in regs:
r.cleanup()
return ExitStatus.OK
regression_classes = [
AccessRenderRegressionTest,
DaemonsRegressionTest,
DatesRegressionTest,
ErrorCodeRegressionTest,
CibadminRegressionTest,
CrmAttributeRegressionTest,
CrmStandbyRegressionTest,
CrmResourceRegressionTest,
CrmTicketRegressionTest,
CrmadminRegressionTest,
CrmShadowRegressionTest,
CrmVerifyRegressionTest,
CrmMonRegressionTest,
AclsRegressionTest,
ValidityRegressionTest,
UpgradeRegressionTest,
RulesRegressionTest,
FeatureSetRegressionTest,
AgentRegressionTest,
]
def main():
"""Run command line regression tests as specified by arguments."""
opts = build_options()
setup_environment(opts.valgrind)
setup_path(opts.path)
# Filter the list of all regression test classes to include only those that
# were requested on the command line. If empty, this defaults to default_tests.
if not opts.run_only:
opts.run_only = default_tests
if opts.run_only == ["tools"]:
opts.run_only = tools_tests
regs = []
for cls in regression_classes:
obj = cls()
if obj.name in opts.run_only:
regs.append(obj)
regs = run_regression_tests(regs, max(1, opts.jobs), valgrind=opts.valgrind)
output_differs = results(regs, opts.save, opts.verbose)
rc = summary(regs, output_differs, opts.verbose)
sys.exit(rc)
if __name__ == "__main__":
main()
# vim: set filetype=python expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=120:
diff --git a/tools/crm_shadow.c b/tools/crm_shadow.c
index 4e28e8ab14..90651bac5a 100644
--- a/tools/crm_shadow.c
+++ b/tools/crm_shadow.c
@@ -1,1322 +1,1309 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SUMMARY "perform Pacemaker configuration changes in a sandbox\n\n" \
"This command sets up an environment in which " \
"configuration tools (cibadmin,\n" \
"crm_resource, etc.) work offline instead of against a " \
"live cluster, allowing\n" \
"changes to be previewed and tested for side effects."
#define INDENT " "
enum shadow_command {
shadow_cmd_none = 0,
shadow_cmd_which,
shadow_cmd_display,
shadow_cmd_diff,
shadow_cmd_file,
shadow_cmd_create,
shadow_cmd_create_empty,
shadow_cmd_commit,
shadow_cmd_delete,
shadow_cmd_edit,
shadow_cmd_reset,
shadow_cmd_switch,
};
/*!
* \internal
* \enum shadow_disp_flags
* \brief Bit flags to control which fields of shadow CIB info are displayed
*
* \note Ignored for XML output.
*/
enum shadow_disp_flags {
shadow_disp_instance = (1 << 0),
shadow_disp_file = (1 << 1),
shadow_disp_content = (1 << 2),
shadow_disp_diff = (1 << 3),
};
static crm_exit_t exit_code = CRM_EX_OK;
static struct {
enum shadow_command cmd;
int cmd_options;
char *instance;
gboolean force;
gboolean batch;
gboolean full_upload;
gchar *validate_with;
} options = {
.cmd_options = cib_sync_call,
};
/*!
* \internal
* \brief Display an instruction to the user
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# Instructional message
*/
PCMK__OUTPUT_ARGS("instruction", "const char *")
static int
instruction_default(pcmk__output_t *out, va_list args)
{
const char *msg = va_arg(args, const char *);
if (msg == NULL) {
return pcmk_rc_no_output;
}
return out->info(out, "%s", msg);
}
/*!
* \internal
* \brief Display an instruction to the user
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# Instructional message
*/
PCMK__OUTPUT_ARGS("instruction", "const char *")
static int
instruction_xml(pcmk__output_t *out, va_list args)
{
const char *msg = va_arg(args, const char *);
if (msg == NULL) {
return pcmk_rc_no_output;
}
pcmk__output_create_xml_text_node(out, "instruction", msg);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Display information about a shadow CIB instance
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# Instance name (can be \p NULL)
* -# Shadow file name (can be \p NULL)
* -# Shadow file content (can be \p NULL)
* -# Patchset containing the changes in the shadow CIB (can be \p NULL)
* -# Group of \p shadow_disp_flags indicating which fields to display
*/
PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
"const xmlNode *", "enum shadow_disp_flags")
static int
shadow_default(pcmk__output_t *out, va_list args)
{
const char *instance = va_arg(args, const char *);
const char *filename = va_arg(args, const char *);
const xmlNode *content = va_arg(args, const xmlNode *);
const xmlNode *diff = va_arg(args, const xmlNode *);
enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
int rc = pcmk_rc_no_output;
if (pcmk_is_set(flags, shadow_disp_instance)) {
rc = out->info(out, "Instance: %s", pcmk__s(instance, ""));
}
if (pcmk_is_set(flags, shadow_disp_file)) {
rc = out->info(out, "File name: %s", pcmk__s(filename, ""));
}
if (pcmk_is_set(flags, shadow_disp_content)) {
rc = out->info(out, "Content:");
if (content != NULL) {
GString *buf = g_string_sized_new(1024);
gchar *str = NULL;
pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
buf, 0);
str = g_string_free(buf, FALSE);
str = pcmk__trim(str);
if (!pcmk__str_empty(str)) {
out->info(out, "%s", str);
}
g_free(str);
} else {
out->info(out, "");
}
}
if (pcmk_is_set(flags, shadow_disp_diff)) {
rc = out->info(out, "Diff:");
if (diff != NULL) {
out->message(out, "xml-patchset", diff);
} else {
out->info(out, "");
}
}
return rc;
}
/*!
* \internal
* \brief Display information about a shadow CIB instance
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# Instance name (can be \p NULL)
* -# Shadow file name (can be \p NULL)
* -# Shadow file content (can be \p NULL)
* -# Patchset containing the changes in the shadow CIB (can be \p NULL)
* -# Group of \p shadow_disp_flags indicating which fields to display
*/
PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
"const xmlNode *", "enum shadow_disp_flags")
static int
shadow_text(pcmk__output_t *out, va_list args)
{
if (!out->is_quiet(out)) {
return shadow_default(out, args);
} else {
const char *instance = va_arg(args, const char *);
const char *filename = va_arg(args, const char *);
const xmlNode *content = va_arg(args, const xmlNode *);
const xmlNode *diff = va_arg(args, const xmlNode *);
enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int);
int rc = pcmk_rc_no_output;
bool quiet_orig = out->quiet;
/* We have to disable quiet mode for the "xml-patchset" message if we
* call it, so we might as well do so for this whole section.
*/
out->quiet = false;
if (pcmk_is_set(flags, shadow_disp_instance) && (instance != NULL)) {
rc = out->info(out, "%s", instance);
}
if (pcmk_is_set(flags, shadow_disp_file) && (filename != NULL)) {
rc = out->info(out, "%s", filename);
}
if (pcmk_is_set(flags, shadow_disp_content) && (content != NULL)) {
GString *buf = g_string_sized_new(1024);
gchar *str = NULL;
pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
buf, 0);
str = g_string_free(buf, FALSE);
str = pcmk__trim(str);
rc = out->info(out, "%s", str);
g_free(str);
}
if (pcmk_is_set(flags, shadow_disp_diff) && (diff != NULL)) {
rc = out->message(out, "xml-patchset", diff);
}
out->quiet = quiet_orig;
return rc;
}
}
/*!
* \internal
* \brief Display information about a shadow CIB instance
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# Instance name (can be \p NULL)
* -# Shadow file name (can be \p NULL)
* -# Shadow file content (can be \p NULL)
* -# Patchset containing the changes in the shadow CIB (can be \p NULL)
* -# Group of \p shadow_disp_flags indicating which fields to display
* (ignored)
*/
PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *",
"const xmlNode *", "enum shadow_disp_flags")
static int
shadow_xml(pcmk__output_t *out, va_list args)
{
const char *instance = va_arg(args, const char *);
const char *filename = va_arg(args, const char *);
const xmlNode *content = va_arg(args, const xmlNode *);
const xmlNode *diff = va_arg(args, const xmlNode *);
enum shadow_disp_flags flags G_GNUC_UNUSED =
(enum shadow_disp_flags) va_arg(args, int);
pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW,
PCMK_XA_INSTANCE, instance,
PCMK_XA_FILE, filename,
NULL);
if (content != NULL) {
GString *buf = g_string_sized_new(1024);
pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
0);
out->output_xml(out, PCMK_XE_CONTENT, buf->str);
g_string_free(buf, TRUE);
}
if (diff != NULL) {
out->message(out, "xml-patchset", diff);
}
pcmk__output_xml_pop_parent(out);
return pcmk_rc_ok;
}
static const pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_NONE,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static const pcmk__message_entry_t fmt_functions[] = {
{ "instruction", "default", instruction_default },
{ "instruction", "xml", instruction_xml },
{ "shadow", "default", shadow_default },
{ "shadow", "text", shadow_text },
{ "shadow", "xml", shadow_xml },
{ NULL, NULL, NULL }
};
/*!
* \internal
* \brief Set the error when \p --force is not passed with a dangerous command
*
* \param[in] reason Why command is dangerous
* \param[in] for_shadow If true, command is dangerous to the shadow file.
* Otherwise, command is dangerous to the active
* cluster.
* \param[in] show_mismatch If true and the supplied shadow instance is not
* the same as the active shadow instance, report
* this
* \param[out] error Where to store error
*/
static void
set_danger_error(const char *reason, bool for_shadow, bool show_mismatch,
GError **error)
{
const char *active = getenv("CIB_shadow");
char *full = NULL;
if (show_mismatch
&& !pcmk__str_eq(active, options.instance, pcmk__str_null_matches)) {
full = crm_strdup_printf("%s.\nAdditionally, the supplied shadow "
"instance (%s) is not the same as the active "
"one (%s)",
reason, options.instance, active);
reason = full;
}
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"%s%sTo prevent accidental destruction of the %s, the --force "
"flag is required in order to proceed.",
pcmk__s(reason, ""), ((reason != NULL)? ".\n" : ""),
(for_shadow? "shadow file" : "cluster"));
free(full);
}
/*!
* \internal
* \brief Get the active shadow instance from the environment
*
* This sets \p options.instance to the value of the \p CIB_shadow env variable.
*
* \param[out] error Where to store error
*/
static int
get_instance_from_env(GError **error)
{
int rc = pcmk_rc_ok;
pcmk__str_update(&options.instance, getenv("CIB_shadow"));
if (options.instance == NULL) {
rc = ENXIO;
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"No active shadow configuration defined");
}
return rc;
}
/*!
* \internal
* \brief Validate that the shadow file does or does not exist, as appropriate
*
* \param[in] filename Absolute path of shadow file
* \param[in] should_exist Whether the shadow file is expected to exist
* \param[out] error Where to store error
*
* \return Standard Pacemaker return code
*/
static int
check_file_exists(const char *filename, bool should_exist, GError **error)
{
struct stat buf;
if (!should_exist && (stat(filename, &buf) == 0)) {
char *reason = crm_strdup_printf("A shadow instance '%s' already "
"exists", options.instance);
exit_code = CRM_EX_CANTCREAT;
set_danger_error(reason, true, false, error);
free(reason);
return EEXIST;
}
if (should_exist && (stat(filename, &buf) < 0)) {
int rc = errno;
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not access shadow instance '%s': %s",
options.instance, strerror(rc));
return errno;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Connect to the "real" (non-shadow) CIB
*
* \param[out] real_cib Where to store CIB connection
* \param[out] error Where to store error
*
* \return Standard Pacemaker return code
*/
static int
connect_real_cib(cib_t **real_cib, GError **error)
{
int rc = pcmk_rc_ok;
*real_cib = cib_new_no_shadow();
if (*real_cib == NULL) {
rc = ENOMEM;
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not create a CIB connection object");
return rc;
}
rc = cib__signon_attempts(*real_cib, cib_command, 5);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not connect to CIB: %s", pcmk_rc_str(rc));
}
return rc;
}
/*!
* \internal
* \brief Query the "real" (non-shadow) CIB and store the result
*
* \param[out] output Where to store query output
* \param[out] error Where to store error
*
* \return Standard Pacemaker return code
*/
static int
query_real_cib(xmlNode **output, GError **error)
{
cib_t *real_cib = NULL;
int rc = connect_real_cib(&real_cib, error);
if (rc != pcmk_rc_ok) {
goto done;
}
rc = real_cib->cmds->query(real_cib, NULL, output, options.cmd_options);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not query the non-shadow CIB: %s", pcmk_rc_str(rc));
}
done:
cib_delete(real_cib);
return rc;
}
/*!
* \internal
* \brief Read XML from the given file
*
* \param[in] filename Path of input file
* \param[out] output Where to store XML read from \p filename
* \param[out] error Where to store error
*
* \return Standard Pacemaker return code
*/
static int
read_xml(const char *filename, xmlNode **output, GError **error)
{
int rc = pcmk_rc_ok;
*output = pcmk__xml_read(filename);
if (*output == NULL) {
rc = pcmk_rc_no_input;
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not parse XML from input file '%s'", filename);
}
return rc;
}
/*!
* \internal
* \brief Write the shadow XML to a file
*
* \param[in] xml Shadow XML
* \param[in] filename Name of destination file
* \param[in] reset Whether the write is a reset (for logging only)
* \param[out] error Where to store error
*/
static int
write_shadow_file(const xmlNode *xml, const char *filename, bool reset,
GError **error)
{
int rc = pcmk__xml_write_file(xml, filename, false);
if (rc != pcmk_rc_ok) {
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not %s the shadow instance '%s': %s",
reset? "reset" : "create", options.instance,
pcmk_rc_str(rc));
}
return rc;
}
/*!
* \internal
* \brief Create a shell prompt based on the given shadow instance name
*
* \return Newly created prompt
*
* \note The caller is responsible for freeing the return value using \p free().
*/
static inline char *
get_shadow_prompt(void)
{
return crm_strdup_printf("shadow[%.40s] # ", options.instance);
}
/*!
* \internal
* \brief Set up environment variables for a shadow instance
*
* \param[in,out] out Output object
* \param[in] do_switch If true, switch to an existing instance (logging
* only)
* \param[out] error Where to store error
*/
static void
shadow_setup(pcmk__output_t *out, bool do_switch, GError **error)
{
const char *active = getenv("CIB_shadow");
const char *prompt = getenv("PS1");
const char *shell = getenv("SHELL");
char *new_prompt = get_shadow_prompt();
if (pcmk__str_eq(active, options.instance, pcmk__str_none)
&& pcmk__str_eq(new_prompt, prompt, pcmk__str_none)) {
// CIB_shadow and prompt environment variables are already set up
goto done;
}
if (!options.batch && (shell != NULL)) {
out->info(out, "Setting up shadow instance");
setenv("PS1", new_prompt, 1);
setenv("CIB_shadow", options.instance, 1);
out->message(out, PCMK_XE_INSTRUCTION,
"Press Ctrl+D to exit the crm_shadow shell");
if (pcmk__str_eq(shell, "(^|/)bash$", pcmk__str_regex)) {
execl(shell, shell, "--norc", "--noprofile", NULL);
} else {
execl(shell, shell, NULL);
}
exit_code = pcmk_rc2exitc(errno);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Failed to launch shell '%s': %s",
shell, pcmk_rc_str(errno));
} else {
char *msg = NULL;
const char *prefix = "A new shadow instance was created. To begin "
"using it";
if (do_switch) {
prefix = "To switch to the named shadow instance";
}
msg = crm_strdup_printf("%s, enter the following into your shell:\n"
"\texport CIB_shadow=%s",
prefix, options.instance);
out->message(out, "instruction", msg);
free(msg);
}
done:
free(new_prompt);
}
/*!
* \internal
* \brief Remind the user to clean up the shadow environment
*
* \param[in,out] out Output object
*/
static void
shadow_teardown(pcmk__output_t *out)
{
const char *active = getenv("CIB_shadow");
const char *prompt = getenv("PS1");
if (pcmk__str_eq(active, options.instance, pcmk__str_none)) {
char *our_prompt = get_shadow_prompt();
if (pcmk__str_eq(prompt, our_prompt, pcmk__str_none)) {
out->message(out, "instruction",
"Press Ctrl+D to exit the crm_shadow shell");
} else {
out->message(out, "instruction",
"Remember to unset the CIB_shadow variable by "
"entering the following into your shell:\n"
"\tunset CIB_shadow");
}
free(our_prompt);
}
}
/*!
* \internal
* \brief Commit the shadow file contents to the active cluster
*
* \param[out] error Where to store error
*/
static void
commit_shadow_file(GError **error)
{
char *filename = NULL;
cib_t *real_cib = NULL;
xmlNodePtr input = NULL;
xmlNodePtr section_xml = NULL;
const char *section = NULL;
int rc = pcmk_rc_ok;
if (!options.force) {
const char *reason = "The commit command overwrites the active cluster "
"configuration";
exit_code = CRM_EX_USAGE;
set_danger_error(reason, false, true, error);
return;
}
filename = get_shadow_file(options.instance);
if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
goto done;
}
if (connect_real_cib(&real_cib, error) != pcmk_rc_ok) {
goto done;
}
if (read_xml(filename, &input, error) != pcmk_rc_ok) {
goto done;
}
section_xml = input;
if (!options.full_upload) {
section = PCMK_XE_CONFIGURATION;
section_xml = pcmk__xe_first_child(input, section, NULL, NULL);
}
rc = real_cib->cmds->replace(real_cib, section, section_xml,
options.cmd_options);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
exit_code = pcmk_rc2exitc(rc);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not commit shadow instance '%s' to the CIB: %s",
options.instance, pcmk_rc_str(rc));
}
done:
free(filename);
cib_delete(real_cib);
pcmk__xml_free(input);
}
/*!
* \internal
* \brief Create a new empty shadow instance
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*
* \note If \p --force is given, we try to write the file regardless of whether
* it already exists.
*/
static void
create_shadow_empty(pcmk__output_t *out, GError **error)
{
char *filename = get_shadow_file(options.instance);
xmlNode *output = NULL;
if (!options.force
&& (check_file_exists(filename, false, error) != pcmk_rc_ok)) {
goto done;
}
output = createEmptyCib(0);
crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
out->info(out, "Created new %s configuration",
crm_element_value(output, PCMK_XA_VALIDATE_WITH));
if (write_shadow_file(output, filename, false, error) != pcmk_rc_ok) {
goto done;
}
shadow_setup(out, false, error);
done:
free(filename);
pcmk__xml_free(output);
}
/*!
* \internal
* \brief Create a shadow instance based on the active CIB
*
* \param[in,out] out Output object
* \param[in] reset If true, overwrite the given existing shadow instance.
* Otherwise, create a new shadow instance with the given
* name.
* \param[out] error Where to store error
*
* \note If \p --force is given, we try to write the file regardless of whether
* it already exists.
*/
static void
create_shadow_from_cib(pcmk__output_t *out, bool reset, GError **error)
{
char *filename = get_shadow_file(options.instance);
xmlNode *output = NULL;
if (!options.force) {
if (reset) {
- /* @COMPAT: Reset is dangerous to the shadow file, but to preserve
- * compatibility we can't require --force unless there's a mismatch.
- * At a compatibility break, call set_danger_error() with for_shadow
- * and show_mismatch set to true.
- */
- const char *local = getenv("CIB_shadow");
-
- if (!pcmk__str_eq(local, options.instance, pcmk__str_null_matches)) {
- exit_code = CRM_EX_USAGE;
- g_set_error(error, PCMK__EXITC_ERROR, exit_code,
- "The supplied shadow instance (%s) is not the same "
- "as the active one (%s).\n"
- "To prevent accidental destruction of the shadow "
- "file, the --force flag is required in order to "
- "proceed.",
- options.instance, local);
- goto done;
- }
- }
+ const char *reason = "The reset command overwrites the active "
+ "shadow configuration";
+ exit_code = CRM_EX_USAGE;
+ set_danger_error(reason, true, true, error);
+ goto done;
+ }
if (check_file_exists(filename, reset, error) != pcmk_rc_ok) {
goto done;
}
}
if (query_real_cib(&output, error) != pcmk_rc_ok) {
goto done;
}
if (write_shadow_file(output, filename, reset, error) != pcmk_rc_ok) {
goto done;
}
shadow_setup(out, false, error);
done:
free(filename);
pcmk__xml_free(output);
}
/*!
* \internal
* \brief Delete the shadow file
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*/
static void
delete_shadow_file(pcmk__output_t *out, GError **error)
{
char *filename = NULL;
if (!options.force) {
const char *reason = "The delete command removes the specified shadow "
"file";
exit_code = CRM_EX_USAGE;
set_danger_error(reason, true, true, error);
return;
}
filename = get_shadow_file(options.instance);
if ((unlink(filename) < 0) && (errno != ENOENT)) {
exit_code = pcmk_rc2exitc(errno);
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not remove shadow instance '%s': %s",
options.instance, strerror(errno));
} else {
shadow_teardown(out);
}
free(filename);
}
/*!
* \internal
* \brief Open the shadow file in a text editor
*
* \param[out] error Where to store error
*
* \note The \p EDITOR environment variable must be set.
*/
static void
edit_shadow_file(GError **error)
{
char *filename = NULL;
const char *editor = NULL;
if (get_instance_from_env(error) != pcmk_rc_ok) {
return;
}
filename = get_shadow_file(options.instance);
if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
goto done;
}
editor = getenv("EDITOR");
if (editor == NULL) {
exit_code = CRM_EX_NOT_CONFIGURED;
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"No value for EDITOR defined");
goto done;
}
execlp(editor, "--", filename, NULL);
exit_code = CRM_EX_OSFILE;
g_set_error(error, PCMK__EXITC_ERROR, exit_code,
"Could not invoke EDITOR (%s %s): %s",
editor, filename, strerror(errno));
done:
free(filename);
}
/*!
* \internal
* \brief Show the contents of the active shadow instance
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*/
static void
show_shadow_contents(pcmk__output_t *out, GError **error)
{
char *filename = NULL;
if (get_instance_from_env(error) != pcmk_rc_ok) {
return;
}
filename = get_shadow_file(options.instance);
if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
xmlNode *output = NULL;
bool quiet_orig = out->quiet;
if (read_xml(filename, &output, error) != pcmk_rc_ok) {
goto done;
}
out->quiet = true;
out->message(out, "shadow",
options.instance, NULL, output, NULL, shadow_disp_content);
out->quiet = quiet_orig;
pcmk__xml_free(output);
}
done:
free(filename);
}
/*!
* \internal
* \brief Show the changes in the active shadow instance
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*/
static void
show_shadow_diff(pcmk__output_t *out, GError **error)
{
char *filename = NULL;
xmlNodePtr old_config = NULL;
xmlNodePtr new_config = NULL;
xmlNodePtr diff = NULL;
bool quiet_orig = out->quiet;
if (get_instance_from_env(error) != pcmk_rc_ok) {
return;
}
filename = get_shadow_file(options.instance);
if (check_file_exists(filename, true, error) != pcmk_rc_ok) {
goto done;
}
if (query_real_cib(&old_config, error) != pcmk_rc_ok) {
goto done;
}
if (read_xml(filename, &new_config, error) != pcmk_rc_ok) {
goto done;
}
xml_track_changes(new_config, NULL, new_config, false);
xml_calculate_changes(old_config, new_config);
diff = xml_create_patchset(0, old_config, new_config, NULL, false);
pcmk__log_xml_changes(LOG_INFO, new_config);
xml_accept_changes(new_config);
out->quiet = true;
out->message(out, "shadow",
options.instance, NULL, NULL, diff, shadow_disp_diff);
out->quiet = quiet_orig;
if (diff != NULL) {
/* @COMPAT: Exit with CRM_EX_DIGEST? This is not really an error; we
* just want to indicate that there are differences (as the diff command
* does).
*/
exit_code = CRM_EX_ERROR;
}
done:
free(filename);
pcmk__xml_free(old_config);
pcmk__xml_free(new_config);
pcmk__xml_free(diff);
}
/*!
* \internal
* \brief Show the absolute path of the active shadow instance
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*/
static void
show_shadow_filename(pcmk__output_t *out, GError **error)
{
if (get_instance_from_env(error) == pcmk_rc_ok) {
char *filename = get_shadow_file(options.instance);
bool quiet_orig = out->quiet;
out->quiet = true;
out->message(out, "shadow",
options.instance, filename, NULL, NULL, shadow_disp_file);
out->quiet = quiet_orig;
free(filename);
}
}
/*!
* \internal
* \brief Show the active shadow instance
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*/
static void
show_shadow_instance(pcmk__output_t *out, GError **error)
{
if (get_instance_from_env(error) == pcmk_rc_ok) {
bool quiet_orig = out->quiet;
out->quiet = true;
out->message(out, "shadow",
options.instance, NULL, NULL, NULL, shadow_disp_instance);
out->quiet = quiet_orig;
}
}
/*!
* \internal
* \brief Switch to the given shadow instance
*
* \param[in,out] out Output object
* \param[out] error Where to store error
*/
static void
switch_shadow_instance(pcmk__output_t *out, GError **error)
{
char *filename = NULL;
filename = get_shadow_file(options.instance);
if (check_file_exists(filename, true, error) == pcmk_rc_ok) {
shadow_setup(out, true, error);
}
free(filename);
}
static gboolean
command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
GError **error)
{
if (pcmk__str_any_of(option_name, "-w", "--which", NULL)) {
options.cmd = shadow_cmd_which;
} else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) {
options.cmd = shadow_cmd_display;
} else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) {
options.cmd = shadow_cmd_diff;
} else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) {
options.cmd = shadow_cmd_file;
} else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) {
options.cmd = shadow_cmd_create;
} else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) {
options.cmd = shadow_cmd_create_empty;
} else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) {
options.cmd = shadow_cmd_commit;
} else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
options.cmd = shadow_cmd_delete;
} else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) {
options.cmd = shadow_cmd_edit;
} else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) {
options.cmd = shadow_cmd_reset;
} else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) {
options.cmd = shadow_cmd_switch;
} else {
// Should be impossible
return FALSE;
}
// optarg may be NULL and that's okay
pcmk__str_update(&options.instance, optarg);
return TRUE;
}
static GOptionEntry query_entries[] = {
{ "which", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Indicate the active shadow copy", NULL },
{ "display", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the contents of the active shadow copy", NULL },
{ "diff", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the changes in the active shadow copy", NULL },
{ "file", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Display the location of the active shadow copy file", NULL },
{ NULL }
};
static GOptionEntry command_entries[] = {
{ "create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
"Create the named shadow copy of the active cluster configuration",
"name" },
{ "create-empty", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
command_cb,
"Create the named shadow copy with an empty cluster configuration.\n"
INDENT "Optional: --validate-with", "name" },
{ "commit", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
"Upload the contents of the named shadow copy to the cluster", "name" },
{ "delete", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
"Delete the contents of the named shadow copy", "name" },
{ "edit", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Edit the contents of the active shadow copy with your favorite $EDITOR",
NULL },
{ "reset", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
"Recreate named shadow copy from the active cluster configuration",
- "name" },
+ "name. Required: --force." },
{ "switch", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb,
"(Advanced) Switch to the named shadow copy", "name" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
"(Advanced) Force the action to be performed", NULL },
{ "batch", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.batch,
"(Advanced) Don't spawn a new shell", NULL },
{ "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.full_upload,
"(Advanced) Upload entire CIB, including status, with --commit", NULL },
{ "validate-with", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
&options.validate_with,
"(Advanced) Create an older configuration version", NULL },
{ NULL }
};
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
{
const char *desc = NULL;
GOptionContext *context = NULL;
desc = "Examples:\n\n"
"Create a blank shadow configuration:\n\n"
"\t# crm_shadow --create-empty myShadow\n\n"
"Create a shadow configuration from the running cluster\n\n"
"\t# crm_shadow --create myShadow\n\n"
"Display the current shadow configuration:\n\n"
"\t# crm_shadow --display\n\n"
"Discard the current shadow configuration (named myShadow):\n\n"
"\t# crm_shadow --delete myShadow --force\n\n"
"Upload current shadow configuration (named myShadow) to running "
"cluster:\n\n"
"\t# crm_shadow --commit myShadow\n\n";
context = pcmk__build_arg_context(args, "text (default), xml", group,
"|");
g_option_context_set_description(context, desc);
pcmk__add_arg_group(context, "queries", "Queries:",
"Show query help", query_entries);
pcmk__add_arg_group(context, "commands", "Commands:",
"Show command help", command_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
int rc = pcmk_rc_ok;
pcmk__output_t *out = NULL;
GError *error = NULL;
GOptionGroup *output_group = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, "CDcersv");
GOptionContext *context = build_arg_context(args, &output_group);
crm_log_preinit(NULL, argc, argv);
pcmk__register_formats(output_group, formats);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != pcmk_rc_ok) {
exit_code = CRM_EX_ERROR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Error creating output format %s: %s", args->output_ty,
pcmk_rc_str(rc));
goto done;
}
if (g_strv_length(processed_args) > 1) {
gchar *help = g_option_context_get_help(context, TRUE, NULL);
GString *extra = g_string_sized_new(128);
for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
if (extra->len > 0) {
g_string_append_c(extra, ' ');
}
g_string_append(extra, processed_args[lpc]);
}
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"non-option ARGV-elements: %s\n\n%s", extra->str, help);
g_free(help);
g_string_free(extra, TRUE);
goto done;
}
if (args->version) {
out->version(out, false);
goto done;
}
pcmk__register_messages(out, fmt_functions);
if (options.cmd == shadow_cmd_none) {
// @COMPAT: Create a default command if other tools have one
gchar *help = g_option_context_get_help(context, TRUE, NULL);
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Must specify a query or command option\n\n%s", help);
g_free(help);
goto done;
}
pcmk__cli_init_logging("crm_shadow", args->verbosity);
if (args->verbosity > 0) {
cib__set_call_options(options.cmd_options, crm_system_name,
cib_verbose);
}
// Run the command
switch (options.cmd) {
case shadow_cmd_commit:
commit_shadow_file(&error);
break;
case shadow_cmd_create:
create_shadow_from_cib(out, false, &error);
break;
case shadow_cmd_create_empty:
create_shadow_empty(out, &error);
break;
case shadow_cmd_reset:
create_shadow_from_cib(out, true, &error);
break;
case shadow_cmd_delete:
delete_shadow_file(out, &error);
break;
case shadow_cmd_diff:
show_shadow_diff(out, &error);
break;
case shadow_cmd_display:
show_shadow_contents(out, &error);
break;
case shadow_cmd_edit:
edit_shadow_file(&error);
break;
case shadow_cmd_file:
show_shadow_filename(out, &error);
break;
case shadow_cmd_switch:
switch_shadow_instance(out, &error);
break;
case shadow_cmd_which:
show_shadow_instance(out, &error);
break;
default:
// Should never reach this point
break;
}
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
pcmk__output_and_clear_error(&error, out);
free(options.instance);
g_free(options.validate_with);
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
}
crm_exit(exit_code);
}