diff --git a/cts/cli/regression.tools.exp b/cts/cli/regression.tools.exp index 35e7a8c74c..221730d01e 100644 --- a/cts/cli/regression.tools.exp +++ b/cts/cli/regression.tools.exp @@ -1,3473 +1,3472 @@ Created new pacemaker configuration Setting up shadow instance A new shadow instance was created. To begin using it paste the following into your shell: CIB_shadow=cts-cli ; export CIB_shadow =#=#=#= Begin test: Validate CIB =#=#=#= =#=#=#= Current cib after: Validate CIB =#=#=#= =#=#=#= End test: Validate CIB - OK (0) =#=#=#= * Passed: cibadmin - Validate CIB =#=#=#= Begin test: Configure something before erasing =#=#=#= =#=#=#= Current cib after: Configure something before erasing =#=#=#= =#=#=#= End test: Configure something before erasing - OK (0) =#=#=#= * Passed: crm_attribute - Configure something before erasing =#=#=#= Begin test: Require --force for CIB erasure =#=#=#= The supplied command is considered dangerous. To prevent accidental destruction of the cluster, the --force flag is required in order to proceed. =#=#=#= Current cib after: Require --force for CIB erasure =#=#=#= =#=#=#= End test: Require --force for CIB erasure - Operation not safe (107) =#=#=#= * Passed: cibadmin - Require --force for CIB erasure =#=#=#= Begin test: Allow CIB erasure with --force =#=#=#= =#=#=#= Current cib after: Allow CIB erasure with --force =#=#=#= =#=#=#= End test: Allow CIB erasure with --force - OK (0) =#=#=#= * Passed: cibadmin - Allow CIB erasure with --force =#=#=#= Begin test: Query CIB =#=#=#= =#=#=#= Current cib after: Query CIB =#=#=#= =#=#=#= End test: Query CIB - OK (0) =#=#=#= * Passed: cibadmin - Query CIB =#=#=#= Begin test: Set cluster option =#=#=#= =#=#=#= Current cib after: Set cluster option =#=#=#= =#=#=#= End test: Set cluster option - OK (0) =#=#=#= * Passed: crm_attribute - Set cluster option =#=#=#= Begin test: Query new cluster option =#=#=#= =#=#=#= Current cib after: Query new cluster option =#=#=#= =#=#=#= End test: Query new cluster option - OK (0) =#=#=#= * Passed: cibadmin - Query new cluster option =#=#=#= Begin test: Query cluster options =#=#=#= =#=#=#= Current cib after: Query cluster options =#=#=#= =#=#=#= End test: Query cluster options - OK (0) =#=#=#= * Passed: cibadmin - Query cluster options =#=#=#= Begin test: Set no-quorum policy =#=#=#= =#=#=#= Current cib after: Set no-quorum policy =#=#=#= =#=#=#= End test: Set no-quorum policy - OK (0) =#=#=#= * Passed: crm_attribute - Set no-quorum policy =#=#=#= Begin test: Delete nvpair =#=#=#= =#=#=#= Current cib after: Delete nvpair =#=#=#= =#=#=#= End test: Delete nvpair - OK (0) =#=#=#= * Passed: cibadmin - Delete nvpair =#=#=#= Begin test: Create operation should fail =#=#=#= Call failed: File exists =#=#=#= Current cib after: Create operation should fail =#=#=#= =#=#=#= End test: Create operation should fail - Requested item already exists (108) =#=#=#= * Passed: cibadmin - Create operation should fail =#=#=#= Begin test: Modify cluster options section =#=#=#= =#=#=#= Current cib after: Modify cluster options section =#=#=#= =#=#=#= End test: Modify cluster options section - OK (0) =#=#=#= * Passed: cibadmin - Modify cluster options section =#=#=#= Begin test: Query updated cluster option =#=#=#= =#=#=#= Current cib after: Query updated cluster option =#=#=#= =#=#=#= End test: Query updated cluster option - OK (0) =#=#=#= * Passed: cibadmin - Query updated cluster option =#=#=#= Begin test: Set duplicate cluster option =#=#=#= =#=#=#= Current cib after: Set duplicate cluster option =#=#=#= =#=#=#= End test: Set duplicate cluster option - OK (0) =#=#=#= * Passed: crm_attribute - Set duplicate cluster option =#=#=#= Begin test: Setting multiply defined cluster option should fail =#=#=#= Multiple attributes match name=cluster-delay Value: 60s (id=cib-bootstrap-options-cluster-delay) Value: 40s (id=duplicate-cluster-delay) Please choose from one of the matches above and supply the 'id' with --attr-id =#=#=#= Current cib after: Setting multiply defined cluster option should fail =#=#=#= =#=#=#= End test: Setting multiply defined cluster option should fail - Multiple items match request (109) =#=#=#= * Passed: crm_attribute - Setting multiply defined cluster option should fail =#=#=#= Begin test: Set cluster option with -s =#=#=#= =#=#=#= Current cib after: Set cluster option with -s =#=#=#= =#=#=#= End test: Set cluster option with -s - OK (0) =#=#=#= * Passed: crm_attribute - Set cluster option with -s =#=#=#= Begin test: Delete cluster option with -i =#=#=#= Deleted crm_config option: id=(null) name=cluster-delay =#=#=#= Current cib after: Delete cluster option with -i =#=#=#= =#=#=#= End test: Delete cluster option with -i - OK (0) =#=#=#= * Passed: crm_attribute - Delete cluster option with -i =#=#=#= Begin test: Create node1 and bring it online =#=#=#= Current cluster status: Performing requested modifications + Bringing node node1 online Transition Summary: Executing cluster transition: Revised cluster status: Online: [ node1 ] =#=#=#= Current cib after: Create node1 and bring it online =#=#=#= =#=#=#= End test: Create node1 and bring it online - OK (0) =#=#=#= * Passed: crm_simulate - Create node1 and bring it online =#=#=#= Begin test: Create node attribute =#=#=#= =#=#=#= Current cib after: Create node attribute =#=#=#= =#=#=#= End test: Create node attribute - OK (0) =#=#=#= * Passed: crm_attribute - Create node attribute =#=#=#= Begin test: Query new node attribute =#=#=#= =#=#=#= Current cib after: Query new node attribute =#=#=#= =#=#=#= End test: Query new node attribute - OK (0) =#=#=#= * Passed: cibadmin - Query new node attribute =#=#=#= Begin test: Set a transient (fail-count) node attribute =#=#=#= =#=#=#= Current cib after: Set a transient (fail-count) node attribute =#=#=#= =#=#=#= End test: Set a transient (fail-count) node attribute - OK (0) =#=#=#= * Passed: crm_attribute - Set a transient (fail-count) node attribute =#=#=#= Begin test: Query a fail count =#=#=#= scope=status name=fail-count-foo value=3 =#=#=#= Current cib after: Query a fail count =#=#=#= =#=#=#= End test: Query a fail count - OK (0) =#=#=#= * Passed: crm_failcount - Query a fail count =#=#=#= Begin test: Delete a transient (fail-count) node attribute =#=#=#= Deleted status attribute: id=status-node1-fail-count-foo name=fail-count-foo =#=#=#= Current cib after: Delete a transient (fail-count) node attribute =#=#=#= =#=#=#= End test: Delete a transient (fail-count) node attribute - OK (0) =#=#=#= * Passed: crm_attribute - Delete a transient (fail-count) node attribute =#=#=#= Begin test: Digest calculation =#=#=#= Digest: =#=#=#= Current cib after: Digest calculation =#=#=#= =#=#=#= End test: Digest calculation - OK (0) =#=#=#= * Passed: cibadmin - Digest calculation =#=#=#= Begin test: Replace operation should fail =#=#=#= Call failed: Update was older than existing configuration =#=#=#= Current cib after: Replace operation should fail =#=#=#= =#=#=#= End test: Replace operation should fail - Update was older than existing configuration (103) =#=#=#= * Passed: cibadmin - Replace operation should fail =#=#=#= Begin test: Default standby value =#=#=#= scope=status name=standby value=off =#=#=#= Current cib after: Default standby value =#=#=#= =#=#=#= End test: Default standby value - OK (0) =#=#=#= * Passed: crm_standby - Default standby value =#=#=#= Begin test: Set standby status =#=#=#= =#=#=#= Current cib after: Set standby status =#=#=#= =#=#=#= End test: Set standby status - OK (0) =#=#=#= * Passed: crm_standby - Set standby status =#=#=#= Begin test: Query standby value =#=#=#= scope=nodes name=standby value=true =#=#=#= Current cib after: Query standby value =#=#=#= =#=#=#= End test: Query standby value - OK (0) =#=#=#= * Passed: crm_standby - Query standby value =#=#=#= Begin test: Delete standby value =#=#=#= Deleted nodes attribute: id=nodes-node1-standby name=standby =#=#=#= Current cib after: Delete standby value =#=#=#= =#=#=#= End test: Delete standby value - OK (0) =#=#=#= * Passed: crm_standby - Delete standby value =#=#=#= Begin test: Create a resource =#=#=#= =#=#=#= Current cib after: Create a resource =#=#=#= =#=#=#= End test: Create a resource - OK (0) =#=#=#= * Passed: cibadmin - Create a resource =#=#=#= Begin test: Create a resource meta attribute =#=#=#= Set 'dummy' option: id=dummy-meta_attributes-is-managed set=dummy-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute =#=#=#= =#=#=#= End test: Create a resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute =#=#=#= Begin test: Query a resource meta attribute =#=#=#= false =#=#=#= Current cib after: Query a resource meta attribute =#=#=#= =#=#=#= End test: Query a resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Query a resource meta attribute =#=#=#= Begin test: Remove a resource meta attribute =#=#=#= Deleted 'dummy' option: id=dummy-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Remove a resource meta attribute =#=#=#= =#=#=#= End test: Remove a resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Remove a resource meta attribute =#=#=#= Begin test: Create another resource meta attribute =#=#=#= Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attributes name=target-role value=Stopped =#=#=#= End test: Create another resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Create another resource meta attribute =#=#=#= Begin test: Show why a resource is not running =#=#=#= Resource dummy is not running - - * Configuration specifies 'dummy' should remain stopped - +Configuration specifies 'dummy' should remain stopped =#=#=#= End test: Show why a resource is not running - OK (0) =#=#=#= * Passed: crm_resource - Show why a resource is not running =#=#=#= Begin test: Remove another resource meta attribute =#=#=#= Deleted 'dummy' option: id=dummy-meta_attributes-target-role name=target-role =#=#=#= End test: Remove another resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Remove another resource meta attribute =#=#=#= Begin test: Create a resource attribute =#=#=#= Set 'dummy' option: id=dummy-instance_attributes-delay set=dummy-instance_attributes name=delay value=10s =#=#=#= Current cib after: Create a resource attribute =#=#=#= =#=#=#= End test: Create a resource attribute - OK (0) =#=#=#= * Passed: crm_resource - Create a resource attribute =#=#=#= Begin test: List the configured resources =#=#=#= - dummy (ocf::pacemaker:Dummy): Stopped +Full List of Resources: + * dummy (ocf::pacemaker:Dummy): Stopped =#=#=#= Current cib after: List the configured resources =#=#=#= =#=#=#= End test: List the configured resources - OK (0) =#=#=#= * Passed: crm_resource - List the configured resources =#=#=#= Begin test: List IDs of instantiated resources =#=#=#= dummy =#=#=#= End test: List IDs of instantiated resources - OK (0) =#=#=#= * Passed: crm_resource - List IDs of instantiated resources =#=#=#= Begin test: Show XML configuration of resource =#=#=#= - dummy (ocf::pacemaker:Dummy): Stopped -xml: +dummy (ocf::pacemaker:Dummy): Stopped +Resource XML: - =#=#=#= End test: Show XML configuration of resource - OK (0) =#=#=#= * Passed: crm_resource - Show XML configuration of resource =#=#=#= Begin test: Require a destination when migrating a resource that is stopped =#=#=#= crm_resource: Resource 'dummy' not moved: active in 0 locations. To prevent 'dummy' from running on a specific location, specify a node. Error performing operation: Invalid argument =#=#=#= Current cib after: Require a destination when migrating a resource that is stopped =#=#=#= =#=#=#= End test: Require a destination when migrating a resource that is stopped - Incorrect usage (64) =#=#=#= * Passed: crm_resource - Require a destination when migrating a resource that is stopped =#=#=#= Begin test: Don't support migration to non-existent locations =#=#=#= crm_resource: Error performing operation: Node not found =#=#=#= Current cib after: Don't support migration to non-existent locations =#=#=#= =#=#=#= End test: Don't support migration to non-existent locations - No such object (105) =#=#=#= * Passed: crm_resource - Don't support migration to non-existent locations =#=#=#= Begin test: Create a fencing resource =#=#=#= =#=#=#= Current cib after: Create a fencing resource =#=#=#= =#=#=#= End test: Create a fencing resource - OK (0) =#=#=#= * Passed: cibadmin - Create a fencing resource =#=#=#= Begin test: Bring resources online =#=#=#= Current cluster status: Online: [ node1 ] dummy (ocf::pacemaker:Dummy): Stopped Fence (stonith:fence_true): Stopped Transition Summary: * Start dummy ( node1 ) * Start Fence ( node1 ) Executing cluster transition: * Resource action: dummy monitor on node1 * Resource action: Fence monitor on node1 * Resource action: dummy start on node1 * Resource action: Fence start on node1 Revised cluster status: Online: [ node1 ] dummy (ocf::pacemaker:Dummy): Started node1 Fence (stonith:fence_true): Started node1 =#=#=#= Current cib after: Bring resources online =#=#=#= =#=#=#= End test: Bring resources online - OK (0) =#=#=#= * Passed: crm_simulate - Bring resources online =#=#=#= Begin test: Try to move a resource to its existing location =#=#=#= crm_resource: Error performing operation: Already in requested state =#=#=#= Current cib after: Try to move a resource to its existing location =#=#=#= =#=#=#= End test: Try to move a resource to its existing location - Requested item already exists (108) =#=#=#= * Passed: crm_resource - Try to move a resource to its existing location =#=#=#= Begin test: Move a resource from its existing location =#=#=#= WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score of -INFINITY for resource dummy on node1. This will prevent dummy from running on node1 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool This will be the case even if node1 is the last node in the cluster =#=#=#= Current cib after: Move a resource from its existing location =#=#=#= =#=#=#= End test: Move a resource from its existing location - OK (0) =#=#=#= * Passed: crm_resource - Move a resource from its existing location =#=#=#= Begin test: Clear out constraints generated by --move =#=#=#= Removing constraint: cli-ban-dummy-on-node1 =#=#=#= Current cib after: Clear out constraints generated by --move =#=#=#= =#=#=#= End test: Clear out constraints generated by --move - OK (0) =#=#=#= * Passed: crm_resource - Clear out constraints generated by --move =#=#=#= Begin test: Default ticket granted state =#=#=#= false =#=#=#= Current cib after: Default ticket granted state =#=#=#= =#=#=#= End test: Default ticket granted state - OK (0) =#=#=#= * Passed: crm_ticket - Default ticket granted state =#=#=#= Begin test: Set ticket granted state =#=#=#= =#=#=#= Current cib after: Set ticket granted state =#=#=#= =#=#=#= End test: Set ticket granted state - OK (0) =#=#=#= * Passed: crm_ticket - Set ticket granted state =#=#=#= Begin test: Query ticket granted state =#=#=#= false =#=#=#= Current cib after: Query ticket granted state =#=#=#= =#=#=#= End test: Query ticket granted state - OK (0) =#=#=#= * Passed: crm_ticket - Query ticket granted state =#=#=#= Begin test: Delete ticket granted state =#=#=#= =#=#=#= Current cib after: Delete ticket granted state =#=#=#= =#=#=#= End test: Delete ticket granted state - OK (0) =#=#=#= * Passed: crm_ticket - Delete ticket granted state =#=#=#= Begin test: Make a ticket standby =#=#=#= =#=#=#= Current cib after: Make a ticket standby =#=#=#= =#=#=#= End test: Make a ticket standby - OK (0) =#=#=#= * Passed: crm_ticket - Make a ticket standby =#=#=#= Begin test: Query ticket standby state =#=#=#= true =#=#=#= Current cib after: Query ticket standby state =#=#=#= =#=#=#= End test: Query ticket standby state - OK (0) =#=#=#= * Passed: crm_ticket - Query ticket standby state =#=#=#= Begin test: Activate a ticket =#=#=#= =#=#=#= Current cib after: Activate a ticket =#=#=#= =#=#=#= End test: Activate a ticket - OK (0) =#=#=#= * Passed: crm_ticket - Activate a ticket =#=#=#= Begin test: Delete ticket standby state =#=#=#= =#=#=#= Current cib after: Delete ticket standby state =#=#=#= =#=#=#= End test: Delete ticket standby state - OK (0) =#=#=#= * Passed: crm_ticket - Delete ticket standby state =#=#=#= Begin test: Ban a resource on unknown node =#=#=#= crm_resource: Error performing operation: Node not found =#=#=#= Current cib after: Ban a resource on unknown node =#=#=#= =#=#=#= End test: Ban a resource on unknown node - No such object (105) =#=#=#= * Passed: crm_resource - Ban a resource on unknown node =#=#=#= Begin test: Create two more nodes and bring them online =#=#=#= Current cluster status: Online: [ node1 ] dummy (ocf::pacemaker:Dummy): Started node1 Fence (stonith:fence_true): Started node1 Performing requested modifications + Bringing node node2 online + Bringing node node3 online Transition Summary: * Move Fence ( node1 -> node2 ) Executing cluster transition: * Resource action: dummy monitor on node3 * Resource action: dummy monitor on node2 * Resource action: Fence stop on node1 * Resource action: Fence monitor on node3 * Resource action: Fence monitor on node2 * Resource action: Fence start on node2 Revised cluster status: Online: [ node1 node2 node3 ] dummy (ocf::pacemaker:Dummy): Started node1 Fence (stonith:fence_true): Started node2 =#=#=#= Current cib after: Create two more nodes and bring them online =#=#=#= =#=#=#= End test: Create two more nodes and bring them online - OK (0) =#=#=#= * Passed: crm_simulate - Create two more nodes and bring them online =#=#=#= Begin test: Ban dummy from node1 =#=#=#= WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score of -INFINITY for resource dummy on node1. This will prevent dummy from running on node1 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool This will be the case even if node1 is the last node in the cluster =#=#=#= Current cib after: Ban dummy from node1 =#=#=#= =#=#=#= End test: Ban dummy from node1 - OK (0) =#=#=#= * Passed: crm_resource - Ban dummy from node1 =#=#=#= Begin test: Show where a resource is running =#=#=#= -resource dummy is running on: node1 +resource dummy is running on: node1 =#=#=#= End test: Show where a resource is running - OK (0) =#=#=#= * Passed: crm_resource - Show where a resource is running =#=#=#= Begin test: Show constraints on a resource =#=#=#= -* dummy - : Node node1 (score=-INFINITY, id=cli-ban-dummy-on-node1) +dummy: + * Locations: + * Node node1 (score=-INFINITY, id=cli-ban-dummy-on-node1) =#=#=#= End test: Show constraints on a resource - OK (0) =#=#=#= * Passed: crm_resource - Show constraints on a resource =#=#=#= Begin test: Ban dummy from node2 =#=#=#= WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node2' with a score of -INFINITY for resource dummy on node2. This will prevent dummy from running on node2 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool This will be the case even if node2 is the last node in the cluster =#=#=#= Current cib after: Ban dummy from node2 =#=#=#= =#=#=#= End test: Ban dummy from node2 - OK (0) =#=#=#= * Passed: crm_resource - Ban dummy from node2 =#=#=#= Begin test: Relocate resources due to ban =#=#=#= Current cluster status: Online: [ node1 node2 node3 ] dummy (ocf::pacemaker:Dummy): Started node1 Fence (stonith:fence_true): Started node2 Transition Summary: * Move dummy ( node1 -> node3 ) Executing cluster transition: * Resource action: dummy stop on node1 * Resource action: dummy start on node3 Revised cluster status: Online: [ node1 node2 node3 ] dummy (ocf::pacemaker:Dummy): Started node3 Fence (stonith:fence_true): Started node2 =#=#=#= Current cib after: Relocate resources due to ban =#=#=#= =#=#=#= End test: Relocate resources due to ban - OK (0) =#=#=#= * Passed: crm_simulate - Relocate resources due to ban =#=#=#= Begin test: Move dummy to node1 =#=#=#= =#=#=#= Current cib after: Move dummy to node1 =#=#=#= =#=#=#= End test: Move dummy to node1 - OK (0) =#=#=#= * Passed: crm_resource - Move dummy to node1 =#=#=#= Begin test: Clear implicit constraints for dummy on node2 =#=#=#= Removing constraint: cli-ban-dummy-on-node2 =#=#=#= Current cib after: Clear implicit constraints for dummy on node2 =#=#=#= =#=#=#= End test: Clear implicit constraints for dummy on node2 - OK (0) =#=#=#= * Passed: crm_resource - Clear implicit constraints for dummy on node2 =#=#=#= Begin test: Drop the status section =#=#=#= =#=#=#= End test: Drop the status section - OK (0) =#=#=#= * Passed: cibadmin - Drop the status section =#=#=#= Begin test: Create a clone =#=#=#= =#=#=#= End test: Create a clone - OK (0) =#=#=#= * Passed: cibadmin - Create a clone =#=#=#= Begin test: Create a resource meta attribute =#=#=#= Performing update of 'is-managed' on 'test-clone', the parent of 'test-primitive' Set 'test-clone' option: id=test-clone-meta_attributes-is-managed set=test-clone-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute =#=#=#= =#=#=#= End test: Create a resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute =#=#=#= Begin test: Create a resource meta attribute in the primitive =#=#=#= Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed set=test-primitive-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute in the primitive =#=#=#= =#=#=#= End test: Create a resource meta attribute in the primitive - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute in the primitive =#=#=#= Begin test: Update resource meta attribute with duplicates =#=#=#= Multiple attributes match name=is-managed Value: false (id=test-primitive-meta_attributes-is-managed) Value: false (id=test-clone-meta_attributes-is-managed) A value for 'is-managed' already exists in child 'test-primitive', performing update on that instead of 'test-clone' Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed value=true =#=#=#= Current cib after: Update resource meta attribute with duplicates =#=#=#= =#=#=#= End test: Update resource meta attribute with duplicates - OK (0) =#=#=#= * Passed: crm_resource - Update resource meta attribute with duplicates =#=#=#= Begin test: Update resource meta attribute with duplicates (force clone) =#=#=#= Set 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-managed value=true =#=#=#= Current cib after: Update resource meta attribute with duplicates (force clone) =#=#=#= =#=#=#= End test: Update resource meta attribute with duplicates (force clone) - OK (0) =#=#=#= * Passed: crm_resource - Update resource meta attribute with duplicates (force clone) =#=#=#= Begin test: Update child resource meta attribute with duplicates =#=#=#= Multiple attributes match name=is-managed Value: true (id=test-primitive-meta_attributes-is-managed) Value: true (id=test-clone-meta_attributes-is-managed) Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed value=false =#=#=#= Current cib after: Update child resource meta attribute with duplicates =#=#=#= =#=#=#= End test: Update child resource meta attribute with duplicates - OK (0) =#=#=#= * Passed: crm_resource - Update child resource meta attribute with duplicates =#=#=#= Begin test: Delete resource meta attribute with duplicates =#=#=#= Multiple attributes match name=is-managed Value: false (id=test-primitive-meta_attributes-is-managed) Value: true (id=test-clone-meta_attributes-is-managed) A value for 'is-managed' already exists in child 'test-primitive', performing delete on that instead of 'test-clone' Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource meta attribute with duplicates =#=#=#= =#=#=#= End test: Delete resource meta attribute with duplicates - OK (0) =#=#=#= * Passed: crm_resource - Delete resource meta attribute with duplicates =#=#=#= Begin test: Delete resource meta attribute in parent =#=#=#= Performing delete of 'is-managed' on 'test-clone', the parent of 'test-primitive' Deleted 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource meta attribute in parent =#=#=#= =#=#=#= End test: Delete resource meta attribute in parent - OK (0) =#=#=#= * Passed: crm_resource - Delete resource meta attribute in parent =#=#=#= Begin test: Create a resource meta attribute in the primitive =#=#=#= Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed set=test-primitive-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute in the primitive =#=#=#= =#=#=#= End test: Create a resource meta attribute in the primitive - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute in the primitive =#=#=#= Begin test: Update existing resource meta attribute =#=#=#= A value for 'is-managed' already exists in child 'test-primitive', performing update on that instead of 'test-clone' Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed value=true =#=#=#= Current cib after: Update existing resource meta attribute =#=#=#= =#=#=#= End test: Update existing resource meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Update existing resource meta attribute =#=#=#= Begin test: Create a resource meta attribute in the parent =#=#=#= Set 'test-clone' option: id=test-clone-meta_attributes-is-managed set=test-clone-meta_attributes name=is-managed value=true =#=#=#= Current cib after: Create a resource meta attribute in the parent =#=#=#= =#=#=#= End test: Create a resource meta attribute in the parent - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute in the parent =#=#=#= Begin test: Copy resources =#=#=#= =#=#=#= End test: Copy resources - OK (0) =#=#=#= * Passed: cibadmin - Copy resources =#=#=#= Begin test: Delete resource parent meta attribute (force) =#=#=#= Deleted 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource parent meta attribute (force) =#=#=#= =#=#=#= End test: Delete resource parent meta attribute (force) - OK (0) =#=#=#= * Passed: crm_resource - Delete resource parent meta attribute (force) =#=#=#= Begin test: Restore duplicates =#=#=#= =#=#=#= Current cib after: Restore duplicates =#=#=#= =#=#=#= End test: Restore duplicates - OK (0) =#=#=#= * Passed: cibadmin - Restore duplicates =#=#=#= Begin test: Delete resource child meta attribute =#=#=#= Multiple attributes match name=is-managed Value: true (id=test-primitive-meta_attributes-is-managed) Value: true (id=test-clone-meta_attributes-is-managed) Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource child meta attribute =#=#=#= =#=#=#= End test: Delete resource child meta attribute - OK (0) =#=#=#= * Passed: crm_resource - Delete resource child meta attribute =#=#=#= Begin test: Create a resource meta attribute in dummy1 =#=#=#= Set 'dummy1' option: id=dummy1-meta_attributes-is-managed set=dummy1-meta_attributes name=is-managed value=true =#=#=#= Current cib after: Create a resource meta attribute in dummy1 =#=#=#= =#=#=#= End test: Create a resource meta attribute in dummy1 - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute in dummy1 =#=#=#= Begin test: Create a resource meta attribute in dummy-group =#=#=#= Set 'dummy1' option: id=dummy1-meta_attributes-is-managed name=is-managed value=false Set 'dummy-group' option: id=dummy-group-meta_attributes-is-managed set=dummy-group-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute in dummy-group =#=#=#= =#=#=#= End test: Create a resource meta attribute in dummy-group - OK (0) =#=#=#= * Passed: crm_resource - Create a resource meta attribute in dummy-group =#=#=#= Begin test: Specify a lifetime when moving a resource =#=#=#= Migration will take effect until: =#=#=#= Current cib after: Specify a lifetime when moving a resource =#=#=#= =#=#=#= End test: Specify a lifetime when moving a resource - OK (0) =#=#=#= * Passed: crm_resource - Specify a lifetime when moving a resource =#=#=#= Begin test: Try to move a resource previously moved with a lifetime =#=#=#= =#=#=#= Current cib after: Try to move a resource previously moved with a lifetime =#=#=#= =#=#=#= End test: Try to move a resource previously moved with a lifetime - OK (0) =#=#=#= * Passed: crm_resource - Try to move a resource previously moved with a lifetime =#=#=#= Begin test: Ban dummy from node1 for a short time =#=#=#= +Migration will take effect until: WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score of -INFINITY for resource dummy on node1. This will prevent dummy from running on node1 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool This will be the case even if node1 is the last node in the cluster -Migration will take effect until: =#=#=#= Current cib after: Ban dummy from node1 for a short time =#=#=#= =#=#=#= End test: Ban dummy from node1 for a short time - OK (0) =#=#=#= * Passed: crm_resource - Ban dummy from node1 for a short time =#=#=#= Begin test: Remove expired constraints =#=#=#= Removing constraint: cli-ban-dummy-on-node1 =#=#=#= Current cib after: Remove expired constraints =#=#=#= =#=#=#= End test: Remove expired constraints - OK (0) =#=#=#= * Passed: crm_resource - Remove expired constraints =#=#=#= Begin test: Clear all implicit constraints for dummy =#=#=#= Removing constraint: cli-prefer-dummy =#=#=#= Current cib after: Clear all implicit constraints for dummy =#=#=#= =#=#=#= End test: Clear all implicit constraints for dummy - OK (0) =#=#=#= * Passed: crm_resource - Clear all implicit constraints for dummy =#=#=#= Begin test: Delete a resource =#=#=#= =#=#=#= Current cib after: Delete a resource =#=#=#= =#=#=#= End test: Delete a resource - OK (0) =#=#=#= * Passed: crm_resource - Delete a resource =#=#=#= Begin test: Create an XML patchset =#=#=#= =#=#=#= End test: Create an XML patchset - Error occurred (1) =#=#=#= * Passed: crm_diff - Create an XML patchset diff --git a/include/crm/crm.h b/include/crm/crm.h index 4eca278523..b07152c46f 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,232 +1,232 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM__H # define CRM__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief A dumping ground * \ingroup core */ # include # include # include # include # include # include /*! * The CRM feature set assists with compatibility in mixed-version clusters. * The major version number increases when nodes with different versions * would not work (rolling upgrades are not allowed). The minor version * number increases when mixed-version clusters are allowed only during * rolling upgrades (a node with the oldest feature set will be elected DC). The * minor-minor version number is ignored, but allows resource agents to detect * cluster support for various features. * * The feature set also affects the processing of old saved CIBs (such as for * many scheduler regression tests). * * Particular feature points currently used by pacemaker: * * >2.1: Operation updates include timing data * >=3.0.5: XML v2 digests are created * >=3.0.8: Peers do not need acks for cancellations * >=3.0.9: DC will send its own shutdown request to all peers * XML v2 patchsets are created by default * >=3.0.13: Fail counts include operation name and interval * >=3.2.0: DC supports PCMK_LRM_OP_INVALID and PCMK_LRM_OP_NOT_CONNECTED */ -# define CRM_FEATURE_SET "3.6.2" +# define CRM_FEATURE_SET "3.6.3" # define EOS '\0' # define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) # ifndef MAX_NAME # define MAX_NAME 256 # endif # ifndef __GNUC__ # define __builtin_expect(expr, result) (expr) # endif /* Some handy macros used by the Linux kernel */ # define __likely(expr) __builtin_expect(expr, 1) # define __unlikely(expr) __builtin_expect(expr, 0) # define CRM_META "CRM_meta" extern char *crm_system_name; /* *INDENT-OFF* */ // Used for some internal IPC timeouts (maybe should be configurable option) # define MAX_IPC_DELAY 120 // How we represent "infinite" scores # define CRM_SCORE_INFINITY 1000000 # define CRM_INFINITY_S "INFINITY" # define CRM_PLUS_INFINITY_S "+" CRM_INFINITY_S # define CRM_MINUS_INFINITY_S "-" CRM_INFINITY_S /* @COMPAT API < 2.0.0 Deprecated "infinity" aliases * * INFINITY might be defined elsewhere (e.g. math.h), so undefine it first. * This, of course, complicates any attempt to use the other definition in any * code that includes this header. */ # undef INFINITY # define INFINITY_S "INFINITY" # define MINUS_INFINITY_S "-INFINITY" # define INFINITY 1000000 /* Sub-systems */ # define CRM_SYSTEM_DC "dc" # define CRM_SYSTEM_DCIB "dcib" /* The master CIB */ # define CRM_SYSTEM_CIB "cib" # define CRM_SYSTEM_CRMD "crmd" # define CRM_SYSTEM_LRMD "lrmd" # define CRM_SYSTEM_PENGINE "pengine" # define CRM_SYSTEM_TENGINE "tengine" # define CRM_SYSTEM_STONITHD "stonithd" # define CRM_SYSTEM_MCP "pacemakerd" // Names of internally generated node attributes # define CRM_ATTR_UNAME "#uname" # define CRM_ATTR_ID "#id" # define CRM_ATTR_KIND "#kind" # define CRM_ATTR_ROLE "#role" # define CRM_ATTR_IS_DC "#is_dc" # define CRM_ATTR_CLUSTER_NAME "#cluster-name" # define CRM_ATTR_SITE_NAME "#site-name" # define CRM_ATTR_UNFENCED "#node-unfenced" # define CRM_ATTR_DIGESTS_ALL "#digests-all" # define CRM_ATTR_DIGESTS_SECURE "#digests-secure" # define CRM_ATTR_RA_VERSION "#ra-version" # define CRM_ATTR_PROTOCOL "#attrd-protocol" /* Valid operations */ # define CRM_OP_NOOP "noop" # define CRM_OP_JOIN_ANNOUNCE "join_announce" # define CRM_OP_JOIN_OFFER "join_offer" # define CRM_OP_JOIN_REQUEST "join_request" # define CRM_OP_JOIN_ACKNAK "join_ack_nack" # define CRM_OP_JOIN_CONFIRM "join_confirm" # define CRM_OP_PING "ping" # define CRM_OP_NODE_INFO "node-info" # define CRM_OP_THROTTLE "throttle" # define CRM_OP_VOTE "vote" # define CRM_OP_NOVOTE "no-vote" # define CRM_OP_HELLO "hello" # define CRM_OP_PECALC "pe_calc" # define CRM_OP_QUIT "quit" # define CRM_OP_LOCAL_SHUTDOWN "start_shutdown" # define CRM_OP_SHUTDOWN_REQ "req_shutdown" # define CRM_OP_SHUTDOWN "do_shutdown" # define CRM_OP_FENCE "stonith" # define CRM_OP_REGISTER "register" # define CRM_OP_IPC_FWD "ipc_fwd" # define CRM_OP_INVOKE_LRM "lrm_invoke" # define CRM_OP_LRM_REFRESH "lrm_refresh" /* Deprecated */ # define CRM_OP_LRM_QUERY "lrm_query" # define CRM_OP_LRM_DELETE "lrm_delete" # define CRM_OP_LRM_FAIL "lrm_fail" # define CRM_OP_PROBED "probe_complete" # define CRM_OP_REPROBE "probe_again" # define CRM_OP_CLEAR_FAILCOUNT "clear_failcount" # define CRM_OP_REMOTE_STATE "remote_state" # define CRM_OP_RELAXED_SET "one-or-more" # define CRM_OP_RELAXED_CLONE "clone-one-or-more" # define CRM_OP_RM_NODE_CACHE "rm_node_cache" # define CRM_OP_MAINTENANCE_NODES "maintenance_nodes" /* Possible cluster membership states */ # define CRMD_JOINSTATE_DOWN "down" # define CRMD_JOINSTATE_PENDING "pending" # define CRMD_JOINSTATE_MEMBER "member" # define CRMD_JOINSTATE_NACK "banned" # define CRMD_ACTION_DELETE "delete" # define CRMD_ACTION_CANCEL "cancel" # define CRMD_ACTION_RELOAD "reload" # define CRMD_ACTION_MIGRATE "migrate_to" # define CRMD_ACTION_MIGRATED "migrate_from" # define CRMD_ACTION_START "start" # define CRMD_ACTION_STARTED "running" # define CRMD_ACTION_STOP "stop" # define CRMD_ACTION_STOPPED "stopped" # define CRMD_ACTION_PROMOTE "promote" # define CRMD_ACTION_PROMOTED "promoted" # define CRMD_ACTION_DEMOTE "demote" # define CRMD_ACTION_DEMOTED "demoted" # define CRMD_ACTION_NOTIFY "notify" # define CRMD_ACTION_NOTIFIED "notified" # define CRMD_ACTION_STATUS "monitor" # define CRMD_ACTION_METADATA "meta-data" # define CRMD_METADATA_CALL_TIMEOUT 30000 /* short names */ # define RSC_DELETE CRMD_ACTION_DELETE # define RSC_CANCEL CRMD_ACTION_CANCEL # define RSC_MIGRATE CRMD_ACTION_MIGRATE # define RSC_MIGRATED CRMD_ACTION_MIGRATED # define RSC_START CRMD_ACTION_START # define RSC_STARTED CRMD_ACTION_STARTED # define RSC_STOP CRMD_ACTION_STOP # define RSC_STOPPED CRMD_ACTION_STOPPED # define RSC_PROMOTE CRMD_ACTION_PROMOTE # define RSC_PROMOTED CRMD_ACTION_PROMOTED # define RSC_DEMOTE CRMD_ACTION_DEMOTE # define RSC_DEMOTED CRMD_ACTION_DEMOTED # define RSC_NOTIFY CRMD_ACTION_NOTIFY # define RSC_NOTIFIED CRMD_ACTION_NOTIFIED # define RSC_STATUS CRMD_ACTION_STATUS # define RSC_METADATA CRMD_ACTION_METADATA /* *INDENT-ON* */ typedef GList *GListPtr; # include # include static inline const char * crm_action_str(const char *task, guint interval_ms) { if ((task != NULL) && (interval_ms == 0) && (strcasecmp(task, RSC_STATUS) == 0)) { return "probe"; } return task; } #ifdef __cplusplus } #endif #endif diff --git a/include/crm/lrmd_internal.h b/include/crm/lrmd_internal.h index 498a9baf2f..720e1a3344 100644 --- a/include/crm/lrmd_internal.h +++ b/include/crm/lrmd_internal.h @@ -1,69 +1,72 @@ /* * Copyright 2015-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef LRMD_INTERNAL__H #define LRMD_INTERNAL__H #include // uint32_t #include // GList, GHashTable, gpointer #include // xmlNode #include // crm_ipc_t #include // mainloop_io_t, ipc_client_callbacks +#include // pcmk__output_t #include // pcmk__remote_t #include // lrmd_t, lrmd_event_data_t int lrmd_send_attribute_alert(lrmd_t *lrmd, GList *alert_list, const char *node, uint32_t nodeid, const char *attr_name, const char *attr_value); int lrmd_send_node_alert(lrmd_t *lrmd, GList *alert_list, const char *node, uint32_t nodeid, const char *state); int lrmd_send_fencing_alert(lrmd_t *lrmd, GList *alert_list, const char *target, const char *task, const char *desc, int op_rc); int lrmd_send_resource_alert(lrmd_t *lrmd, GList *alert_list, const char *node, lrmd_event_data_t *op); int lrmd_tls_send_msg(pcmk__remote_t *session, xmlNode *msg, uint32_t id, const char *msg_type); /* Shared functions for IPC proxy back end */ typedef struct remote_proxy_s { char *node_name; char *session_id; gboolean is_local; crm_ipc_t *ipc; mainloop_io_t *source; uint32_t last_request_id; lrmd_t *lrm; } remote_proxy_t; remote_proxy_t *remote_proxy_new(lrmd_t *lrmd, struct ipc_client_callbacks *proxy_callbacks, const char *node_name, const char *session_id, const char *channel); int remote_proxy_check(lrmd_t *lrmd, GHashTable *hash); void remote_proxy_cb(lrmd_t *lrmd, const char *node_name, xmlNode *msg); void remote_proxy_ack_shutdown(lrmd_t *lrmd); void remote_proxy_nack_shutdown(lrmd_t *lrmd); int remote_proxy_dispatch(const char *buffer, ssize_t length, gpointer userdata); void remote_proxy_disconnected(gpointer data); void remote_proxy_free(gpointer data); void remote_proxy_relay_event(remote_proxy_t *proxy, xmlNode *msg); void remote_proxy_relay_response(remote_proxy_t *proxy, xmlNode *msg, int msg_id); +void lrmd__register_messages(pcmk__output_t *out); + #endif diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index d658e86cf2..396d7077e4 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -1,612 +1,615 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PE_INTERNAL__H # define PE_INTERNAL__H # include # include # include # include # include # define pe_rsc_info(rsc, fmt, args...) crm_log_tag(LOG_INFO, rsc ? rsc->id : "", fmt, ##args) # define pe_rsc_debug(rsc, fmt, args...) crm_log_tag(LOG_DEBUG, rsc ? rsc->id : "", fmt, ##args) # define pe_rsc_trace(rsc, fmt, args...) crm_log_tag(LOG_TRACE, rsc ? rsc->id : "", fmt, ##args) # define pe_err(fmt...) do { \ was_processing_error = TRUE; \ pcmk__config_err(fmt); \ } while (0) # define pe_warn(fmt...) do { \ was_processing_warning = TRUE; \ pcmk__config_warn(fmt); \ } while (0) # define pe_proc_err(fmt...) { was_processing_error = TRUE; crm_err(fmt); } # define pe_proc_warn(fmt...) { was_processing_warning = TRUE; crm_warn(fmt); } #define pe__set_working_set_flags(working_set, flags_to_set) do { \ (working_set)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Working set", crm_system_name, \ (working_set)->flags, (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_working_set_flags(working_set, flags_to_clear) do { \ (working_set)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Working set", crm_system_name, \ (working_set)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) #define pe__set_resource_flags(resource, flags_to_set) do { \ (resource)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_resource_flags(resource, flags_to_clear) do { \ (resource)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) #define pe__set_action_flags(action, flags_to_set) do { \ (action)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_set), \ #flags_to_set); \ } while (0) #define pe__clear_action_flags(action, flags_to_clear) do { \ (action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_raw_action_flags(action_flags, action_name, flags_to_set) do { \ action_flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Action", action_name, \ (action_flags), \ (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_raw_action_flags(action_flags, action_name, flags_to_clear) do { \ action_flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", action_name, \ (action_flags), \ (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_action_flags_as(function, line, action, flags_to_set) do { \ (action)->flags = pcmk__set_flags_as((function), (line), \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_set), \ #flags_to_set); \ } while (0) #define pe__clear_action_flags_as(function, line, action, flags_to_clear) do { \ (action)->flags = pcmk__clear_flags_as((function), (line), \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_order_flags(order_flags, flags_to_set) do { \ order_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Ordering", "constraint", \ order_flags, (flags_to_set), \ #flags_to_set); \ } while (0) #define pe__clear_order_flags(order_flags, flags_to_clear) do { \ order_flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \ "Ordering", "constraint", \ order_flags, (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_graph_flags(graph_flags, gr_action, flags_to_set) do { \ graph_flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Graph", \ (gr_action)->uuid, graph_flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_graph_flags(graph_flags, gr_action, flags_to_clear) do { \ graph_flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Graph", \ (gr_action)->uuid, graph_flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) // Some warnings we don't want to print every transition enum pe_warn_once_e { pe_wo_blind = 0x0001, pe_wo_restart_type = 0x0002, pe_wo_role_after = 0x0004, pe_wo_poweroff = 0x0008, pe_wo_require_all = 0x0010, pe_wo_order_score = 0x0020, pe_wo_neg_threshold = 0x0040, }; extern uint32_t pe_wo; #define pe_warn_once(pe_wo_bit, fmt...) do { \ if (!pcmk_is_set(pe_wo, pe_wo_bit)) { \ if (pe_wo_bit == pe_wo_blind) { \ crm_warn(fmt); \ } else { \ pe_warn(fmt); \ } \ pe_wo = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Warn-once", "logging", pe_wo, \ (pe_wo_bit), #pe_wo_bit); \ } \ } while (0); typedef struct pe__location_constraint_s { char *id; // Constraint XML ID pe_resource_t *rsc_lh; // Resource being located enum rsc_role_e role_filter; // Role to locate enum pe_discover_e discover_mode; // Resource discovery GListPtr node_list_rh; // List of pe_node_t* } pe__location_t; typedef struct pe__order_constraint_s { int id; enum pe_ordering type; void *lh_opaque; pe_resource_t *lh_rsc; pe_action_t *lh_action; char *lh_action_task; void *rh_opaque; pe_resource_t *rh_rsc; pe_action_t *rh_action; char *rh_action_task; } pe__ordering_t; typedef struct notify_data_s { GSList *keys; // Environment variable name/value pairs const char *action; pe_action_t *pre; pe_action_t *post; pe_action_t *pre_done; pe_action_t *post_done; GListPtr active; /* notify_entry_t* */ GListPtr inactive; /* notify_entry_t* */ GListPtr start; /* notify_entry_t* */ GListPtr stop; /* notify_entry_t* */ GListPtr demote; /* notify_entry_t* */ GListPtr promote; /* notify_entry_t* */ GListPtr master; /* notify_entry_t* */ GListPtr slave; /* notify_entry_t* */ GHashTable *allowed_nodes; } notify_data_t; bool pe_can_fence(pe_working_set_t *data_set, pe_node_t *node); int pe__add_scores(int score1, int score2); void add_hash_param(GHashTable * hash, const char *name, const char *value); char *native_parameter(pe_resource_t * rsc, pe_node_t * node, gboolean create, const char *name, pe_working_set_t * data_set); pe_node_t *native_location(const pe_resource_t *rsc, GList **list, int current); void pe_metadata(void); void verify_pe_options(GHashTable * options); void common_update_score(pe_resource_t * rsc, const char *id, int score); void native_add_running(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set); gboolean native_unpack(pe_resource_t * rsc, pe_working_set_t * data_set); gboolean group_unpack(pe_resource_t * rsc, pe_working_set_t * data_set); gboolean clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set); gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set); pe_resource_t *native_find_rsc(pe_resource_t *rsc, const char *id, const pe_node_t *node, int flags); gboolean native_active(pe_resource_t * rsc, gboolean all); gboolean group_active(pe_resource_t * rsc, gboolean all); gboolean clone_active(pe_resource_t * rsc, gboolean all); gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all); void native_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data); void group_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data); void clone_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data); void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); gchar * pcmk__native_output_string(pe_resource_t *rsc, const char *name, pe_node_t *node, long options, const char *target_role, bool show_nodes); int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name , size_t pairs_count, ...); char *pe__node_display_name(pe_node_t *node, bool print_detail); static inline const char * pe__rsc_bool_str(pe_resource_t *rsc, uint64_t rsc_flag) { return pcmk__btoa(pcmk_is_set(rsc->flags, rsc_flag)); } int pe__ban_html(pcmk__output_t *out, va_list args); int pe__ban_text(pcmk__output_t *out, va_list args); int pe__ban_xml(pcmk__output_t *out, va_list args); int pe__clone_xml(pcmk__output_t *out, va_list args); int pe__clone_html(pcmk__output_t *out, va_list args); int pe__clone_text(pcmk__output_t *out, va_list args); int pe__cluster_counts_html(pcmk__output_t *out, va_list args); int pe__cluster_counts_text(pcmk__output_t *out, va_list args); int pe__cluster_counts_xml(pcmk__output_t *out, va_list args); int pe__cluster_dc_html(pcmk__output_t *out, va_list args); int pe__cluster_dc_text(pcmk__output_t *out, va_list args); int pe__cluster_dc_xml(pcmk__output_t *out, va_list args); int pe__cluster_maint_mode_html(pcmk__output_t *out, va_list args); int pe__cluster_maint_mode_text(pcmk__output_t *out, va_list args); int pe__cluster_options_html(pcmk__output_t *out, va_list args); int pe__cluster_options_log(pcmk__output_t *out, va_list args); int pe__cluster_options_text(pcmk__output_t *out, va_list args); int pe__cluster_options_xml(pcmk__output_t *out, va_list args); int pe__cluster_stack_html(pcmk__output_t *out, va_list args); int pe__cluster_stack_text(pcmk__output_t *out, va_list args); int pe__cluster_stack_xml(pcmk__output_t *out, va_list args); int pe__cluster_summary(pcmk__output_t *out, va_list args); int pe__cluster_summary_html(pcmk__output_t *out, va_list args); int pe__cluster_times_html(pcmk__output_t *out, va_list args); int pe__cluster_times_xml(pcmk__output_t *out, va_list args); int pe__cluster_times_text(pcmk__output_t *out, va_list args); int pe__failed_action_text(pcmk__output_t *out, va_list args); int pe__failed_action_xml(pcmk__output_t *out, va_list args); int pe__group_xml(pcmk__output_t *out, va_list args); int pe__group_html(pcmk__output_t *out, va_list args); int pe__group_text(pcmk__output_t *out, va_list args); int pe__bundle_xml(pcmk__output_t *out, va_list args); int pe__bundle_html(pcmk__output_t *out, va_list args); int pe__bundle_text(pcmk__output_t *out, va_list args); int pe__node_html(pcmk__output_t *out, va_list args); int pe__node_text(pcmk__output_t *out, va_list args); int pe__node_xml(pcmk__output_t *out, va_list args); +int pe__node_and_op(pcmk__output_t *out, va_list args); +int pe__node_and_op_xml(pcmk__output_t *out, va_list args); int pe__node_attribute_html(pcmk__output_t *out, va_list args); int pe__node_attribute_text(pcmk__output_t *out, va_list args); int pe__node_attribute_xml(pcmk__output_t *out, va_list args); int pe__node_list_html(pcmk__output_t *out, va_list args); int pe__node_list_text(pcmk__output_t *out, va_list args); int pe__node_list_xml(pcmk__output_t *out, va_list args); int pe__op_history_text(pcmk__output_t *out, va_list args); int pe__op_history_xml(pcmk__output_t *out, va_list args); +int pe__resource_config(pcmk__output_t *out, va_list args); int pe__resource_history_text(pcmk__output_t *out, va_list args); int pe__resource_history_xml(pcmk__output_t *out, va_list args); int pe__resource_xml(pcmk__output_t *out, va_list args); int pe__resource_html(pcmk__output_t *out, va_list args); int pe__resource_text(pcmk__output_t *out, va_list args); int pe__resource_list(pcmk__output_t *out, va_list args); int pe__ticket_html(pcmk__output_t *out, va_list args); int pe__ticket_text(pcmk__output_t *out, va_list args); int pe__ticket_xml(pcmk__output_t *out, va_list args); void native_free(pe_resource_t * rsc); void group_free(pe_resource_t * rsc); void clone_free(pe_resource_t * rsc); void pe__free_bundle(pe_resource_t *rsc); enum rsc_role_e native_resource_state(const pe_resource_t * rsc, gboolean current); enum rsc_role_e group_resource_state(const pe_resource_t * rsc, gboolean current); enum rsc_role_e clone_resource_state(const pe_resource_t * rsc, gboolean current); enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current); void pe__count_common(pe_resource_t *rsc); void pe__count_bundle(pe_resource_t *rsc); gboolean common_unpack(xmlNode * xml_obj, pe_resource_t ** rsc, pe_resource_t * parent, pe_working_set_t * data_set); void common_free(pe_resource_t * rsc); pe_node_t *pe__copy_node(const pe_node_t *this_node); extern time_t get_effective_time(pe_working_set_t * data_set); /* Failure handling utilities (from failcounts.c) */ // bit flags for fail count handling options enum pe_fc_flags_e { pe_fc_default = 0x00, pe_fc_effective = 0x01, // don't count expired failures pe_fc_fillers = 0x02, // if container, include filler failures in count }; int pe_get_failcount(pe_node_t *node, pe_resource_t *rsc, time_t *last_failure, uint32_t flags, xmlNode *xml_op, pe_working_set_t *data_set); pe_action_t *pe__clear_failcount(pe_resource_t *rsc, pe_node_t *node, const char *reason, pe_working_set_t *data_set); /* Functions for finding/counting a resource's active nodes */ pe_node_t *pe__find_active_on(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); pe_node_t *pe__find_active_requires(const pe_resource_t *rsc, unsigned int *count); static inline pe_node_t * pe__current_node(const pe_resource_t *rsc) { return pe__find_active_on(rsc, NULL, NULL); } /* Binary like operators for lists of nodes */ extern void node_list_exclude(GHashTable * list, GListPtr list2, gboolean merge_scores); GHashTable *pe__node_list2table(GList *list); static inline gpointer pe_hash_table_lookup(GHashTable * hash, gconstpointer key) { if (hash) { return g_hash_table_lookup(hash, key); } return NULL; } extern pe_action_t *get_pseudo_op(const char *name, pe_working_set_t * data_set); extern gboolean order_actions(pe_action_t * lh_action, pe_action_t * rh_action, enum pe_ordering order); /* Printing functions for debug */ extern void print_node(const char *pre_text, pe_node_t * node, gboolean details); extern void print_str_str(gpointer key, gpointer value, gpointer user_data); extern void pe__output_node(pe_node_t * node, gboolean details, pcmk__output_t *out); extern void dump_node_capacity(int level, const char *comment, pe_node_t * node); extern void dump_rsc_utilization(int level, const char *comment, pe_resource_t * rsc, pe_node_t * node); void pe__show_node_weights_as(const char *file, const char *function, int line, bool to_log, pe_resource_t *rsc, const char *comment, GHashTable *nodes); #define pe__show_node_weights(level, rsc, text, nodes) \ pe__show_node_weights_as(__FILE__, __func__, __LINE__, \ (level), (rsc), (text), (nodes)) /* Sorting functions */ extern gint sort_rsc_priority(gconstpointer a, gconstpointer b); extern gint sort_rsc_index(gconstpointer a, gconstpointer b); extern xmlNode *find_rsc_op_entry(pe_resource_t * rsc, const char *key); extern pe_action_t *custom_action(pe_resource_t * rsc, char *key, const char *task, pe_node_t * on_node, gboolean optional, gboolean foo, pe_working_set_t * data_set); # define delete_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DELETE, 0) # define delete_action(rsc, node, optional) custom_action( \ rsc, delete_key(rsc), CRMD_ACTION_DELETE, node, \ optional, TRUE, data_set); # define stopped_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOPPED, 0) # define stopped_action(rsc, node, optional) custom_action( \ rsc, stopped_key(rsc), CRMD_ACTION_STOPPED, node, \ optional, TRUE, data_set); # define stop_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOP, 0) # define stop_action(rsc, node, optional) custom_action( \ rsc, stop_key(rsc), CRMD_ACTION_STOP, node, \ optional, TRUE, data_set); # define reload_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_RELOAD, 0) # define start_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_START, 0) # define start_action(rsc, node, optional) custom_action( \ rsc, start_key(rsc), CRMD_ACTION_START, node, \ optional, TRUE, data_set) # define started_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STARTED, 0) # define started_action(rsc, node, optional) custom_action( \ rsc, started_key(rsc), CRMD_ACTION_STARTED, node, \ optional, TRUE, data_set) # define promote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTE, 0) # define promote_action(rsc, node, optional) custom_action( \ rsc, promote_key(rsc), CRMD_ACTION_PROMOTE, node, \ optional, TRUE, data_set) # define promoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTED, 0) # define promoted_action(rsc, node, optional) custom_action( \ rsc, promoted_key(rsc), CRMD_ACTION_PROMOTED, node, \ optional, TRUE, data_set) # define demote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTE, 0) # define demote_action(rsc, node, optional) custom_action( \ rsc, demote_key(rsc), CRMD_ACTION_DEMOTE, node, \ optional, TRUE, data_set) # define demoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTED, 0) # define demoted_action(rsc, node, optional) custom_action( \ rsc, demoted_key(rsc), CRMD_ACTION_DEMOTED, node, \ optional, TRUE, data_set) extern int pe_get_configured_timeout(pe_resource_t *rsc, const char *action, pe_working_set_t *data_set); extern pe_action_t *find_first_action(GListPtr input, const char *uuid, const char *task, pe_node_t * on_node); extern enum action_tasks get_complex_task(pe_resource_t * rsc, const char *name, gboolean allow_non_atomic); extern GListPtr find_actions(GListPtr input, const char *key, const pe_node_t *on_node); GList *find_actions_exact(GList *input, const char *key, const pe_node_t *on_node); extern GListPtr find_recurring_actions(GListPtr input, pe_node_t * not_on_node); GList *pe__resource_actions(const pe_resource_t *rsc, const pe_node_t *node, const char *task, bool require_node); extern void pe_free_action(pe_action_t * action); extern void resource_location(pe_resource_t * rsc, pe_node_t * node, int score, const char *tag, pe_working_set_t * data_set); extern gint sort_op_by_callid(gconstpointer a, gconstpointer b); extern gboolean get_target_role(pe_resource_t * rsc, enum rsc_role_e *role); extern pe_resource_t *find_clone_instance(pe_resource_t * rsc, const char *sub_id, pe_working_set_t * data_set); extern void destroy_ticket(gpointer data); extern pe_ticket_t *ticket_new(const char *ticket_id, pe_working_set_t * data_set); // Resources for manipulating resource names const char *pe_base_name_end(const char *id); char *clone_strip(const char *last_rsc_id); char *clone_zero(const char *last_rsc_id); static inline bool pe_base_name_eq(pe_resource_t *rsc, const char *id) { if (id && rsc && rsc->id) { // Number of characters in rsc->id before any clone suffix size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1; return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len); } return FALSE; } int pe__target_rc_from_xml(xmlNode *xml_op); gint sort_node_uname(gconstpointer a, gconstpointer b); bool is_set_recursive(pe_resource_t * rsc, long long flag, bool any); enum rsc_digest_cmp_val { /*! Digests are the same */ RSC_DIGEST_MATCH = 0, /*! Params that require a restart changed */ RSC_DIGEST_RESTART, /*! Some parameter changed. */ RSC_DIGEST_ALL, /*! rsc op didn't have a digest associated with it, so * it is unknown if parameters changed or not. */ RSC_DIGEST_UNKNOWN, }; typedef struct op_digest_cache_s { enum rsc_digest_cmp_val rc; xmlNode *params_all; xmlNode *params_secure; xmlNode *params_restart; char *digest_all_calc; char *digest_secure_calc; char *digest_restart_calc; } op_digest_cache_t; op_digest_cache_t *rsc_action_digest_cmp(pe_resource_t * rsc, xmlNode * xml_op, pe_node_t * node, pe_working_set_t * data_set); pe_action_t *pe_fence_op(pe_node_t * node, const char *op, bool optional, const char *reason, bool priority_delay, pe_working_set_t * data_set); void trigger_unfencing( pe_resource_t * rsc, pe_node_t *node, const char *reason, pe_action_t *dependency, pe_working_set_t * data_set); void pe_action_set_reason(pe_action_t *action, const char *reason, bool overwrite); void pe_action_set_flag_reason(const char *function, long line, pe_action_t *action, pe_action_t *reason, const char *text, enum pe_action_flags flags, bool overwrite); #define pe_action_required(action, reason, text) \ pe_action_set_flag_reason(__func__, __LINE__, action, reason, text, \ pe_action_optional, FALSE) #define pe_action_implies(action, reason, flag) \ pe_action_set_flag_reason(__func__, __LINE__, action, reason, NULL, \ flag, FALSE) void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags); void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags); gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref); void print_rscs_brief(GListPtr rsc_list, const char * pre_text, long options, void * print_data, gboolean print_all); int pe__rscs_brief_output(pcmk__output_t *out, GListPtr rsc_list, long options, gboolean print_all); void pe_fence_node(pe_working_set_t * data_set, pe_node_t * node, const char *reason, bool priority_delay); pe_node_t *pe_create_node(const char *id, const char *uname, const char *type, const char *score, pe_working_set_t * data_set); void common_print(pe_resource_t * rsc, const char *pre_text, const char *name, pe_node_t *node, long options, void *print_data); int pe__common_output_text(pcmk__output_t *out, pe_resource_t * rsc, const char *name, pe_node_t *node, long options); int pe__common_output_html(pcmk__output_t *out, pe_resource_t * rsc, const char *name, pe_node_t *node, long options); pe_resource_t *pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node); bool pe__bundle_needs_remote_name(pe_resource_t *rsc); const char *pe__add_bundle_remote_name(pe_resource_t *rsc, xmlNode *xml, const char *field); const char *pe_node_attribute_calculated(const pe_node_t *node, const char *name, const pe_resource_t *rsc); const char *pe_node_attribute_raw(pe_node_t *node, const char *name); bool pe__is_universal_clone(pe_resource_t *rsc, pe_working_set_t *data_set); void pe__add_param_check(xmlNode *rsc_op, pe_resource_t *rsc, pe_node_t *node, enum pe_check_parameters, pe_working_set_t *data_set); void pe__foreach_param_check(pe_working_set_t *data_set, void (*cb)(pe_resource_t*, pe_node_t*, xmlNode*, enum pe_check_parameters, pe_working_set_t*)); void pe__free_param_checks(pe_working_set_t *data_set); bool pe__shutdown_requested(pe_node_t *node); void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set); /*! * \internal * \brief Register xml formatting message functions. */ void pe__register_messages(pcmk__output_t *out); void pe__unpack_dataset_nvpairs(xmlNode *xml_obj, const char *set_name, pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, pe_working_set_t *data_set); bool pe__resource_is_disabled(pe_resource_t *rsc); pe_action_t *pe__clear_resource_history(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set); GListPtr pe__rscs_with_tag(pe_working_set_t *data_set, const char *tag_name); GListPtr pe__unames_with_tag(pe_working_set_t *data_set, const char *tag_name); bool pe__rsc_has_tag(pe_working_set_t *data_set, const char *rsc, const char *tag); bool pe__uname_has_tag(pe_working_set_t *data_set, const char *node, const char *tag); bool pe__rsc_running_on_any_node_in_list(pe_resource_t *rsc, GListPtr node_list); GListPtr pe__filter_rsc_list(GListPtr rscs, GListPtr filter); bool pcmk__rsc_filtered_by_node(pe_resource_t *rsc, GListPtr only_node); gboolean pe__bundle_is_filtered(pe_resource_t *rsc, GListPtr only_rsc, gboolean check_parent); gboolean pe__clone_is_filtered(pe_resource_t *rsc, GListPtr only_rsc, gboolean check_parent); gboolean pe__group_is_filtered(pe_resource_t *rsc, GListPtr only_rsc, gboolean check_parent); gboolean pe__native_is_filtered(pe_resource_t *rsc, GListPtr only_rsc, gboolean check_parent); #endif diff --git a/include/pcmki/pcmki_output.h b/include/pcmki/pcmki_output.h index 2b750fbafb..0faef35e93 100644 --- a/include/pcmki/pcmki_output.h +++ b/include/pcmki/pcmki_output.h @@ -1,28 +1,35 @@ /* * Copyright 2019-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMKI_OUTPUT_H # define PCMKI_OUTPUT_H #ifdef __cplusplus extern "C" { #endif # include # include extern pcmk__supported_format_t pcmk__out_formats[]; int pcmk__out_prologue(pcmk__output_t **out, xmlNodePtr *xml); void pcmk__out_epilogue(pcmk__output_t *out, xmlNodePtr *xml, int retval); +/* This function registers only the formatted output messages that are a part + * of libpacemaker. It is not to be confused with pcmk__register_messages, + * which is a part of formatted output support and registers a whole table of + * messages at a time. + */ +void pcmk__register_lib_messages(pcmk__output_t *out); + #ifdef __cplusplus } #endif #endif diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index 6a6ed6ee1a..716d10f408 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,479 +1,481 @@ /* * Copyright 2019-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #ifndef PCMK__CONFIG_H # define PCMK__CONFIG_H # include #endif #include #include #include #include #include #include #include #include static gboolean legacy_xml = FALSE; static gboolean simple_list = FALSE; static gboolean substitute = FALSE; GOptionEntry pcmk__xml_output_entries[] = { { "xml-legacy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &legacy_xml, NULL, NULL }, { "xml-simple-list", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &simple_list, NULL, NULL }, { "xml-substitute", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &substitute, NULL, NULL }, { NULL } }; typedef struct subst_s { const char *from; const char *to; } subst_t; static subst_t substitutions[] = { { "Active Resources", "resources" }, { "Full List of Resources", "resources" }, { "Inactive Resources", "resources" }, { "Cluster Summary", "summary" }, { "Failed Resource Actions", "failures" }, { "Fencing History", "fence_history" }, { "Migration Summary", "node_history" }, { "Operations", "node_history" }, { "Negative Location Constraints", "bans" }, { "Node Attributes", "node_attributes" }, + { "Resource Config", "resource_config" }, + { "Resource Operations", "operations" }, { NULL, NULL } }; typedef struct private_data_s { xmlNode *root; GQueue *parent_q; GSList *errors; bool legacy_xml; } private_data_t; static void xml_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } xmlFreeNode(priv->root); g_queue_free(priv->parent_q); g_slist_free(priv->errors); free(priv); out->priv = NULL; } static bool xml_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If xml_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } if (legacy_xml) { priv->root = create_xml_node(NULL, "crm_mon"); xmlSetProp(priv->root, (pcmkXmlStr) "version", (pcmkXmlStr) VERSION); } else { priv->root = create_xml_node(NULL, "pacemaker-result"); xmlSetProp(priv->root, (pcmkXmlStr) "api-version", (pcmkXmlStr) PCMK__API_VERSION); if (out->request != NULL) { xmlSetProp(priv->root, (pcmkXmlStr) "request", (pcmkXmlStr) out->request); } } priv->parent_q = g_queue_new(); priv->errors = NULL; g_queue_push_tail(priv->parent_q, priv->root); /* Copy this from the file-level variable. This means that it is only settable * as a command line option, and that pcmk__output_new must be called after all * command line processing is completed. */ priv->legacy_xml = legacy_xml; return true; } static void add_error_node(gpointer data, gpointer user_data) { char *str = (char *) data; xmlNodePtr node = (xmlNodePtr) user_data; pcmk_create_xml_text_node(node, "error", str); } static void finish_reset_common(pcmk__output_t *out, crm_exit_t exit_status, bool print) { xmlNodePtr node; private_data_t *priv = out->priv; if (legacy_xml) { GSList *node = priv->errors; if (exit_status != CRM_EX_OK) { fprintf(stderr, "%s\n", crm_exit_str(exit_status)); } while (node != NULL) { fprintf(stderr, "%s\n", (char *) node->data); node = node->next; } } else { char *rc_as_str = crm_itoa(exit_status); node = create_xml_node(priv->root, "status"); xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str); xmlSetProp(node, (pcmkXmlStr) "message", (pcmkXmlStr) crm_exit_str(exit_status)); if (g_slist_length(priv->errors) > 0) { xmlNodePtr errors_node = create_xml_node(node, "errors"); g_slist_foreach(priv->errors, add_error_node, (gpointer) errors_node); } free(rc_as_str); } if (print) { char *buf = dump_xml_formatted_with_text(priv->root); fprintf(out->dest, "%s", buf); free(buf); } } static void xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { private_data_t *priv = out->priv; /* If root is NULL, xml_init failed and we are being called from pcmk__output_free * in the pcmk__output_new path. */ if (priv == NULL || priv->root == NULL) { return; } finish_reset_common(out, exit_status, print); if (copy_dest != NULL) { *copy_dest = copy_xml(priv->root); } } static void xml_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); if (out->priv != NULL) { finish_reset_common(out, CRM_EX_OK, true); } xml_free_priv(out); xml_init(out); } static void xml_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { xmlNodePtr node, child_node; char *rc_as_str = NULL; rc_as_str = crm_itoa(exit_status); node = pcmk__output_xml_create_parent(out, "command"); xmlSetProp(node, (pcmkXmlStr) "code", (pcmkXmlStr) rc_as_str); if (proc_stdout != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stdout); xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stdout"); } if (proc_stderr != NULL) { child_node = pcmk_create_xml_text_node(node, "output", proc_stderr); xmlSetProp(child_node, (pcmkXmlStr) "source", (pcmkXmlStr) "stderr"); } pcmk__output_xml_add_node(out, node); free(rc_as_str); } static void xml_version(pcmk__output_t *out, bool extended) { xmlNodePtr node; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); node = pcmk__output_create_xml_node(out, "version"); xmlSetProp(node, (pcmkXmlStr) "program", (pcmkXmlStr) "Pacemaker"); xmlSetProp(node, (pcmkXmlStr) "version", (pcmkXmlStr) PACEMAKER_VERSION); xmlSetProp(node, (pcmkXmlStr) "author", (pcmkXmlStr) "Andrew Beekhof"); xmlSetProp(node, (pcmkXmlStr) "build", (pcmkXmlStr) BUILD_VERSION); xmlSetProp(node, (pcmkXmlStr) "features", (pcmkXmlStr) CRM_FEATURES); } G_GNUC_PRINTF(2, 3) static void xml_err(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = out->priv; int len = 0; char *buf = NULL; va_list ap; CRM_ASSERT(priv != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); priv->errors = g_slist_append(priv->errors, buf); } G_GNUC_PRINTF(2, 3) static void xml_info(pcmk__output_t *out, const char *format, ...) { /* This function intentially left blank */ } static void xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { xmlNodePtr parent = NULL; xmlNodePtr cdata_node = NULL; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); parent = pcmk__output_create_xml_node(out, name); cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf)); xmlAddChild(parent, cdata_node); } G_GNUC_PRINTF(4, 5) static void xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { va_list ap; char *name = NULL; char *buf = NULL; int len; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); if (substitute) { for (subst_t *s = substitutions; s->from != NULL; s++) { if (!strcmp(s->from, buf)) { name = g_strdup(s->to); break; } } } if (name == NULL) { name = g_ascii_strdown(buf, -1); } if (legacy_xml || simple_list) { pcmk__output_xml_create_parent(out, name); } else { xmlNodePtr list_node = NULL; list_node = pcmk__output_xml_create_parent(out, "list"); xmlSetProp(list_node, (pcmkXmlStr) "name", (pcmkXmlStr) name); } g_free(name); free(buf); } G_GNUC_PRINTF(3, 4) static void xml_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { private_data_t *priv = out->priv; xmlNodePtr item_node = NULL; va_list ap; char *buf = NULL; int len; CRM_ASSERT(priv != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); item_node = pcmk__output_create_xml_text_node(out, "item", buf); if (name != NULL) { xmlSetProp(item_node, (pcmkXmlStr) "name", (pcmkXmlStr) name); } free(buf); } static void xml_increment_list(pcmk__output_t *out) { /* This function intentially left blank */ } static void xml_end_list(pcmk__output_t *out) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); if (priv->legacy_xml || simple_list) { g_queue_pop_tail(priv->parent_q); } else { char *buf = NULL; xmlNodePtr node; node = g_queue_pop_tail(priv->parent_q); buf = crm_strdup_printf("%lu", xmlChildElementCount(node)); xmlSetProp(node, (pcmkXmlStr) "count", (pcmkXmlStr) buf); free(buf); } } static bool xml_is_quiet(pcmk__output_t *out) { return false; } static void xml_spacer(pcmk__output_t *out) { /* This function intentionally left blank */ } pcmk__output_t * pcmk__mk_xml_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "xml"; retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv); retval->init = xml_init; retval->free_priv = xml_free_priv; retval->finish = xml_finish; retval->reset = xml_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = xml_subprocess_output; retval->version = xml_version; retval->info = xml_info; retval->err = xml_err; retval->output_xml = xml_output_xml; retval->begin_list = xml_begin_list; retval->list_item = xml_list_item; retval->increment_list = xml_increment_list; retval->end_list = xml_end_list; retval->is_quiet = xml_is_quiet; retval->spacer = xml_spacer; return retval; } xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name) { xmlNodePtr node = pcmk__output_create_xml_node(out, name); pcmk__output_xml_push_parent(out, node); return node; } void pcmk__output_xml_add_node(pcmk__output_t *out, xmlNodePtr node) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); CRM_ASSERT(node != NULL); xmlAddChild(g_queue_peek_tail(priv->parent_q), node); } xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); return create_xml_node(g_queue_peek_tail(priv->parent_q), name); } xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content) { xmlNodePtr node = pcmk__output_create_xml_node(out, name); xmlNodeSetContent(node, (pcmkXmlStr) content); return node; } void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); CRM_ASSERT(parent != NULL); g_queue_push_tail(priv->parent_q, parent); } void pcmk__output_xml_pop_parent(pcmk__output_t *out) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); CRM_ASSERT(g_queue_get_length(priv->parent_q) > 0); g_queue_pop_tail(priv->parent_q); } xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); /* If queue is empty NULL will be returned */ return g_queue_peek_tail(priv->parent_q); } diff --git a/lib/lrmd/Makefile.am b/lib/lrmd/Makefile.am index 41ba7fd633..09e40b1014 100644 --- a/lib/lrmd/Makefile.am +++ b/lib/lrmd/Makefile.am @@ -1,21 +1,21 @@ # -# Copyright 2012-2018 the Pacemaker project contributors +# Copyright 2012-2020 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU Lesser General Public License # version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/common.mk lib_LTLIBRARIES = liblrmd.la liblrmd_la_LDFLAGS = -version-info 29:1:1 liblrmd_la_CFLAGS = $(CFLAGS_HARDENED_LIB) liblrmd_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) liblrmd_la_LIBADD = $(top_builddir)/lib/common/libcrmcommon.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/fencing/libstonithd.la -liblrmd_la_SOURCES = lrmd_client.c proxy_common.c lrmd_alerts.c +liblrmd_la_SOURCES = lrmd_client.c proxy_common.c lrmd_alerts.c lrmd_output.c diff --git a/lib/lrmd/lrmd_output.c b/lib/lrmd/lrmd_output.c new file mode 100644 index 0000000000..7dc0709374 --- /dev/null +++ b/lib/lrmd/lrmd_output.c @@ -0,0 +1,145 @@ +/* + * Copyright 2020 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include +#include + +#include +#include + +static int +default_list(pcmk__output_t *out, lrmd_list_t *list, const char *title) { + lrmd_list_t *iter = NULL; + + out->begin_list(out, NULL, NULL, "%s", title); + + for (iter = list; iter != NULL; iter = iter->next) { + out->list_item(out, NULL, "%s", iter->val); + } + + out->end_list(out); + lrmd_list_freeall(list); + return pcmk_rc_ok; +} + +static int +xml_list(pcmk__output_t *out, lrmd_list_t *list, const char *ele) { + lrmd_list_t *iter = NULL; + + for (iter = list; iter != NULL; iter = iter->next) { + pcmk__output_create_xml_text_node(out, ele, iter->val); + } + + lrmd_list_freeall(list); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("alternatives-list", "lrmd_list_t *", "const char *") +static int +lrmd__alternatives_list_xml(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + const char *agent_spec = va_arg(args, const char *); + + xmlNodePtr node = pcmk__output_xml_create_parent(out, "providers"); + + xmlSetProp(node, (pcmkXmlStr) "for", (pcmkXmlStr) agent_spec); + return xml_list(out, list, "provider"); +} + +PCMK__OUTPUT_ARGS("alternatives-list", "lrmd_list_t *", "const char *") +static int +lrmd__alternatives_list(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + const char *agent_spec G_GNUC_UNUSED = va_arg(args, const char *); + + return default_list(out, list, "Providers"); +} + +PCMK__OUTPUT_ARGS("agents-list", "lrmd_list_t *", "const char *", "char *") +static int +lrmd__agents_list_xml(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + const char *agent_spec = va_arg(args, const char *); + char *provider = va_arg(args, char *); + + xmlNodePtr node = pcmk__output_xml_create_parent(out, "agents"); + xmlSetProp(node, (pcmkXmlStr) "standard", (pcmkXmlStr) agent_spec); + + if (!pcmk__str_empty(provider)) { + xmlSetProp(node, (pcmkXmlStr) "provider", (pcmkXmlStr) provider); + } + + return xml_list(out, list, "agent"); +} + +PCMK__OUTPUT_ARGS("agents-list", "lrmd_list_t *", "const char *", "char *") +static int +lrmd__agents_list(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + const char *agent_spec = va_arg(args, const char *); + char *provider = va_arg(args, char *); + + int rc; + char *title = crm_strdup_printf("%s agents", pcmk__str_empty(provider) ? agent_spec : provider); + + rc = default_list(out, list, title); + free(title); + return rc; +} + +PCMK__OUTPUT_ARGS("providers-list", "lrmd_list_t *", "const char *") +static int +lrmd__providers_list_xml(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + const char *agent_spec = va_arg(args, const char *); + + xmlNodePtr node = pcmk__output_xml_create_parent(out, "providers"); + + xmlSetProp(node, (pcmkXmlStr) "standard", (pcmkXmlStr) "ocf"); + + if (agent_spec != NULL) { + xmlSetProp(node, (pcmkXmlStr) "agent", (pcmkXmlStr) agent_spec); + } + + return xml_list(out, list, "provider"); +} + +PCMK__OUTPUT_ARGS("providers-list", "lrmd_list_t *", "const char *") +static int +lrmd__providers_list(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + const char *agent_spec G_GNUC_UNUSED = va_arg(args, const char *); + + return default_list(out, list, "Providers"); +} + +PCMK__OUTPUT_ARGS("standards-list", "lrmd_list_t *") +static int +lrmd__standards_list(pcmk__output_t *out, va_list args) { + lrmd_list_t *list = va_arg(args, lrmd_list_t *); + + return default_list(out, list, "Standards"); +} + +static pcmk__message_entry_t fmt_functions[] = { + { "alternatives-list", "default", lrmd__alternatives_list }, + { "alternatives-list", "xml", lrmd__alternatives_list_xml }, + { "agents-list", "default", lrmd__agents_list }, + { "agents-list", "xml", lrmd__agents_list_xml }, + { "providers-list", "default", lrmd__providers_list }, + { "providers-list", "xml", lrmd__providers_list_xml }, + { "standards-list", "default", lrmd__standards_list }, + + { NULL, NULL, NULL } +}; + +void +lrmd__register_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); +} diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index adf4c34482..306e561db2 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -1,48 +1,374 @@ /* * Copyright 2019-2020 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 pcmk__supported_format_t pcmk__out_formats[] = { PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; int pcmk__out_prologue(pcmk__output_t **out, xmlNodePtr *xml) { int rc = pcmk_rc_ok; if (*xml != NULL) { xmlFreeNode(*xml); } pcmk__register_formats(NULL, pcmk__out_formats); rc = pcmk__output_new(out, "xml", NULL, NULL); if (rc != pcmk_rc_ok) { return rc; } stonith__register_messages(*out); return rc; } void pcmk__out_epilogue(pcmk__output_t *out, xmlNodePtr *xml, int retval) { if (retval == pcmk_rc_ok) { out->finish(out, 0, FALSE, (void **) xml); } pcmk__output_free(out); } + +PCMK__OUTPUT_ARGS("colocations-list", "pe_resource_t *", "gboolean", "gboolean") +static int colocations_list(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + gboolean dependents = va_arg(args, gboolean); + gboolean recursive = va_arg(args, gboolean); + + GListPtr lpc = NULL; + GListPtr list = rsc->rsc_cons; + bool printed_header = false; + + if (dependents) { + list = rsc->rsc_cons_lhs; + } + + if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { + return pcmk_rc_no_output; + } + + pe__set_resource_flags(rsc, pe_rsc_allocating); + for (lpc = list; lpc != NULL; lpc = lpc->next) { + rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data; + + char *score = NULL; + pe_resource_t *peer = cons->rsc_rh; + + if (dependents) { + peer = cons->rsc_lh; + } + + if (pcmk_is_set(peer->flags, pe_rsc_allocating)) { + if (dependents == FALSE) { + if (!printed_header) { + out->begin_list(out, NULL, NULL, "Colocations"); + printed_header = true; + } + + out->list_item(out, NULL, "%s (id=%s - loop)", peer->id, cons->id); + } + continue; + } + + if (dependents && recursive) { + if (!printed_header) { + out->begin_list(out, NULL, NULL, "Colocations"); + printed_header = true; + } + + out->message(out, "colocations-list", rsc, dependents, recursive); + } + + if (!printed_header) { + out->begin_list(out, NULL, NULL, "Colocations"); + printed_header = true; + } + + score = score2char(cons->score); + if (cons->role_rh > RSC_ROLE_STARTED) { + out->list_item(out, NULL, "%s (score=%s, %s role=%s, id=%s", + peer->id, score, dependents ? "needs" : "with", + role2text(cons->role_rh), cons->id); + } else { + out->list_item(out, NULL, "%s (score=%s, id=%s", + peer->id, score, cons->id); + } + + free(score); + out->message(out, "locations-list", peer); + + if (!dependents && recursive) { + out->message(out, "colocations-list", rsc, dependents, recursive); + } + } + + if (printed_header) { + out->end_list(out); + } + + return pcmk_rc_no_output; +} + +PCMK__OUTPUT_ARGS("colocations-list", "pe_resource_t *", "gboolean", "gboolean") +static int colocations_list_xml(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + gboolean dependents = va_arg(args, gboolean); + gboolean recursive = va_arg(args, gboolean); + + GListPtr lpc = NULL; + GListPtr list = rsc->rsc_cons; + bool printed_header = false; + + if (dependents) { + list = rsc->rsc_cons_lhs; + } + + if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { + return pcmk_rc_ok; + } + + pe__set_resource_flags(rsc, pe_rsc_allocating); + for (lpc = list; lpc != NULL; lpc = lpc->next) { + rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data; + pe_resource_t *peer = cons->rsc_rh; + char *score = NULL; + + if (dependents) { + peer = cons->rsc_lh; + } + + if (pcmk_is_set(peer->flags, pe_rsc_allocating)) { + if (dependents == FALSE) { + xmlNodePtr node; + + if (!printed_header) { + pcmk__output_xml_create_parent(out, "colocations"); + printed_header = true; + } + + node = pcmk__output_create_xml_node(out, "colocation"); + xmlSetProp(node, (pcmkXmlStr) "peer", (pcmkXmlStr) peer->id); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); + } + continue; + } + + if (dependents && recursive) { + if (!printed_header) { + pcmk__output_xml_create_parent(out, "colocations"); + printed_header = true; + } + + out->message(out, "colocations-list", rsc, dependents, recursive); + } + + if (!printed_header) { + pcmk__output_xml_create_parent(out, "colocations"); + printed_header = true; + } + + score = score2char(cons->score); + if (cons->role_rh > RSC_ROLE_STARTED) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "colocation"); + xmlSetProp(node, (pcmkXmlStr) "peer", (pcmkXmlStr) peer->id); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); + xmlSetProp(node, (pcmkXmlStr) "score", (pcmkXmlStr) score); + xmlSetProp(node, (pcmkXmlStr) "dependents", + (pcmkXmlStr) (dependents ? "needs" : "with")); + xmlSetProp(node, (pcmkXmlStr) "role", (pcmkXmlStr) role2text(cons->role_rh)); + } else { + xmlNodePtr node = pcmk__output_create_xml_node(out, "colocation"); + xmlSetProp(node, (pcmkXmlStr) "peer", (pcmkXmlStr) peer->id); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); + xmlSetProp(node, (pcmkXmlStr) "score", (pcmkXmlStr) score); + } + + free(score); + out->message(out, "locations-list", peer); + + if (!dependents && recursive) { + out->message(out, "colocations-list", rsc, dependents, recursive); + } + } + + if (printed_header) { + pcmk__output_xml_pop_parent(out); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *") +static int locations_list(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc G_GNUC_UNUSED = va_arg(args, pe_resource_t *); + + GListPtr lpc = NULL; + GListPtr list = rsc->rsc_location; + + out->begin_list(out, NULL, NULL, "Locations"); + + for (lpc = list; lpc != NULL; lpc = lpc->next) { + pe__location_t *cons = lpc->data; + + GListPtr lpc2 = NULL; + + for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) { + pe_node_t *node = (pe_node_t *) lpc2->data; + char *score = score2char(node->weight); + + out->list_item(out, NULL, "Node %s (score=%s, id=%s)", + node->details->uname, score, cons->id); + free(score); + } + } + + out->end_list(out); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("locations-list", "pe_resource_t *") +static int locations_list_xml(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + + GListPtr lpc = NULL; + GListPtr list = rsc->rsc_location; + + pcmk__output_xml_create_parent(out, "locations"); + + for (lpc = list; lpc != NULL; lpc = lpc->next) { + pe__location_t *cons = lpc->data; + + GListPtr lpc2 = NULL; + + for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) { + pe_node_t *node = (pe_node_t *) lpc2->data; + char *score = score2char(node->weight); + + xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "location"); + xmlSetProp(xml_node, (pcmkXmlStr) "host", (pcmkXmlStr) node->details->uname); + xmlSetProp(xml_node, (pcmkXmlStr) "id", (pcmkXmlStr) cons->id); + xmlSetProp(xml_node, (pcmkXmlStr) "score", (pcmkXmlStr) score); + + free(score); + } + } + + pcmk__output_xml_pop_parent(out); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "gboolean") +static int +stacks_and_constraints(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc G_GNUC_UNUSED = va_arg(args, pe_resource_t *); + pe_working_set_t *data_set G_GNUC_UNUSED = va_arg(args, pe_working_set_t *); + gboolean recursive G_GNUC_UNUSED = va_arg(args, gboolean); + + GListPtr lpc = NULL; + xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, + data_set->input); + + unpack_constraints(cib_constraints, data_set); + + // Constraints apply to group/clone, not member/instance + rsc = uber_parent(rsc); + + for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *r = (pe_resource_t *) lpc->data; + + pe__clear_resource_flags(r, pe_rsc_allocating); + } + + out->message(out, "colocations-list", rsc, TRUE, recursive); + + out->begin_list(out, NULL, NULL, "%s", rsc->id); + out->message(out, "locations-list", rsc); + out->end_list(out); + + for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *r = (pe_resource_t *) lpc->data; + + pe__clear_resource_flags(r, pe_rsc_allocating); + } + + out->message(out, "colocations-list", rsc, FALSE, recursive); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("stacks-constraints", "pe_resource_t *", "pe_working_set_t *", "gboolean") +static int +stacks_and_constraints_xml(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + gboolean recursive = va_arg(args, gboolean); + + GListPtr lpc = NULL; + xmlNodePtr node = NULL; + xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, + data_set->input); + + unpack_constraints(cib_constraints, data_set); + + // Constraints apply to group/clone, not member/instance + rsc = uber_parent(rsc); + + for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *r = (pe_resource_t *) lpc->data; + + pe__clear_resource_flags(r, pe_rsc_allocating); + } + + pcmk__output_xml_create_parent(out, "constraints"); + + out->message(out, "colocations-list", rsc, TRUE, recursive); + + node = pcmk__output_xml_create_parent(out, "resource"); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc->id); + out->message(out, "locations-list", rsc); + pcmk__output_xml_pop_parent(out); + + for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *r = (pe_resource_t *) lpc->data; + + pe__clear_resource_flags(r, pe_rsc_allocating); + } + + out->message(out, "colocations-list", rsc, FALSE, recursive); + return pcmk_rc_ok; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "colocations-list", "default", colocations_list }, + { "colocations-list", "xml", colocations_list_xml }, + { "locations-list", "default", locations_list }, + { "locations-list", "xml", locations_list_xml }, + { "stacks-constraints", "default", stacks_and_constraints }, + { "stacks-constraints", "xml", stacks_and_constraints_xml }, + + { NULL, NULL, NULL } +}; + +void +pcmk__register_lib_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); +} diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 9d43e5fbe4..1a3f93d766 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1,1926 +1,2052 @@ /* * Copyright 2019-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include static char * failed_action_string(xmlNodePtr xml_op) { const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0"); int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0"); const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON); time_t last_change = 0; if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, &last_change) == pcmk_ok) { crm_time_t *crm_when = crm_time_new(NULL); char *time_s = NULL; char *buf = NULL; crm_time_set_timet(crm_when, &last_change); time_s = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); buf = crm_strdup_printf("%s on %s '%s' (%d): call=%s, status='%s', " "exitreason='%s', " XML_RSC_OP_LAST_CHANGE "='%s', queued=%sms, exec=%sms", op_key ? op_key : ID(xml_op), crm_element_value(xml_op, XML_ATTR_UNAME), services_ocf_exitcode_str(rc), rc, crm_element_value(xml_op, XML_LRM_ATTR_CALLID), services_lrm_status_str(status), exit_reason ? exit_reason : "none", time_s, crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); crm_time_free(crm_when); free(time_s); return buf; } else { return crm_strdup_printf("%s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'", op_key ? op_key : ID(xml_op), crm_element_value(xml_op, XML_ATTR_UNAME), services_ocf_exitcode_str(rc), rc, crm_element_value(xml_op, XML_LRM_ATTR_CALLID), services_lrm_status_str(status), exit_reason ? exit_reason : "none"); } } static const char * get_cluster_stack(pe_working_set_t *data_set) { xmlNode *stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']", data_set->input, LOG_DEBUG); return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown"; } static char * last_changed_string(const char *last_written, const char *user, const char *client, const char *origin) { if (last_written != NULL || user != NULL || client != NULL || origin != NULL) { return crm_strdup_printf("%s%s%s%s%s%s%s", last_written ? last_written : "", user ? " by " : "", user ? user : "", client ? " via " : "", client ? client : "", origin ? " on " : "", origin ? origin : ""); } else { return strdup(""); } } static char * op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s, int rc, gboolean print_timing) { const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID); char *interval_str = NULL; char *buf = NULL; if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { char *pair = pcmk_format_nvpair("interval", interval_ms_s, "ms"); interval_str = crm_strdup_printf(" %s", pair); free(pair); } if (print_timing) { char *last_change_str = NULL; char *last_run_str = NULL; char *exec_str = NULL; char *queue_str = NULL; const char *value = NULL; time_t epoch = 0; if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, &epoch) == pcmk_ok) && (epoch > 0)) { char *time = pcmk_format_named_time(XML_RSC_OP_LAST_CHANGE, epoch); last_change_str = crm_strdup_printf(" %s", time); free(time); } if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_RUN, &epoch) == pcmk_ok) && (epoch > 0)) { char *time = pcmk_format_named_time(XML_RSC_OP_LAST_RUN, epoch); last_run_str = crm_strdup_printf(" %s", time); free(time); } value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC); if (value) { char *pair = pcmk_format_nvpair(XML_RSC_OP_T_EXEC, value, "ms"); exec_str = crm_strdup_printf(" %s", pair); free(pair); } value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE); if (value) { char *pair = pcmk_format_nvpair(XML_RSC_OP_T_QUEUE, value, "ms"); queue_str = crm_strdup_printf(" %s", pair); free(pair); } buf = crm_strdup_printf("(%s) %s:%s%s%s%s%s rc=%d (%s)", call, task, interval_str ? interval_str : "", last_change_str ? last_change_str : "", last_run_str ? last_run_str : "", exec_str ? exec_str : "", queue_str ? queue_str : "", rc, services_ocf_exitcode_str(rc)); if (last_change_str) { free(last_change_str); } if (last_run_str) { free(last_run_str); } if (exec_str) { free(exec_str); } if (queue_str) { free(queue_str); } } else { buf = crm_strdup_printf("(%s) %s%s%s", call, task, interval_str ? ":" : "", interval_str ? interval_str : ""); } if (interval_str) { free(interval_str); } return buf; } static char * resource_history_string(pe_resource_t *rsc, const char *rsc_id, gboolean all, int failcount, time_t last_failure) { char *buf = NULL; if (rsc == NULL) { buf = crm_strdup_printf("%s: orphan", rsc_id); } else if (all || failcount || last_failure > 0) { char *failcount_s = NULL; char *lastfail_s = NULL; if (failcount > 0) { failcount_s = crm_strdup_printf(" %s=%d", PCMK__FAIL_COUNT_PREFIX, failcount); } else { failcount_s = strdup(""); } if (last_failure > 0) { lastfail_s = crm_strdup_printf(" %s='%s'", PCMK__LAST_FAILURE_PREFIX, pcmk__epoch2str(&last_failure)); } buf = crm_strdup_printf("%s: migration-threshold=%d%s%s", rsc_id, rsc->migration_threshold, failcount_s, lastfail_s? lastfail_s : ""); free(failcount_s); free(lastfail_s); } else { buf = crm_strdup_printf("%s:", rsc_id); } return buf; } PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *", "gboolean", "gboolean", "gboolean", "gboolean", "gboolean", "gboolean") int pe__cluster_summary(pcmk__output_t *out, va_list args) { pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); gboolean print_clone_detail = va_arg(args, gboolean); gboolean show_stack = va_arg(args, gboolean); gboolean show_dc = va_arg(args, gboolean); gboolean show_times = va_arg(args, gboolean); gboolean show_counts = va_arg(args, gboolean); gboolean show_options = va_arg(args, gboolean); int rc = pcmk_rc_no_output; const char *stack_s = get_cluster_stack(data_set); if (show_stack) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-stack", stack_s); } /* Always print DC if none, even if not requested */ if (data_set->dc_node == NULL || show_dc) { xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']", data_set->input, LOG_DEBUG); const char *dc_version_s = dc_version? crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE) : NULL; const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM); char *dc_name = data_set->dc_node ? pe__node_display_name(data_set->dc_node, print_clone_detail) : NULL; PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-dc", data_set->dc_node, quorum, dc_version_s, dc_name); free(dc_name); } if (show_times) { const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN); const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER); const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT); const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG); PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-times", last_written, user, client, origin); } if (show_counts) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-counts", g_list_length(data_set->nodes), data_set->ninstances, data_set->disabled_resources, data_set->blocked_resources); } if (show_options) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-options", data_set); } PCMK__OUTPUT_LIST_FOOTER(out, rc); if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) { rc = pcmk_rc_ok; } return rc; } PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *", "gboolean", "gboolean", "gboolean", "gboolean", "gboolean", "gboolean") int pe__cluster_summary_html(pcmk__output_t *out, va_list args) { pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); gboolean print_clone_detail = va_arg(args, gboolean); gboolean show_stack = va_arg(args, gboolean); gboolean show_dc = va_arg(args, gboolean); gboolean show_times = va_arg(args, gboolean); gboolean show_counts = va_arg(args, gboolean); gboolean show_options = va_arg(args, gboolean); int rc = pcmk_rc_no_output; const char *stack_s = get_cluster_stack(data_set); if (show_stack) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-stack", stack_s); } /* Always print DC if none, even if not requested */ if (data_set->dc_node == NULL || show_dc) { xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']", data_set->input, LOG_DEBUG); const char *dc_version_s = dc_version? crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE) : NULL; const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM); char *dc_name = data_set->dc_node ? pe__node_display_name(data_set->dc_node, print_clone_detail) : NULL; PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-dc", data_set->dc_node, quorum, dc_version_s, dc_name); free(dc_name); } if (show_times) { const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN); const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER); const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT); const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG); PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-times", last_written, user, client, origin); } if (show_counts) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Cluster Summary"); out->message(out, "cluster-counts", g_list_length(data_set->nodes), data_set->ninstances, data_set->disabled_resources, data_set->blocked_resources); } if (show_options) { /* Kind of a hack - close the list we may have opened earlier in this * function so we can put all the options into their own list. We * only want to do this on HTML output, though. */ PCMK__OUTPUT_LIST_FOOTER(out, rc); out->begin_list(out, NULL, NULL, "Config Options"); out->message(out, "cluster-options", data_set); } PCMK__OUTPUT_LIST_FOOTER(out, rc); if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) { rc = pcmk_rc_ok; } return rc; } char * pe__node_display_name(pe_node_t *node, bool print_detail) { char *node_name; const char *node_host = NULL; const char *node_id = NULL; int name_len; CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL)); /* Host is displayed only if this is a guest node */ if (pe__is_guest_node(node)) { pe_node_t *host_node = pe__current_node(node->details->remote_rsc); if (host_node && host_node->details) { node_host = host_node->details->uname; } if (node_host == NULL) { node_host = ""; /* so we at least get "uname@" to indicate guest */ } } /* Node ID is displayed if different from uname and detail is requested */ if (print_detail && !pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) { node_id = node->details->id; } /* Determine name length */ name_len = strlen(node->details->uname) + 1; if (node_host) { name_len += strlen(node_host) + 1; /* "@node_host" */ } if (node_id) { name_len += strlen(node_id) + 3; /* + " (node_id)" */ } /* Allocate and populate display name */ node_name = malloc(name_len); CRM_ASSERT(node_name != NULL); strcpy(node_name, node->details->uname); if (node_host) { strcat(node_name, "@"); strcat(node_name, node_host); } if (node_id) { strcat(node_name, " ("); strcat(node_name, node_id); strcat(node_name, ")"); } return node_name; } int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name , size_t pairs_count, ...) { xmlNodePtr xml_node = NULL; va_list args; CRM_ASSERT(tag_name != NULL); xml_node = pcmk__output_xml_peek_parent(out); CRM_ASSERT(xml_node != NULL); xml_node = is_list ? create_xml_node(xml_node, tag_name) : xmlNewChild(xml_node, NULL, (pcmkXmlStr) tag_name, NULL); va_start(args, pairs_count); while(pairs_count--) { const char *param_name = va_arg(args, const char *); const char *param_value = va_arg(args, const char *); if (param_name && param_value) { xmlSetProp(xml_node, (pcmkXmlStr)param_name, (pcmkXmlStr)param_value); } }; va_end(args); if (is_list) { pcmk__output_xml_push_parent(out, xml_node); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "gboolean") int pe__ban_html(pcmk__output_t *out, va_list args) { pe_node_t *pe_node = va_arg(args, pe_node_t *); pe__location_t *location = va_arg(args, pe__location_t *); gboolean print_clone_detail = va_arg(args, gboolean); char *node_name = pe__node_display_name(pe_node, print_clone_detail); char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s", location->id, location->rsc_lh->id, location->role_filter == RSC_ROLE_MASTER ? "as Master " : "", node_name); pcmk__output_create_html_node(out, "li", NULL, NULL, buf); free(node_name); free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "gboolean") int pe__ban_text(pcmk__output_t *out, va_list args) { pe_node_t *pe_node = va_arg(args, pe_node_t *); pe__location_t *location = va_arg(args, pe__location_t *); gboolean print_clone_detail = va_arg(args, gboolean); char *node_name = pe__node_display_name(pe_node, print_clone_detail); out->list_item(out, NULL, "%s\tprevents %s from running %son %s", location->id, location->rsc_lh->id, location->role_filter == RSC_ROLE_MASTER ? "as Master " : "", node_name); free(node_name); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "gboolean") int pe__ban_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "ban"); pe_node_t *pe_node = va_arg(args, pe_node_t *); pe__location_t *location = va_arg(args, pe__location_t *); gboolean print_clone_detail G_GNUC_UNUSED = va_arg(args, gboolean); char *weight_s = crm_itoa(pe_node->weight); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) location->id); xmlSetProp(node, (pcmkXmlStr) "resource", (pcmkXmlStr) location->rsc_lh->id); xmlSetProp(node, (pcmkXmlStr) "node", (pcmkXmlStr) pe_node->details->uname); xmlSetProp(node, (pcmkXmlStr) "weight", (pcmkXmlStr) weight_s); xmlSetProp(node, (pcmkXmlStr) "master_only", (pcmkXmlStr) pcmk__btoa(location->role_filter == RSC_ROLE_MASTER)); free(weight_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") int pe__cluster_counts_html(pcmk__output_t *out, va_list args) { xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li"); xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li"); unsigned int nnodes = va_arg(args, unsigned int); int nresources = va_arg(args, int); int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); char *nnodes_str = crm_strdup_printf("%d node%s configured", nnodes, pcmk__plural_s(nnodes)); pcmk_create_html_node(nodes_node, "span", NULL, NULL, nnodes_str); free(nnodes_str); if (ndisabled && nblocked) { char *s = crm_strdup_printf("%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), ndisabled); pcmk_create_html_node(resources_node, "span", NULL, NULL, s); free(s); pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED"); s = crm_strdup_printf(", %d ", nblocked); pcmk_create_html_node(resources_node, "span", NULL, NULL, s); free(s); pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED"); pcmk_create_html_node(resources_node, "span", NULL, NULL, " from further action due to failure)"); } else if (ndisabled && !nblocked) { char *s = crm_strdup_printf("%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), ndisabled); pcmk_create_html_node(resources_node, "span", NULL, NULL, s); free(s); pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED"); pcmk_create_html_node(resources_node, "span", NULL, NULL, ")"); } else if (!ndisabled && nblocked) { char *s = crm_strdup_printf("%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), nblocked); pcmk_create_html_node(resources_node, "span", NULL, NULL, s); free(s); pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED"); pcmk_create_html_node(resources_node, "span", NULL, NULL, " from further action due to failure)"); } else { char *s = crm_strdup_printf("%d resource instance%s configured", nresources, pcmk__plural_s(nresources)); pcmk_create_html_node(resources_node, "span", NULL, NULL, s); free(s); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") int pe__cluster_counts_text(pcmk__output_t *out, va_list args) { unsigned int nnodes = va_arg(args, unsigned int); int nresources = va_arg(args, int); int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); out->list_item(out, NULL, "%d node%s configured", nnodes, pcmk__plural_s(nnodes)); if (ndisabled && nblocked) { out->list_item(out, NULL, "%d resource instance%s configured " "(%d DISABLED, %d BLOCKED from " "further action due to failure)", nresources, pcmk__plural_s(nresources), ndisabled, nblocked); } else if (ndisabled && !nblocked) { out->list_item(out, NULL, "%d resource instance%s configured " "(%d DISABLED)", nresources, pcmk__plural_s(nresources), ndisabled); } else if (!ndisabled && nblocked) { out->list_item(out, NULL, "%d resource instance%s configured " "(%d BLOCKED from further action " "due to failure)", nresources, pcmk__plural_s(nresources), nblocked); } else { out->list_item(out, NULL, "%d resource instance%s configured", nresources, pcmk__plural_s(nresources)); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") int pe__cluster_counts_xml(pcmk__output_t *out, va_list args) { xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "nodes_configured"); xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "resources_configured"); unsigned int nnodes = va_arg(args, unsigned int); int nresources = va_arg(args, int); int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); char *s = crm_itoa(nnodes); xmlSetProp(nodes_node, (pcmkXmlStr) "number", (pcmkXmlStr) s); free(s); s = crm_itoa(nresources); xmlSetProp(resources_node, (pcmkXmlStr) "number", (pcmkXmlStr) s); free(s); s = crm_itoa(ndisabled); xmlSetProp(resources_node, (pcmkXmlStr) "disabled", (pcmkXmlStr) s); free(s); s = crm_itoa(nblocked); xmlSetProp(resources_node, (pcmkXmlStr) "blocked", (pcmkXmlStr) s); free(s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *", "char *") int pe__cluster_dc_html(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); pe_node_t *dc = va_arg(args, pe_node_t *); const char *quorum = va_arg(args, const char *); const char *dc_version_s = va_arg(args, const char *); char *dc_name = va_arg(args, char *); pcmk_create_html_node(node, "span", NULL, "bold", "Current DC: "); if (dc) { if (crm_is_true(quorum)) { char *buf = crm_strdup_printf("%s (version %s) - partition with quorum", dc_name, dc_version_s ? dc_version_s : "unknown"); pcmk_create_html_node(node, "span", NULL, NULL, buf); free(buf); } else { char *buf = crm_strdup_printf("%s (version %s) - partition", dc_name, dc_version_s ? dc_version_s : "unknown"); pcmk_create_html_node(node, "span", NULL, NULL, buf); free(buf); pcmk_create_html_node(node, "span", NULL, "warning", "WITHOUT"); pcmk_create_html_node(node, "span", NULL, NULL, "quorum"); } } else { pcmk_create_html_node(node ,"span", NULL, "warning", "NONE"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *", "char *") int pe__cluster_dc_text(pcmk__output_t *out, va_list args) { pe_node_t *dc = va_arg(args, pe_node_t *); const char *quorum = va_arg(args, const char *); const char *dc_version_s = va_arg(args, const char *); char *dc_name = va_arg(args, char *); if (dc) { out->list_item(out, "Current DC", "%s (version %s) - partition %s quorum", dc_name, dc_version_s ? dc_version_s : "unknown", crm_is_true(quorum) ? "with" : "WITHOUT"); } else { out->list_item(out, "Current DC", "NONE"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *", "char *") int pe__cluster_dc_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "current_dc"); pe_node_t *dc = va_arg(args, pe_node_t *); const char *quorum = va_arg(args, const char *); const char *dc_version_s = va_arg(args, const char *); char *dc_name G_GNUC_UNUSED = va_arg(args, char *); if (dc) { xmlSetProp(node, (pcmkXmlStr) "present", (pcmkXmlStr) "true"); xmlSetProp(node, (pcmkXmlStr) "version", (pcmkXmlStr) (dc_version_s ? dc_version_s : "")); xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) dc->details->uname); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) dc->details->id); xmlSetProp(node, (pcmkXmlStr) "with_quorum", (pcmkXmlStr) pcmk__btoa(crm_is_true(quorum))); } else { xmlSetProp(node, (pcmkXmlStr) "present", (pcmkXmlStr) "false"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int") int pe__cluster_maint_mode_text(pcmk__output_t *out, va_list args) { unsigned long long flags = va_arg(args, unsigned long long); if (pcmk_is_set(flags, pe_flag_maintenance_mode)) { fprintf(out->dest, "\n *** Resource management is DISABLED ***"); fprintf(out->dest, "\n The cluster will not attempt to start, stop or recover services"); fprintf(out->dest, "\n"); return pcmk_rc_ok; } else if (pcmk_is_set(flags, pe_flag_stop_everything)) { fprintf(out->dest, "\n *** Resource management is DISABLED ***"); fprintf(out->dest, "\n The cluster will keep all resources stopped"); fprintf(out->dest, "\n"); return pcmk_rc_ok; } else { return pcmk_rc_no_output; } } PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") int pe__cluster_options_html(pcmk__output_t *out, va_list args) { pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); out->list_item(out, NULL, "STONITH of failed nodes %s", pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled"); out->list_item(out, NULL, "Cluster is %s", pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric"); switch (data_set->no_quorum_policy) { case no_quorum_freeze: out->list_item(out, NULL, "No quorum policy: Freeze resources"); break; case no_quorum_stop: out->list_item(out, NULL, "No quorum policy: Stop ALL resources"); break; case no_quorum_demote: out->list_item(out, NULL, "No quorum policy: Demote promotable " "resources and stop all other resources"); break; case no_quorum_ignore: out->list_item(out, NULL, "No quorum policy: Ignore"); break; case no_quorum_suicide: out->list_item(out, NULL, "No quorum policy: Suicide"); break; } if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: "); pcmk_create_html_node(node, "span", NULL, "bold", "DISABLED"); pcmk_create_html_node(node, "span", NULL, NULL, " (the cluster will not attempt to start, stop, or recover services)"); } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) { xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: "); pcmk_create_html_node(node, "span", NULL, "bold", "STOPPED"); pcmk_create_html_node(node, "span", NULL, NULL, " (the cluster will keep all resources stopped)"); } else { out->list_item(out, NULL, "Resource management: enabled"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") int pe__cluster_options_log(pcmk__output_t *out, va_list args) { pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { out->info(out, "Resource management is DISABLED. The cluster will not attempt to start, stop or recover services."); return pcmk_rc_ok; } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) { out->info(out, "Resource management is DISABLED. The cluster has stopped all resources."); return pcmk_rc_ok; } else { return pcmk_rc_no_output; } } PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") int pe__cluster_options_text(pcmk__output_t *out, va_list args) { pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); out->list_item(out, NULL, "STONITH of failed nodes %s", pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled"); out->list_item(out, NULL, "Cluster is %s", pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric"); switch (data_set->no_quorum_policy) { case no_quorum_freeze: out->list_item(out, NULL, "No quorum policy: Freeze resources"); break; case no_quorum_stop: out->list_item(out, NULL, "No quorum policy: Stop ALL resources"); break; case no_quorum_demote: out->list_item(out, NULL, "No quorum policy: Demote promotable " "resources and stop all other resources"); break; case no_quorum_ignore: out->list_item(out, NULL, "No quorum policy: Ignore"); break; case no_quorum_suicide: out->list_item(out, NULL, "No quorum policy: Suicide"); break; } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") int pe__cluster_options_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "cluster_options"); pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); xmlSetProp(node, (pcmkXmlStr) "stonith-enabled", (pcmkXmlStr) pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stonith_enabled))); xmlSetProp(node, (pcmkXmlStr) "symmetric-cluster", (pcmkXmlStr) pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster))); switch (data_set->no_quorum_policy) { case no_quorum_freeze: xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "freeze"); break; case no_quorum_stop: xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "stop"); break; case no_quorum_demote: xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "demote"); break; case no_quorum_ignore: xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "ignore"); break; case no_quorum_suicide: xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "suicide"); break; } xmlSetProp(node, (pcmkXmlStr) "maintenance-mode", (pcmkXmlStr) pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_maintenance_mode))); xmlSetProp(node, (pcmkXmlStr) "stop-all-resources", (pcmkXmlStr) pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stop_everything))); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-stack", "const char *") int pe__cluster_stack_html(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); const char *stack_s = va_arg(args, const char *); pcmk_create_html_node(node, "span", NULL, "bold", "Stack: "); pcmk_create_html_node(node, "span", NULL, NULL, stack_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-stack", "const char *") int pe__cluster_stack_text(pcmk__output_t *out, va_list args) { const char *stack_s = va_arg(args, const char *); out->list_item(out, "Stack", "%s", stack_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-stack", "const char *") int pe__cluster_stack_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "stack"); const char *stack_s = va_arg(args, const char *); xmlSetProp(node, (pcmkXmlStr) "type", (pcmkXmlStr) stack_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *") int pe__cluster_times_html(pcmk__output_t *out, va_list args) { xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li"); xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li"); const char *last_written = va_arg(args, const char *); const char *user = va_arg(args, const char *); const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); char *buf = last_changed_string(last_written, user, client, origin); pcmk_create_html_node(updated_node, "span", NULL, "bold", "Last updated: "); pcmk_create_html_node(updated_node, "span", NULL, NULL, pcmk__epoch2str(NULL)); pcmk_create_html_node(changed_node, "span", NULL, "bold", "Last change: "); pcmk_create_html_node(changed_node, "span", NULL, NULL, buf); free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *") int pe__cluster_times_xml(pcmk__output_t *out, va_list args) { xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "last_update"); xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "last_change"); const char *last_written = va_arg(args, const char *); const char *user = va_arg(args, const char *); const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); xmlSetProp(updated_node, (pcmkXmlStr) "time", (pcmkXmlStr) pcmk__epoch2str(NULL)); xmlSetProp(changed_node, (pcmkXmlStr) "time", (pcmkXmlStr) (last_written ? last_written : "")); xmlSetProp(changed_node, (pcmkXmlStr) "user", (pcmkXmlStr) (user ? user : "")); xmlSetProp(changed_node, (pcmkXmlStr) "client", (pcmkXmlStr) (client ? client : "")); xmlSetProp(changed_node, (pcmkXmlStr) "origin", (pcmkXmlStr) (origin ? origin : "")); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *") int pe__cluster_times_text(pcmk__output_t *out, va_list args) { const char *last_written = va_arg(args, const char *); const char *user = va_arg(args, const char *); const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); char *buf = last_changed_string(last_written, user, client, origin); out->list_item(out, "Last updated", "%s", pcmk__epoch2str(NULL)); out->list_item(out, "Last change", " %s", buf); free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr") int pe__failed_action_text(pcmk__output_t *out, va_list args) { xmlNodePtr xml_op = va_arg(args, xmlNodePtr); char *s = failed_action_string(xml_op); out->list_item(out, NULL, "%s", s); free(s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr") int pe__failed_action_xml(pcmk__output_t *out, va_list args) { xmlNodePtr xml_op = va_arg(args, xmlNodePtr); const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE); int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0"); int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0"); const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON); char *rc_s = crm_itoa(rc); char *reason_s = crm_xml_escape(exit_reason ? exit_reason : "none"); xmlNodePtr node = pcmk__output_create_xml_node(out, "failure"); xmlSetProp(node, (pcmkXmlStr) (op_key ? "op_key" : "id"), (pcmkXmlStr) (op_key ? op_key : "id")); xmlSetProp(node, (pcmkXmlStr) "node", (pcmkXmlStr) crm_element_value(xml_op, XML_ATTR_UNAME)); xmlSetProp(node, (pcmkXmlStr) "exitstatus", (pcmkXmlStr) services_ocf_exitcode_str(rc)); xmlSetProp(node, (pcmkXmlStr) "exitreason", (pcmkXmlStr) reason_s); xmlSetProp(node, (pcmkXmlStr) "exitcode", (pcmkXmlStr) rc_s); xmlSetProp(node, (pcmkXmlStr) "call", (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_CALLID)); xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) services_lrm_status_str(status)); if (last) { guint interval_ms = 0; char *s = NULL; time_t when = crm_parse_int(last, "0"); crm_time_t *crm_when = crm_time_new(NULL); char *rc_change = NULL; crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms); s = crm_itoa(interval_ms); crm_time_set_timet(crm_when, &when); rc_change = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_CHANGE, (pcmkXmlStr) rc_change); xmlSetProp(node, (pcmkXmlStr) "queued", (pcmkXmlStr) crm_element_value(xml_op, XML_RSC_OP_T_QUEUE)); xmlSetProp(node, (pcmkXmlStr) "exec", (pcmkXmlStr) crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); xmlSetProp(node, (pcmkXmlStr) "interval", (pcmkXmlStr) s); xmlSetProp(node, (pcmkXmlStr) "task", (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_TASK)); free(s); free(rc_change); crm_time_free(crm_when); } free(reason_s); free(rc_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node", "pe_node_t *", "unsigned int", "gboolean", "const char *", "gboolean", "gboolean", "gboolean", "GListPtr", "GListPtr") int pe__node_html(pcmk__output_t *out, va_list args) { pe_node_t *node = va_arg(args, pe_node_t *); unsigned int print_opts = va_arg(args, unsigned int); gboolean full = va_arg(args, gboolean); const char *node_mode G_GNUC_UNUSED = va_arg(args, const char *); gboolean print_clone_detail = va_arg(args, gboolean); gboolean print_brief = va_arg(args, gboolean); gboolean group_by_node = va_arg(args, gboolean); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); char *node_name = pe__node_display_name(node, print_clone_detail); char *buf = crm_strdup_printf("Node: %s", node_name); if (full) { xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li"); pcmk_create_html_node(item_node, "span", NULL, NULL, buf); if (node->details->standby_onfail && node->details->online) { pcmk_create_html_node(item_node, "span", NULL, "standby", " standby (on-fail)"); } else if (node->details->standby && node->details->online) { char *s = crm_strdup_printf(" standby%s", node->details->running_rsc ? " (with active resources)" : ""); pcmk_create_html_node(item_node, "span", NULL, " standby", s); free(s); } else if (node->details->standby) { pcmk_create_html_node(item_node, "span", NULL, "offline", " OFFLINE (standby)"); } else if (node->details->maintenance && node->details->online) { pcmk_create_html_node(item_node, "span", NULL, "maint", " maintenance"); } else if (node->details->maintenance) { pcmk_create_html_node(item_node, "span", NULL, "offline", " OFFLINE (maintenance)"); } else if (node->details->online) { pcmk_create_html_node(item_node, "span", NULL, "online", " online"); } else { pcmk_create_html_node(item_node, "span", NULL, "offline", " OFFLINE"); } if (print_brief && group_by_node) { GListPtr rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); if (rscs != NULL) { out->begin_list(out, NULL, NULL, NULL); pe__rscs_brief_output(out, rscs, print_opts | pe_print_rsconly, FALSE); out->end_list(out); } } else if (group_by_node) { GListPtr lpc2 = NULL; out->begin_list(out, NULL, NULL, NULL); for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) { pe_resource_t *rsc = (pe_resource_t *) lpc2->data; out->message(out, crm_map_element_name(rsc->xml), print_opts | pe_print_rsconly, rsc, only_node, only_rsc); } out->end_list(out); } } else { out->begin_list(out, NULL, NULL, "%s", buf); } free(buf); free(node_name); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node", "pe_node_t *", "unsigned int", "gboolean", "const char *", "gboolean", "gboolean", "gboolean", "GListPtr", "GListPtr") int pe__node_text(pcmk__output_t *out, va_list args) { pe_node_t *node = va_arg(args, pe_node_t *); unsigned int print_opts = va_arg(args, unsigned int); gboolean full = va_arg(args, gboolean); const char *node_mode = va_arg(args, const char *); gboolean print_clone_detail = va_arg(args, gboolean); gboolean print_brief = va_arg(args, gboolean); gboolean group_by_node = va_arg(args, gboolean); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); if (full) { char *node_name = pe__node_display_name(node, print_clone_detail); char *buf = NULL; /* Print the node name and status */ if (pe__is_guest_node(node)) { buf = crm_strdup_printf("GuestNode %s: %s", node_name, node_mode); } else if (pe__is_remote_node(node)) { buf = crm_strdup_printf("RemoteNode %s: %s", node_name, node_mode); } else { buf = crm_strdup_printf("Node %s: %s", node_name, node_mode); } /* If we're grouping by node, print its resources */ if (group_by_node) { if (print_brief) { GListPtr rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); if (rscs != NULL) { out->begin_list(out, NULL, NULL, "%s", buf); out->begin_list(out, NULL, NULL, "Resources"); pe__rscs_brief_output(out, rscs, print_opts | pe_print_rsconly, FALSE); out->end_list(out); out->end_list(out); } } else { GListPtr gIter2 = NULL; out->begin_list(out, NULL, NULL, "%s", buf); out->begin_list(out, NULL, NULL, "Resources"); for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) { pe_resource_t *rsc = (pe_resource_t *) gIter2->data; out->message(out, crm_map_element_name(rsc->xml), print_opts | pe_print_rsconly, rsc, only_node, only_rsc); } out->end_list(out); out->end_list(out); } } else { out->list_item(out, NULL, "%s", buf); } free(buf); free(node_name); } else { out->begin_list(out, NULL, NULL, "Node: %s", pe__node_display_name(node, print_clone_detail)); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node", "pe_node_t *", "unsigned int", "gboolean", "const char *", "gboolean", "gboolean", "gboolean", "GListPtr", "GListPtr") int pe__node_xml(pcmk__output_t *out, va_list args) { pe_node_t *node = va_arg(args, pe_node_t *); unsigned int print_opts = va_arg(args, unsigned int); gboolean full = va_arg(args, gboolean); const char *node_mode G_GNUC_UNUSED = va_arg(args, const char *); gboolean print_clone_detail G_GNUC_UNUSED = va_arg(args, gboolean); gboolean print_brief G_GNUC_UNUSED = va_arg(args, gboolean); gboolean group_by_node = va_arg(args, gboolean); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); if (full) { const char *node_type = "unknown"; char *length_s = crm_itoa(g_list_length(node->details->running_rsc)); switch (node->details->type) { case node_member: node_type = "member"; break; case node_remote: node_type = "remote"; break; case node_ping: node_type = "ping"; break; } pe__name_and_nvpairs_xml(out, true, "node", 13, "name", node->details->uname, "id", node->details->id, "online", pcmk__btoa(node->details->online), "standby", pcmk__btoa(node->details->standby), "standby_onfail", pcmk__btoa(node->details->standby_onfail), "maintenance", pcmk__btoa(node->details->maintenance), "pending", pcmk__btoa(node->details->pending), "unclean", pcmk__btoa(node->details->unclean), "shutdown", pcmk__btoa(node->details->shutdown), "expected_up", pcmk__btoa(node->details->expected_up), "is_dc", pcmk__btoa(node->details->is_dc), "resources_running", length_s, "type", node_type); if (pe__is_guest_node(node)) { xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out); xmlSetProp(xml_node, (pcmkXmlStr) "id_as_resource", (pcmkXmlStr) node->details->remote_rsc->container->id); } if (group_by_node) { GListPtr lpc = NULL; for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) { pe_resource_t *rsc = (pe_resource_t *) lpc->data; out->message(out, crm_map_element_name(rsc->xml), print_opts | pe_print_rsconly, rsc, only_node, only_rsc); } } free(length_s); out->end_list(out); } else { xmlNodePtr parent = pcmk__output_xml_create_parent(out, "node"); xmlSetProp(parent, (pcmkXmlStr) "name", (pcmkXmlStr) node->details->uname); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "gboolean", "int") int pe__node_attribute_text(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); gboolean add_extra = va_arg(args, gboolean); int expected_score = va_arg(args, int); if (add_extra) { int v = crm_parse_int(value, "0"); if (v <= 0) { out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value); } else if (v < expected_score) { out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score); } else { out->list_item(out, NULL, "%-32s\t: %-10s", name, value); } } else { out->list_item(out, NULL, "%-32s\t: %-10s", name, value); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "gboolean", "int") int pe__node_attribute_html(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); gboolean add_extra = va_arg(args, gboolean); int expected_score = va_arg(args, int); if (add_extra) { int v = crm_parse_int(value, "0"); char *s = crm_strdup_printf("%s: %s", name, value); xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li"); pcmk_create_html_node(item_node, "span", NULL, NULL, s); free(s); if (v <= 0) { pcmk_create_html_node(item_node, "span", NULL, "bold", "(connectivity is lost)"); } else if (v < expected_score) { char *buf = crm_strdup_printf("(connectivity is degraded -- expected %d", expected_score); pcmk_create_html_node(item_node, "span", NULL, "bold", buf); free(buf); } } else { out->list_item(out, NULL, "%s: %s", name, value); } return pcmk_rc_ok; } +PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr") +int +pe__node_and_op(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + + pe_resource_t *rsc = NULL; + gchar *node_str = NULL; + char *last_change_str = NULL; + + const char *op_rsc = crm_element_value(xml_op, "resource"); + const char *status_s = crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS); + const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); + int status = crm_parse_int(status_s, "0"); + time_t last_change = 0; + + rsc = pe_find_resource(data_set->resources, op_rsc); + + if (rsc) { + pe_node_t *node = pe__current_node(rsc); + const char *target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); + int opts = pe_print_rsconly | pe_print_pending; + + if (node == NULL) { + node = rsc->pending_node; + } + + node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node, + opts, target_role, false); + } else { + node_str = crm_strdup_printf("Unknown resource %s", op_rsc); + } + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change) == pcmk_ok) { + last_change_str = crm_strdup_printf(", %s=%s, exec=%sms", + XML_RSC_OP_LAST_CHANGE, + crm_strip_trailing_newline(ctime(&last_change)), + crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); + } + + out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s", + node_str, op_key ? op_key : ID(xml_op), + crm_element_value(xml_op, XML_ATTR_UNAME), + crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + crm_element_value(xml_op, XML_LRM_ATTR_RC), + last_change_str ? last_change_str : "", + services_lrm_status_str(status)); + + g_free(node_str); + free(last_change_str); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr") +int +pe__node_and_op_xml(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + + pe_resource_t *rsc = NULL; + const char *op_rsc = crm_element_value(xml_op, "resource"); + const char *status_s = crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS); + const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); + int status = crm_parse_int(status_s, "0"); + time_t last_change = 0; + + xmlNode *node = pcmk__output_create_xml_node(out, "operation"); + + rsc = pe_find_resource(data_set->resources, op_rsc); + + if (rsc) { + const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); + const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); + char *agent_tuple = NULL; + + agent_tuple = crm_strdup_printf("%s:%s:%s", class, + pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider) ? crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER) : "", + kind); + + xmlSetProp(node, (pcmkXmlStr) "rsc", (pcmkXmlStr) rsc_printable_id(rsc)); + xmlSetProp(node, (pcmkXmlStr) "agent", (pcmkXmlStr) agent_tuple); + free(agent_tuple); + } + + xmlSetProp(node, (pcmkXmlStr) "op", (pcmkXmlStr) (op_key ? op_key : ID(xml_op))); + xmlSetProp(node, (pcmkXmlStr) "node", (pcmkXmlStr) crm_element_value(xml_op, XML_ATTR_UNAME)); + xmlSetProp(node, (pcmkXmlStr) "call", (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_CALLID)); + xmlSetProp(node, (pcmkXmlStr) "rc", (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_RC)); + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change) == pcmk_ok) { + xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_CHANGE, + (pcmkXmlStr) crm_strip_trailing_newline(ctime(&last_change))); + xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_T_EXEC, + (pcmkXmlStr) crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); + } + + xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) services_lrm_status_str(status)); + + return pcmk_rc_ok; +} + PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "gboolean", "int") int pe__node_attribute_xml(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); gboolean add_extra = va_arg(args, gboolean); int expected_score = va_arg(args, int); xmlNodePtr node = pcmk__output_create_xml_node(out, "attribute"); xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) name); xmlSetProp(node, (pcmkXmlStr) "value", (pcmkXmlStr) value); if (add_extra) { char *buf = crm_itoa(expected_score); xmlSetProp(node, (pcmkXmlStr) "expected", (pcmkXmlStr) buf); free(buf); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-list", "GListPtr", "GListPtr", "GListPtr", "unsigned int", "gboolean", "gboolean", "gboolean") int pe__node_list_html(pcmk__output_t *out, va_list args) { GListPtr nodes = va_arg(args, GListPtr); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); unsigned int print_opts = va_arg(args, unsigned int); gboolean print_clone_detail = va_arg(args, gboolean); gboolean print_brief = va_arg(args, gboolean); gboolean group_by_node = va_arg(args, gboolean); int rc = pcmk_rc_no_output; for (GListPtr gIter = nodes; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; if (!pcmk__str_in_list(only_node, node->details->uname)) { continue; } PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Node List"); out->message(out, "node", node, print_opts, TRUE, NULL, print_clone_detail, print_brief, group_by_node, only_node, only_rsc); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-list", "GListPtr", "GListPtr", "GListPtr", "unsigned int", "gboolean", "gboolean", "gboolean") int pe__node_list_text(pcmk__output_t *out, va_list args) { GListPtr nodes = va_arg(args, GListPtr); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); unsigned int print_opts = va_arg(args, unsigned int); gboolean print_clone_detail = va_arg(args, gboolean); gboolean print_brief = va_arg(args, gboolean); gboolean group_by_node = va_arg(args, gboolean); /* space-separated lists of node names */ char *online_nodes = NULL; char *online_remote_nodes = NULL; char *online_guest_nodes = NULL; char *offline_nodes = NULL; char *offline_remote_nodes = NULL; size_t online_nodes_len = 0; size_t online_remote_nodes_len = 0; size_t online_guest_nodes_len = 0; size_t offline_nodes_len = 0; size_t offline_remote_nodes_len = 0; int rc = pcmk_rc_no_output; for (GListPtr gIter = nodes; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; const char *node_mode = NULL; char *node_name = pe__node_display_name(node, print_clone_detail); if (!pcmk__str_in_list(only_node, node->details->uname)) { free(node_name); continue; } PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Node List"); /* Get node mode */ if (node->details->unclean) { if (node->details->online) { node_mode = "UNCLEAN (online)"; } else if (node->details->pending) { node_mode = "UNCLEAN (pending)"; } else { node_mode = "UNCLEAN (offline)"; } } else if (node->details->pending) { node_mode = "pending"; } else if (node->details->standby_onfail && node->details->online) { node_mode = "standby (on-fail)"; } else if (node->details->standby) { if (node->details->online) { if (node->details->running_rsc) { node_mode = "standby (with active resources)"; } else { node_mode = "standby"; } } else { node_mode = "OFFLINE (standby)"; } } else if (node->details->maintenance) { if (node->details->online) { node_mode = "maintenance"; } else { node_mode = "OFFLINE (maintenance)"; } } else if (node->details->online) { node_mode = "online"; if (group_by_node == FALSE) { if (pe__is_guest_node(node)) { pcmk__add_word(&online_guest_nodes, &online_guest_nodes_len, node_name); } else if (pe__is_remote_node(node)) { pcmk__add_word(&online_remote_nodes, &online_remote_nodes_len, node_name); } else { pcmk__add_word(&online_nodes, &online_nodes_len, node_name); } free(node_name); continue; } } else { node_mode = "OFFLINE"; if (group_by_node == FALSE) { if (pe__is_remote_node(node)) { pcmk__add_word(&offline_remote_nodes, &offline_remote_nodes_len, node_name); } else if (pe__is_guest_node(node)) { /* ignore offline guest nodes */ } else { pcmk__add_word(&offline_nodes, &offline_nodes_len, node_name); } free(node_name); continue; } } /* If we get here, node is in bad state, or we're grouping by node */ out->message(out, "node", node, print_opts, TRUE, node_mode, print_clone_detail, print_brief, group_by_node, only_node, only_rsc); free(node_name); } /* If we're not grouping by node, summarize nodes by status */ if (online_nodes) { out->list_item(out, "Online", "[ %s ]", online_nodes); free(online_nodes); } if (offline_nodes) { out->list_item(out, "OFFLINE", "[ %s ]", offline_nodes); free(offline_nodes); } if (online_remote_nodes) { out->list_item(out, "RemoteOnline", "[ %s ]", online_remote_nodes); free(online_remote_nodes); } if (offline_remote_nodes) { out->list_item(out, "RemoteOFFLINE", "[ %s ]", offline_remote_nodes); free(offline_remote_nodes); } if (online_guest_nodes) { out->list_item(out, "GuestOnline", "[ %s ]", online_guest_nodes); free(online_guest_nodes); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-list", "GListPtr", "GListPtr", "GListPtr", "unsigned int", "gboolean", "gboolean", "gboolean") int pe__node_list_xml(pcmk__output_t *out, va_list args) { GListPtr nodes = va_arg(args, GListPtr); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); unsigned int print_opts = va_arg(args, unsigned int); gboolean print_clone_detail = va_arg(args, gboolean); gboolean print_brief = va_arg(args, gboolean); gboolean group_by_node = va_arg(args, gboolean); out->begin_list(out, NULL, NULL, "nodes"); for (GListPtr gIter = nodes; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; if (!pcmk__str_in_list(only_node, node->details->uname)) { continue; } out->message(out, "node", node, print_opts, TRUE, NULL, print_clone_detail, print_brief, group_by_node, only_node, only_rsc); } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("op-history", "struct xmlNode *", "const char *", "const char *", "int", "gboolean") int pe__op_history_text(pcmk__output_t *out, va_list args) { xmlNode *xml_op = va_arg(args, xmlNode *); const char *task = va_arg(args, const char *); const char *interval_ms_s = va_arg(args, const char *); int rc = va_arg(args, int); gboolean print_timing = va_arg(args, gboolean); char *buf = op_history_string(xml_op, task, interval_ms_s, rc, print_timing); out->list_item(out, NULL, "%s", buf); free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("op-history", "struct xmlNode *", "const char *", "const char *", "int", "gboolean") int pe__op_history_xml(pcmk__output_t *out, va_list args) { xmlNode *xml_op = va_arg(args, xmlNode *); const char *task = va_arg(args, const char *); const char *interval_ms_s = va_arg(args, const char *); int rc = va_arg(args, int); gboolean print_timing = va_arg(args, gboolean); char *rc_s = NULL; xmlNodePtr node = pcmk__output_create_xml_node(out, "operation_history"); xmlSetProp(node, (pcmkXmlStr) "call", (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_CALLID)); xmlSetProp(node, (pcmkXmlStr) "task", (pcmkXmlStr) task); if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { char *s = crm_strdup_printf("%sms", interval_ms_s); xmlSetProp(node, (pcmkXmlStr) "interval", (pcmkXmlStr) s); free(s); } if (print_timing) { const char *value = NULL; value = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE); if (value) { time_t int_value = (time_t) crm_parse_int(value, NULL); if (int_value > 0) { xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_CHANGE, (pcmkXmlStr) pcmk__epoch2str(&int_value)); } } value = crm_element_value(xml_op, XML_RSC_OP_LAST_RUN); if (value) { time_t int_value = (time_t) crm_parse_int(value, NULL); if (int_value > 0) { xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_RUN, (pcmkXmlStr) pcmk__epoch2str(&int_value)); } } value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC); if (value) { char *s = crm_strdup_printf("%sms", value); xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_T_EXEC, (pcmkXmlStr) s); free(s); } value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE); if (value) { char *s = crm_strdup_printf("%sms", value); xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_T_QUEUE, (pcmkXmlStr) s); free(s); } } rc_s = crm_itoa(rc); xmlSetProp(node, (pcmkXmlStr) "rc", (pcmkXmlStr) rc_s); xmlSetProp(node, (pcmkXmlStr) "rc_text", (pcmkXmlStr) services_ocf_exitcode_str(rc)); free(rc_s); return pcmk_rc_ok; } +PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "gboolean") +int pe__resource_config(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + gboolean raw = va_arg(args, gboolean); + + char *rsc_xml = NULL; + + if (raw) { + rsc_xml = dump_xml_formatted(rsc->orig_xml ? rsc->orig_xml : rsc->xml); + } else { + rsc_xml = dump_xml_formatted(rsc->xml); + } + + out->info(out, "Resource XML:"); + out->output_xml(out, "xml", rsc_xml); + + free(rsc_xml); + return pcmk_rc_ok; +} + PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "gboolean", "int", "time_t", "gboolean") int pe__resource_history_text(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); const char *rsc_id = va_arg(args, const char *); gboolean all = va_arg(args, gboolean); int failcount = va_arg(args, int); time_t last_failure = va_arg(args, int); gboolean as_header = va_arg(args, gboolean); char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure); if (as_header) { out->begin_list(out, NULL, NULL, "%s", buf); } else { out->list_item(out, NULL, "%s", buf); } free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "gboolean", "int", "time_t", "gboolean") int pe__resource_history_xml(pcmk__output_t *out, va_list args) { pe_resource_t *rsc = va_arg(args, pe_resource_t *); const char *rsc_id = va_arg(args, const char *); gboolean all = va_arg(args, gboolean); int failcount = va_arg(args, int); time_t last_failure = va_arg(args, int); gboolean as_header = va_arg(args, gboolean); xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource_history"); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc_id); if (rsc == NULL) { xmlSetProp(node, (pcmkXmlStr) "orphan", (pcmkXmlStr) "true"); } else if (all || failcount || last_failure > 0) { char *migration_s = crm_itoa(rsc->migration_threshold); xmlSetProp(node, (pcmkXmlStr) "orphan", (pcmkXmlStr) "false"); xmlSetProp(node, (pcmkXmlStr) "migration-threshold", (pcmkXmlStr) migration_s); free(migration_s); if (failcount > 0) { char *s = crm_itoa(failcount); xmlSetProp(node, (pcmkXmlStr) PCMK__FAIL_COUNT_PREFIX, (pcmkXmlStr) s); free(s); } if (last_failure > 0) { xmlSetProp(node, (pcmkXmlStr) PCMK__LAST_FAILURE_PREFIX, (pcmkXmlStr) pcmk__epoch2str(&last_failure)); } } if (as_header == FALSE) { pcmk__output_xml_pop_parent(out); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-list", "pe_working_set_t *", "unsigned int", "gboolean", "gboolean", "gboolean", "gboolean", "GListPtr", "GListPtr", "gboolean") int pe__resource_list(pcmk__output_t *out, va_list args) { pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); unsigned int print_opts = va_arg(args, unsigned int); gboolean group_by_node = va_arg(args, gboolean); gboolean inactive_resources = va_arg(args, gboolean); gboolean brief_output = va_arg(args, gboolean); gboolean print_summary = va_arg(args, gboolean); GListPtr only_node = va_arg(args, GListPtr); GListPtr only_rsc = va_arg(args, GListPtr); gboolean print_spacer = va_arg(args, gboolean); GListPtr rsc_iter; int rc = pcmk_rc_no_output; /* If we already showed active resources by node, and * we're not showing inactive resources, we have nothing to do */ if (group_by_node && !inactive_resources) { return rc; } PCMK__OUTPUT_SPACER_IF(out, print_spacer); if (group_by_node) { /* Active resources have already been printed by node */ out->begin_list(out, NULL, NULL, "Inactive Resources"); } else if (inactive_resources) { out->begin_list(out, NULL, NULL, "Full List of Resources"); } else { out->begin_list(out, NULL, NULL, "Active Resources"); } /* If we haven't already printed resources grouped by node, * and brief output was requested, print resource summary */ if (brief_output && !group_by_node) { GListPtr rscs = pe__filter_rsc_list(data_set->resources, only_rsc); pe__rscs_brief_output(out, rscs, print_opts, inactive_resources); g_list_free(rscs); } /* For each resource, display it if appropriate */ for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) { pe_resource_t *rsc = (pe_resource_t *) rsc_iter->data; int x; /* Complex resources may have some sub-resources active and some inactive */ gboolean is_active = rsc->fns->active(rsc, TRUE); gboolean partially_active = rsc->fns->active(rsc, FALSE); /* Skip inactive orphans (deleted but still in CIB) */ if (pcmk_is_set(rsc->flags, pe_rsc_orphan) && !is_active) { continue; /* Skip active resources if we already displayed them by node */ } else if (group_by_node) { if (is_active) { continue; } /* Skip primitives already counted in a brief summary */ } else if (brief_output && (rsc->variant == pe_native)) { continue; /* Skip resources that aren't at least partially active, * unless we're displaying inactive resources */ } else if (!partially_active && !inactive_resources) { continue; } else if (partially_active && !pe__rsc_running_on_any_node_in_list(rsc, only_node)) { continue; } /* Print this resource */ x = out->message(out, crm_map_element_name(rsc->xml), print_opts, rsc, only_node, only_rsc); if (x == pcmk_rc_ok) { rc = pcmk_rc_ok; } } if (print_summary && rc != pcmk_rc_ok) { if (group_by_node) { out->list_item(out, NULL, "No inactive resources"); } else if (inactive_resources) { out->list_item(out, NULL, "No resources"); } else { out->list_item(out, NULL, "No active resources"); } } out->end_list(out); return rc; } PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *") int pe__ticket_html(pcmk__output_t *out, va_list args) { pe_ticket_t *ticket = va_arg(args, pe_ticket_t *); if (ticket->last_granted > -1) { char *time = pcmk_format_named_time("last-granted", ticket->last_granted); out->list_item(out, NULL, "%s:\t%s%s %s", ticket->id, ticket->granted ? "granted" : "revoked", ticket->standby ? " [standby]" : "", time); free(time); } else { out->list_item(out, NULL, "%s:\t%s%s", ticket->id, ticket->granted ? "granted" : "revoked", ticket->standby ? " [standby]" : ""); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *") int pe__ticket_text(pcmk__output_t *out, va_list args) { pe_ticket_t *ticket = va_arg(args, pe_ticket_t *); if (ticket->last_granted > -1) { char *time = pcmk_format_named_time("last-granted", ticket->last_granted); out->list_item(out, ticket->id, "\t%s%s %s", ticket->granted ? "granted" : "revoked", ticket->standby ? " [standby]" : "", time); free(time); } else { out->list_item(out, ticket->id, "\t%s%s", ticket->granted ? "granted" : "revoked", ticket->standby ? " [standby]" : ""); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *") int pe__ticket_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = NULL; pe_ticket_t *ticket = va_arg(args, pe_ticket_t *); node = pcmk__output_create_xml_node(out, "ticket"); xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) ticket->id); xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) (ticket->granted ? "granted" : "revoked")); xmlSetProp(node, (pcmkXmlStr) "standby", (pcmkXmlStr) pcmk__btoa(ticket->standby)); if (ticket->last_granted > -1) { xmlSetProp(node, (pcmkXmlStr) "last-granted", (pcmkXmlStr) pcmk__epoch2str(&ticket->last_granted)); } return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "ban", "html", pe__ban_html }, { "ban", "log", pe__ban_text }, { "ban", "text", pe__ban_text }, { "ban", "xml", pe__ban_xml }, { "bundle", "xml", pe__bundle_xml }, { "bundle", "html", pe__bundle_html }, { "bundle", "text", pe__bundle_text }, { "bundle", "log", pe__bundle_text }, { "clone", "xml", pe__clone_xml }, { "clone", "html", pe__clone_html }, { "clone", "text", pe__clone_text }, { "clone", "log", pe__clone_text }, { "cluster-counts", "html", pe__cluster_counts_html }, { "cluster-counts", "log", pe__cluster_counts_text }, { "cluster-counts", "text", pe__cluster_counts_text }, { "cluster-counts", "xml", pe__cluster_counts_xml }, { "cluster-dc", "html", pe__cluster_dc_html }, { "cluster-dc", "log", pe__cluster_dc_text }, { "cluster-dc", "text", pe__cluster_dc_text }, { "cluster-dc", "xml", pe__cluster_dc_xml }, { "cluster-options", "html", pe__cluster_options_html }, { "cluster-options", "log", pe__cluster_options_log }, { "cluster-options", "text", pe__cluster_options_text }, { "cluster-options", "xml", pe__cluster_options_xml }, { "cluster-summary", "default", pe__cluster_summary }, { "cluster-summary", "html", pe__cluster_summary_html }, { "cluster-stack", "html", pe__cluster_stack_html }, { "cluster-stack", "log", pe__cluster_stack_text }, { "cluster-stack", "text", pe__cluster_stack_text }, { "cluster-stack", "xml", pe__cluster_stack_xml }, { "cluster-times", "html", pe__cluster_times_html }, { "cluster-times", "log", pe__cluster_times_text }, { "cluster-times", "text", pe__cluster_times_text }, { "cluster-times", "xml", pe__cluster_times_xml }, { "failed-action", "default", pe__failed_action_text }, { "failed-action", "xml", pe__failed_action_xml }, { "group", "xml", pe__group_xml }, { "group", "html", pe__group_html }, { "group", "text", pe__group_text }, { "group", "log", pe__group_text }, { "maint-mode", "text", pe__cluster_maint_mode_text }, { "node", "html", pe__node_html }, { "node", "log", pe__node_text }, { "node", "text", pe__node_text }, { "node", "xml", pe__node_xml }, + { "node-and-op", "default", pe__node_and_op }, + { "node-and-op", "xml", pe__node_and_op_xml }, { "node-list", "html", pe__node_list_html }, { "node-list", "log", pe__node_list_text }, { "node-list", "text", pe__node_list_text }, { "node-list", "xml", pe__node_list_xml }, { "node-attribute", "html", pe__node_attribute_html }, { "node-attribute", "log", pe__node_attribute_text }, { "node-attribute", "text", pe__node_attribute_text }, { "node-attribute", "xml", pe__node_attribute_xml }, { "op-history", "default", pe__op_history_text }, { "op-history", "xml", pe__op_history_xml }, { "primitive", "xml", pe__resource_xml }, { "primitive", "html", pe__resource_html }, { "primitive", "text", pe__resource_text }, { "primitive", "log", pe__resource_text }, + { "resource-config", "default", pe__resource_config }, { "resource-history", "default", pe__resource_history_text }, { "resource-history", "xml", pe__resource_history_xml }, { "resource-list", "default", pe__resource_list }, { "ticket", "html", pe__ticket_html }, { "ticket", "log", pe__ticket_text }, { "ticket", "text", pe__ticket_text }, { "ticket", "xml", pe__ticket_xml }, { NULL, NULL, NULL } }; void pe__register_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); } void pe__output_node(pe_node_t *node, gboolean details, pcmk__output_t *out) { if (node == NULL) { crm_trace(""); return; } CRM_ASSERT(node->details); crm_trace("%sNode %s: (weight=%d, fixed=%s)", node->details->online ? "" : "Unavailable/Unclean ", node->details->uname, node->weight, node->fixed ? "True" : "False"); if (details) { char *pe_mutable = strdup("\t\t"); GListPtr gIter = node->details->running_rsc; GListPtr all = NULL; all = g_list_prepend(all, strdup("*")); crm_trace("\t\t===Node Attributes"); g_hash_table_foreach(node->details->attrs, print_str_str, pe_mutable); free(pe_mutable); crm_trace("\t\t=== Resources"); for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; out->message(out, crm_map_element_name(rsc->xml), pe_print_pending, rsc, all, all); } g_list_free_full(all, free); } } diff --git a/tools/crm_resource.c b/tools/crm_resource.c index acaddc085d..2c62ff6d1c 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,1974 +1,2037 @@ /* * Copyright 2004-2020 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 #include #include #define SUMMARY "crm_resource - perform tasks related to Pacemaker cluster resources" enum rsc_command { cmd_none = 0, // No command option given (yet) cmd_ban, cmd_cleanup, cmd_clear, cmd_colocations, cmd_colocations_deep, cmd_cts, cmd_delete, cmd_delete_param, cmd_execute_agent, cmd_fail, cmd_get_param, cmd_get_property, cmd_list_active_ops, cmd_list_agents, cmd_list_all_ops, cmd_list_alternatives, cmd_list_instances, cmd_list_providers, cmd_list_resources, cmd_list_standards, cmd_locate, cmd_metadata, cmd_move, cmd_query_raw_xml, cmd_query_xml, cmd_refresh, cmd_restart, cmd_set_param, cmd_set_property, cmd_wait, cmd_why, }; struct { enum rsc_command rsc_cmd; // The crm_resource command to perform const char *attr_set_type; int cib_options; gboolean clear_expired; int find_flags; /* Flags to use when searching for resource */ gboolean force; gchar *host_uname; gchar *interval_spec; gchar *move_lifetime; gchar *operation; GHashTable *override_params; gchar *prop_id; char *prop_name; gchar *prop_set; gchar *prop_value; gboolean recursive; gchar **remainder; gboolean require_cib; // Whether command requires CIB connection gboolean require_crmd; /* whether command requires controller connection */ gboolean require_dataset; /* whether command requires populated dataset instance */ gboolean require_resource; /* whether command requires that resource be specified */ int resource_verbose; gchar *rsc_id; gchar *rsc_type; gboolean promoted_role_only; int timeout_ms; char *agent_spec; // Standard and/or provider and/or agent char *v_agent; char *v_class; char *v_provider; gboolean validate_cmdline; /* whether we are just validating based on command line options */ GHashTable *validate_options; gchar *xml_file; } options = { .attr_set_type = XML_TAG_ATTR_SETS, .cib_options = cib_sync_call, .require_cib = TRUE, .require_dataset = TRUE, .require_resource = TRUE, }; #if 0 // @COMPAT @TODO enable this at next backward compatibility break #define SET_COMMAND(cmd) do { \ if (options.rsc_cmd != cmd_none) { \ g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE, \ "Only one command option may be specified"); \ return FALSE; \ } \ options.rsc_cmd = (cmd); \ } while (0) #else #define SET_COMMAND(cmd) do { options.rsc_cmd = (cmd); } while (0) #endif gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean option_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); -bool BE_QUIET = FALSE; static crm_exit_t exit_code = CRM_EX_OK; +static pcmk__output_t *out = NULL; // Things that should be cleaned up on exit static GError *error = NULL; static GMainLoop *mainloop = NULL; static cib_t *cib_conn = NULL; static pcmk_ipc_api_t *controld_api = NULL; static pe_working_set_t *data_set = NULL; #define MESSAGE_TIMEOUT_S 60 #define INDENT " " +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + // Clean up and exit static crm_exit_t bye(crm_exit_t ec) { if (error != NULL) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + if (out != NULL) { + out->err(out, "%s: %s", g_get_prgname(), error->message); + } else { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + } + g_clear_error(&error); } + if (out != NULL) { + out->finish(out, ec, true, NULL); + pcmk__output_free(out); + } + if (cib_conn != NULL) { cib_t *save_cib_conn = cib_conn; cib_conn = NULL; // Ensure we can't free this twice save_cib_conn->cmds->signoff(save_cib_conn); cib_delete(save_cib_conn); } if (controld_api != NULL) { pcmk_ipc_api_t *save_controld_api = controld_api; controld_api = NULL; // Ensure we can't free this twice pcmk_free_ipc_api(save_controld_api); } if (mainloop != NULL) { g_main_loop_unref(mainloop); mainloop = NULL; } pe_free_working_set(data_set); data_set = NULL; crm_exit(ec); return ec; } static void quit_main_loop(crm_exit_t ec) { exit_code = ec; if (mainloop != NULL) { GMainLoop *mloop = mainloop; mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static gboolean resource_ipc_timeout(gpointer data) { // Start with newline because "Waiting for ..." message doesn't have one if (error != NULL) { g_clear_error(&error); } g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_TIMEOUT, - "\nAborting because no messages received in %d seconds", MESSAGE_TIMEOUT_S); + "Aborting because no messages received in %d seconds", MESSAGE_TIMEOUT_S); quit_main_loop(CRM_EX_TIMEOUT); return FALSE; } static void controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected crm_info("Connection to controller was terminated"); } quit_main_loop(exit_code); break; case pcmk_ipc_event_reply: if (status != CRM_EX_OK) { - fprintf(stderr, "\nError: bad reply from controller: %s\n", - crm_exit_str(status)); + out->err(out, "Error: bad reply from controller: %s", + crm_exit_str(status)); pcmk_disconnect_ipc(api); quit_main_loop(status); } else { - fprintf(stderr, "."); if ((pcmk_controld_api_replies_expected(api) == 0) && mainloop && g_main_loop_is_running(mainloop)) { - fprintf(stderr, " OK\n"); + out->info(out, "... got reply (done)"); crm_debug("Got all the replies we expected"); pcmk_disconnect_ipc(api); quit_main_loop(CRM_EX_OK); + } else { + out->info(out, "... got reply"); } } break; default: break; } } static void start_mainloop(pcmk_ipc_api_t *capi) { unsigned int count = pcmk_controld_api_replies_expected(capi); if (count > 0) { - fprintf(stderr, "Waiting for %d %s from the controller", - count, pcmk__plural_alt(count, "reply", "replies")); + out->info(out, "Waiting for %d %s from the controller", + count, pcmk__plural_alt(count, "reply", "replies")); exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects mainloop = g_main_loop_new(NULL, FALSE); g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL); g_main_loop_run(mainloop); } } static int compare_id(gconstpointer a, gconstpointer b) { return strcmp((const char *)a, (const char *)b); } static GListPtr build_constraint_list(xmlNode *root) { GListPtr retval = NULL; xmlNode *cib_constraints = NULL; xmlXPathObjectPtr xpathObj = NULL; int ndx = 0; cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root); xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION); for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) { xmlNode *match = getXpathResult(xpathObj, ndx); retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id); } freeXpathObject(xpathObj); return retval; } /* short option letters still available: eEJkKXyYZ */ static GOptionEntry query_entries[] = { { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List all cluster resources with status", NULL }, { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List IDs of all instantiated resources (individual members\n" INDENT "rather than groups etc.)", NULL }, { "list-cts", 'c', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, NULL, NULL }, { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List active resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-all-operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb, "List all resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_standards_cb, "List supported standards", NULL }, { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_providers_cb, "List all available OCF providers", NULL }, { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_agents_cb, "List all agents available for the named standard and/or provider", "STD/PROV" }, { "list-ocf-alternatives", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, list_alternatives_cb, "List all available providers for the named OCF agent", "AGENT" }, { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, metadata_cb, "Show the metadata for the named class:provider:agent", "SPEC" }, { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Show XML configuration of resource (after any template expansion)", NULL }, { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Show XML configuration of resource (before any template expansion)", NULL }, { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, get_param_prop_cb, "Display named parameter for resource (use instance attribute\n" INDENT "unless --meta or --utilization is specified)", "PARAM" }, { "get-property", 'G', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, get_param_prop_cb, "Display named property of resource ('class', 'type', or 'provider') " "(requires --resource)", "PROPERTY" }, { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Show node(s) currently running resource", NULL }, { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Display the (co)location constraints that apply to a resource\n" INDENT "and the resources is it colocated with", NULL }, { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Display the (co)location constraints that apply to a resource", NULL }, { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, why_cb, "Show why resources are not running, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { NULL } }; static GOptionEntry command_entries[] = { { "validate", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "Validate resource configuration by calling agent's validate-all\n" INDENT "action. The configuration may be specified either by giving an\n" INDENT "existing resource name with -r, or by specifying --class,\n" INDENT "--agent, and --provider arguments, along with any number of\n" INDENT "--option arguments.", NULL }, { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb, "If resource has any past failures, clear its history and fail\n" INDENT "count. Optionally filtered by --resource, --node, --operation\n" INDENT "and --interval (otherwise all). --operation and --interval\n" INDENT "apply to fail counts, but entire history is always clear, to\n" INDENT "allow current state to be rechecked. If the named resource is\n" INDENT "part of a group, or one numbered instance of a clone or bundled\n" INDENT "resource, the clean-up applies to the whole collective resource\n" INDENT "unless --force is given.", NULL }, { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb, "Delete resource's history (including failures) so its current state\n" INDENT "is rechecked. Optionally filtered by --resource and --node\n" INDENT "(otherwise all). If the named resource is part of a group, or one\n" INDENT "numbered instance of a clone or bundled resource, the refresh\n" INDENT "applies to the whole collective resource unless --force is given.", NULL }, { "set-parameter", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb, "Set named parameter for resource (requires -v). Use instance\n" INDENT "attribute unless --meta or --utilization is specified.", "PARAM" }, { "delete-parameter", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb, "Delete named parameter for resource. Use instance attribute\n" INDENT "unless --meta or --utilization is specified.", "PARAM" }, { "set-property", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, set_prop_cb, "Set named property of resource ('class', 'type', or 'provider') " "(requires -r, -t, -v)", "PROPERTY" }, { NULL } }; static GOptionEntry location_entries[] = { { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Create a constraint to move resource. If --node is specified,\n" INDENT "the constraint will be to move to that node, otherwise it\n" INDENT "will be to ban the current node. Unless --force is specified\n" INDENT "this will return an error if the resource is already running\n" INDENT "on the specified node. If --force is specified, this will\n" INDENT "always ban the current node.\n" INDENT "Optional: --lifetime, --master. NOTE: This may prevent the\n" INDENT "resource from running on its previous location until the\n" INDENT "implicit constraint expires or is removed with --clear.", NULL }, { "ban", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Create a constraint to keep resource off a node.\n" INDENT "Optional: --node, --lifetime, --master.\n" INDENT "NOTE: This will prevent the resource from running on the\n" INDENT "affected node until the implicit constraint expires or is\n" INDENT "removed with --clear. If --node is not specified, it defaults\n" INDENT "to the node currently running the resource for primitives\n" INDENT "and groups, or the master for promotable clones with\n" INDENT "promoted-max=1 (all other situations result in an error as\n" INDENT "there is no sane default).", NULL }, { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb, "Remove all constraints created by the --ban and/or --move\n" INDENT "commands. Requires: --resource. Optional: --node, --master,\n" INDENT "--expired. If --node is not specified, all constraints created\n" INDENT "by --ban and --move will be removed for the named resource. If\n" INDENT "--node and --force are specified, any constraint created by\n" INDENT "--move will be cleared, even if it is not for the specified\n" INDENT "node. If --expired is specified, only those constraints whose\n" INDENT "lifetimes have expired will be removed.", NULL }, { "expired", 'e', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, expired_cb, "Modifies the --clear argument to remove constraints with\n" INDENT "expired lifetimes.", NULL }, { "lifetime", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.move_lifetime, "Lifespan (as ISO 8601 duration) of created constraints (with\n" INDENT "-B, -M) see https://en.wikipedia.org/wiki/ISO_8601#Durations)", "TIMESPEC" }, { "master", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.promoted_role_only, "Limit scope of command to Master role (with -B, -M, -U). For\n" INDENT "-B and -M the previous master may remain active in the Slave role.", NULL }, { NULL } }; static GOptionEntry advanced_entries[] = { { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb, "(Advanced) Delete a resource from the CIB. Required: -t", NULL }, { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, fail_cb, "(Advanced) Tell the cluster this resource has failed", NULL }, { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, restart_cb, "(Advanced) Tell the cluster to restart this resource and\n" INDENT "anything that depends on it", NULL }, { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, wait_cb, "(Advanced) Wait until the cluster settles into a stable state", NULL }, { "force-demote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and demote a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-stop", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and stop a resource on the local node", NULL }, { "force-start", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and start a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-promote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and promote a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-check", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, validate_or_force_cb, "(Advanced) Bypass the cluster and check the state of a resource on\n" INDENT "the local node", NULL }, { NULL } }; static GOptionEntry validate_entries[] = { { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, class_cb, "The standard the resource agent confirms to (for example, ocf).\n" INDENT "Use with --agent, --provider, --option, and --validate.", "CLASS" }, { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, "The agent to use (for example, IPaddr). Use with --class,\n" INDENT "--provider, --option, and --validate.", "AGENT" }, { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb, "The vendor that supplies the resource agent (for example,\n" INDENT "heartbeat). Use with --class, --agent, --option, and --validate.", "PROVIDER" }, { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb, "Specify a device configuration parameter as NAME=VALUE (may be\n" INDENT "specified multiple times). Use with --validate and without the\n" INDENT "-r option.", "PARAM" }, { NULL } }; static GOptionEntry addl_entries[] = { { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname, "Node name", "NAME" }, { "recursive", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.recursive, "Follow colocation chains when using --set-parameter", NULL }, { "resource-type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_type, "Resource XML element (primitive, group, etc.) (with -D)", "ELEMENT" }, { "parameter-value", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_value, "Value to use with -p", "PARAM" }, { "meta", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource meta-attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource utilization attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "operation", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.operation, "Operation to clear instead of all (with -C -r)", "OPERATION" }, { "interval", 'I', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.interval_spec, "Interval of operation to clear (default 0) (with -C -r -n)", "N" }, { "set-name", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_set, "(Advanced) XML ID of attributes element to use (with -p, -d)", "ID" }, { "nvpair", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_id, "(Advanced) XML ID of nvpair element to use (with -p, -d)", "ID" }, { "timeout", 'T', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, timeout_cb, "(Advanced) Abort if command does not finish in this time (with\n" INDENT "--restart, --wait, --force-*)", "N" }, { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force, "If making CIB changes, do so regardless of quorum. See help for\n" INDENT "individual commands for additional behavior.", NULL }, { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &options.xml_file, NULL, "FILE" }, { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname, NULL, "HOST" }, { NULL } }; gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.validate_cmdline = TRUE; options.require_resource = FALSE; if (pcmk__str_eq(option_name, "--provider", pcmk__str_casei)) { if (options.v_provider) { free(options.v_provider); } options.v_provider = strdup(optarg); } else { if (options.v_agent) { free(options.v_agent); } options.v_agent = strdup(optarg); } return TRUE; } gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-m", "--meta", NULL)) { options.attr_set_type = XML_TAG_META_SETS; } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) { options.attr_set_type = XML_TAG_UTILIZATION; } return TRUE; } gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (!(pcmk_get_ra_caps(optarg) & pcmk_ra_cap_params)) { - if (BE_QUIET == FALSE) { + if (!out->is_quiet(out)) { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Standard %s does not support parameters\n", optarg); } return FALSE; } else { if (options.v_class != NULL) { free(options.v_class); } options.v_class = strdup(optarg); } options.validate_cmdline = TRUE; options.require_resource = FALSE; return TRUE; } gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) { SET_COMMAND(cmd_cleanup); } else { SET_COMMAND(cmd_refresh); } options.require_resource = FALSE; if (getenv("CIB_file") == NULL) { options.require_crmd = TRUE; } options.find_flags = pe_find_renamed|pe_find_anon; return TRUE; } gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_dataset = FALSE; SET_COMMAND(cmd_delete); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.clear_expired = TRUE; options.require_resource = FALSE; return TRUE; } static void get_agent_spec(const gchar *optarg) { options.require_cib = FALSE; options.require_dataset = FALSE; options.require_resource = FALSE; if (options.agent_spec != NULL) { free(options.agent_spec); options.agent_spec = NULL; } if (optarg != NULL) { options.agent_spec = strdup(optarg); } } gboolean list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_agents); get_agent_spec(optarg); return TRUE; } gboolean list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_providers); get_agent_spec(optarg); return TRUE; } gboolean list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_standards); options.require_cib = FALSE; options.require_dataset = FALSE; options.require_resource = FALSE; return TRUE; } gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_list_alternatives); get_agent_spec(optarg); return TRUE; } gboolean metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_metadata); get_agent_spec(optarg); return TRUE; } gboolean option_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { char *name = NULL; char *value = NULL; if (pcmk_scan_nvpair(optarg, &name, &value) != 2) { return FALSE; } if (options.validate_options == NULL) { options.validate_options = crm_str_table_new(); } g_hash_table_replace(options.validate_options, name, value); return TRUE; } gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_crmd = TRUE; SET_COMMAND(cmd_fail); return TRUE; } gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_clear); } else if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_ban); } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_move); } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) { options.find_flags = pe_find_renamed|pe_find_any; SET_COMMAND(cmd_query_xml); } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) { options.find_flags = pe_find_renamed|pe_find_any; SET_COMMAND(cmd_query_raw_xml); } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_locate); } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_colocations_deep); } else { options.find_flags = pe_find_renamed|pe_find_anon; SET_COMMAND(cmd_colocations); } return TRUE; } gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) { SET_COMMAND(cmd_get_param); } else { SET_COMMAND(cmd_get_property); } if (options.prop_name) { free(options.prop_name); } options.prop_name = strdup(optarg); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) { SET_COMMAND(cmd_cts); } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) { SET_COMMAND(cmd_list_resources); } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) { SET_COMMAND(cmd_list_instances); } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) { SET_COMMAND(cmd_list_active_ops); } else { SET_COMMAND(cmd_list_all_ops); } options.require_resource = FALSE; return TRUE; } gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) { SET_COMMAND(cmd_set_param); } else { SET_COMMAND(cmd_delete_param); } if (options.prop_name) { free(options.prop_name); } options.prop_name = strdup(optarg); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_dataset = FALSE; if (options.prop_name) { free(options.prop_name); } options.prop_name = strdup(optarg); SET_COMMAND(cmd_set_property); options.find_flags = pe_find_renamed|pe_find_any; return TRUE; } gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.timeout_ms = crm_get_msec(optarg); return TRUE; } gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_execute_agent); if (options.operation) { g_free(options.operation); } options.operation = g_strdup(option_name + 2); // skip "--" options.find_flags = pe_find_renamed|pe_find_anon; options.override_params = crm_str_table_new(); return TRUE; } gboolean restart_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_restart); options.find_flags = pe_find_renamed|pe_find_anon; return TRUE; } gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { SET_COMMAND(cmd_wait); options.require_resource = FALSE; options.require_dataset = FALSE; return TRUE; } gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.require_resource = FALSE; SET_COMMAND(cmd_why); options.find_flags = pe_find_renamed|pe_find_anon; return TRUE; } static int -ban_or_move(pe_resource_t *rsc, const char *move_lifetime, crm_exit_t *exit_code) +ban_or_move(pcmk__output_t *out, pe_resource_t *rsc, const char *move_lifetime, + crm_exit_t *exit_code) { int rc = pcmk_rc_ok; pe_node_t *current = NULL; unsigned int nactive = 0; CRM_CHECK(rsc != NULL, return EINVAL); current = pe__find_active_requires(rsc, &nactive); if (nactive == 1) { - rc = cli_resource_ban(options.rsc_id, current->details->uname, move_lifetime, NULL, + rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); } else if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { int count = 0; GListPtr iter = NULL; current = NULL; for(iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = (pe_resource_t *)iter->data; enum rsc_role_e child_role = child->fns->state(child, TRUE); if(child_role == RSC_ROLE_MASTER) { count++; current = pe__current_node(child); } } if(count == 1 && current) { - rc = cli_resource_ban(options.rsc_id, current->details->uname, move_lifetime, NULL, + rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); } else { rc = EINVAL; *exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Resource '%s' not moved: active in %d locations (promoted in %d).\n" "To prevent '%s' from running on a specific location, " "specify a node." "To prevent '%s' from being promoted at a specific " "location, specify a node and the master option.", options.rsc_id, nactive, count, options.rsc_id, options.rsc_id); } } else { rc = EINVAL; *exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Resource '%s' not moved: active in %d locations.\n" "To prevent '%s' from running on a specific location, " "specify a node.", options.rsc_id, nactive, options.rsc_id); } return rc; } static void -cleanup(pe_resource_t *rsc) +cleanup(pcmk__output_t *out, pe_resource_t *rsc) { int rc = pcmk_rc_ok; if (options.force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Erasing failures of %s (%s requested) on %s", rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes")); - rc = cli_resource_delete(controld_api, options.host_uname, rsc, options.operation, + rc = cli_resource_delete(out, controld_api, options.host_uname, rsc, options.operation, options.interval_spec, TRUE, data_set, options.force); - if ((rc == pcmk_rc_ok) && !BE_QUIET) { + if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) { // Show any reasons why resource might stay stopped - cli_resource_check(cib_conn, rsc); + cli_resource_check(out, cib_conn, rsc); } if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } static int -clear_constraints(xmlNodePtr *cib_xml_copy) +clear_constraints(pcmk__output_t *out, xmlNodePtr *cib_xml_copy) { GListPtr before = NULL; GListPtr after = NULL; GListPtr remaining = NULL; GListPtr ele = NULL; pe_node_t *dest = NULL; int rc = pcmk_rc_ok; - if (BE_QUIET == FALSE) { + if (!out->is_quiet(out)) { before = build_constraint_list(data_set->input); } if (options.clear_expired) { rc = cli_resource_clear_all_expired(data_set->input, cib_conn, options.cib_options, options.rsc_id, options.host_uname, options.promoted_role_only); } else if (options.host_uname) { dest = pe_find_node(data_set->nodes, options.host_uname); if (dest == NULL) { rc = pcmk_rc_node_unknown; - if (BE_QUIET == FALSE) { + if (!out->is_quiet(out)) { g_list_free(before); } return rc; } rc = cli_resource_clear(options.rsc_id, dest->details->uname, NULL, cib_conn, options.cib_options, TRUE, options.force); } else { rc = cli_resource_clear(options.rsc_id, NULL, data_set->nodes, cib_conn, options.cib_options, TRUE, options.force); } - if (BE_QUIET == FALSE) { + if (!out->is_quiet(out)) { rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not get modified CIB: %s\n", pcmk_strerror(rc)); g_list_free(before); return rc; } data_set->input = *cib_xml_copy; cluster_status(data_set); after = build_constraint_list(data_set->input); remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp); for (ele = remaining; ele != NULL; ele = ele->next) { - printf("Removing constraint: %s\n", (char *) ele->data); + out->info(out, "Removing constraint: %s", (char *) ele->data); } g_list_free(before); g_list_free(after); g_list_free(remaining); } return rc; } static int delete() { int rc = pcmk_rc_ok; xmlNode *msg_data = NULL; if (options.rsc_type == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "You need to specify a resource type with -t"); return rc; } msg_data = create_xml_node(NULL, options.rsc_type); crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, options.cib_options); rc = pcmk_legacy2rc(rc); free_xml(msg_data); return rc; } static int -list_agents(const char *agent_spec, crm_exit_t *exit_code) +list_agents(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code) { int rc = pcmk_rc_ok; - lrmd_list_t *list = NULL; - lrmd_list_t *iter = NULL; char *provider = strchr(agent_spec, ':'); lrmd_t *lrmd_conn = lrmd_api_new(); + lrmd_list_t *list = NULL; if (provider) { *provider++ = 0; } + rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, agent_spec, provider); if (rc > 0) { - for (iter = list; iter != NULL; iter = iter->next) { - printf("%s\n", iter->val); - } - lrmd_list_freeall(list); - rc = pcmk_rc_ok; + rc = out->message(out, "agents-list", list, agent_spec, provider); } else { - *exit_code = CRM_EX_NOSUCH; rc = pcmk_rc_error; + } + + if (rc != pcmk_rc_ok) { + *exit_code = CRM_EX_NOSUCH; if (provider == NULL) { g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "No agents found for standard '%s'", agent_spec); } else { g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "No agents found for standard '%s' and provider '%s'", agent_spec, provider); } } lrmd_api_delete(lrmd_conn); return rc; } static int -list_providers(const char *agent_spec, crm_exit_t *exit_code) +list_providers(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code) { int rc; const char *text = NULL; - lrmd_list_t *list = NULL; - lrmd_list_t *iter = NULL; lrmd_t *lrmd_conn = lrmd_api_new(); + lrmd_list_t *list = NULL; switch (options.rsc_cmd) { + case cmd_list_alternatives: + rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list); + + if (rc > 0) { + rc = out->message(out, "alternatives-list", list, agent_spec); + } else { + rc = pcmk_rc_error; + } + + text = "OCF providers"; + break; case cmd_list_standards: rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list); + + if (rc > 0) { + rc = out->message(out, "standards-list", list); + } else { + rc = pcmk_rc_error; + } + text = "standards"; break; case cmd_list_providers: - case cmd_list_alternatives: - rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, - &list); + rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list); + + if (rc > 0) { + rc = out->message(out, "providers-list", list, agent_spec); + } else { + rc = pcmk_rc_error; + } + text = "OCF providers"; break; default: *exit_code = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Bug"); lrmd_api_delete(lrmd_conn); return pcmk_rc_error; } - if (rc > 0) { - for (iter = list; iter != NULL; iter = iter->next) { - printf("%s\n", iter->val); - } - lrmd_list_freeall(list); - rc = pcmk_rc_ok; - - } else if (agent_spec != NULL) { - *exit_code = CRM_EX_NOSUCH; - rc = pcmk_rc_error; - g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, - "No %s found for %s", text, agent_spec); + if (rc != pcmk_rc_ok) { + if (agent_spec != NULL) { + *exit_code = CRM_EX_NOSUCH; + rc = pcmk_rc_error; + g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, + "No %s found for %s", text, agent_spec); - } else { - *exit_code = CRM_EX_NOSUCH; - rc = pcmk_rc_error; - g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, - "No %s found", text); + } else { + *exit_code = CRM_EX_NOSUCH; + rc = pcmk_rc_error; + g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, + "No %s found", text); + } } lrmd_api_delete(lrmd_conn); return rc; } -static int -list_raw() -{ - int rc = pcmk_rc_ok; - int found = 0; - GListPtr lpc = NULL; - - for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *rsc = (pe_resource_t *) lpc->data; - - found++; - cli_resource_print_raw(rsc); - } - - if (found == 0) { - printf("NO resources configured\n"); - rc = ENXIO; - } - - return rc; -} - -static void -list_stacks_and_constraints(pe_resource_t *rsc, bool recursive) -{ - GListPtr lpc = NULL; - xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, - data_set->input); - - unpack_constraints(cib_constraints, data_set); - - // Constraints apply to group/clone, not member/instance - rsc = uber_parent(rsc); - - for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *r = (pe_resource_t *) lpc->data; - - pe__clear_resource_flags(r, pe_rsc_allocating); - } - - cli_resource_print_colocation(rsc, TRUE, recursive, 1); - - fprintf(stdout, "* %s\n", rsc->id); - cli_resource_print_location(rsc, NULL); - - for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *r = (pe_resource_t *) lpc->data; - - pe__clear_resource_flags(r, pe_rsc_allocating); - } - - cli_resource_print_colocation(rsc, FALSE, recursive, 1); -} - static int populate_working_set(xmlNodePtr *cib_xml_copy) { int rc = pcmk_rc_ok; if (options.xml_file != NULL) { *cib_xml_copy = filename2xml(options.xml_file); } else { rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); } if(rc != pcmk_rc_ok) { return rc; } /* Populate the working set instance */ data_set = pe_new_working_set(); if (data_set == NULL) { rc = ENOMEM; return rc; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); rc = update_working_set_xml(data_set, cib_xml_copy); if (rc == pcmk_rc_ok) { cluster_status(data_set); } return rc; } static int -refresh() +refresh(pcmk__output_t *out) { int rc = pcmk_rc_ok; const char *router_node = options.host_uname; int attr_options = pcmk__node_attr_none; if (options.host_uname) { pe_node_t *node = pe_find_node(data_set->nodes, options.host_uname); if (pe__is_guest_or_remote_node(node)) { node = pe__current_node(node->details->remote_rsc); if (node == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "No cluster connection to Pacemaker Remote node %s detected", options.host_uname); return rc; } router_node = node->details->uname; attr_options |= pcmk__node_attr_remote; } } if (controld_api == NULL) { - printf("Dry run: skipping clean-up of %s due to CIB_file\n", - options.host_uname? options.host_uname : "all nodes"); + out->info(out, "Dry run: skipping clean-up of %s due to CIB_file", + options.host_uname? options.host_uname : "all nodes"); rc = pcmk_rc_ok; return rc; } crm_debug("Re-checking the state of all resources on %s", options.host_uname?options.host_uname:"all nodes"); rc = pcmk__node_attr_request_clear(NULL, options.host_uname, NULL, NULL, NULL, NULL, attr_options); if (pcmk_controld_api_reprobe(controld_api, options.host_uname, router_node) == pcmk_rc_ok) { start_mainloop(controld_api); } return rc; } static void -refresh_resource(pe_resource_t *rsc) +refresh_resource(pcmk__output_t *out, pe_resource_t *rsc) { int rc = pcmk_rc_ok; if (options.force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Re-checking the state of %s (%s requested) on %s", rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes")); - rc = cli_resource_delete(controld_api, options.host_uname, rsc, NULL, 0, FALSE, - data_set, options.force); + rc = cli_resource_delete(out, controld_api, options.host_uname, rsc, NULL, + 0, FALSE, data_set, options.force); - if ((rc == pcmk_rc_ok) && !BE_QUIET) { + if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) { // Show any reasons why resource might stay stopped - cli_resource_check(cib_conn, rsc); + cli_resource_check(out, cib_conn, rsc); } if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } static int set_property() { int rc = pcmk_rc_ok; xmlNode *msg_data = NULL; if (pcmk__str_empty(options.rsc_type)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Must specify -t with resource type"); rc = ENXIO; return rc; } else if (pcmk__str_empty(options.prop_value)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Must supply -v with new value"); rc = EINVAL; return rc; } CRM_LOG_ASSERT(options.prop_name != NULL); msg_data = create_xml_node(NULL, options.rsc_type); crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id); crm_xml_add(msg_data, options.prop_name, options.prop_value); rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data, options.cib_options); rc = pcmk_legacy2rc(rc); free_xml(msg_data); return rc; } static int -show_metadata(const char *agent_spec, crm_exit_t *exit_code) +show_metadata(pcmk__output_t *out, const char *agent_spec, crm_exit_t *exit_code) { int rc = pcmk_rc_ok; char *standard = NULL; char *provider = NULL; char *type = NULL; char *metadata = NULL; lrmd_t *lrmd_conn = lrmd_api_new(); rc = crm_parse_agent_spec(agent_spec, &standard, &provider, &type); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard, provider, type, &metadata, 0); rc = pcmk_legacy2rc(rc); if (metadata) { - printf("%s\n", metadata); + out->output_xml(out, "metadata", metadata); } else { *exit_code = crm_errno2exit(rc); g_set_error(&error, PCMK__EXITC_ERROR, *exit_code, "Metadata query for %s failed: %s", agent_spec, pcmk_rc_str(rc)); } } else { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "'%s' is not a valid agent specification", agent_spec); } lrmd_api_delete(lrmd_conn); return rc; } static void validate_cmdline(crm_exit_t *exit_code) { // -r cannot be used with any of --class, --agent, or --provider if (options.rsc_id != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--resource cannot be used with --class, --agent, and --provider"); // If --class, --agent, or --provider are given, --validate must also be given. } else if (options.rsc_cmd != cmd_execute_agent) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--class, --agent, and --provider require --validate"); // Not all of --class, --agent, and --provider need to be given. Not all // classes support the concept of a provider. Check that what we were given // is valid. } else if (pcmk__str_eq(options.v_class, "stonith", pcmk__str_none)) { if (options.v_provider != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "stonith does not support providers"); } else if (stonith_agent_exists(options.v_agent, 0) == FALSE) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "%s is not a known stonith agent", options.v_agent ? options.v_agent : ""); } } else if (resources_agent_exists(options.v_class, options.v_provider, options.v_agent) == FALSE) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "%s:%s:%s is not a known resource", options.v_class ? options.v_class : "", options.v_provider ? options.v_provider : "", options.v_agent ? options.v_agent : ""); } if (error == NULL) { if (options.validate_options == NULL) { options.validate_options = crm_str_table_new(); } - *exit_code = cli_resource_execute_from_params("test", options.v_class, options.v_provider, options.v_agent, + *exit_code = cli_resource_execute_from_params(out, "test", options.v_class, options.v_provider, options.v_agent, "validate-all", options.validate_options, options.override_params, options.timeout_ms, options.resource_verbose, options.force); } } static GOptionContext * -build_arg_context(pcmk__common_args_t *args) { +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &(args->quiet), "Be less descriptive in output.", NULL }, { "resource", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_id, "Resource ID", "ID" }, { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder, NULL, NULL }, { NULL } }; const char *description = "Examples:\n\n" "List the available OCF agents:\n\n" "\t# crm_resource --list-agents ocf\n\n" "List the available OCF agents from the linux-ha project:\n\n" "\t# crm_resource --list-agents ocf:heartbeat\n\n" "Move 'myResource' to a specific node:\n\n" "\t# crm_resource --resource myResource --move --node altNode\n\n" "Allow (but not force) 'myResource' to move back to its original " "location:\n\n" "\t# crm_resource --resource myResource --clear\n\n" "Stop 'myResource' (and anything that depends on it):\n\n" "\t# crm_resource --resource myResource --set-parameter target-role " "--meta --parameter-value Stopped\n\n" "Tell the cluster not to manage 'myResource' (the cluster will not " "attempt to start or stop the\n" "resource under any circumstances; useful when performing maintenance " "tasks on a resource):\n\n" "\t# crm_resource --resource myResource --set-parameter is-managed " "--meta --parameter-value false\n\n" "Erase the operation history of 'myResource' on 'aNode' (the cluster " "will 'forget' the existing\n" "resource state, including any errors, and attempt to recover the" "resource; useful when a resource\n" "had failed permanently and has been repaired by an administrator):\n\n" "\t# crm_resource --resource myResource --cleanup --node aNode\n\n"; - context = pcmk__build_arg_context(args, NULL, NULL, NULL); + context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); g_option_context_set_description(context, description); /* Add the -Q option, which cannot be part of the globally supported options * because some tools use that flag for something else. */ pcmk__add_main_args(context, extra_prog_entries); 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, "locations", "Locations:", "Show location help", location_entries); pcmk__add_arg_group(context, "validate", "Validate:", "Show validate help", validate_entries); pcmk__add_arg_group(context, "advanced", "Advanced:", "Show advanced option help", advanced_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { xmlNode *cib_xml_copy = NULL; pe_resource_t *rsc = NULL; int rc = pcmk_rc_ok; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); GOptionContext *context = NULL; + GOptionGroup *output_group = NULL; gchar **processed_args = NULL; - context = build_arg_context(args); + context = build_arg_context(args, &output_group); + pcmk__register_formats(output_group, formats); crm_log_cli_init("crm_resource"); processed_args = pcmk__cmdline_preproc(argv, "GINSTdginpstuv"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + fprintf(stderr, "Error creating output format %s: %s\n", + args->output_ty, pcmk_rc_str(rc)); + exit_code = CRM_EX_ERROR; + goto done; + } + options.resource_verbose = args->verbosity; - BE_QUIET = args->quiet; + out->quiet = args->quiet; crm_log_args(argc, argv); if (options.host_uname) { crm_trace("Option host => %s", options.host_uname); } // If the user didn't explicitly specify a command, list resources if (options.rsc_cmd == cmd_none) { options.rsc_cmd = cmd_list_resources; options.require_resource = FALSE; } // --expired without --clear/-U doesn't make sense if (options.clear_expired && (options.rsc_cmd != cmd_clear)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "--expired requires --clear or -U"); goto done; } if ((options.remainder != NULL) && (options.override_params != NULL)) { // Commands that use positional arguments will create override_params for (gchar **s = options.remainder; *s; s++) { char *name = calloc(1, strlen(*s)); char *value = calloc(1, strlen(*s)); int rc = sscanf(*s, "%[^=]=%s", name, value); if (rc == 2) { g_hash_table_replace(options.override_params, name, value); } else { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Error parsing '%s' as a name=value pair", argv[optind]); free(value); free(name); goto done; } } } else if (options.remainder != NULL) { gchar **strv = NULL; gchar *msg = NULL; int i = 1; int len = 0; for (gchar **s = options.remainder; *s; s++) { len++; } CRM_ASSERT(len > 0); strv = calloc(len, sizeof(char *)); strv[0] = strdup("non-option ARGV-elements:"); for (gchar **s = options.remainder; *s; s++) { strv[i] = crm_strdup_printf("[%d of %d] %s\n", i, len, *s); i++; } msg = g_strjoinv("", strv); g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "%s", msg); for(i = 0; i < len; i++) { free(strv[i]); } g_free(msg); g_free(strv); goto done; } + if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) { + /* Kind of a hack to display XML lists using a real tag instead of . This just + * saves from having to write custom messages to build the lists around all these things + */ + if (options.rsc_cmd == cmd_list_resources || options.rsc_cmd == cmd_query_xml || + options.rsc_cmd == cmd_query_raw_xml || options.rsc_cmd == cmd_list_active_ops || + options.rsc_cmd == cmd_list_all_ops) { + pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname()); + } else { + pcmk__force_args(context, &error, "%s --xml-substitute", g_get_prgname()); + } + } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) { + if (options.rsc_cmd == cmd_colocations || options.rsc_cmd == cmd_colocations_deep || + options.rsc_cmd == cmd_list_resources) { + pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname()); + } + } + + pe__register_messages(out); + crm_resource_register_messages(out); + lrmd__register_messages(out); + pcmk__register_lib_messages(out); + if (args->version) { - /* FIXME: When crm_resource is converted to use formatted output, this can go. */ - pcmk__cli_help('v', CRM_EX_USAGE); + out->version(out, false); + goto done; } if (optind > argc) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Invalid option(s) supplied, use --help for valid usage"); exit_code = CRM_EX_USAGE; goto done; } // Sanity check validating from command line parameters. If everything checks out, // go ahead and run the validation. This way we don't need a CIB connection. if (options.validate_cmdline) { validate_cmdline(&exit_code); goto done; } else if (options.validate_options != NULL) { // @COMPAT @TODO error out here when we can break backward compatibility g_hash_table_destroy(options.validate_options); options.validate_options = NULL; } if (error != NULL) { exit_code = CRM_EX_USAGE; goto done; } if (options.force) { crm_debug("Forcing..."); cib__set_call_options(options.cib_options, crm_system_name, cib_quorum_override); } if (options.require_resource && !options.rsc_id) { rc = ENXIO; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Must supply a resource id with -r"); goto done; } if (options.find_flags && options.rsc_id) { options.require_dataset = TRUE; } // Establish a connection to the CIB if needed if (options.require_cib) { cib_conn = cib_new(); if ((cib_conn == NULL) || (cib_conn->cmds == NULL)) { rc = pcmk_rc_error; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_DISCONNECT, "Could not create CIB connection"); goto done; } rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); goto done; } } /* Populate working set from XML file if specified or CIB query otherwise */ if (options.require_dataset) { rc = populate_working_set(&cib_xml_copy); if (rc != pcmk_rc_ok) { goto done; } } // If command requires that resource exist if specified, find it if (options.find_flags && options.rsc_id) { rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id, options.find_flags); if (rsc == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, "Resource '%s' not found", options.rsc_id); goto done; } } // Establish a connection to the controller if needed if (options.require_crmd) { rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (rc != pcmk_rc_ok) { - CMD_ERR("Error connecting to the controller: %s", pcmk_rc_str(rc)); + g_set_error(&error, PCMK__RC_ERROR, rc, + "Error connecting to the controller: %s", pcmk_rc_str(rc)); goto done; } pcmk_register_ipc_callback(controld_api, controller_event_callback, NULL); rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Error connecting to the controller: %s", pcmk_rc_str(rc)); goto done; } } switch (options.rsc_cmd) { - case cmd_list_resources: - rc = pcmk_rc_ok; - cli_resource_print_list(data_set, FALSE); + case cmd_list_resources: { + GListPtr all = NULL; + all = g_list_prepend(all, strdup("*")); + rc = out->message(out, "resource-list", data_set, + pe_print_rsconly | pe_print_pending, + FALSE, TRUE, FALSE, TRUE, all, all, FALSE); + g_list_free_full(all, free); + + if (rc == pcmk_rc_no_output) { + rc = ENXIO; + } break; + } case cmd_list_instances: - rc = list_raw(); + rc = out->message(out, "resource-names-list", data_set->resources); + + if (rc != pcmk_rc_ok) { + rc = ENXIO; + } + break; case cmd_list_standards: case cmd_list_providers: case cmd_list_alternatives: - rc = list_providers(options.agent_spec, &exit_code); + rc = list_providers(out, options.agent_spec, &exit_code); break; case cmd_list_agents: - rc = list_agents(options.agent_spec, &exit_code); + rc = list_agents(out, options.agent_spec, &exit_code); break; case cmd_metadata: - rc = show_metadata(options.agent_spec, &exit_code); + rc = show_metadata(out, options.agent_spec, &exit_code); break; case cmd_restart: /* We don't pass data_set because rsc needs to stay valid for the * entire lifetime of cli_resource_restart(), but it will reset and * update the working set multiple times, so it needs to use its own * copy. */ - rc = cli_resource_restart(rsc, options.host_uname, + rc = cli_resource_restart(out, rsc, options.host_uname, options.move_lifetime, options.timeout_ms, cib_conn, options.cib_options, options.promoted_role_only, options.force); break; case cmd_wait: - rc = wait_till_stable(options.timeout_ms, cib_conn); + rc = wait_till_stable(out, options.timeout_ms, cib_conn); break; case cmd_execute_agent: - exit_code = cli_resource_execute(rsc, options.rsc_id, + exit_code = cli_resource_execute(out, rsc, options.rsc_id, options.operation, options.override_params, options.timeout_ms, cib_conn, data_set, options.resource_verbose, options.force); break; case cmd_colocations: - list_stacks_and_constraints(rsc, false); + rc = out->message(out, "stacks-constraints", rsc, data_set, false); break; case cmd_colocations_deep: - list_stacks_and_constraints(rsc, true); + rc = out->message(out, "stacks-constraints", rsc, data_set, true); break; case cmd_cts: rc = pcmk_rc_ok; + for (GList *lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { rsc = (pe_resource_t *) lpc->data; - cli_resource_print_cts(rsc); + cli_resource_print_cts(out, rsc); } - cli_resource_print_cts_constraints(data_set); + + cli_resource_print_cts_constraints(out, data_set); break; case cmd_fail: - rc = cli_resource_fail(controld_api, options.host_uname, + rc = cli_resource_fail(out, controld_api, options.host_uname, options.rsc_id, data_set); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } break; case cmd_list_active_ops: - rc = cli_resource_print_operations(options.rsc_id, + rc = cli_resource_print_operations(out, options.rsc_id, options.host_uname, TRUE, data_set); break; case cmd_list_all_ops: - rc = cli_resource_print_operations(options.rsc_id, + rc = cli_resource_print_operations(out, options.rsc_id, options.host_uname, FALSE, data_set); break; - case cmd_locate: - cli_resource_search(rsc, options.rsc_id, data_set); - rc = pcmk_rc_ok; + case cmd_locate: { + GListPtr resources = cli_resource_search(out, rsc, options.rsc_id, data_set); + rc = out->message(out, "resource-search", resources, rsc, options.rsc_id); break; + } case cmd_query_xml: - rc = cli_resource_print(rsc, data_set, TRUE); + rc = cli_resource_print(out, rsc, data_set, TRUE); break; case cmd_query_raw_xml: - rc = cli_resource_print(rsc, data_set, FALSE); + rc = cli_resource_print(out, rsc, data_set, FALSE); break; case cmd_why: { pe_node_t *dest = NULL; if (options.host_uname) { dest = pe_find_node(data_set->nodes, options.host_uname); if (dest == NULL) { rc = pcmk_rc_node_unknown; goto done; } } - cli_resource_why(cib_conn, data_set->resources, rsc, dest); + out->message(out, "resource-why", cib_conn, data_set->resources, rsc, dest); rc = pcmk_rc_ok; } break; case cmd_clear: - rc = clear_constraints(&cib_xml_copy); + rc = clear_constraints(out, &cib_xml_copy); break; case cmd_move: if (options.host_uname == NULL) { - rc = ban_or_move(rsc, options.move_lifetime, &exit_code); + rc = ban_or_move(out, rsc, options.move_lifetime, &exit_code); } else { - rc = cli_resource_move(rsc, options.rsc_id, options.host_uname, + rc = cli_resource_move(out, rsc, options.rsc_id, options.host_uname, options.move_lifetime, cib_conn, options.cib_options, data_set, options.promoted_role_only, options.force); } break; case cmd_ban: if (options.host_uname == NULL) { - rc = ban_or_move(rsc, options.move_lifetime, &exit_code); + rc = ban_or_move(out, rsc, options.move_lifetime, &exit_code); } else { pe_node_t *dest = pe_find_node(data_set->nodes, options.host_uname); if (dest == NULL) { rc = pcmk_rc_node_unknown; goto done; } - rc = cli_resource_ban(options.rsc_id, dest->details->uname, + rc = cli_resource_ban(out, options.rsc_id, dest->details->uname, options.move_lifetime, NULL, cib_conn, options.cib_options, options.promoted_role_only); } break; case cmd_get_property: - rc = cli_resource_print_property(rsc, options.prop_name, data_set); + rc = out->message(out, "property", rsc, options.prop_name); + if (rc == pcmk_rc_no_output) { + rc = ENXIO; + } + break; case cmd_set_property: rc = set_property(); break; - case cmd_get_param: - rc = cli_resource_print_attribute(rsc, options.prop_name, - options.attr_set_type, data_set); + case cmd_get_param: { + unsigned int count = 0; + GHashTable *params = NULL; + pe_node_t *current = pe__find_active_on(rsc, &count, NULL); + + if (count > 1) { + out->err(out, "%s is active on more than one node," + " returning the default value for %s", rsc->id, crm_str(options.prop_name)); + current = NULL; + } + + params = crm_str_table_new(); + + if (pcmk__str_eq(options.attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) { + get_rsc_attributes(params, rsc, current, data_set); + + } else if (pcmk__str_eq(options.attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { + /* No need to redirect to the parent */ + get_meta_attributes(params, rsc, current, data_set); + + } else { + pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params, + NULL, FALSE, data_set); + } + + crm_debug("Looking up %s in %s", options.prop_name, rsc->id); + rc = out->message(out, "attribute", rsc, options.prop_name, params); + g_hash_table_destroy(params); break; + } case cmd_set_param: if (pcmk__str_empty(options.prop_value)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "You need to supply a value with the -v option"); rc = EINVAL; goto done; } /* coverity[var_deref_model] False positive */ - rc = cli_resource_update_attribute(rsc, options.rsc_id, + rc = cli_resource_update_attribute(out, rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, options.prop_value, options.recursive, cib_conn, options.cib_options, data_set, options.force); break; case cmd_delete_param: /* coverity[var_deref_model] False positive */ - rc = cli_resource_delete_attribute(rsc, options.rsc_id, + rc = cli_resource_delete_attribute(out, rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, cib_conn, options.cib_options, data_set, options.force); break; case cmd_cleanup: if (rsc == NULL) { - rc = cli_cleanup_all(controld_api, options.host_uname, + rc = cli_cleanup_all(out, controld_api, options.host_uname, options.operation, options.interval_spec, data_set); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } else { - cleanup(rsc); + cleanup(out, rsc); } break; case cmd_refresh: if (rsc == NULL) { - rc = refresh(); + rc = refresh(out); } else { - refresh_resource(rsc); + refresh_resource(out, rsc); } break; case cmd_delete: rc = delete(); break; default: g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_SOFTWARE, "Unimplemented command: %d", (int) options.rsc_cmd); break; } done: if (rc != pcmk_rc_ok) { if (rc == pcmk_rc_no_quorum) { g_prefix_error(&error, "To ignore quorum, use the force option.\n"); } if (error != NULL) { char *msg = crm_strdup_printf("%s\nError performing operation: %s", error->message, pcmk_rc_str(rc)); g_clear_error(&error); g_set_error(&error, PCMK__RC_ERROR, rc, "%s", msg); free(msg); } else { g_set_error(&error, PCMK__RC_ERROR, rc, "Error performing operation: %s", pcmk_rc_str(rc)); } if (exit_code == CRM_EX_OK) { exit_code = pcmk_rc2exitc(rc); } } g_free(options.host_uname); g_free(options.interval_spec); g_free(options.move_lifetime); g_free(options.operation); g_free(options.prop_id); free(options.prop_name); g_free(options.prop_set); g_free(options.prop_value); g_free(options.rsc_id); g_free(options.rsc_type); free(options.agent_spec); free(options.v_agent); free(options.v_class); free(options.v_provider); g_free(options.xml_file); g_strfreev(options.remainder); if (options.override_params != NULL) { g_hash_table_destroy(options.override_params); } /* options.validate_options does not need to be destroyed here. See the * comments in cli_resource_execute_from_params. */ g_strfreev(processed_args); g_option_context_free(context); return bye(exit_code); } diff --git a/tools/crm_resource.h b/tools/crm_resource.h index e979ef83a4..5bfadb7ce4 100644 --- a/tools/crm_resource.h +++ b/tools/crm_resource.h @@ -1,99 +1,114 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include +#include #include #include #include #include #include #include -extern bool BE_QUIET; +enum resource_check_flags { + rsc_remain_stopped = (1 << 0), + rsc_unpromotable = (1 << 1), + rsc_unmanaged = (1 << 2) +}; + +typedef struct resource_checks_s { + pe_resource_t *rsc; + unsigned int flags; + const char *lock_node; +} resource_checks_t; + +resource_checks_t *cli_check_resource(pe_resource_t *rsc, char *role_s, char *managed); /* ban */ -int cli_resource_prefer(const char *rsc_id, const char *host, const char *move_lifetime, - cib_t * cib_conn, int cib_options, gboolean promoted_role_only); -int cli_resource_ban(const char *rsc_id, const char *host, const char *move_lifetime, - GListPtr allnodes, cib_t * cib_conn, int cib_options, - gboolean promoted_role_only); +int cli_resource_prefer(pcmk__output_t *out, const char *rsc_id, const char *host, + const char *move_lifetime, cib_t * cib_conn, int cib_options, + gboolean promoted_role_only); +int cli_resource_ban(pcmk__output_t *out, const char *rsc_id, const char *host, + const char *move_lifetime, GListPtr allnodes, cib_t * cib_conn, + int cib_options, gboolean promoted_role_only); int cli_resource_clear(const char *rsc_id, const char *host, GListPtr allnodes, cib_t * cib_conn, int cib_options, bool clear_ban_constraints, gboolean force); int cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, int cib_options, const char *rsc, const char *node, gboolean promoted_role_only); /* print */ -void cli_resource_print_cts(pe_resource_t * rsc); -void cli_resource_print_raw(pe_resource_t * rsc); -void cli_resource_print_cts_constraints(pe_working_set_t * data_set); -void cli_resource_print_location(pe_resource_t * rsc, const char *prefix); -void cli_resource_print_colocation(pe_resource_t * rsc, bool dependents, bool recursive, int offset); +void cli_resource_print_cts(pcmk__output_t *out, pe_resource_t * rsc); +void cli_resource_print_raw(pcmk__output_t *out, pe_resource_t * rsc); +void cli_resource_print_cts_constraints(pcmk__output_t *out, pe_working_set_t * data_set); -int cli_resource_print(pe_resource_t *rsc, pe_working_set_t *data_set, +int cli_resource_print(pcmk__output_t *out, pe_resource_t *rsc, pe_working_set_t *data_set, bool expanded); -int cli_resource_print_list(pe_working_set_t * data_set, bool raw); -int cli_resource_print_attribute(pe_resource_t *rsc, const char *attr, const char *attr_set_type, - pe_working_set_t *data_set); -int cli_resource_print_property(pe_resource_t *rsc, const char *attr, - pe_working_set_t *data_set); -int cli_resource_print_operations(const char *rsc_id, const char *host_uname, bool active, pe_working_set_t * data_set); +int cli_resource_print_operations(pcmk__output_t *out, const char *rsc_id, + const char *host_uname, bool active, + pe_working_set_t * data_set); /* runtime */ -void cli_resource_check(cib_t * cib, pe_resource_t *rsc); -int cli_resource_fail(pcmk_ipc_api_t *controld_api, +int cli_resource_check(pcmk__output_t *out, cib_t * cib, pe_resource_t *rsc); +int cli_resource_fail(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, const char *host_uname, const char *rsc_id, pe_working_set_t *data_set); -int cli_resource_search(pe_resource_t *rsc, const char *requested_name, - pe_working_set_t *data_set); -int cli_resource_delete(pcmk_ipc_api_t *controld_api, +GListPtr cli_resource_search(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, pe_working_set_t *data_set); +int cli_resource_delete(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, const char *host_uname, pe_resource_t *rsc, const char *operation, const char *interval_spec, bool just_failures, pe_working_set_t *data_set, gboolean force); -int cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name, - const char *operation, const char *interval_spec, - pe_working_set_t *data_set); -int cli_resource_restart(pe_resource_t *rsc, const char *host, const char *move_lifetime, - int timeout_ms, cib_t *cib, int cib_options, - gboolean promoted_role_only, gboolean force); -int cli_resource_move(pe_resource_t *rsc, const char *rsc_id, - const char *host_name, const char *move_lifetime, - cib_t *cib, int cib_options, pe_working_set_t *data_set, +int cli_cleanup_all(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, + const char *node_name, const char *operation, + const char *interval_spec, pe_working_set_t *data_set); +int cli_resource_restart(pcmk__output_t *out, pe_resource_t *rsc, const char *host, + const char *move_lifetime, int timeout_ms, cib_t *cib, + int cib_options, gboolean promoted_role_only, gboolean force); +int cli_resource_move(pcmk__output_t *out, pe_resource_t *rsc, const char *rsc_id, + const char *host_name, const char *move_lifetime, cib_t *cib, + int cib_options, pe_working_set_t *data_set, gboolean promoted_role_only, gboolean force); -crm_exit_t cli_resource_execute_from_params(const char *rsc_name, const char *rsc_class, - const char *rsc_prov, const char *rsc_type, - const char *rsc_action, GHashTable *params, - GHashTable *override_hash, int timeout_ms, - int resource_verbose, gboolean force); -crm_exit_t cli_resource_execute(pe_resource_t *rsc, const char *requested_name, - const char *rsc_action, GHashTable *override_hash, - int timeout_ms, cib_t *cib, pe_working_set_t *data_set, - int resource_verbose, gboolean force); +crm_exit_t cli_resource_execute_from_params(pcmk__output_t *out, const char *rsc_name, + const char *rsc_class, const char *rsc_prov, + const char *rsc_type, const char *rsc_action, + GHashTable *params, GHashTable *override_hash, + int timeout_ms, int resource_verbose, + gboolean force); +crm_exit_t cli_resource_execute(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, const char *rsc_action, + GHashTable *override_hash, int timeout_ms, cib_t *cib, + pe_working_set_t *data_set, int resource_verbose, + gboolean force); -int cli_resource_update_attribute(pe_resource_t *rsc, const char *requested_name, - const char *attr_set, const char *attr_set_type, - const char *attr_id, const char *attr_name, - const char *attr_value, gboolean recursive, cib_t *cib, - int cib_options, pe_working_set_t *data_set, gboolean force); -int cli_resource_delete_attribute(pe_resource_t *rsc, const char *requested_name, - const char *attr_set, const char *attr_set_type, - const char *attr_id, const char *attr_name, cib_t *cib, - int cib_options, pe_working_set_t *data_set, gboolean force); +int cli_resource_update_attribute(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, const char *attr_set, + const char *attr_set_type, const char *attr_id, + const char *attr_name, const char *attr_value, + gboolean recursive, cib_t *cib, int cib_options, + pe_working_set_t *data_set, gboolean force); +int cli_resource_delete_attribute(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, const char *attr_set, + const char *attr_set_type, const char *attr_id, + const char *attr_name, cib_t *cib, int cib_options, + pe_working_set_t *data_set, gboolean force); int update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml); -int wait_till_stable(int timeout_ms, cib_t * cib); -void cli_resource_why(cib_t *cib_conn, GListPtr resources, pe_resource_t *rsc, - pe_node_t *node); +int wait_till_stable(pcmk__output_t *out, int timeout_ms, cib_t * cib); + +bool resource_is_running_on(pe_resource_t *rsc, const char *host); + +void crm_resource_register_messages(pcmk__output_t *out); diff --git a/tools/crm_resource_ban.c b/tools/crm_resource_ban.c index e055797460..4e3ab8bdba 100644 --- a/tools/crm_resource_ban.c +++ b/tools/crm_resource_ban.c @@ -1,461 +1,462 @@ /* * Copyright 2004-2020 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 #define XPATH_MAX 1024 static char * -parse_cli_lifetime(const char *move_lifetime) +parse_cli_lifetime(pcmk__output_t *out, const char *move_lifetime) { char *later_s = NULL; crm_time_t *now = NULL; crm_time_t *later = NULL; crm_time_t *duration = NULL; if (move_lifetime == NULL) { return NULL; } duration = crm_time_parse_duration(move_lifetime); if (duration == NULL) { - CMD_ERR("Invalid duration specified: %s", move_lifetime); - CMD_ERR("Please refer to" - " https://en.wikipedia.org/wiki/ISO_8601#Durations" - " for examples of valid durations"); + out->err(out, "Invalid duration specified: %s\n" + "Please refer to https://en.wikipedia.org/wiki/ISO_8601#Durations " + "for examples of valid durations", move_lifetime); return NULL; } now = crm_time_new(NULL); later = crm_time_add(now, duration); if (later == NULL) { - CMD_ERR("Unable to add %s to current time", move_lifetime); - CMD_ERR("Please report to " PACKAGE_BUGREPORT " as possible bug"); + out->err(out, "Unable to add %s to current time\n" + "Please report to " PACKAGE_BUGREPORT " as possible bug", + move_lifetime); crm_time_free(now); crm_time_free(duration); return NULL; } crm_time_log(LOG_INFO, "now ", now, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_INFO, "later ", later, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_INFO, "duration", duration, crm_time_log_date | crm_time_log_timeofday); later_s = crm_time_as_string(later, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); - printf("Migration will take effect until: %s\n", later_s); + out->info(out, "Migration will take effect until: %s", later_s); crm_time_free(duration); crm_time_free(later); crm_time_free(now); return later_s; } // \return Standard Pacemaker return code int -cli_resource_ban(const char *rsc_id, const char *host, const char *move_lifetime, - GListPtr allnodes, cib_t * cib_conn, int cib_options, - gboolean promoted_role_only) +cli_resource_ban(pcmk__output_t *out, const char *rsc_id, const char *host, + const char *move_lifetime, GListPtr allnodes, cib_t * cib_conn, + int cib_options, gboolean promoted_role_only) { char *later_s = NULL; int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; if(host == NULL) { GListPtr n = allnodes; for(; n && rc == pcmk_rc_ok; n = n->next) { pe_node_t *target = n->data; - rc = cli_resource_ban(rsc_id, target->details->uname, move_lifetime, + rc = cli_resource_ban(out, rsc_id, target->details->uname, move_lifetime, NULL, cib_conn, cib_options, promoted_role_only); } return rc; } - later_s = parse_cli_lifetime(move_lifetime); + later_s = parse_cli_lifetime(out, move_lifetime); if(move_lifetime && later_s == NULL) { return EINVAL; } fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); - if (BE_QUIET == FALSE) { - CMD_ERR("WARNING: Creating rsc_location constraint '%s'" - " with a score of -INFINITY for resource %s" - " on %s.", ID(location), rsc_id, host); - CMD_ERR("\tThis will prevent %s from %s on %s until the constraint " - "is removed using the clear option or by editing the CIB " - "with an appropriate tool", - rsc_id, (promoted_role_only? "being promoted" : "running"), host); - CMD_ERR("\tThis will be the case even if %s is" - " the last node in the cluster", host); + if (!out->is_quiet(out)) { + out->info(out, "WARNING: Creating rsc_location constraint '%s' with a " + "score of -INFINITY for resource %s on %s.\n\tThis will " + "prevent %s from %s on %s until the constraint is removed " + "using the clear option or by editing the CIB with an " + "appropriate tool\n\tThis will be the case even if %s " + "is the last node in the cluster", + ID(location), rsc_id, host, rsc_id, + (promoted_role_only? "being promoted" : "running"), + host, host); } crm_xml_add(location, XML_LOC_ATTR_SOURCE, rsc_id); if(promoted_role_only) { crm_xml_add(location, XML_RULE_ATTR_ROLE, RSC_ROLE_MASTER_S); } else { crm_xml_add(location, XML_RULE_ATTR_ROLE, RSC_ROLE_STARTED_S); } if (later_s == NULL) { /* Short form */ crm_xml_add(location, XML_CIB_TAG_NODE, host); crm_xml_add(location, XML_RULE_ATTR_SCORE, CRM_MINUS_INFINITY_S); } else { xmlNode *rule = create_xml_node(location, XML_TAG_RULE); xmlNode *expr = create_xml_node(rule, XML_TAG_EXPRESSION); crm_xml_set_id(rule, "cli-ban-%s-on-%s-rule", rsc_id, host); crm_xml_add(rule, XML_RULE_ATTR_SCORE, CRM_MINUS_INFINITY_S); crm_xml_add(rule, XML_RULE_ATTR_BOOLEAN_OP, "and"); crm_xml_set_id(expr, "cli-ban-%s-on-%s-expr", rsc_id, host); crm_xml_add(expr, XML_EXPR_ATTR_ATTRIBUTE, CRM_ATTR_UNAME); crm_xml_add(expr, XML_EXPR_ATTR_OPERATION, "eq"); crm_xml_add(expr, XML_EXPR_ATTR_VALUE, host); crm_xml_add(expr, XML_EXPR_ATTR_TYPE, "string"); expr = create_xml_node(rule, "date_expression"); crm_xml_set_id(expr, "cli-ban-%s-on-%s-lifetime", rsc_id, host); crm_xml_add(expr, "operation", "lt"); crm_xml_add(expr, "end", later_s); } crm_log_xml_notice(fragment, "Modify"); rc = cib_conn->cmds->update(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); rc = pcmk_legacy2rc(rc); free_xml(fragment); free(later_s); return rc; } // \return Standard Pacemaker return code int -cli_resource_prefer(const char *rsc_id, const char *host, const char *move_lifetime, - cib_t * cib_conn, int cib_options, gboolean promoted_role_only) +cli_resource_prefer(pcmk__output_t *out,const char *rsc_id, const char *host, + const char *move_lifetime, cib_t * cib_conn, int cib_options, + gboolean promoted_role_only) { - char *later_s = parse_cli_lifetime(move_lifetime); + char *later_s = parse_cli_lifetime(out, move_lifetime); int rc = pcmk_rc_ok; xmlNode *location = NULL; xmlNode *fragment = NULL; if(move_lifetime && later_s == NULL) { return EINVAL; } if(cib_conn == NULL) { free(later_s); return ENOTCONN; } fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-prefer-%s", rsc_id); crm_xml_add(location, XML_LOC_ATTR_SOURCE, rsc_id); if(promoted_role_only) { crm_xml_add(location, XML_RULE_ATTR_ROLE, RSC_ROLE_MASTER_S); } else { crm_xml_add(location, XML_RULE_ATTR_ROLE, RSC_ROLE_STARTED_S); } if (later_s == NULL) { /* Short form */ crm_xml_add(location, XML_CIB_TAG_NODE, host); crm_xml_add(location, XML_RULE_ATTR_SCORE, CRM_INFINITY_S); } else { xmlNode *rule = create_xml_node(location, XML_TAG_RULE); xmlNode *expr = create_xml_node(rule, XML_TAG_EXPRESSION); crm_xml_set_id(rule, "cli-prefer-rule-%s", rsc_id); crm_xml_add(rule, XML_RULE_ATTR_SCORE, CRM_INFINITY_S); crm_xml_add(rule, XML_RULE_ATTR_BOOLEAN_OP, "and"); crm_xml_set_id(expr, "cli-prefer-expr-%s", rsc_id); crm_xml_add(expr, XML_EXPR_ATTR_ATTRIBUTE, CRM_ATTR_UNAME); crm_xml_add(expr, XML_EXPR_ATTR_OPERATION, "eq"); crm_xml_add(expr, XML_EXPR_ATTR_VALUE, host); crm_xml_add(expr, XML_EXPR_ATTR_TYPE, "string"); expr = create_xml_node(rule, "date_expression"); crm_xml_set_id(expr, "cli-prefer-lifetime-end-%s", rsc_id); crm_xml_add(expr, "operation", "lt"); crm_xml_add(expr, "end", later_s); } crm_log_xml_info(fragment, "Modify"); rc = cib_conn->cmds->update(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); rc = pcmk_legacy2rc(rc); free_xml(fragment); free(later_s); return rc; } /* Nodes can be specified two different ways in the CIB, so we have two different * functions to try clearing out any constraints on them: * * (1) The node could be given by attribute=/value= in an expression XML node. * That's what resource_clear_node_in_expr handles. That XML looks like this: * * * * * * * * * (2) The mode could be given by node= in an rsc_location XML node. That's * what resource_clear_node_in_location handles. That XML looks like this: * * * * \return Standard Pacemaker return code */ static int resource_clear_node_in_expr(const char *rsc_id, const char *host, cib_t * cib_conn, int cib_options) { int rc = pcmk_rc_ok; char *xpath_string = NULL; xpath_string = crm_strdup_printf("//rsc_location[@id='cli-prefer-%s'][rule[@id='cli-prefer-rule-%s']/expression[@attribute='#uname' and @value='%s']]", rsc_id, rsc_id, host); rc = cib_conn->cmds->remove(cib_conn, xpath_string, NULL, cib_xpath | cib_options); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } free(xpath_string); return rc; } // \return Standard Pacemaker return code static int resource_clear_node_in_location(const char *rsc_id, const char *host, cib_t * cib_conn, int cib_options, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); if (clear_ban_constraints == TRUE) { location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); } location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "cli-prefer-%s", rsc_id); if (force == FALSE) { crm_xml_add(location, XML_CIB_TAG_NODE, host); } crm_log_xml_info(fragment, "Delete"); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } free(fragment); return rc; } // \return Standard Pacemaker return code int cli_resource_clear(const char *rsc_id, const char *host, GListPtr allnodes, cib_t * cib_conn, int cib_options, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; if(cib_conn == NULL) { return ENOTCONN; } if (host) { rc = resource_clear_node_in_expr(rsc_id, host, cib_conn, cib_options); /* rc does not tell us whether the previous operation did anything, only * whether it failed or not. Thus, as long as it did not fail, we need * to try the second clear method. */ if (rc == pcmk_rc_ok) { rc = resource_clear_node_in_location(rsc_id, host, cib_conn, cib_options, clear_ban_constraints, force); } } else { GListPtr n = allnodes; /* Iterate over all nodes, attempting to clear the constraint from each. * On the first error, abort. */ for(; n; n = n->next) { pe_node_t *target = n->data; rc = cli_resource_clear(rsc_id, target->details->uname, NULL, cib_conn, cib_options, clear_ban_constraints, force); if (rc != pcmk_rc_ok) { break; } } } return rc; } static char * build_clear_xpath_string(xmlNode *constraint_node, const char *rsc, const char *node, gboolean promoted_role_only) { int offset = 0; char *xpath_string = NULL; char *first_half = NULL; char *rsc_role_substr = NULL; char *date_substr = NULL; if (pcmk__starts_with(ID(constraint_node), "cli-ban-")) { date_substr = crm_strdup_printf("//date_expression[@id='%s-lifetime']", ID(constraint_node)); } else if (pcmk__starts_with(ID(constraint_node), "cli-prefer-")) { date_substr = crm_strdup_printf("//date_expression[@id='cli-prefer-lifetime-end-%s']", crm_element_value(constraint_node, "rsc")); } else { return NULL; } first_half = calloc(1, XPATH_MAX); offset += snprintf(first_half + offset, XPATH_MAX - offset, "//rsc_location"); if (node != NULL || rsc != NULL || promoted_role_only == TRUE) { offset += snprintf(first_half + offset, XPATH_MAX - offset, "["); if (node != NULL) { if (rsc != NULL || promoted_role_only == TRUE) { offset += snprintf(first_half + offset, XPATH_MAX - offset, "@node='%s' and ", node); } else { offset += snprintf(first_half + offset, XPATH_MAX - offset, "@node='%s'", node); } } if (rsc != NULL && promoted_role_only == TRUE) { rsc_role_substr = crm_strdup_printf("@rsc='%s' and @role='%s'", rsc, RSC_ROLE_MASTER_S); offset += snprintf(first_half + offset, XPATH_MAX - offset, "@rsc='%s' and @role='%s']", rsc, RSC_ROLE_MASTER_S); } else if (rsc != NULL) { rsc_role_substr = crm_strdup_printf("@rsc='%s'", rsc); offset += snprintf(first_half + offset, XPATH_MAX - offset, "@rsc='%s']", rsc); } else if (promoted_role_only == TRUE) { rsc_role_substr = crm_strdup_printf("@role='%s'", RSC_ROLE_MASTER_S); offset += snprintf(first_half + offset, XPATH_MAX - offset, "@role='%s']", RSC_ROLE_MASTER_S); } else { offset += snprintf(first_half + offset, XPATH_MAX - offset, "]"); } } if (node != NULL) { if (rsc_role_substr != NULL) { xpath_string = crm_strdup_printf("%s|//rsc_location[%s]/rule[expression[@attribute='#uname' and @value='%s']]%s", first_half, rsc_role_substr, node, date_substr); } else { xpath_string = crm_strdup_printf("%s|//rsc_location/rule[expression[@attribute='#uname' and @value='%s']]%s", first_half, node, date_substr); } } else { xpath_string = crm_strdup_printf("%s%s", first_half, date_substr); } free(first_half); free(date_substr); free(rsc_role_substr); return xpath_string; } // \return Standard Pacemaker return code int cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, int cib_options, const char *rsc, const char *node, gboolean promoted_role_only) { xmlXPathObject *xpathObj = NULL; xmlNode *cib_constraints = NULL; crm_time_t *now = crm_time_new(NULL); int i; int rc = pcmk_rc_ok; cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root); xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION); for (i = 0; i < numXpathResults(xpathObj); i++) { xmlNode *constraint_node = getXpathResult(xpathObj, i); xmlNode *date_expr_node = NULL; crm_time_t *end = NULL; char *xpath_string = NULL; xpath_string = build_clear_xpath_string(constraint_node, rsc, node, promoted_role_only); if (xpath_string == NULL) { continue; } date_expr_node = get_xpath_object(xpath_string, constraint_node, LOG_DEBUG); if (date_expr_node == NULL) { free(xpath_string); continue; } /* And then finally, see if the date expression is expired. If so, * clear the constraint. */ end = crm_time_new(crm_element_value(date_expr_node, "end")); if (crm_time_compare(now, end) == 1) { xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = create_xml_node(NULL, XML_CIB_TAG_CONSTRAINTS); location = create_xml_node(fragment, XML_CONS_TAG_RSC_LOCATION); crm_xml_set_id(location, "%s", ID(constraint_node)); crm_log_xml_info(fragment, "Delete"); rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_CONSTRAINTS, fragment, cib_options); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { free(xpath_string); goto bail; } free_xml(fragment); } crm_time_free(end); free(xpath_string); } bail: freeXpathObject(xpathObj); crm_time_free(now); return rc; } diff --git a/tools/crm_resource_print.c b/tools/crm_resource_print.c index 1dbc2e20d7..6303863135 100644 --- a/tools/crm_resource_print.c +++ b/tools/crm_resource_print.c @@ -1,336 +1,577 @@ /* * Copyright 2004-2020 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 #define cons_string(x) x?x:"NA" void -cli_resource_print_cts_constraints(pe_working_set_t * data_set) +cli_resource_print_cts_constraints(pcmk__output_t *out, pe_working_set_t * data_set) { xmlNode *xml_obj = NULL; xmlNode *lifetime = NULL; xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); for (xml_obj = pcmk__xe_first_child(cib_constraints); xml_obj != NULL; xml_obj = pcmk__xe_next(xml_obj)) { const char *id = crm_element_value(xml_obj, XML_ATTR_ID); if (id == NULL) { continue; } // @COMPAT lifetime is deprecated lifetime = first_named_child(xml_obj, "lifetime"); if (pe_evaluate_rules(lifetime, NULL, data_set->now, NULL) == FALSE) { continue; } - if (pcmk__str_eq(XML_CONS_TAG_RSC_DEPEND, crm_element_name(xml_obj), pcmk__str_casei)) { - printf("Constraint %s %s %s %s %s %s %s\n", - crm_element_name(xml_obj), - cons_string(crm_element_value(xml_obj, XML_ATTR_ID)), - cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE)), - cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET)), - cons_string(crm_element_value(xml_obj, XML_RULE_ATTR_SCORE)), - cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE)), - cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET_ROLE))); - - } else if (pcmk__str_eq(XML_CONS_TAG_RSC_LOCATION, crm_element_name(xml_obj), pcmk__str_casei)) { - /* unpack_location(xml_obj, data_set); */ + if (!pcmk__str_eq(XML_CONS_TAG_RSC_DEPEND, crm_element_name(xml_obj), pcmk__str_casei)) { + continue; } + + out->info(out, "Constraint %s %s %s %s %s %s %s", + crm_element_name(xml_obj), + cons_string(crm_element_value(xml_obj, XML_ATTR_ID)), + cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE)), + cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET)), + cons_string(crm_element_value(xml_obj, XML_RULE_ATTR_SCORE)), + cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE)), + cons_string(crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET_ROLE))); } } void -cli_resource_print_cts(pe_resource_t * rsc) +cli_resource_print_cts(pcmk__output_t *out, pe_resource_t * rsc) { GListPtr lpc = NULL; const char *host = NULL; bool needs_quorum = TRUE; const char *rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE); const char *rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); const char *rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); pe_node_t *node = pe__current_node(rsc); if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { needs_quorum = FALSE; } else { // @TODO check requires in resource meta-data and rsc_defaults } if (node != NULL) { host = node->details->uname; } - printf("Resource: %s %s %s %s %s %s %s %s %d %lld 0x%.16llx\n", - crm_element_name(rsc->xml), rsc->id, - rsc->clone_name ? rsc->clone_name : rsc->id, rsc->parent ? rsc->parent->id : "NA", - rprov ? rprov : "NA", rclass, rtype, host ? host : "NA", needs_quorum, rsc->flags, - rsc->flags); + out->info(out, "Resource: %s %s %s %s %s %s %s %s %d %lld 0x%.16llx", + crm_element_name(rsc->xml), rsc->id, + rsc->clone_name ? rsc->clone_name : rsc->id, rsc->parent ? rsc->parent->id : "NA", + rprov ? rprov : "NA", rclass, rtype, host ? host : "NA", needs_quorum, rsc->flags, + rsc->flags); for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) { pe_resource_t *child = (pe_resource_t *) lpc->data; - cli_resource_print_cts(child); + cli_resource_print_cts(out, child); } } - -void -cli_resource_print_raw(pe_resource_t * rsc) +// \return Standard Pacemaker return code +int +cli_resource_print_operations(pcmk__output_t *out, const char *rsc_id, + const char *host_uname, bool active, + pe_working_set_t * data_set) { - GListPtr lpc = NULL; - GListPtr children = rsc->children; + int rc = pcmk_rc_no_output; + GListPtr ops = find_operations(rsc_id, host_uname, active, data_set); - if (children == NULL) { - printf("%s\n", rsc->id); + if (!ops) { + return rc; } - for (lpc = children; lpc != NULL; lpc = lpc->next) { - pe_resource_t *child = (pe_resource_t *) lpc->data; + out->begin_list(out, NULL, NULL, "Resource Operations"); + rc = pcmk_rc_ok; - cli_resource_print_raw(child); + for (GListPtr lpc = ops; lpc != NULL; lpc = lpc->next) { + xmlNode *xml_op = (xmlNode *) lpc->data; + out->message(out, "node-and-op", data_set, xml_op); } + + out->end_list(out); + return rc; } // \return Standard Pacemaker return code int -cli_resource_print_list(pe_working_set_t * data_set, bool raw) +cli_resource_print(pcmk__output_t *out, pe_resource_t *rsc, + pe_working_set_t *data_set, bool expanded) { - int found = 0; + unsigned int opts = pe_print_pending; + GListPtr all = NULL; - GListPtr lpc = NULL; - int opts = pe_print_printf | pe_print_rsconly | pe_print_pending; + all = g_list_prepend(all, strdup("*")); - for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *rsc = (pe_resource_t *) lpc->data; + out->begin_list(out, NULL, NULL, "Resource Config"); + out->message(out, crm_map_element_name(rsc->xml), opts, rsc, all, all); + out->message(out, "resource-config", rsc, !expanded); + out->end_list(out); - if (pcmk_is_set(rsc->flags, pe_rsc_orphan) - && rsc->fns->active(rsc, TRUE) == FALSE) { - continue; - } - rsc->fns->print(rsc, NULL, opts, stdout); - found++; + g_list_free_full(all, free); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("attribute", "pe_resource_t *", "char *", "GHashTable *") +static int +attribute_default(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + char *attr = va_arg(args, char *); + GHashTable *params = va_arg(args, GHashTable *); + + const char *value = g_hash_table_lookup(params, attr); + + if (value != NULL) { + out->begin_list(out, NULL, NULL, "Attributes"); + out->list_item(out, attr, "%s", value); + out->end_list(out); + } else { + out->err(out, "Attribute '%s' not found for '%s'", attr, rsc->id); } - if (found == 0) { - printf("NO resources configured\n"); - return ENXIO; + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("attribute", "pe_resource_t *", "char *", "GHashTable *") +static int +attribute_text(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + char *attr = va_arg(args, char *); + GHashTable *params = va_arg(args, GHashTable *); + + const char *value = g_hash_table_lookup(params, attr); + + if (value != NULL) { + out->info(out, "%s", value); + } else { + out->err(out, "Attribute '%s' not found for '%s'", attr, rsc->id); } return pcmk_rc_ok; } -// \return Standard Pacemaker return code -int -cli_resource_print_operations(const char *rsc_id, const char *host_uname, bool active, - pe_working_set_t * data_set) -{ - pe_resource_t *rsc = NULL; - int opts = pe_print_printf | pe_print_rsconly | pe_print_suppres_nl | pe_print_pending; - GListPtr ops = find_operations(rsc_id, host_uname, active, data_set); - GListPtr lpc = NULL; +PCMK__OUTPUT_ARGS("property", "pe_resource_t *", "char *") +static int +property_default(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + char *attr = va_arg(args, char *); - for (lpc = ops; lpc != NULL; lpc = lpc->next) { - xmlNode *xml_op = (xmlNode *) lpc->data; + const char *value = crm_element_value(rsc->xml, attr); - const char *op_rsc = crm_element_value(xml_op, "resource"); - const char *status_s = crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS); - const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); - int status = crm_parse_int(status_s, "0"); - time_t last_change = 0; + if (value != NULL) { + out->begin_list(out, NULL, NULL, "Properties"); + out->list_item(out, attr, "%s", value); + out->end_list(out); + } - rsc = pe_find_resource(data_set->resources, op_rsc); - if(rsc) { - rsc->fns->print(rsc, "", opts, stdout); - } else { - fprintf(stdout, "Unknown resource %s", op_rsc); - } + return pcmk_rc_ok; +} - fprintf(stdout, ": %s (node=%s, call=%s, rc=%s", - op_key ? op_key : ID(xml_op), - crm_element_value(xml_op, XML_ATTR_UNAME), - crm_element_value(xml_op, XML_LRM_ATTR_CALLID), - crm_element_value(xml_op, XML_LRM_ATTR_RC)); - - if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, - &last_change) == pcmk_ok) { - fprintf(stdout, ", " XML_RSC_OP_LAST_CHANGE "=%s, exec=%sms", - crm_strip_trailing_newline(ctime(&last_change)), - crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); - } - fprintf(stdout, "): %s\n", services_lrm_status_str(status)); +PCMK__OUTPUT_ARGS("property", "pe_resource_t *", "char *") +static int +property_text(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + char *attr = va_arg(args, char *); + + const char *value = crm_element_value(rsc->xml, attr); + + if (value != NULL) { + out->info(out, "%s", value); } + return pcmk_rc_ok; } -void -cli_resource_print_location(pe_resource_t * rsc, const char *prefix) -{ - GListPtr lpc = NULL; - GListPtr list = rsc->rsc_location; - int offset = 0; +PCMK__OUTPUT_ARGS("resource-check", "resource_checks_t *") +static int +resource_check_default(pcmk__output_t *out, va_list args) { + resource_checks_t *checks = va_arg(args, resource_checks_t *); + + pe_resource_t *parent = uber_parent(checks->rsc); + int rc = pcmk_rc_no_output; + bool printed = false; - if (prefix) { - offset = strlen(prefix) - 2; + if (checks->flags != 0 || checks->lock_node != NULL) { + printed = true; + out->begin_list(out, NULL, NULL, "Resource Checks"); } - for (lpc = list; lpc != NULL; lpc = lpc->next) { - pe__location_t *cons = lpc->data; + if (pcmk_is_set(checks->flags, rsc_remain_stopped)) { + out->list_item(out, "check", "Configuration specifies '%s' should remain stopped", + parent->id); + } - GListPtr lpc2 = NULL; + if (pcmk_is_set(checks->flags, rsc_unpromotable)) { + out->list_item(out, "check", "Configuration specifies '%s' should not be promoted", + parent->id); + } - for (lpc2 = cons->node_list_rh; lpc2 != NULL; lpc2 = lpc2->next) { - pe_node_t *node = (pe_node_t *) lpc2->data; - char *score = score2char(node->weight); + if (pcmk_is_set(checks->flags, rsc_unmanaged)) { + out->list_item(out, "check", "Configuration prevents cluster from stopping or starting unmanaged '%s'", + parent->id); + } - fprintf(stdout, "%s: Node %-*s (score=%s, id=%s)\n", - prefix ? prefix : " ", 71 - offset, node->details->uname, score, cons->id); - free(score); - } + if (checks->lock_node) { + out->list_item(out, "check", "'%s' is locked to node %s due to shutdown", + parent->id, checks->lock_node); + } + + if (printed) { + out->end_list(out); + rc = pcmk_rc_ok; } + + return rc; } -void -cli_resource_print_colocation(pe_resource_t * rsc, bool dependents, bool recursive, int offset) -{ - char *prefix = NULL; - GListPtr lpc = NULL; - GListPtr list = rsc->rsc_cons; +PCMK__OUTPUT_ARGS("resource-check", "resource_checks_t *") +static int +resource_check_xml(pcmk__output_t *out, va_list args) { + resource_checks_t *checks = va_arg(args, resource_checks_t *); + + pe_resource_t *parent = uber_parent(checks->rsc); + int rc = pcmk_rc_no_output; - prefix = calloc(1, (offset * 4) + 1); - memset(prefix, ' ', offset * 4); + xmlNode *node = pcmk__output_create_xml_node(out, "check"); - if (dependents) { - list = rsc->rsc_cons_lhs; + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) parent->id); + + if (pcmk_is_set(checks->flags, rsc_remain_stopped)) { + xmlSetProp(node, (pcmkXmlStr) "remain_stopped", (pcmkXmlStr) "true"); } - if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { - /* Break colocation loops */ - printf("loop %s\n", rsc->id); - free(prefix); - return; + if (pcmk_is_set(checks->flags, rsc_unpromotable)) { + xmlSetProp(node, (pcmkXmlStr) "promotable", (pcmkXmlStr) "false"); } - pe__set_resource_flags(rsc, pe_rsc_allocating); - for (lpc = list; lpc != NULL; lpc = lpc->next) { - rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data; + if (pcmk_is_set(checks->flags, rsc_unmanaged)) { + xmlSetProp(node, (pcmkXmlStr) "unmanaged", (pcmkXmlStr) "true"); + } - char *score = NULL; - pe_resource_t *peer = cons->rsc_rh; + if (checks->lock_node) { + xmlSetProp(node, (pcmkXmlStr) "locked-to", (pcmkXmlStr) checks->lock_node); + } - if (dependents) { - peer = cons->rsc_lh; - } + return rc; +} - if (pcmk_is_set(peer->flags, pe_rsc_allocating)) { - if (dependents == FALSE) { - fprintf(stdout, "%s%-*s (id=%s - loop)\n", prefix, 80 - (4 * offset), peer->id, - cons->id); - } - continue; - } +PCMK__OUTPUT_ARGS("resource-search", "GListPtr", "pe_resource_t *", "gchar *") +static int +resource_search_default(pcmk__output_t *out, va_list args) +{ + GListPtr nodes = va_arg(args, GListPtr); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + gchar *requested_name = va_arg(args, gchar *); + + bool printed = false; + int rc = pcmk_rc_no_output; + + if (!out->is_quiet(out) && nodes == NULL) { + out->err(out, "resource %s is NOT running", requested_name); + return rc; + } + + for (GListPtr lpc = nodes; lpc != NULL; lpc = lpc->next) { + pe_node_t *node = (pe_node_t *) lpc->data; - if (dependents && recursive) { - cli_resource_print_colocation(peer, dependents, recursive, offset + 1); + if (!printed) { + out->begin_list(out, NULL, NULL, "Nodes"); + printed = true; + rc = pcmk_rc_ok; } - score = score2char(cons->score); - if (cons->role_rh > RSC_ROLE_STARTED) { - fprintf(stdout, "%s%-*s (score=%s, %s role=%s, id=%s)\n", prefix, 80 - (4 * offset), - peer->id, score, dependents ? "needs" : "with", role2text(cons->role_rh), - cons->id); + if (out->is_quiet(out)) { + out->list_item(out, "node", "%s", node->details->uname); } else { - fprintf(stdout, "%s%-*s (score=%s, id=%s)\n", prefix, 80 - (4 * offset), - peer->id, score, cons->id); - } - cli_resource_print_location(peer, prefix); - free(score); + const char *state = ""; - if (!dependents && recursive) { - cli_resource_print_colocation(peer, dependents, recursive, offset + 1); + if (!pe_rsc_is_clone(rsc) && rsc->fns->state(rsc, TRUE) == RSC_ROLE_MASTER) { + state = " Master"; + } + out->list_item(out, "node", "resource %s is running on: %s%s", + requested_name, node->details->uname, state); } } - free(prefix); + + if (printed) { + out->end_list(out); + } + + return rc; } -// \return Standard Pacemaker return code -int -cli_resource_print(pe_resource_t *rsc, pe_working_set_t *data_set, bool expanded) + +PCMK__OUTPUT_ARGS("resource-search", "GListPtr", "pe_resource_t *", "gchar *") +static int +resource_search_xml(pcmk__output_t *out, va_list args) { - char *rsc_xml = NULL; - int opts = pe_print_printf | pe_print_pending; + GListPtr nodes = va_arg(args, GListPtr); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + gchar *requested_name = va_arg(args, gchar *); - rsc->fns->print(rsc, NULL, opts, stdout); + xmlNode *xml_node = pcmk__output_xml_create_parent(out, "nodes"); + + xmlSetProp(xml_node, (pcmkXmlStr) "resource", (pcmkXmlStr) requested_name); + + for (GListPtr lpc = nodes; lpc != NULL; lpc = lpc->next) { + pe_node_t *node = (pe_node_t *) lpc->data; + xmlNode *sub_node = pcmk__output_create_xml_text_node(out, "node", node->details->uname); + + if (!pe_rsc_is_clone(rsc) && rsc->fns->state(rsc, TRUE) == RSC_ROLE_MASTER) { + xmlSetProp(sub_node, (pcmkXmlStr) "state", (pcmkXmlStr) "promoted"); + } + } - rsc_xml = dump_xml_formatted((!expanded && rsc->orig_xml)? - rsc->orig_xml : rsc->xml); - fprintf(stdout, "%sxml:\n%s\n", expanded ? "" : "raw ", rsc_xml); - free(rsc_xml); return pcmk_rc_ok; } -// \return Standard Pacemaker return code -int -cli_resource_print_attribute(pe_resource_t *rsc, const char *attr, const char *attr_set_type, - pe_working_set_t * data_set) +PCMK__OUTPUT_ARGS("resource-why", "cib_t *", "GListPtr", "pe_resource_t *", + "pe_node_t *") +static int +resource_why_default(pcmk__output_t *out, va_list args) { - int rc = ENXIO; - unsigned int count = 0; - GHashTable *params = NULL; - const char *value = NULL; - pe_node_t *current = pe__find_active_on(rsc, &count, NULL); - - if (count > 1) { - CMD_ERR("%s is active on more than one node," - " returning the default value for %s", rsc->id, crm_str(attr)); - current = NULL; + cib_t *cib_conn = va_arg(args, cib_t *); + GListPtr resources = va_arg(args, GListPtr); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + + const char *host_uname = (node == NULL)? NULL : node->details->uname; + + out->begin_list(out, NULL, NULL, "Resource Reasons"); + + if ((rsc == NULL) && (host_uname == NULL)) { + GListPtr lpc = NULL; + GListPtr hosts = NULL; + + for (lpc = resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + rsc->fns->location(rsc, &hosts, TRUE); + + if (hosts == NULL) { + out->list_item(out, "reason", "Resource %s is not running", rsc->id); + } else { + out->list_item(out, "reason", "Resource %s is running", rsc->id); + } + + cli_resource_check(out, cib_conn, rsc); + g_list_free(hosts); + hosts = NULL; + } + + } else if ((rsc != NULL) && (host_uname != NULL)) { + if (resource_is_running_on(rsc, host_uname)) { + out->list_item(out, "reason", "Resource %s is running on host %s", + rsc->id, host_uname); + } else { + out->list_item(out, "reason", "Resource %s is not running on host %s", + rsc->id, host_uname); + } + + cli_resource_check(out, cib_conn, rsc); + + } else if ((rsc == NULL) && (host_uname != NULL)) { + const char* host_uname = node->details->uname; + GListPtr allResources = node->details->allocated_rsc; + GListPtr activeResources = node->details->running_rsc; + GListPtr unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp); + GListPtr lpc = NULL; + + for (lpc = activeResources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + out->list_item(out, "reason", "Resource %s is running on host %s", + rsc->id, host_uname); + cli_resource_check(out, cib_conn, rsc); + } + + for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + out->list_item(out, "reason", "Resource %s is assigned to host %s but not running", + rsc->id, host_uname); + cli_resource_check(out, cib_conn, rsc); + } + + g_list_free(allResources); + g_list_free(activeResources); + g_list_free(unactiveResources); + + } else if ((rsc != NULL) && (host_uname == NULL)) { + GListPtr hosts = NULL; + + rsc->fns->location(rsc, &hosts, TRUE); + out->list_item(out, "reason", "Resource %s is %srunning", + rsc->id, (hosts? "" : "not ")); + cli_resource_check(out, cib_conn, rsc); + g_list_free(hosts); } - params = crm_str_table_new(); + out->end_list(out); + return pcmk_rc_ok; +} - if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) { - get_rsc_attributes(params, rsc, current, data_set); +PCMK__OUTPUT_ARGS("resource-why", "cib_t *", "GListPtr", "pe_resource_t *", + "pe_node_t *") +static int +resource_why_xml(pcmk__output_t *out, va_list args) +{ + cib_t *cib_conn = va_arg(args, cib_t *); + GListPtr resources = va_arg(args, GListPtr); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); - } else if (pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { - /* No need to redirect to the parent */ - get_meta_attributes(params, rsc, current, data_set); + const char *host_uname = (node == NULL)? NULL : node->details->uname; - } else { - pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params, - NULL, FALSE, data_set); + xmlNode *xml_node = pcmk__output_xml_create_parent(out, "reason"); + + if ((rsc == NULL) && (host_uname == NULL)) { + GListPtr lpc = NULL; + GListPtr hosts = NULL; + + pcmk__output_xml_create_parent(out, "resources"); + + for (lpc = resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + xmlNode *rsc_node = NULL; + + rsc->fns->location(rsc, &hosts, TRUE); + + rsc_node = pcmk__output_xml_create_parent(out, "resource"); + xmlSetProp(rsc_node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc->id); + xmlSetProp(rsc_node, (pcmkXmlStr) "running", + (pcmkXmlStr) pcmk__btoa(hosts != NULL)); + + cli_resource_check(out, cib_conn, rsc); + pcmk__output_xml_pop_parent(out); + g_list_free(hosts); + hosts = NULL; + } + + pcmk__output_xml_pop_parent(out); + + } else if ((rsc != NULL) && (host_uname != NULL)) { + if (resource_is_running_on(rsc, host_uname)) { + xmlSetProp(xml_node, (pcmkXmlStr) "running_on", (pcmkXmlStr) host_uname); + } + + cli_resource_check(out, cib_conn, rsc); + + } else if ((rsc == NULL) && (host_uname != NULL)) { + const char* host_uname = node->details->uname; + GListPtr allResources = node->details->allocated_rsc; + GListPtr activeResources = node->details->running_rsc; + GListPtr unactiveResources = pcmk__subtract_lists(allResources, activeResources, (GCompareFunc) strcmp); + GListPtr lpc = NULL; + + pcmk__output_xml_create_parent(out, "resources"); + + for (lpc = activeResources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + xmlNode *rsc_node = NULL; + + rsc_node = pcmk__output_xml_create_parent(out, "resource"); + xmlSetProp(rsc_node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc->id); + xmlSetProp(rsc_node, (pcmkXmlStr) "running", (pcmkXmlStr) "true"); + xmlSetProp(rsc_node, (pcmkXmlStr) "host", (pcmkXmlStr) host_uname); + + cli_resource_check(out, cib_conn, rsc); + pcmk__output_xml_pop_parent(out); + } + + for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + xmlNode *rsc_node = NULL; + + rsc_node = pcmk__output_xml_create_parent(out, "resource"); + xmlSetProp(rsc_node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc->id); + xmlSetProp(rsc_node, (pcmkXmlStr) "running", (pcmkXmlStr) "false"); + xmlSetProp(rsc_node, (pcmkXmlStr) "host", (pcmkXmlStr) host_uname); + + cli_resource_check(out, cib_conn, rsc); + pcmk__output_xml_pop_parent(out); + } + + pcmk__output_xml_pop_parent(out); + g_list_free(allResources); + g_list_free(activeResources); + g_list_free(unactiveResources); + + } else if ((rsc != NULL) && (host_uname == NULL)) { + GListPtr hosts = NULL; + + rsc->fns->location(rsc, &hosts, TRUE); + xmlSetProp(xml_node, (pcmkXmlStr) "running", + (pcmkXmlStr) pcmk__btoa(hosts != NULL)); + cli_resource_check(out, cib_conn, rsc); + g_list_free(hosts); } - crm_debug("Looking up %s in %s", attr, rsc->id); - value = g_hash_table_lookup(params, attr); - if (value != NULL) { - fprintf(stdout, "%s\n", value); - rc = pcmk_rc_ok; + return pcmk_rc_ok; +} +static void +add_resource_name(pcmk__output_t *out, pe_resource_t *rsc) { + if (rsc->children == NULL) { + out->list_item(out, "resource", "%s", rsc->id); } else { - CMD_ERR("Attribute '%s' not found for '%s'", attr, rsc->id); + for (GListPtr lpc = rsc->children; lpc != NULL; lpc = lpc->next) { + pe_resource_t *child = (pe_resource_t *) lpc->data; + add_resource_name(out, child); + } } - - g_hash_table_destroy(params); - return rc; } -// \return Standard Pacemaker return code -int -cli_resource_print_property(pe_resource_t *rsc, const char *attr, pe_working_set_t * data_set) -{ - const char *value = crm_element_value(rsc->xml, attr); +PCMK__OUTPUT_ARGS("resource-names-list", "GListPtr") +static int +resource_names(pcmk__output_t *out, va_list args) { + GListPtr resources = va_arg(args, GListPtr); - if (value != NULL) { - fprintf(stdout, "%s\n", value); - return pcmk_rc_ok; + if (resources == NULL) { + out->err(out, "NO resources configured\n"); + return pcmk_rc_no_output; } - return ENXIO; + + out->begin_list(out, NULL, NULL, "Resource Names"); + + for (GListPtr lpc = resources; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + add_resource_name(out, rsc); + } + + out->end_list(out); + return pcmk_rc_ok; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "attribute", "default", attribute_default }, + { "attribute", "text", attribute_text }, + { "property", "default", property_default }, + { "property", "text", property_text }, + { "resource-check", "default", resource_check_default }, + { "resource-check", "xml", resource_check_xml }, + { "resource-search", "default", resource_search_default }, + { "resource-search", "xml", resource_search_xml }, + { "resource-why", "default", resource_why_default }, + { "resource-why", "xml", resource_why_xml }, + { "resource-names-list", "default", resource_names }, + + { NULL, NULL, NULL } +}; + +void +crm_resource_register_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); } diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c index d133219166..bbd8bc1e53 100644 --- a/tools/crm_resource_runtime.c +++ b/tools/crm_resource_runtime.c @@ -1,2093 +1,1959 @@ /* * Copyright 2004-2020 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 -static int -do_find_resource(const char *rsc, pe_resource_t * the_rsc, pe_working_set_t * data_set) +resource_checks_t * +cli_check_resource(pe_resource_t *rsc, char *role_s, char *managed) { - int found = 0; - GListPtr lpc = NULL; - - for (lpc = the_rsc->running_on; lpc != NULL; lpc = lpc->next) { - pe_node_t *node = (pe_node_t *) lpc->data; + pe_resource_t *parent = uber_parent(rsc); + resource_checks_t *rc = calloc(1, sizeof(resource_checks_t)); - if (BE_QUIET) { - fprintf(stdout, "%s\n", node->details->uname); - } else { - const char *state = ""; + if (role_s) { + enum rsc_role_e role = text2role(role_s); - if (!pe_rsc_is_clone(the_rsc) && the_rsc->fns->state(the_rsc, TRUE) == RSC_ROLE_MASTER) { - state = "Master"; - } - fprintf(stdout, "resource %s is running on: %s %s\n", rsc, node->details->uname, state); + if (role == RSC_ROLE_STOPPED) { + rc->flags |= rsc_remain_stopped; + } else if (pcmk_is_set(parent->flags, pe_rsc_promotable) && + role == RSC_ROLE_SLAVE) { + rc->flags |= rsc_unpromotable; } + } - found++; + if (managed && !crm_is_true(managed)) { + rc->flags |= rsc_unmanaged; } - if (BE_QUIET == FALSE && found == 0) { - fprintf(stderr, "resource %s is NOT running\n", rsc); + if (rsc->lock_node) { + rc->lock_node = rsc->lock_node->details->uname; } - return found; + rc->rsc = rsc; + return rc; } -int -cli_resource_search(pe_resource_t *rsc, const char *requested_name, +GListPtr +cli_resource_search(pcmk__output_t *out, pe_resource_t *rsc, const char *requested_name, pe_working_set_t *data_set) { - int found = 0; + GListPtr found = NULL; pe_resource_t *parent = uber_parent(rsc); if (pe_rsc_is_clone(rsc)) { for (GListPtr iter = rsc->children; iter != NULL; iter = iter->next) { - found += do_find_resource(requested_name, iter->data, data_set); + GListPtr extra = ((pe_resource_t *) iter->data)->running_on; + if (extra != NULL) { + found = g_list_concat(found, extra); + } } /* The anonymous clone children's common ID is supplied */ } else if (pe_rsc_is_clone(parent) && !pcmk_is_set(rsc->flags, pe_rsc_unique) && rsc->clone_name && pcmk__str_eq(requested_name, rsc->clone_name, pcmk__str_casei) && !pcmk__str_eq(requested_name, rsc->id, pcmk__str_casei)) { for (GListPtr iter = parent->children; iter; iter = iter->next) { - found += do_find_resource(requested_name, iter->data, data_set); + GListPtr extra = ((pe_resource_t *) iter->data)->running_on; + if (extra != NULL) { + found = g_list_concat(found, extra); + } } - } else { - found += do_find_resource(requested_name, rsc, data_set); + } else if (rsc->running_on != NULL) { + found = g_list_concat(found, rsc->running_on); } return found; } #define XPATH_MAX 1024 // \return Standard Pacemaker return code static int -find_resource_attr(cib_t * the_cib, const char *attr, const char *rsc, const char *attr_set_type, - const char *set_name, const char *attr_id, const char *attr_name, char **value) +find_resource_attr(pcmk__output_t *out, cib_t * the_cib, const char *attr, + const char *rsc, const char *attr_set_type, const char *set_name, + const char *attr_id, const char *attr_name, char **value) { int offset = 0; int rc = pcmk_rc_ok; xmlNode *xml_search = NULL; char *xpath_string = NULL; if(value) { *value = NULL; } if(the_cib == NULL) { return ENOTCONN; } xpath_string = calloc(1, XPATH_MAX); offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "%s", get_object_path("resources")); offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//*[@id=\"%s\"]", rsc); if (attr_set_type) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "/%s", attr_set_type); if (set_name) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "[@id=\"%s\"]", set_name); } } offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//nvpair["); if (attr_id) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@id=\"%s\"", attr_id); } if (attr_name) { if (attr_id) { offset += snprintf(xpath_string + offset, XPATH_MAX - offset, " and "); } offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "@name=\"%s\"", attr_name); } offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "]"); CRM_LOG_ASSERT(offset > 0); rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search, cib_sync_call | cib_scope_local | cib_xpath); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { goto bail; } crm_log_xml_debug(xml_search, "Match"); if (xml_has_children(xml_search)) { xmlNode *child = NULL; rc = EINVAL; - printf("Multiple attributes match name=%s\n", attr_name); + out->info(out, "Multiple attributes match name=%s", attr_name); for (child = pcmk__xml_first_child(xml_search); child != NULL; child = pcmk__xml_next(child)) { - printf(" Value: %s \t(id=%s)\n", - crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child)); + out->info(out, " Value: %s \t(id=%s)", + crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child)); } + out->spacer(out); + } else if(value) { const char *tmp = crm_element_value(xml_search, attr); if (tmp) { *value = strdup(tmp); } } bail: free(xpath_string); free_xml(xml_search); return rc; } /* PRIVATE. Use the find_matching_attr_resources instead. */ static void -find_matching_attr_resources_recursive(GList/* */ ** result, pe_resource_t * rsc, const char * rsc_id, - const char * attr_set, const char * attr_set_type, const char * attr_id, - const char * attr_name, cib_t * cib, const char * cmd, int depth) +find_matching_attr_resources_recursive(pcmk__output_t *out, GList/* */ ** result, + pe_resource_t * rsc, const char * rsc_id, + const char * attr_set, const char * attr_set_type, + const char * attr_id, const char * attr_name, + cib_t * cib, const char * cmd, int depth) { int rc = pcmk_rc_ok; char *lookup_id = clone_strip(rsc->id); char *local_attr_id = NULL; /* visit the children */ for(GList *gIter = rsc->children; gIter; gIter = gIter->next) { - find_matching_attr_resources_recursive(result, (pe_resource_t*)gIter->data, + find_matching_attr_resources_recursive(out, result, (pe_resource_t*)gIter->data, rsc_id, attr_set, attr_set_type, attr_id, attr_name, cib, cmd, depth+1); /* do it only once for clones */ if(pe_clone == rsc->variant) { break; } } - rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); + rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, + attr_set, attr_id, attr_name, &local_attr_id); /* Post-order traversal. * The root is always on the list and it is the last item. */ if((0 == depth) || (pcmk_rc_ok == rc)) { /* push the head */ *result = g_list_append(*result, rsc); } free(local_attr_id); free(lookup_id); } /* The result is a linearized pre-ordered tree of resources. */ static GList/**/ * -find_matching_attr_resources(pe_resource_t * rsc, const char * rsc_id, const char * attr_set, +find_matching_attr_resources(pcmk__output_t *out, pe_resource_t * rsc, + const char * rsc_id, const char * attr_set, const char * attr_set_type, const char * attr_id, - const char * attr_name, cib_t * cib, const char * cmd, gboolean force) + const char * attr_name, cib_t * cib, const char * cmd, + gboolean force) { int rc = pcmk_rc_ok; char *lookup_id = NULL; char *local_attr_id = NULL; GList * result = NULL; /* If --force is used, update only the requested resource (clone or primitive). * Otherwise, if the primitive has the attribute, use that. * Otherwise use the clone. */ if(force == TRUE) { return g_list_append(result, rsc); } if(rsc->parent && pe_clone == rsc->parent->variant) { int rc = pcmk_rc_ok; char *local_attr_id = NULL; - rc = find_resource_attr(cib, XML_ATTR_ID, rsc_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); + rc = find_resource_attr(out, cib, XML_ATTR_ID, rsc_id, attr_set_type, + attr_set, attr_id, attr_name, &local_attr_id); free(local_attr_id); if(rc != pcmk_rc_ok) { rsc = rsc->parent; - if (BE_QUIET == FALSE) { - printf("Performing %s of '%s' on '%s', the parent of '%s'\n", cmd, attr_name, rsc->id, rsc_id); + if (!out->is_quiet(out)) { + out->info(out, "Performing %s of '%s' on '%s', the parent of '%s'", + cmd, attr_name, rsc->id, rsc_id); } } return g_list_append(result, rsc); } else if(rsc->parent == NULL && rsc->children && pe_clone == rsc->variant) { pe_resource_t *child = rsc->children->data; if(child->variant == pe_native) { lookup_id = clone_strip(child->id); /* Could be a cloned group! */ - rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, &local_attr_id); + rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, + attr_set, attr_id, attr_name, &local_attr_id); if(rc == pcmk_rc_ok) { rsc = child; - if (BE_QUIET == FALSE) { - printf("A value for '%s' already exists in child '%s', performing %s on that instead of '%s'\n", attr_name, lookup_id, cmd, rsc_id); + if (!out->is_quiet(out)) { + out->info(out, "A value for '%s' already exists in child '%s', performing %s on that instead of '%s'", + attr_name, lookup_id, cmd, rsc_id); } } free(local_attr_id); free(lookup_id); } return g_list_append(result, rsc); } /* If the resource is a group ==> children inherit the attribute if defined. */ - find_matching_attr_resources_recursive(&result, rsc, rsc_id, attr_set, + find_matching_attr_resources_recursive(out, &result, rsc, rsc_id, attr_set, attr_set_type, attr_id, attr_name, cib, cmd, 0); return result; } // \return Standard Pacemaker return code int -cli_resource_update_attribute(pe_resource_t *rsc, const char *requested_name, - const char *attr_set, const char *attr_set_type, - const char *attr_id, const char *attr_name, - const char *attr_value, gboolean recursive, cib_t *cib, - int cib_options, pe_working_set_t *data_set, gboolean force) +cli_resource_update_attribute(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, const char *attr_set, + const char *attr_set_type, const char *attr_id, + const char *attr_name, const char *attr_value, + gboolean recursive, cib_t *cib, int cib_options, + pe_working_set_t *data_set, gboolean force) { int rc = pcmk_rc_ok; static bool need_init = TRUE; char *local_attr_id = NULL; char *local_attr_set = NULL; GList/**/ *resources = NULL; const char *common_attr_id = attr_id; - if(attr_id == NULL - && force == FALSE - && find_resource_attr( - cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL) == EINVAL) { - printf("\n"); + if (attr_id == NULL && force == FALSE) { + find_resource_attr (out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, + NULL, NULL, attr_name, NULL); } if (pcmk__str_eq(attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) { if (force == FALSE) { - rc = find_resource_attr(cib, XML_ATTR_ID, uber_parent(rsc)->id, + rc = find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id, XML_TAG_META_SETS, attr_set, attr_id, attr_name, &local_attr_id); - if (rc == pcmk_rc_ok && BE_QUIET == FALSE) { - printf("WARNING: There is already a meta attribute for '%s' called '%s' (id=%s)\n", - uber_parent(rsc)->id, attr_name, local_attr_id); - printf(" Delete '%s' first or use the force option to override\n", - local_attr_id); + if (rc == pcmk_rc_ok && !out->is_quiet(out)) { + out->err(out, "WARNING: There is already a meta attribute for '%s' called '%s' (id=%s)", + uber_parent(rsc)->id, attr_name, local_attr_id); + out->err(out, " Delete '%s' first or use the force option to override", + local_attr_id); } free(local_attr_id); if (rc == pcmk_rc_ok) { return ENOTUNIQ; } } resources = g_list_append(resources, rsc); } else { - resources = find_matching_attr_resources(rsc, requested_name, attr_set, attr_set_type, + resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type, attr_id, attr_name, cib, "update", force); } /* If either attr_set or attr_id is specified, * one clearly intends to modify a single resource. * It is the last item on the resource list.*/ for(GList *gIter = (attr_set||attr_id) ? g_list_last(resources) : resources ; gIter; gIter = gIter->next) { char *lookup_id = NULL; xmlNode *xml_top = NULL; xmlNode *xml_obj = NULL; local_attr_id = NULL; local_attr_set = NULL; rsc = (pe_resource_t*)gIter->data; attr_id = common_attr_id; lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */ - rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, - &local_attr_id); + rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, + attr_set, attr_id, attr_name, &local_attr_id); if (rc == pcmk_rc_ok) { crm_debug("Found a match for name=%s: id=%s", attr_name, local_attr_id); attr_id = local_attr_id; } else if (rc != ENXIO) { free(lookup_id); free(local_attr_id); g_list_free(resources); return rc; } else { const char *tag = crm_element_name(rsc->xml); if (attr_set == NULL) { local_attr_set = crm_strdup_printf("%s-%s", lookup_id, attr_set_type); attr_set = local_attr_set; } if (attr_id == NULL) { local_attr_id = crm_strdup_printf("%s-%s", attr_set, attr_name); attr_id = local_attr_id; } xml_top = create_xml_node(NULL, tag); crm_xml_add(xml_top, XML_ATTR_ID, lookup_id); xml_obj = create_xml_node(xml_top, attr_set_type); crm_xml_add(xml_obj, XML_ATTR_ID, attr_set); } xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value); if (xml_top == NULL) { xml_top = xml_obj; } crm_log_xml_debug(xml_top, "Update"); rc = cib->cmds->modify(cib, XML_CIB_TAG_RESOURCES, xml_top, cib_options); rc = pcmk_legacy2rc(rc); - if (rc == pcmk_rc_ok && BE_QUIET == FALSE) { - printf("Set '%s' option: id=%s%s%s%s%s value=%s\n", lookup_id, local_attr_id, - attr_set ? " set=" : "", attr_set ? attr_set : "", - attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value); + if (rc == pcmk_rc_ok && !out->is_quiet(out)) { + out->info(out, "Set '%s' option: id=%s%s%s%s%s value=%s", lookup_id, local_attr_id, + attr_set ? " set=" : "", attr_set ? attr_set : "", + attr_name ? " name=" : "", attr_name ? attr_name : "", attr_value); } free_xml(xml_top); free(lookup_id); free(local_attr_id); free(local_attr_set); if(recursive && pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { GListPtr lpc = NULL; if(need_init) { xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, data_set->input); need_init = FALSE; unpack_constraints(cib_constraints, data_set); for (lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags(r, pe_rsc_allocating); } } crm_debug("Looking for dependencies %p", rsc->rsc_cons_lhs); pe__set_resource_flags(rsc, pe_rsc_allocating); for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) { rsc_colocation_t *cons = (rsc_colocation_t *) lpc->data; pe_resource_t *peer = cons->rsc_lh; crm_debug("Checking %s %d", cons->id, cons->score); if (cons->score > 0 && !pcmk_is_set(peer->flags, pe_rsc_allocating)) { /* Don't get into colocation loops */ crm_debug("Setting %s=%s for dependent resource %s", attr_name, attr_value, peer->id); - cli_resource_update_attribute(peer, peer->id, NULL, attr_set_type, + cli_resource_update_attribute(out, peer, peer->id, NULL, attr_set_type, NULL, attr_name, attr_value, recursive, cib, cib_options, data_set, force); } } } } g_list_free(resources); return rc; } // \return Standard Pacemaker return code int -cli_resource_delete_attribute(pe_resource_t *rsc, const char *requested_name, - const char *attr_set, const char *attr_set_type, - const char *attr_id, const char *attr_name, cib_t *cib, - int cib_options, pe_working_set_t *data_set, gboolean force) +cli_resource_delete_attribute(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, const char *attr_set, + const char *attr_set_type, const char *attr_id, + const char *attr_name, cib_t *cib, int cib_options, + pe_working_set_t *data_set, gboolean force) { int rc = pcmk_rc_ok; GList/**/ *resources = NULL; - if(attr_id == NULL - && force == FALSE - && find_resource_attr( - cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, NULL, NULL, attr_name, NULL) == EINVAL) { - printf("\n"); + if (attr_id == NULL && force == FALSE) { + find_resource_attr(out, cib, XML_ATTR_ID, uber_parent(rsc)->id, NULL, + NULL, NULL, attr_name, NULL); } if(pcmk__str_eq(attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) { - resources = find_matching_attr_resources(rsc, requested_name, attr_set, attr_set_type, + resources = find_matching_attr_resources(out, rsc, requested_name, attr_set, attr_set_type, attr_id, attr_name, cib, "delete", force); } else { resources = g_list_append(resources, rsc); } for(GList *gIter = resources; gIter; gIter = gIter->next) { char *lookup_id = NULL; xmlNode *xml_obj = NULL; char *local_attr_id = NULL; rsc = (pe_resource_t*)gIter->data; lookup_id = clone_strip(rsc->id); - rc = find_resource_attr(cib, XML_ATTR_ID, lookup_id, attr_set_type, attr_set, attr_id, attr_name, - &local_attr_id); + rc = find_resource_attr(out, cib, XML_ATTR_ID, lookup_id, attr_set_type, + attr_set, attr_id, attr_name, &local_attr_id); if (rc == ENXIO) { free(lookup_id); rc = pcmk_rc_ok; continue; } else if (rc != pcmk_rc_ok) { free(lookup_id); g_list_free(resources); return rc; } if (attr_id == NULL) { attr_id = local_attr_id; } xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, NULL); crm_log_xml_debug(xml_obj, "Delete"); CRM_ASSERT(cib); rc = cib->cmds->remove(cib, XML_CIB_TAG_RESOURCES, xml_obj, cib_options); rc = pcmk_legacy2rc(rc); - if (rc == pcmk_rc_ok && BE_QUIET == FALSE) { - printf("Deleted '%s' option: id=%s%s%s%s%s\n", lookup_id, local_attr_id, - attr_set ? " set=" : "", attr_set ? attr_set : "", - attr_name ? " name=" : "", attr_name ? attr_name : ""); + if (rc == pcmk_rc_ok && !out->is_quiet(out)) { + out->info(out, "Deleted '%s' option: id=%s%s%s%s%s", lookup_id, local_attr_id, + attr_set ? " set=" : "", attr_set ? attr_set : "", + attr_name ? " name=" : "", attr_name ? attr_name : ""); } free(lookup_id); free_xml(xml_obj); free(local_attr_id); } g_list_free(resources); return rc; } // \return Standard Pacemaker return code static int -send_lrm_rsc_op(pcmk_ipc_api_t *controld_api, bool do_fail_resource, - const char *host_uname, const char *rsc_id, - pe_working_set_t *data_set) +send_lrm_rsc_op(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, bool do_fail_resource, + const char *host_uname, const char *rsc_id, pe_working_set_t *data_set) { const char *router_node = host_uname; const char *rsc_api_id = NULL; const char *rsc_long_id = NULL; const char *rsc_class = NULL; const char *rsc_provider = NULL; const char *rsc_type = NULL; bool cib_only = false; pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); if (rsc == NULL) { - CMD_ERR("Resource %s not found", rsc_id); + out->err(out, "Resource %s not found", rsc_id); return ENXIO; } else if (rsc->variant != pe_native) { - CMD_ERR("We can only process primitive resources, not %s", rsc_id); + out->err(out, "We can only process primitive resources, not %s", rsc_id); return EINVAL; } rsc_class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); rsc_provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER), rsc_type = crm_element_value(rsc->xml, XML_ATTR_TYPE); if ((rsc_class == NULL) || (rsc_type == NULL)) { - CMD_ERR("Resource %s does not have a class and type", rsc_id); + out->err(out, "Resource %s does not have a class and type", rsc_id); return EINVAL; } if (host_uname == NULL) { - CMD_ERR("Please specify a node name"); + out->err(out, "Please specify a node name"); return EINVAL; } else { pe_node_t *node = pe_find_node(data_set->nodes, host_uname); if (node == NULL) { - CMD_ERR("Node %s not found", host_uname); + out->err(out, "Node %s not found", host_uname); return pcmk_rc_node_unknown; } if (!(node->details->online)) { if (do_fail_resource) { - CMD_ERR("Node %s is not online", host_uname); + out->err(out, "Node %s is not online", host_uname); return ENOTCONN; } else { cib_only = true; } } if (!cib_only && pe__is_guest_or_remote_node(node)) { node = pe__current_node(node->details->remote_rsc); if (node == NULL) { - CMD_ERR("No cluster connection to Pacemaker Remote node %s detected", - host_uname); + out->err(out, "No cluster connection to Pacemaker Remote node %s detected", + host_uname); return ENOTCONN; } router_node = node->details->uname; } } if (rsc->clone_name) { rsc_api_id = rsc->clone_name; rsc_long_id = rsc->id; } else { rsc_api_id = rsc->id; } if (do_fail_resource) { return pcmk_controld_api_fail(controld_api, host_uname, router_node, rsc_api_id, rsc_long_id, rsc_class, rsc_provider, rsc_type); } else { return pcmk_controld_api_refresh(controld_api, host_uname, router_node, rsc_api_id, rsc_long_id, rsc_class, rsc_provider, rsc_type, cib_only); } } /*! * \internal * \brief Get resource name as used in failure-related node attributes * * \param[in] rsc Resource to check * * \return Newly allocated string containing resource's fail name * \note The caller is responsible for freeing the result. */ static inline char * rsc_fail_name(pe_resource_t *rsc) { const char *name = (rsc->clone_name? rsc->clone_name : rsc->id); return pcmk_is_set(rsc->flags, pe_rsc_unique)? strdup(name) : clone_strip(name); } // \return Standard Pacemaker return code static int -clear_rsc_history(pcmk_ipc_api_t *controld_api, const char *host_uname, - const char *rsc_id, pe_working_set_t *data_set) +clear_rsc_history(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, + const char *host_uname, const char *rsc_id, + pe_working_set_t *data_set) { int rc = pcmk_rc_ok; /* Erase the resource's entire LRM history in the CIB, even if we're only * clearing a single operation's fail count. If we erased only entries for a * single operation, we might wind up with a wrong idea of the current * resource state, and we might not re-probe the resource. */ - rc = send_lrm_rsc_op(controld_api, false, host_uname, rsc_id, data_set); + rc = send_lrm_rsc_op(out, controld_api, false, host_uname, rsc_id, data_set); if (rc != pcmk_rc_ok) { return rc; } crm_trace("Processing %d mainloop inputs", pcmk_controld_api_replies_expected(controld_api)); while (g_main_context_iteration(NULL, FALSE)) { crm_trace("Processed mainloop input, %d still remaining", pcmk_controld_api_replies_expected(controld_api)); } return rc; } // \return Standard Pacemaker return code static int -clear_rsc_failures(pcmk_ipc_api_t *controld_api, const char *node_name, - const char *rsc_id, const char *operation, +clear_rsc_failures(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, + const char *node_name, const char *rsc_id, const char *operation, const char *interval_spec, pe_working_set_t *data_set) { int rc = pcmk_rc_ok; const char *failed_value = NULL; const char *failed_id = NULL; const char *interval_ms_s = NULL; GHashTable *rscs = NULL; GHashTableIter iter; /* Create a hash table to use as a set of resources to clean. This lets us * clean each resource only once (per node) regardless of how many failed * operations it has. */ rscs = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, NULL); // Normalize interval to milliseconds for comparison to history entry if (operation) { interval_ms_s = crm_strdup_printf("%u", crm_parse_interval_spec(interval_spec)); } for (xmlNode *xml_op = pcmk__xml_first_child(data_set->failed); xml_op != NULL; xml_op = pcmk__xml_next(xml_op)) { failed_id = crm_element_value(xml_op, XML_LRM_ATTR_RSCID); if (failed_id == NULL) { // Malformed history entry, should never happen continue; } // No resource specified means all resources match if (rsc_id) { pe_resource_t *fail_rsc = pe_find_resource_with_flags(data_set->resources, failed_id, pe_find_renamed|pe_find_anon); if (!fail_rsc || !pcmk__str_eq(rsc_id, fail_rsc->id, pcmk__str_casei)) { continue; } } // Host name should always have been provided by this point failed_value = crm_element_value(xml_op, XML_ATTR_UNAME); if (!pcmk__str_eq(node_name, failed_value, pcmk__str_casei)) { continue; } // No operation specified means all operations match if (operation) { failed_value = crm_element_value(xml_op, XML_LRM_ATTR_TASK); if (!pcmk__str_eq(operation, failed_value, pcmk__str_casei)) { continue; } // Interval (if operation was specified) defaults to 0 (not all) failed_value = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS); if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) { continue; } } /* not available until glib 2.32 g_hash_table_add(rscs, (gpointer) failed_id); */ g_hash_table_insert(rscs, (gpointer) failed_id, (gpointer) failed_id); } g_hash_table_iter_init(&iter, rscs); while (g_hash_table_iter_next(&iter, (gpointer *) &failed_id, NULL)) { crm_debug("Erasing failures of %s on %s", failed_id, node_name); - rc = clear_rsc_history(controld_api, node_name, failed_id, data_set); + rc = clear_rsc_history(out, controld_api, node_name, failed_id, data_set); if (rc != pcmk_rc_ok) { return rc; } } g_hash_table_destroy(rscs); return rc; } // \return Standard Pacemaker return code static int clear_rsc_fail_attrs(pe_resource_t *rsc, const char *operation, const char *interval_spec, pe_node_t *node) { int rc = pcmk_rc_ok; int attr_options = pcmk__node_attr_none; char *rsc_name = rsc_fail_name(rsc); if (pe__is_guest_or_remote_node(node)) { attr_options |= pcmk__node_attr_remote; } rc = pcmk__node_attr_request_clear(NULL, node->details->uname, rsc_name, operation, interval_spec, NULL, attr_options); free(rsc_name); return rc; } // \return Standard Pacemaker return code int -cli_resource_delete(pcmk_ipc_api_t *controld_api, const char *host_uname, - pe_resource_t *rsc, const char *operation, +cli_resource_delete(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, + const char *host_uname, pe_resource_t *rsc, const char *operation, const char *interval_spec, bool just_failures, pe_working_set_t *data_set, gboolean force) { int rc = pcmk_rc_ok; pe_node_t *node = NULL; if (rsc == NULL) { return ENXIO; } else if (rsc->children) { GListPtr lpc = NULL; for (lpc = rsc->children; lpc != NULL; lpc = lpc->next) { pe_resource_t *child = (pe_resource_t *) lpc->data; - rc = cli_resource_delete(controld_api, host_uname, child, operation, + rc = cli_resource_delete(out, controld_api, host_uname, child, operation, interval_spec, just_failures, data_set, force); if (rc != pcmk_rc_ok) { return rc; } } return pcmk_rc_ok; } else if (host_uname == NULL) { GListPtr lpc = NULL; GListPtr nodes = g_hash_table_get_values(rsc->known_on); if(nodes == NULL && force) { nodes = pcmk__copy_node_list(data_set->nodes, false); } else if(nodes == NULL && rsc->exclusive_discover) { GHashTableIter iter; pe_node_t *node = NULL; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void**)&node)) { if(node->weight >= 0) { nodes = g_list_prepend(nodes, node); } } } else if(nodes == NULL) { nodes = g_hash_table_get_values(rsc->allowed_nodes); } for (lpc = nodes; lpc != NULL; lpc = lpc->next) { node = (pe_node_t *) lpc->data; if (node->details->online) { - rc = cli_resource_delete(controld_api, node->details->uname, + rc = cli_resource_delete(out, controld_api, node->details->uname, rsc, operation, interval_spec, just_failures, data_set, force); } if (rc != pcmk_rc_ok) { g_list_free(nodes); return rc; } } g_list_free(nodes); return pcmk_rc_ok; } node = pe_find_node(data_set->nodes, host_uname); if (node == NULL) { - printf("Unable to clean up %s because node %s not found\n", - rsc->id, host_uname); + out->err(out, "Unable to clean up %s because node %s not found", + rsc->id, host_uname); return ENODEV; } if (!node->details->rsc_discovery_enabled) { - printf("Unable to clean up %s because resource discovery disabled on %s\n", - rsc->id, host_uname); + out->err(out, "Unable to clean up %s because resource discovery disabled on %s", + rsc->id, host_uname); return EOPNOTSUPP; } if (controld_api == NULL) { - printf("Dry run: skipping clean-up of %s on %s due to CIB_file\n", - rsc->id, host_uname); + out->err(out, "Dry run: skipping clean-up of %s on %s due to CIB_file", + rsc->id, host_uname); return pcmk_rc_ok; } rc = clear_rsc_fail_attrs(rsc, operation, interval_spec, node); if (rc != pcmk_rc_ok) { - printf("Unable to clean up %s failures on %s: %s\n", - rsc->id, host_uname, pcmk_rc_str(rc)); + out->err(out, "Unable to clean up %s failures on %s: %s", + rsc->id, host_uname, pcmk_rc_str(rc)); return rc; } if (just_failures) { - rc = clear_rsc_failures(controld_api, host_uname, rsc->id, operation, + rc = clear_rsc_failures(out, controld_api, host_uname, rsc->id, operation, interval_spec, data_set); } else { - rc = clear_rsc_history(controld_api, host_uname, rsc->id, data_set); + rc = clear_rsc_history(out, controld_api, host_uname, rsc->id, data_set); } if (rc != pcmk_rc_ok) { - printf("Cleaned %s failures on %s, but unable to clean history: %s\n", - rsc->id, host_uname, pcmk_strerror(rc)); + out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s", + rsc->id, host_uname, pcmk_strerror(rc)); } else { - printf("Cleaned up %s on %s\n", rsc->id, host_uname); + out->info(out, "Cleaned up %s on %s", rsc->id, host_uname); } return rc; } // \return Standard Pacemaker return code int -cli_cleanup_all(pcmk_ipc_api_t *controld_api, const char *node_name, - const char *operation, const char *interval_spec, - pe_working_set_t *data_set) +cli_cleanup_all(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, + const char *node_name, const char *operation, + const char *interval_spec, pe_working_set_t *data_set) { int rc = pcmk_rc_ok; int attr_options = pcmk__node_attr_none; const char *display_name = node_name? node_name : "all nodes"; if (controld_api == NULL) { - printf("Dry run: skipping clean-up of %s due to CIB_file\n", - display_name); + out->info(out, "Dry run: skipping clean-up of %s due to CIB_file", + display_name); return rc; } if (node_name) { pe_node_t *node = pe_find_node(data_set->nodes, node_name); if (node == NULL) { - CMD_ERR("Unknown node: %s", node_name); + out->err(out, "Unknown node: %s", node_name); return ENXIO; } if (pe__is_guest_or_remote_node(node)) { attr_options |= pcmk__node_attr_remote; } } rc = pcmk__node_attr_request_clear(NULL, node_name, NULL, operation, interval_spec, NULL, attr_options); if (rc != pcmk_rc_ok) { - printf("Unable to clean up all failures on %s: %s\n", - display_name, pcmk_rc_str(rc)); + out->err(out, "Unable to clean up all failures on %s: %s", + display_name, pcmk_rc_str(rc)); return rc; } if (node_name) { - rc = clear_rsc_failures(controld_api, node_name, NULL, + rc = clear_rsc_failures(out, controld_api, node_name, NULL, operation, interval_spec, data_set); if (rc != pcmk_rc_ok) { - printf("Cleaned all resource failures on %s, but unable to clean history: %s\n", - node_name, pcmk_strerror(rc)); + out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s", + node_name, pcmk_strerror(rc)); return rc; } } else { for (GList *iter = data_set->nodes; iter; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; - rc = clear_rsc_failures(controld_api, node->details->uname, NULL, + rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL, operation, interval_spec, data_set); if (rc != pcmk_rc_ok) { - printf("Cleaned all resource failures on all nodes, but unable to clean history: %s\n", - pcmk_strerror(rc)); + out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s", + pcmk_strerror(rc)); return rc; } } } - printf("Cleaned up all resources on %s\n", display_name); + out->info(out, "Cleaned up all resources on %s", display_name); return rc; } -void -cli_resource_check(cib_t * cib_conn, pe_resource_t *rsc) +int +cli_resource_check(pcmk__output_t *out, cib_t * cib_conn, pe_resource_t *rsc) { - bool printed = false; char *role_s = NULL; char *managed = NULL; pe_resource_t *parent = uber_parent(rsc); + int rc = pcmk_rc_no_output; + resource_checks_t *checks = NULL; - find_resource_attr(cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id, + find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id, NULL, NULL, NULL, XML_RSC_ATTR_MANAGED, &managed); - find_resource_attr(cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id, + find_resource_attr(out, cib_conn, XML_NVPAIR_ATTR_VALUE, parent->id, NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &role_s); - if(role_s) { - enum rsc_role_e role = text2role(role_s); - - free(role_s); - if(role == RSC_ROLE_UNKNOWN) { - // Treated as if unset - - } else if(role == RSC_ROLE_STOPPED) { - printf("\n * Configuration specifies '%s' should remain stopped\n", - parent->id); - printed = true; + checks = cli_check_resource(rsc, role_s, managed); - } else if (pcmk_is_set(parent->flags, pe_rsc_promotable) - && (role == RSC_ROLE_SLAVE)) { - printf("\n * Configuration specifies '%s' should not be promoted\n", - parent->id); - printed = true; - } + if (checks->flags != 0 || checks->lock_node != NULL) { + rc = out->message(out, "resource-check", checks); } - if (managed && !crm_is_true(managed)) { - printf("%s * Configuration prevents cluster from stopping or starting unmanaged '%s'\n", - (printed? "" : "\n"), parent->id); - printed = true; - } + free(role_s); free(managed); - - if (rsc->lock_node) { - printf("%s * '%s' is locked to node %s due to shutdown\n", - (printed? "" : "\n"), parent->id, rsc->lock_node->details->uname); - } - - if (printed) { - printf("\n"); - } + free(checks); + return rc; } // \return Standard Pacemaker return code int -cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname, - const char *rsc_id, pe_working_set_t *data_set) +cli_resource_fail(pcmk__output_t *out, pcmk_ipc_api_t *controld_api, + const char *host_uname, const char *rsc_id, + pe_working_set_t *data_set) { crm_notice("Failing %s on %s", rsc_id, host_uname); - return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, data_set); + return send_lrm_rsc_op(out, controld_api, true, host_uname, rsc_id, data_set); } static GHashTable * generate_resource_params(pe_resource_t * rsc, pe_working_set_t * data_set) { GHashTable *params = NULL; GHashTable *meta = NULL; GHashTable *combined = NULL; GHashTableIter iter; if (!rsc) { crm_err("Resource does not exist in config"); return NULL; } params = crm_str_table_new(); meta = crm_str_table_new(); combined = crm_str_table_new(); get_rsc_attributes(params, rsc, NULL /* TODO: Pass in local node */ , data_set); get_meta_attributes(meta, rsc, NULL /* TODO: Pass in local node */ , data_set); if (params) { char *key = NULL; char *value = NULL; g_hash_table_iter_init(&iter, params); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) { g_hash_table_insert(combined, strdup(key), strdup(value)); } g_hash_table_destroy(params); } if (meta) { char *key = NULL; char *value = NULL; g_hash_table_iter_init(&iter, meta); while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) { char *crm_name = crm_meta_name(key); g_hash_table_insert(combined, crm_name, strdup(value)); } g_hash_table_destroy(meta); } return combined; } -static bool resource_is_running_on(pe_resource_t *rsc, const char *host) +bool resource_is_running_on(pe_resource_t *rsc, const char *host) { bool found = TRUE; GListPtr hIter = NULL; GListPtr hosts = NULL; if(rsc == NULL) { return FALSE; } rsc->fns->location(rsc, &hosts, TRUE); for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) { pe_node_t *node = (pe_node_t *) hIter->data; if(strcmp(host, node->details->uname) == 0) { crm_trace("Resource %s is running on %s\n", rsc->id, host); goto done; } else if(strcmp(host, node->details->id) == 0) { crm_trace("Resource %s is running on %s\n", rsc->id, host); goto done; } } if(host != NULL) { crm_trace("Resource %s is not running on: %s\n", rsc->id, host); found = FALSE; } else if(host == NULL && hosts == NULL) { crm_trace("Resource %s is not running\n", rsc->id); found = FALSE; } done: g_list_free(hosts); return found; } /*! * \internal * \brief Create a list of all resources active on host from a given list * * \param[in] host Name of host to check whether resources are active * \param[in] rsc_list List of resources to check * * \return New list of resources from list that are active on host */ static GList * get_active_resources(const char *host, GList *rsc_list) { GList *rIter = NULL; GList *active = NULL; for (rIter = rsc_list; rIter != NULL; rIter = rIter->next) { pe_resource_t *rsc = (pe_resource_t *) rIter->data; /* Expand groups to their members, because if we're restarting a member * other than the first, we can't otherwise tell which resources are * stopping and starting. */ if (rsc->variant == pe_group) { active = g_list_concat(active, get_active_resources(host, rsc->children)); } else if (resource_is_running_on(rsc, host)) { active = g_list_append(active, strdup(rsc->id)); } } return active; } static void dump_list(GList *items, const char *tag) { int lpc = 0; GList *item = NULL; for (item = items; item != NULL; item = item->next) { crm_trace("%s[%d]: %s", tag, lpc, (char*)item->data); lpc++; } } -static void display_list(GList *items, const char *tag) +static void display_list(pcmk__output_t *out, GList *items, const char *tag) { GList *item = NULL; for (item = items; item != NULL; item = item->next) { - fprintf(stdout, "%s%s\n", tag, (const char *)item->data); + out->info(out, "%s%s", tag, (const char *)item->data); } } /*! * \internal * \brief Upgrade XML to latest schema version and use it as working set input * * This also updates the working set timestamp to the current time. * * \param[in] data_set Working set instance to update * \param[in] xml XML to use as input * * \return Standard Pacemaker return code * \note On success, caller is responsible for freeing memory allocated for * data_set->now. * \todo This follows the example of other callers of cli_config_update() * and returns ENOKEY ("Required key not available") if that fails, * but perhaps pcmk_rc_schema_validation would be better in that case. */ int update_working_set_xml(pe_working_set_t *data_set, xmlNode **xml) { if (cli_config_update(xml, NULL, FALSE) == FALSE) { return ENOKEY; } data_set->input = *xml; data_set->now = crm_time_new(NULL); return pcmk_rc_ok; } /*! * \internal * \brief Update a working set's XML input based on a CIB query * * \param[in] data_set Data set instance to initialize * \param[in] cib Connection to the CIB manager * * \return Standard Pacemaker return code * \note On success, caller is responsible for freeing memory allocated for * data_set->input and data_set->now. */ static int -update_working_set_from_cib(pe_working_set_t * data_set, cib_t *cib) +update_working_set_from_cib(pcmk__output_t *out, pe_working_set_t * data_set, + cib_t *cib) { xmlNode *cib_xml_copy = NULL; int rc = pcmk_rc_ok; rc = cib->cmds->query(cib, NULL, &cib_xml_copy, cib_scope_local | cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { - fprintf(stderr, "Could not obtain the current CIB: %s (%d)\n", pcmk_strerror(rc), rc); + out->err(out, "Could not obtain the current CIB: %s (%d)", pcmk_strerror(rc), rc); return rc; } rc = update_working_set_xml(data_set, &cib_xml_copy); if (rc != pcmk_rc_ok) { - fprintf(stderr, "Could not upgrade the current CIB XML\n"); + out->err(out, "Could not upgrade the current CIB XML"); free_xml(cib_xml_copy); return rc; } return rc; } // \return Standard Pacemaker return code static int -update_dataset(cib_t *cib, pe_working_set_t * data_set, bool simulate) +update_dataset(pcmk__output_t *out, cib_t *cib, pe_working_set_t * data_set, + bool simulate) { char *pid = NULL; char *shadow_file = NULL; cib_t *shadow_cib = NULL; int rc = pcmk_rc_ok; pe_reset_working_set(data_set); - rc = update_working_set_from_cib(data_set, cib); + rc = update_working_set_from_cib(out, data_set, cib); if (rc != pcmk_rc_ok) { return rc; } if(simulate) { pid = pcmk__getpid_s(); shadow_cib = cib_shadow_new(pid); shadow_file = get_shadow_file(pid); if (shadow_cib == NULL) { - fprintf(stderr, "Could not create shadow cib: '%s'\n", pid); + out->err(out, "Could not create shadow cib: '%s'", pid); rc = ENXIO; goto cleanup; } rc = write_xml_file(data_set->input, shadow_file, FALSE); if (rc < 0) { - fprintf(stderr, "Could not populate shadow cib: %s (%d)\n", pcmk_strerror(rc), rc); + out->err(out, "Could not populate shadow cib: %s (%d)", pcmk_strerror(rc), rc); goto cleanup; } rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { - fprintf(stderr, "Could not connect to shadow cib: %s (%d)\n", pcmk_strerror(rc), rc); + out->err(out, "Could not connect to shadow cib: %s (%d)", pcmk_strerror(rc), rc); goto cleanup; } pcmk__schedule_actions(data_set, data_set->input, NULL); run_simulation(data_set, shadow_cib, NULL, TRUE); - rc = update_dataset(shadow_cib, data_set, FALSE); + rc = update_dataset(out, shadow_cib, data_set, FALSE); } else { cluster_status(data_set); } cleanup: /* Do not free data_set->input here, we need rsc->xml to be valid later on */ cib_delete(shadow_cib); free(pid); if(shadow_file) { unlink(shadow_file); free(shadow_file); } return rc; } static int max_delay_for_resource(pe_working_set_t * data_set, pe_resource_t *rsc) { int delay = 0; int max_delay = 0; if(rsc && rsc->children) { GList *iter = NULL; for(iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = (pe_resource_t *)iter->data; delay = max_delay_for_resource(data_set, child); if(delay > max_delay) { double seconds = delay / 1000.0; crm_trace("Calculated new delay of %.1fs due to %s", seconds, child->id); max_delay = delay; } } } else if(rsc) { char *key = crm_strdup_printf("%s_%s_0", rsc->id, RSC_STOP); pe_action_t *stop = custom_action(rsc, key, RSC_STOP, NULL, TRUE, FALSE, data_set); const char *value = g_hash_table_lookup(stop->meta, XML_ATTR_TIMEOUT); max_delay = value? (int) crm_parse_ll(value, NULL) : -1; pe_free_action(stop); } return max_delay; } static int max_delay_in(pe_working_set_t * data_set, GList *resources) { int max_delay = 0; GList *item = NULL; for (item = resources; item != NULL; item = item->next) { int delay = 0; pe_resource_t *rsc = pe_find_resource(data_set->resources, (const char *)item->data); delay = max_delay_for_resource(data_set, rsc); if(delay > max_delay) { double seconds = delay / 1000.0; crm_trace("Calculated new delay of %.1fs due to %s", seconds, rsc->id); max_delay = delay; } } return 5 + (max_delay / 1000); } #define waiting_for_starts(d, r, h) ((d != NULL) || \ (resource_is_running_on((r), (h)) == FALSE)) /*! * \internal * \brief Restart a resource (on a particular host if requested). * * \param[in] rsc The resource to restart * \param[in] host The host to restart the resource on (or NULL for all) * \param[in] timeout_ms Consider failed if actions do not complete in this time * (specified in milliseconds, but a two-second * granularity is actually used; if 0, a timeout will be * calculated based on the resource timeout) * \param[in] cib Connection to the CIB manager * * \return Standard Pacemaker return code (exits on certain failures) */ int -cli_resource_restart(pe_resource_t *rsc, const char *host, const char *move_lifetime, - int timeout_ms, cib_t *cib, int cib_options, - gboolean promoted_role_only, gboolean force) +cli_resource_restart(pcmk__output_t *out, pe_resource_t *rsc, const char *host, + const char *move_lifetime, int timeout_ms, cib_t *cib, + int cib_options, gboolean promoted_role_only, gboolean force) { int rc = pcmk_rc_ok; int lpc = 0; int before = 0; int step_timeout_s = 0; int sleep_interval = 2; int timeout = timeout_ms / 1000; bool stop_via_ban = FALSE; char *rsc_id = NULL; char *orig_target_role = NULL; GList *list_delta = NULL; GList *target_active = NULL; GList *current_active = NULL; GList *restart_target_active = NULL; pe_working_set_t *data_set = NULL; if(resource_is_running_on(rsc, host) == FALSE) { const char *id = rsc->clone_name?rsc->clone_name:rsc->id; if(host) { - printf("%s is not running on %s and so cannot be restarted\n", id, host); + out->err(out, "%s is not running on %s and so cannot be restarted", id, host); } else { - printf("%s is not running anywhere and so cannot be restarted\n", id); + out->err(out, "%s is not running anywhere and so cannot be restarted", id); } return ENXIO; } rsc_id = strdup(rsc->id); if ((pe_rsc_is_clone(rsc) || pe_bundle_replicas(rsc)) && host) { stop_via_ban = TRUE; } /* grab full cib determine originally active resources disable or ban poll cib and watch for affected resources to get stopped without --timeout, calculate the stop timeout for each step and wait for that if we hit --timeout or the service timeout, re-enable or un-ban, report failure and indicate which resources we couldn't take down if everything stopped, re-enable or un-ban poll cib and watch for affected resources to get started without --timeout, calculate the start timeout for each step and wait for that if we hit --timeout or the service timeout, report (different) failure and indicate which resources we couldn't bring back up report success Optimizations: - use constraints to determine ordered list of affected resources - Allow a --no-deps option (aka. --force-restart) */ data_set = pe_new_working_set(); if (data_set == NULL) { crm_perror(LOG_ERR, "Could not allocate working set"); rc = ENOMEM; goto done; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); - rc = update_dataset(cib, data_set, FALSE); + rc = update_dataset(out, cib, data_set, FALSE); if(rc != pcmk_rc_ok) { - fprintf(stdout, "Could not get new resource list: %s (%d)\n", pcmk_strerror(rc), rc); + out->err(out, "Could not get new resource list: %s (%d)", pcmk_strerror(rc), rc); goto done; } restart_target_active = get_active_resources(host, data_set->resources); current_active = get_active_resources(host, data_set->resources); dump_list(current_active, "Origin"); if (stop_via_ban) { /* Stop the clone or bundle instance by banning it from the host */ - BE_QUIET = TRUE; - rc = cli_resource_ban(rsc_id, host, move_lifetime, NULL, cib, + out->quiet = true; + rc = cli_resource_ban(out, rsc_id, host, move_lifetime, NULL, cib, cib_options, promoted_role_only); } else { /* Stop the resource by setting target-role to Stopped. * Remember any existing target-role so we can restore it later * (though it only makes any difference if it's Slave). */ char *lookup_id = clone_strip(rsc->id); - find_resource_attr(cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL, + find_resource_attr(out, cib, XML_NVPAIR_ATTR_VALUE, lookup_id, NULL, NULL, NULL, XML_RSC_ATTR_TARGET_ROLE, &orig_target_role); free(lookup_id); - rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, - XML_RSC_ATTR_TARGET_ROLE, + rc = cli_resource_update_attribute(out, rsc, rsc_id, NULL, XML_TAG_META_SETS, + NULL, XML_RSC_ATTR_TARGET_ROLE, RSC_STOPPED, FALSE, cib, cib_options, data_set, force); } if(rc != pcmk_rc_ok) { - fprintf(stderr, "Could not set target-role for %s: %s (%d)\n", rsc_id, pcmk_strerror(rc), rc); + out->err(out, "Could not set target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc); if (current_active) { g_list_free_full(current_active, free); } if (restart_target_active) { g_list_free_full(restart_target_active, free); } goto done; } - rc = update_dataset(cib, data_set, TRUE); + rc = update_dataset(out, cib, data_set, TRUE); if(rc != pcmk_rc_ok) { - fprintf(stderr, "Could not determine which resources would be stopped\n"); + out->err(out, "Could not determine which resources would be stopped"); goto failure; } target_active = get_active_resources(host, data_set->resources); dump_list(target_active, "Target"); list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp); - fprintf(stdout, "Waiting for %d resources to stop:\n", g_list_length(list_delta)); - display_list(list_delta, " * "); + out->info(out, "Waiting for %d resources to stop:", g_list_length(list_delta)); + display_list(out, list_delta, " * "); step_timeout_s = timeout / sleep_interval; while (list_delta != NULL) { before = g_list_length(list_delta); if(timeout_ms == 0) { step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval; } /* We probably don't need the entire step timeout */ for(lpc = 0; (lpc < step_timeout_s) && (list_delta != NULL); lpc++) { sleep(sleep_interval); if(timeout) { timeout -= sleep_interval; crm_trace("%ds remaining", timeout); } - rc = update_dataset(cib, data_set, FALSE); + rc = update_dataset(out, cib, data_set, FALSE); if(rc != pcmk_rc_ok) { - fprintf(stderr, "Could not determine which resources were stopped\n"); + out->err(out, "Could not determine which resources were stopped"); goto failure; } if (current_active) { g_list_free_full(current_active, free); } current_active = get_active_resources(host, data_set->resources); g_list_free(list_delta); list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp); dump_list(current_active, "Current"); dump_list(list_delta, "Delta"); } crm_trace("%d (was %d) resources remaining", g_list_length(list_delta), before); if(before == g_list_length(list_delta)) { /* aborted during stop phase, print the contents of list_delta */ - fprintf(stderr, "Could not complete shutdown of %s, %d resources remaining\n", rsc_id, g_list_length(list_delta)); - display_list(list_delta, " * "); + out->info(out, "Could not complete shutdown of %s, %d resources remaining", rsc_id, g_list_length(list_delta)); + display_list(out, list_delta, " * "); rc = ETIME; goto failure; } } if (stop_via_ban) { rc = cli_resource_clear(rsc_id, host, NULL, cib, cib_options, TRUE, force); } else if (orig_target_role) { - rc = cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, + rc = cli_resource_update_attribute(out, rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, orig_target_role, FALSE, cib, cib_options, data_set, force); free(orig_target_role); orig_target_role = NULL; } else { - rc = cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, - XML_RSC_ATTR_TARGET_ROLE, cib, + rc = cli_resource_delete_attribute(out, rsc, rsc_id, NULL, XML_TAG_META_SETS, + NULL, XML_RSC_ATTR_TARGET_ROLE, cib, cib_options, data_set, force); } if(rc != pcmk_rc_ok) { - fprintf(stderr, "Could not unset target-role for %s: %s (%d)\n", rsc_id, pcmk_strerror(rc), rc); + out->err(out, "Could not unset target-role for %s: %s (%d)", rsc_id, pcmk_strerror(rc), rc); goto done; } if (target_active) { g_list_free_full(target_active, free); } target_active = restart_target_active; list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp); - fprintf(stdout, "Waiting for %d resources to start again:\n", g_list_length(list_delta)); - display_list(list_delta, " * "); + out->info(out, "Waiting for %d resources to start again:", g_list_length(list_delta)); + display_list(out, list_delta, " * "); step_timeout_s = timeout / sleep_interval; while (waiting_for_starts(list_delta, rsc, host)) { before = g_list_length(list_delta); if(timeout_ms == 0) { step_timeout_s = max_delay_in(data_set, list_delta) / sleep_interval; } /* We probably don't need the entire step timeout */ for (lpc = 0; (lpc < step_timeout_s) && waiting_for_starts(list_delta, rsc, host); lpc++) { sleep(sleep_interval); if(timeout) { timeout -= sleep_interval; crm_trace("%ds remaining", timeout); } - rc = update_dataset(cib, data_set, FALSE); + rc = update_dataset(out, cib, data_set, FALSE); if(rc != pcmk_rc_ok) { - fprintf(stderr, "Could not determine which resources were started\n"); + out->err(out, "Could not determine which resources were started"); goto failure; } if (current_active) { g_list_free_full(current_active, free); } /* It's OK if dependent resources moved to a different node, * so we check active resources on all nodes. */ current_active = get_active_resources(NULL, data_set->resources); g_list_free(list_delta); list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp); dump_list(current_active, "Current"); dump_list(list_delta, "Delta"); } if(before == g_list_length(list_delta)) { /* aborted during start phase, print the contents of list_delta */ - fprintf(stdout, "Could not complete restart of %s, %d resources remaining\n", rsc_id, g_list_length(list_delta)); - display_list(list_delta, " * "); + out->info(out, "Could not complete restart of %s, %d resources remaining", rsc_id, g_list_length(list_delta)); + display_list(out, list_delta, " * "); rc = ETIME; goto failure; } } rc = pcmk_rc_ok; goto done; failure: if (stop_via_ban) { cli_resource_clear(rsc_id, host, NULL, cib, cib_options, TRUE, force); } else if (orig_target_role) { - cli_resource_update_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, + cli_resource_update_attribute(out, rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, orig_target_role, FALSE, cib, cib_options, data_set, force); free(orig_target_role); } else { - cli_resource_delete_attribute(rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, + cli_resource_delete_attribute(out, rsc, rsc_id, NULL, XML_TAG_META_SETS, NULL, XML_RSC_ATTR_TARGET_ROLE, cib, cib_options, data_set, force); } done: if (list_delta) { g_list_free(list_delta); } if (current_active) { g_list_free_full(current_active, free); } if (target_active && (target_active != restart_target_active)) { g_list_free_full(target_active, free); } if (restart_target_active) { g_list_free_full(restart_target_active, free); } free(rsc_id); pe_free_working_set(data_set); return rc; } static inline bool action_is_pending(pe_action_t *action) { if (pcmk_any_flags_set(action->flags, pe_action_optional|pe_action_pseudo) || !pcmk_is_set(action->flags, pe_action_runnable) || pcmk__str_eq("notify", action->task, pcmk__str_casei)) { return false; } return true; } /*! * \internal * \brief Return TRUE if any actions in a list are pending * * \param[in] actions List of actions to check * * \return TRUE if any actions in the list are pending, FALSE otherwise */ static bool actions_are_pending(GListPtr actions) { GListPtr action; for (action = actions; action != NULL; action = action->next) { pe_action_t *a = (pe_action_t *)action->data; if (action_is_pending(a)) { crm_notice("Waiting for %s (flags=0x%.8x)", a->uuid, a->flags); return TRUE; } } return FALSE; } -/*! - * \internal - * \brief Print pending actions to stderr - * - * \param[in] actions List of actions to check - * - * \return void - */ static void -print_pending_actions(GListPtr actions) +print_pending_actions(pcmk__output_t *out, GListPtr actions) { GListPtr action; - fprintf(stderr, "Pending actions:\n"); + out->info(out, "Pending actions:"); for (action = actions; action != NULL; action = action->next) { pe_action_t *a = (pe_action_t *) action->data; - if (action_is_pending(a)) { - fprintf(stderr, "\tAction %d: %s", a->id, a->uuid); - if (a->node) { - fprintf(stderr, "\ton %s", a->node->details->uname); - } - fprintf(stderr, "\n"); + if (!action_is_pending(a)) { + continue; + } + + if (a->node) { + out->info(out, "\tAction %d: %s\ton %s", a->id, a->uuid, a->node->details->uname); + } else { + out->info(out, "\tAction %d: %s", a->id, a->uuid); } } } /* For --wait, timeout (in seconds) to use if caller doesn't specify one */ #define WAIT_DEFAULT_TIMEOUT_S (60 * 60) /* For --wait, how long to sleep between cluster state checks */ #define WAIT_SLEEP_S (2) /*! * \internal * \brief Wait until all pending cluster actions are complete * * This waits until either the CIB's transition graph is idle or a timeout is * reached. * * \param[in] timeout_ms Consider failed if actions do not complete in this time * (specified in milliseconds, but one-second granularity * is actually used; if 0, a default will be used) * \param[in] cib Connection to the CIB manager * * \return Standard Pacemaker return code */ int -wait_till_stable(int timeout_ms, cib_t * cib) +wait_till_stable(pcmk__output_t *out, int timeout_ms, cib_t * cib) { pe_working_set_t *data_set = NULL; int rc = pcmk_rc_ok; int timeout_s = timeout_ms? ((timeout_ms + 999) / 1000) : WAIT_DEFAULT_TIMEOUT_S; time_t expire_time = time(NULL) + timeout_s; time_t time_diff; - bool printed_version_warning = BE_QUIET; // i.e. don't print if quiet + bool printed_version_warning = out->is_quiet(out); // i.e. don't print if quiet data_set = pe_new_working_set(); if (data_set == NULL) { return ENOMEM; } pe__set_working_set_flags(data_set, pe_flag_no_counts|pe_flag_no_compat); do { /* Abort if timeout is reached */ time_diff = expire_time - time(NULL); if (time_diff > 0) { crm_info("Waiting up to %ld seconds for cluster actions to complete", time_diff); } else { - print_pending_actions(data_set->actions); + print_pending_actions(out, data_set->actions); pe_free_working_set(data_set); return ETIME; } if (rc == pcmk_rc_ok) { /* this avoids sleep on first loop iteration */ sleep(WAIT_SLEEP_S); } /* Get latest transition graph */ pe_reset_working_set(data_set); - rc = update_working_set_from_cib(data_set, cib); + rc = update_working_set_from_cib(out, data_set, cib); if (rc != pcmk_rc_ok) { pe_free_working_set(data_set); return rc; } pcmk__schedule_actions(data_set, data_set->input, NULL); if (!printed_version_warning) { /* If the DC has a different version than the local node, the two * could come to different conclusions about what actions need to be * done. Warn the user in this case. * * @TODO A possible long-term solution would be to reimplement the * wait as a new controller operation that would be forwarded to the * DC. However, that would have potential problems of its own. */ const char *dc_version = g_hash_table_lookup(data_set->config_hash, "dc-version"); if (!pcmk__str_eq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION, pcmk__str_casei)) { - printf("warning: wait option may not work properly in " - "mixed-version cluster\n"); + out->info(out, "warning: wait option may not work properly in " + "mixed-version cluster"); printed_version_warning = TRUE; } } } while (actions_are_pending(data_set->actions)); pe_free_working_set(data_set); return rc; } crm_exit_t -cli_resource_execute_from_params(const char *rsc_name, const char *rsc_class, - const char *rsc_prov, const char *rsc_type, - const char *action, GHashTable *params, - GHashTable *override_hash, int timeout_ms, - int resource_verbose, gboolean force) +cli_resource_execute_from_params(pcmk__output_t *out, const char *rsc_name, + const char *rsc_class, const char *rsc_prov, + const char *rsc_type, const char *action, + GHashTable *params, GHashTable *override_hash, + int timeout_ms, int resource_verbose, gboolean force) { GHashTable *params_copy = NULL; crm_exit_t exit_code = CRM_EX_OK; svc_action_t *op = NULL; if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { - CMD_ERR("Sorry, the %s option doesn't support %s resources yet", - action, rsc_class); + out->err(out, "Sorry, the %s option doesn't support %s resources yet", + action, rsc_class); crm_exit(CRM_EX_UNIMPLEMENT_FEATURE); } /* If no timeout was provided, grab the default. */ if (timeout_ms == 0) { timeout_ms = crm_get_msec(CRM_DEFAULT_OP_TIMEOUT_S); } /* add meta_timeout env needed by some resource agents */ g_hash_table_insert(params, strdup("CRM_meta_timeout"), crm_strdup_printf("%d", timeout_ms)); /* add crm_feature_set env needed by some resource agents */ g_hash_table_insert(params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET)); /* resources_action_create frees the params hash table it's passed, but we * may need to reuse it in a second call to resources_action_create. Thus * we'll make a copy here so that gets freed and the original remains for * reuse. */ params_copy = crm_str_table_dup(params); op = resources_action_create(rsc_name, rsc_class, rsc_prov, rsc_type, action, 0, timeout_ms, params_copy, 0); if (op == NULL) { /* Re-run with stderr enabled so we can display a sane error message */ crm_enable_stderr(TRUE); params_copy = crm_str_table_dup(params); op = resources_action_create(rsc_name, rsc_class, rsc_prov, rsc_type, action, 0, timeout_ms, params_copy, 0); /* Callers of cli_resource_execute expect that the params hash table will * be freed. That function uses this one, so for that reason and for * making the two act the same, we should free the hash table here too. */ g_hash_table_destroy(params); /* We know op will be NULL, but this makes static analysis happy */ services_action_free(op); crm_exit(CRM_EX_DATAERR); return exit_code; // Never reached, but helps static analysis } setenv("HA_debug", resource_verbose > 0 ? "1" : "0", 1); if(resource_verbose > 1) { setenv("OCF_TRACE_RA", "1", 1); } /* A resource agent using the standard ocf-shellfuncs library will not print * messages to stderr if it doesn't have a controlling terminal (e.g. if * crm_resource is called via script or ssh). This forces it to do so. */ setenv("OCF_TRACE_FILE", "/dev/stderr", 0); if (override_hash) { GHashTableIter iter; char *name = NULL; char *value = NULL; g_hash_table_iter_init(&iter, override_hash); while (g_hash_table_iter_next(&iter, (gpointer *) & name, (gpointer *) & value)) { - printf("Overriding the cluster configuration for '%s' with '%s' = '%s'\n", - rsc_name, name, value); + out->info(out, "Overriding the cluster configuration for '%s' with '%s' = '%s'", + rsc_name, name, value); g_hash_table_replace(op->params, strdup(name), strdup(value)); } } if (services_action_sync(op)) { - int more, lpc, last; - char *local_copy = NULL; - exit_code = op->rc; if (op->status == PCMK_LRM_OP_DONE) { - printf("Operation %s for %s (%s:%s:%s) returned: '%s' (%d)\n", - action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type, - services_ocf_exitcode_str(op->rc), op->rc); + out->info(out, "Operation %s for %s (%s:%s:%s) returned: '%s' (%d)", + action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type, + services_ocf_exitcode_str(op->rc), op->rc); } else { - printf("Operation %s for %s (%s:%s:%s) failed: '%s' (%d)\n", - action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type, - services_lrm_status_str(op->status), op->status); + out->info(out, "Operation %s for %s (%s:%s:%s) failed: '%s' (%d)", + action, rsc_name, rsc_class, rsc_prov ? rsc_prov : "", rsc_type, + services_lrm_status_str(op->status), op->status); } /* hide output for validate-all if not in verbose */ if (resource_verbose == 0 && pcmk__str_eq(action, "validate-all", pcmk__str_casei)) goto done; - if (op->stdout_data) { - local_copy = strdup(op->stdout_data); - more = strlen(local_copy); - last = 0; - - for (lpc = 0; lpc < more; lpc++) { - if (local_copy[lpc] == '\n' || local_copy[lpc] == 0) { - local_copy[lpc] = 0; - printf(" > stdout: %s\n", local_copy + last); - last = lpc + 1; - } - } - free(local_copy); - } - if (op->stderr_data) { - local_copy = strdup(op->stderr_data); - more = strlen(local_copy); - last = 0; - - for (lpc = 0; lpc < more; lpc++) { - if (local_copy[lpc] == '\n' || local_copy[lpc] == 0) { - local_copy[lpc] = 0; - printf(" > stderr: %s\n", local_copy + last); - last = lpc + 1; - } - } - free(local_copy); + if (op->stdout_data || op->stderr_data) { + out->subprocess_output(out, op->rc, op->stdout_data, op->stderr_data); } } else { exit_code = op->rc == 0 ? CRM_EX_ERROR : op->rc; } done: services_action_free(op); /* See comment above about why we free params here. */ g_hash_table_destroy(params); return exit_code; } crm_exit_t -cli_resource_execute(pe_resource_t *rsc, const char *requested_name, - const char *rsc_action, GHashTable *override_hash, - int timeout_ms, cib_t * cib, pe_working_set_t *data_set, +cli_resource_execute(pcmk__output_t *out, pe_resource_t *rsc, + const char *requested_name, const char *rsc_action, + GHashTable *override_hash, int timeout_ms, + cib_t * cib, pe_working_set_t *data_set, int resource_verbose, gboolean force) { crm_exit_t exit_code = CRM_EX_OK; const char *rid = NULL; const char *rtype = NULL; const char *rprov = NULL; const char *rclass = NULL; const char *action = NULL; GHashTable *params = NULL; if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) { action = "validate-all"; } else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) { action = "monitor"; } else if (pcmk__str_eq(rsc_action, "force-stop", pcmk__str_casei)) { action = rsc_action+6; } else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote", "force-promote", NULL)) { action = rsc_action+6; if(pe_rsc_is_clone(rsc)) { - int rc = cli_resource_search(rsc, requested_name, data_set); - if(rc > 0 && force == FALSE) { - CMD_ERR("It is not safe to %s %s here: the cluster claims it is already active", - action, rsc->id); - CMD_ERR("Try setting target-role=Stopped first or specifying " - "the force option"); - crm_exit(CRM_EX_UNSAFE); + GListPtr rscs = cli_resource_search(out, rsc, requested_name, data_set); + if(rscs != NULL && force == FALSE) { + out->err(out, "It is not safe to %s %s here: the cluster claims it is already active", + action, rsc->id); + out->err(out, "Try setting target-role=Stopped first or specifying " + "the force option"); + return CRM_EX_UNSAFE; } } } else { action = rsc_action; } if(pe_rsc_is_clone(rsc)) { /* Grab the first child resource in the hope it's not a group */ rsc = rsc->children->data; } if(rsc->variant == pe_group) { - CMD_ERR("Sorry, the %s option doesn't support group resources", - rsc_action); - crm_exit(CRM_EX_UNIMPLEMENT_FEATURE); + out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action); + return CRM_EX_UNIMPLEMENT_FEATURE; } rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); rprov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE); params = generate_resource_params(rsc, data_set); if (timeout_ms == 0) { timeout_ms = pe_get_configured_timeout(rsc, action, data_set); } rid = pe_rsc_is_anon_clone(rsc->parent)? requested_name : rsc->id; - exit_code = cli_resource_execute_from_params(rid, rclass, rprov, rtype, action, + exit_code = cli_resource_execute_from_params(out, rid, rclass, rprov, rtype, action, params, override_hash, timeout_ms, resource_verbose, force); return exit_code; } // \return Standard Pacemaker return code int -cli_resource_move(pe_resource_t *rsc, const char *rsc_id, const char *host_name, - const char *move_lifetime, cib_t *cib, int cib_options, - pe_working_set_t *data_set, gboolean promoted_role_only, - gboolean force) +cli_resource_move(pcmk__output_t *out, pe_resource_t *rsc, const char *rsc_id, + const char *host_name, const char *move_lifetime, cib_t *cib, + int cib_options, pe_working_set_t *data_set, + gboolean promoted_role_only, gboolean force) { int rc = pcmk_rc_ok; unsigned int count = 0; pe_node_t *current = NULL; pe_node_t *dest = pe_find_node(data_set->nodes, host_name); bool cur_is_dest = FALSE; if (dest == NULL) { return pcmk_rc_node_unknown; } if (promoted_role_only && !pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pe_resource_t *p = uber_parent(rsc); if (pcmk_is_set(p->flags, pe_rsc_promotable)) { - CMD_ERR("Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id); + out->info(out, "Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id); rsc_id = p->id; rsc = p; } else { - CMD_ERR("Ignoring master option: %s is not promotable", rsc_id); + out->info(out, "Ignoring master option: %s is not promotable", rsc_id); promoted_role_only = FALSE; } } current = pe__find_active_requires(rsc, &count); if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { GListPtr iter = NULL; unsigned int master_count = 0; pe_node_t *master_node = NULL; for(iter = rsc->children; iter; iter = iter->next) { pe_resource_t *child = (pe_resource_t *)iter->data; enum rsc_role_e child_role = child->fns->state(child, TRUE); if(child_role == RSC_ROLE_MASTER) { rsc = child; master_node = pe__current_node(child); master_count++; } } if (promoted_role_only || master_count) { count = master_count; current = master_node; } } if (count > 1) { if (pe_rsc_is_clone(rsc)) { current = NULL; } else { return pcmk_rc_multiple; } } if (current && (current->details == dest->details)) { cur_is_dest = TRUE; if (force) { crm_info("%s is already %s on %s, reinforcing placement with location constraint.", rsc_id, promoted_role_only?"promoted":"active", dest->details->uname); } else { return pcmk_rc_already; } } /* Clear any previous prefer constraints across all nodes. */ cli_resource_clear(rsc_id, NULL, data_set->nodes, cib, cib_options, FALSE, force); /* Clear any previous ban constraints on 'dest'. */ cli_resource_clear(rsc_id, dest->details->uname, data_set->nodes, cib, cib_options, TRUE, force); /* Record an explicit preference for 'dest' */ - rc = cli_resource_prefer(rsc_id, dest->details->uname, move_lifetime, + rc = cli_resource_prefer(out, rsc_id, dest->details->uname, move_lifetime, cib, cib_options, promoted_role_only); crm_trace("%s%s now prefers node %s%s", rsc->id, promoted_role_only?" (master)":"", dest->details->uname, force?"(forced)":""); /* only ban the previous location if current location != destination location. * it is possible to use -M to enforce a location without regard of where the * resource is currently located */ if(force && (cur_is_dest == FALSE)) { /* Ban the original location if possible */ if(current) { - (void)cli_resource_ban(rsc_id, current->details->uname, move_lifetime, + (void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime, NULL, cib, cib_options, promoted_role_only); } else if(count > 1) { - CMD_ERR("Resource '%s' is currently %s in %d locations. " - "One may now move to %s", - rsc_id, (promoted_role_only? "promoted" : "active"), - count, dest->details->uname); - CMD_ERR("To prevent '%s' from being %s at a specific location, " - "specify a node.", - rsc_id, (promoted_role_only? "promoted" : "active")); + out->info(out, "Resource '%s' is currently %s in %d locations. " + "One may now move to %s", + rsc_id, (promoted_role_only? "promoted" : "active"), + count, dest->details->uname); + out->info(out, "To prevent '%s' from being %s at a specific location, " + "specify a node.", + rsc_id, (promoted_role_only? "promoted" : "active")); } else { crm_trace("Not banning %s from its current location: not active", rsc_id); } } return rc; } - -static void -cli_resource_why_without_rsc_and_host(cib_t *cib_conn,GListPtr resources) -{ - GListPtr lpc = NULL; - GListPtr hosts = NULL; - - for (lpc = resources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *rsc = (pe_resource_t *) lpc->data; - rsc->fns->location(rsc, &hosts, TRUE); - - if (hosts == NULL) { - printf("Resource %s is not running\n", rsc->id); - } else { - printf("Resource %s is running\n", rsc->id); - } - - cli_resource_check(cib_conn, rsc); - g_list_free(hosts); - hosts = NULL; - } - -} - -static void -cli_resource_why_with_rsc_and_host(cib_t *cib_conn, GListPtr resources, - pe_resource_t *rsc, const char *host_uname) -{ - if (resource_is_running_on(rsc, host_uname)) { - printf("Resource %s is running on host %s\n",rsc->id,host_uname); - } else { - printf("Resource %s is not running on host %s\n", rsc->id, host_uname); - } - cli_resource_check(cib_conn, rsc); -} - -static void -cli_resource_why_without_rsc_with_host(cib_t *cib_conn,GListPtr resources,pe_node_t *node) -{ - const char* host_uname = node->details->uname; - GListPtr allResources = node->details->allocated_rsc; - GListPtr activeResources = node->details->running_rsc; - GListPtr unactiveResources = pcmk__subtract_lists(allResources,activeResources,(GCompareFunc) strcmp); - GListPtr lpc = NULL; - - for (lpc = activeResources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *rsc = (pe_resource_t *) lpc->data; - printf("Resource %s is running on host %s\n",rsc->id,host_uname); - cli_resource_check(cib_conn,rsc); - } - - for(lpc = unactiveResources; lpc != NULL; lpc = lpc->next) { - pe_resource_t *rsc = (pe_resource_t *) lpc->data; - printf("Resource %s is assigned to host %s but not running\n", - rsc->id, host_uname); - cli_resource_check(cib_conn,rsc); - } - - g_list_free(allResources); - g_list_free(activeResources); - g_list_free(unactiveResources); -} - -static void -cli_resource_why_with_rsc_without_host(cib_t *cib_conn, GListPtr resources, - pe_resource_t *rsc) -{ - GListPtr hosts = NULL; - - rsc->fns->location(rsc, &hosts, TRUE); - printf("Resource %s is %srunning\n", rsc->id, (hosts? "" : "not ")); - cli_resource_check(cib_conn, rsc); - g_list_free(hosts); -} - -void cli_resource_why(cib_t *cib_conn, GListPtr resources, pe_resource_t *rsc, - pe_node_t *node) -{ - const char *host_uname = (node == NULL)? NULL : node->details->uname; - - if ((rsc == NULL) && (host_uname == NULL)) { - cli_resource_why_without_rsc_and_host(cib_conn, resources); - - } else if ((rsc != NULL) && (host_uname != NULL)) { - cli_resource_why_with_rsc_and_host(cib_conn, resources, rsc, - host_uname); - - } else if ((rsc == NULL) && (host_uname != NULL)) { - cli_resource_why_without_rsc_with_host(cib_conn, resources, node); - - } else if ((rsc != NULL) && (host_uname == NULL)) { - cli_resource_why_with_rsc_without_host(cib_conn, resources, rsc); - } -} diff --git a/xml/Makefile.am b/xml/Makefile.am index 2f99f1c0ae..a56258dd9a 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,268 +1,268 @@ # # Copyright 2004-2019 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 $(top_srcdir)/mk/common.mk noarch_pkgconfig_DATA = $(builddir)/pacemaker-schemas.pc # Pacemaker has 3 schemas: the CIB schema, the API schema (for command-line # tool XML output), and a legacy schema for crm_mon --as-xml. # # See Readme.md for details on updating CIB schema files (API is similar) # The CIB and crm_mon schemas are installed directly in CRM_SCHEMA_DIRECTORY # for historical reasons, while the API schema is installed in a subdirectory. APIdir = $(CRM_SCHEMA_DIRECTORY)/api CIBdir = $(CRM_SCHEMA_DIRECTORY) MONdir = $(CRM_SCHEMA_DIRECTORY) # Extract a sorted list of available numeric schema versions # from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng numeric_versions = $(shell ls -1 $(1) \ | sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \ | sort -u -t. -k 1,1n -k 2,2n -k 3,3n) version_pairs = $(join \ $(1),$(addprefix \ -,$(wordlist \ 2,$(words $(1)),$(1) \ ) next \ ) \ ) version_pairs_last = $(wordlist \ $(words \ $(wordlist \ 2,$(1),$(2) \ ) \ ),$(1),$(2) \ ) # NOTE: All files in API_request_base, CIB_cfg_base, API_base, and CIB_base # need to start with a unique prefix. These variables all get iterated over # and globbed, and two files starting with the same prefix will cause # problems. # Names of API schemas that form the choices for pacemaker-result content -API_request_base = command-output crm_mon crmadmin stonith_admin version +API_request_base = command-output crm_mon crm_resource crmadmin stonith_admin version # Names of CIB schemas that form the choices for cib/configuration content CIB_cfg_base = options nodes resources constraints fencing acls tags alerts # Names of all schemas (including top level and those included by others) API_base = $(API_request_base) fence-event generic-list item resources status CIB_base = cib $(CIB_cfg_base) status score rule nvset # Static schema files and transforms (only CIB has transforms) # # This is more complicated than it should be due to the need to support # VPATH builds and "make distcheck". We need the absolute paths for reliable # substitution back and forth, and relative paths for distributed files. API_abs_files = $(foreach base,$(API_base),$(wildcard $(abs_srcdir)/api/$(base)*.rng)) CIB_abs_files = $(foreach base,$(CIB_base),$(wildcard $(abs_srcdir)/$(base).rng $(abs_srcdir)/$(base)-*.rng)) CIB_abs_xsl = $(abs_srcdir)/upgrade-1.3.xsl \ $(abs_srcdir)/upgrade-2.10.xsl \ $(wildcard $(abs_srcdir)/upgrade-*enter.xsl) \ $(wildcard $(abs_srcdir)/upgrade-*leave.xsl) MON_abs_files = $(abs_srcdir)/crm_mon.rng API_files = $(foreach base,$(API_base),$(wildcard $(srcdir)/api/$(base)*.rng)) CIB_files = $(foreach base,$(CIB_base),$(wildcard $(srcdir)/$(base).rng $(srcdir)/$(base)-*.rng)) CIB_xsl = $(srcdir)/upgrade-1.3.xsl \ $(srcdir)/upgrade-2.10.xsl \ $(wildcard $(srcdir)/upgrade-*enter.xsl) \ $(wildcard $(srcdir)/upgrade-*leave.xsl) MON_files = $(srcdir)/crm_mon.rng # Sorted lists of all numeric schema versions API_numeric_versions = $(call numeric_versions,${API_files}) CIB_numeric_versions = $(call numeric_versions,${CIB_files}) # The highest numeric schema version API_max ?= $(lastword $(API_numeric_versions)) CIB_max ?= $(lastword $(CIB_numeric_versions)) # Sorted lists of all schema versions (including "next") API_versions = next $(API_numeric_versions) CIB_versions = next $(CIB_numeric_versions) # Build tree locations of static schema files and transforms (for VPATH builds) API_build_copies = $(foreach f,$(API_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) CIB_build_copies = $(foreach f,$(CIB_abs_files) $(CIB_abs_xsl),$(subst $(abs_srcdir),$(abs_builddir),$(f))) MON_build_copies = $(foreach f,$(MON_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) # Dynamically generated schema files API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng) CIB_generated = pacemaker.rng $(foreach base,$(CIB_versions),pacemaker-$(base).rng) versions.rng CIB_version_pairs = $(call version_pairs,${CIB_numeric_versions}) CIB_version_pairs_cnt = $(words ${CIB_version_pairs}) CIB_version_pairs_last = $(call version_pairs_last,${CIB_version_pairs_cnt},${CIB_version_pairs}) dist_API_DATA = $(API_files) dist_CIB_DATA = $(CIB_files) $(CIB_xsl) dist_MON_DATA = $(MON_files) nodist_API_DATA = $(API_generated) nodist_CIB_DATA = $(CIB_generated) EXTRA_DIST = Readme.md \ best-match.sh \ cibtr-2.rng \ context-of.xsl \ ocf-meta2man.xsl \ regression.sh \ upgrade-2.10-roundtrip.xsl \ upgrade-detail.xsl \ xslt_cibtr-2.rng \ assets \ test-2 \ test-2-enter \ test-2-leave \ test-2-roundtrip cib-versions: @echo "Max: $(CIB_max)" @echo "Available: $(CIB_versions)" api-versions: @echo "Max: $(API_max)" @echo "Available: $(API_versions)" # Dynamically generated top-level API schema api/api-result.rng: api/api-result-$(API_max).rng $(AM_V_at)$(MKDIR_P) api # might not exist in VPATH build $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ api/api-result-%.rng: $(API_build_copies) best-match.sh Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)for rng in $(API_request_base); do $(srcdir)/best-match.sh api/$$rng $(*) $(@) " " || :; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh api/status $(*) $(@) " " || : $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated top-level CIB schema pacemaker.rng: pacemaker-$(CIB_max).rng $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ pacemaker-%.rng: $(CIB_build_copies) best-match.sh Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh cib $(*) $(@) " " $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)for rng in $(CIB_cfg_base); do $(srcdir)/best-match.sh $$rng $(*) $(@) " " || :; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh status $(*) $(@) " " $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated CIB schema listing all pacemaker versions versions.rng: Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' none' >> $@ $(AM_V_at)echo ' pacemaker-0.6' >> $@ $(AM_V_at)echo ' transitional-0.6' >> $@ $(AM_V_at)echo ' pacemaker-0.7' >> $@ $(AM_V_at)echo ' pacemaker-1.1' >> $@ $(AM_V_at)for rng in $(CIB_versions); do echo " pacemaker-$$rng" >> $@; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # diff fails with ec=2 if no predecessor is found; # this uses '=' GNU extension to sed, if that's not available, # one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`; # XXX: use line information from hunk to avoid "not detected" for ambiguity version_diff = \ @for p in $(1); do \ set `echo "$${p}" | tr '-' ' '`; \ echo "\#\#\# *-$$2.rng vs. predecessor"; \ for v in *-$$2.rng; do \ echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \ old=`./best-match.sh $${b} $$1`; \ p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \ case $$? in \ 1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \ -e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \ | while read hline; do \ read h && read i || break; \ iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \ ctxt="(not detected)"; \ if test `echo "$${iline}" | wc -l` -eq 1; then \ ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \ echo "$${i}"; \ sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \ } | $(XSLTPROC) --param skip 1 context-of.xsl -`; \ fi; \ echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \ -e '/^\(+++\|---\)/p'; \ echo "$${h} context: $${ctxt}"; \ echo "$${p}" | sed -n -e "1,$${hline}d" \ -e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \ done; \ ;; \ 2) echo "\#\#\#\#\# $${v} has no predecessor";; \ esac; \ done; \ done diff: best-match.sh @echo "# Comparing changes in + since $(CIB_max)" $(call version_diff,${CIB_version_pairs_last}) fulldiff: best-match.sh @echo "# Comparing all changes across all the subsequent increments" $(call version_diff,${CIB_version_pairs}) CLEANFILES = $(API_generated) $(CIB_generated) clean-local: if [ "x$(srcdir)" != "x$(builddir)" ]; then \ rm -f $(API_build_copies) $(CIB_build_copies) $(MON_build_copies); \ fi # Enable ability to use $@ in prerequisite .SECONDEXPANSION: # For VPATH builds, copy the static schema files into the build tree $(API_build_copies) $(CIB_build_copies) $(MON_build_copies): $$(subst $(abs_builddir),$(srcdir),$$(@)) $(AM_V_GEN)if [ "x$(srcdir)" != "x$(builddir)" ]; then \ $(MKDIR_P) "$(dir $(@))"; \ cp "$(<)" "$(@)"; \ fi diff --git a/xml/api/crm_resource-2.4.rng b/xml/api/crm_resource-2.4.rng new file mode 100644 index 0000000000..1bcb969886 --- /dev/null +++ b/xml/api/crm_resource-2.4.rng @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + promoted + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ocf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + false + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + needs + with + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +