diff --git a/cts/cli/regression.validity.exp b/cts/cli/regression.validity.exp
index 763f577296..6a56f9d212 100644
--- a/cts/cli/regression.validity.exp
+++ b/cts/cli/regression.validity.exp
@@ -1,470 +1,424 @@
Created new pacemaker configuration
A new shadow instance was created. To begin using it, enter the following into your shell:
export CIB_shadow=cts-cli
=#=#=#= Begin test: Try to make resulting CIB invalid (enum violation) =#=#=#=
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
Call failed: Update does not conform to the configured schema
=#=#=#= Current cib after: Try to make resulting CIB invalid (enum violation) =#=#=#=
=#=#=#= End test: Try to make resulting CIB invalid (enum violation) - Invalid configuration (78) =#=#=#=
* Passed: cibadmin - Try to make resulting CIB invalid (enum violation)
=#=#=#= Begin test: Run crm_simulate with invalid CIB (enum violation) =#=#=#=
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-1.2 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-1.3 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.0 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.1 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.2 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.3 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.4 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.5 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.6 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.7 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.8 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.9 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-2.10 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.0 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.1 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.2 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.3 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.4 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.5 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.6 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.7 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.8 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.9 does not validate
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
pcmk__update_schema debug: Schema pacemaker-3.10 does not validate
-Cannot upgrade configuration (claiming pacemaker-1.2 schema) to at least pacemaker-3.0 because it does not validate with any schema from pacemaker-1.2 to pacemaker-3.10
+Cannot upgrade configuration (claiming pacemaker-1.2 schema) to at least pacemaker-3.0 because it does not validate with any schema from pacemaker-1.2 to the latest
=#=#=#= End test: Run crm_simulate with invalid CIB (enum violation) - Invalid configuration (78) =#=#=#=
* Passed: crm_simulate - Run crm_simulate with invalid CIB (enum violation)
=#=#=#= Begin test: Try to make resulting CIB invalid (unrecognized validate-with) =#=#=#=
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
Call failed: Update does not conform to the configured schema
=#=#=#= Current cib after: Try to make resulting CIB invalid (unrecognized validate-with) =#=#=#=
=#=#=#= End test: Try to make resulting CIB invalid (unrecognized validate-with) - Invalid configuration (78) =#=#=#=
* Passed: cibadmin - Try to make resulting CIB invalid (unrecognized validate-with)
=#=#=#= Begin test: Run crm_simulate with invalid CIB (unrecognized validate-with) =#=#=#=
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-1.0 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-1.2 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-1.3 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.0 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.1 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.2 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.3 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.4 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.5 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.6 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.7 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.8 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.9 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-2.10 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.0 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.1 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.2 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.3 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.4 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.5 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.6 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.7 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.8 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.9 does not validate
element cib: Relax-NG validity error : Invalid attribute validate-with for element cib
pcmk__update_schema debug: Schema pacemaker-3.10 does not validate
-Cannot upgrade configuration (claiming pacemaker-9999.0 schema) to at least pacemaker-3.0 because it does not validate with any schema from unknown to pacemaker-3.10
+Cannot upgrade configuration (claiming pacemaker-9999.0 schema) to at least pacemaker-3.0 because it does not validate with any schema from the first to the latest
=#=#=#= End test: Run crm_simulate with invalid CIB (unrecognized validate-with) - Invalid configuration (78) =#=#=#=
* Passed: crm_simulate - Run crm_simulate with invalid CIB (unrecognized validate-with)
=#=#=#= Begin test: Try to make resulting CIB invalid, but possibly recoverable (valid with X.Y+1) =#=#=#=
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
Call failed: Update does not conform to the configured schema
=#=#=#= Current cib after: Try to make resulting CIB invalid, but possibly recoverable (valid with X.Y+1) =#=#=#=
=#=#=#= End test: Try to make resulting CIB invalid, but possibly recoverable (valid with X.Y+1) - Invalid configuration (78) =#=#=#=
* Passed: cibadmin - Try to make resulting CIB invalid, but possibly recoverable (valid with X.Y+1)
=#=#=#= Begin test: Run crm_simulate with invalid, but possibly recoverable CIB (valid with X.Y+1) =#=#=#=
element tags: Relax-NG validity error : Element configuration has extra content: tags
pcmk__update_schema debug: Schema pacemaker-1.2 does not validate
pcmk__update_schema debug: Schema pacemaker-1.3 validates
pcmk__update_schema debug: Schema pacemaker-2.0 validates
pcmk__update_schema debug: Schema pacemaker-2.1 validates
pcmk__update_schema debug: Schema pacemaker-2.2 validates
pcmk__update_schema debug: Schema pacemaker-2.3 validates
pcmk__update_schema debug: Schema pacemaker-2.4 validates
pcmk__update_schema debug: Schema pacemaker-2.5 validates
pcmk__update_schema debug: Schema pacemaker-2.6 validates
pcmk__update_schema debug: Schema pacemaker-2.7 validates
pcmk__update_schema debug: Schema pacemaker-2.8 validates
pcmk__update_schema debug: Schema pacemaker-2.9 validates
pcmk__update_schema debug: Schema pacemaker-2.10 validates
pcmk__update_schema debug: Schema pacemaker-3.0 validates
pcmk__update_schema debug: Schema pacemaker-3.1 validates
pcmk__update_schema debug: Schema pacemaker-3.2 validates
pcmk__update_schema debug: Schema pacemaker-3.3 validates
pcmk__update_schema debug: Schema pacemaker-3.4 validates
pcmk__update_schema debug: Schema pacemaker-3.5 validates
pcmk__update_schema debug: Schema pacemaker-3.6 validates
pcmk__update_schema debug: Schema pacemaker-3.7 validates
pcmk__update_schema debug: Schema pacemaker-3.8 validates
pcmk__update_schema debug: Schema pacemaker-3.9 validates
pcmk__update_schema debug: Schema pacemaker-3.10 validates
pcmk__update_schema info: Transformed the configuration schema to pacemaker-3.10
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
Current cluster status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
Transition Summary:
Executing Cluster Transition:
Revised Cluster Status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
=#=#=#= End test: Run crm_simulate with invalid, but possibly recoverable CIB (valid with X.Y+1) - OK (0) =#=#=#=
* Passed: crm_simulate - Run crm_simulate with invalid, but possibly recoverable CIB (valid with X.Y+1)
=#=#=#= Begin test: Make resulting CIB valid, although without validate-with attribute =#=#=#=
=#=#=#= Current cib after: Make resulting CIB valid, although without validate-with attribute =#=#=#=
=#=#=#= End test: Make resulting CIB valid, although without validate-with attribute - OK (0) =#=#=#=
* Passed: cibadmin - Make resulting CIB valid, although without validate-with attribute
=#=#=#= Begin test: Run crm_simulate with valid CIB, but without validate-with attribute =#=#=#=
Schema validation of configuration is disabled (enabling is encouraged and prevents common misconfigurations)
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
Current cluster status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
Transition Summary:
Executing Cluster Transition:
Revised Cluster Status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
=#=#=#= End test: Run crm_simulate with valid CIB, but without validate-with attribute - OK (0) =#=#=#=
* Passed: crm_simulate - Run crm_simulate with valid CIB, but without validate-with attribute
=#=#=#= Begin test: Make resulting CIB invalid, and without validate-with attribute =#=#=#=
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
=#=#=#= Current cib after: Make resulting CIB invalid, and without validate-with attribute =#=#=#=
=#=#=#= End test: Make resulting CIB invalid, and without validate-with attribute - OK (0) =#=#=#=
* Passed: cibadmin - Make resulting CIB invalid, and without validate-with attribute
=#=#=#= Begin test: Run crm_simulate with invalid CIB, also without validate-with attribute =#=#=#=
Schema validation of configuration is disabled (enabling is encouraged and prevents common misconfigurations)
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Invalid attribute first-action for element rsc_order
validity.bad.xml:10: element rsc_order: Relax-NG validity error : Element constraints has extra content: rsc_order
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
unpack_resources error: Resource start-up disabled since no STONITH resources have been defined
unpack_resources error: Either configure some or disable STONITH with the stonith-enabled option
unpack_resources error: NOTE: Clusters with shared data need STONITH to ensure data integrity
Current cluster status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
Transition Summary:
Executing Cluster Transition:
Revised Cluster Status:
* Full List of Resources:
* dummy1 (ocf:pacemaker:Dummy): Stopped
* dummy2 (ocf:pacemaker:Dummy): Stopped
=#=#=#= End test: Run crm_simulate with invalid CIB, also without validate-with attribute - OK (0) =#=#=#=
* Passed: crm_simulate - Run crm_simulate with invalid CIB, also without validate-with attribute
diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c
index 0f8f03fd33..f22582de40 100644
--- a/daemons/based/based_io.c
+++ b/daemons/based/based_io.c
@@ -1,477 +1,477 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
crm_trigger_t *cib_writer = NULL;
int write_cib_contents(gpointer p);
static void
cib_rename(const char *old)
{
int new_fd;
char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root);
umask(S_IWGRP | S_IWOTH | S_IROTH);
new_fd = mkstemp(new);
if ((new_fd < 0) || (rename(old, new) < 0)) {
crm_err("Couldn't archive unusable file %s (disabling disk writes and continuing)",
old);
cib_writes_enabled = FALSE;
} else {
crm_err("Archived unusable file %s as %s", old, new);
}
if (new_fd > 0) {
close(new_fd);
}
free(new);
}
/*
* It is the callers responsibility to free the output of this function
*/
static xmlNode *
retrieveCib(const char *filename, const char *sigfile)
{
xmlNode *root = NULL;
crm_info("Reading cluster configuration file %s (digest: %s)",
filename, sigfile);
switch (cib_file_read_and_verify(filename, sigfile, &root)) {
case -pcmk_err_cib_corrupt:
crm_warn("Continuing but %s will NOT be used.", filename);
break;
case -pcmk_err_cib_modified:
/* Archive the original files so the contents are not lost */
crm_warn("Continuing but %s will NOT be used.", filename);
cib_rename(filename);
cib_rename(sigfile);
break;
}
return root;
}
/*
* for OSs without support for direntry->d_type, like Solaris
*/
#ifndef DT_UNKNOWN
# define DT_UNKNOWN 0
# define DT_FIFO 1
# define DT_CHR 2
# define DT_DIR 4
# define DT_BLK 6
# define DT_REG 8
# define DT_LNK 10
# define DT_SOCK 12
# define DT_WHT 14
#endif /*DT_UNKNOWN*/
static int cib_archive_filter(const struct dirent * a)
{
int rc = 0;
/* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */
struct stat s;
char *a_path = crm_strdup_printf("%s/%s", cib_root, a->d_name);
if(stat(a_path, &s) != 0) {
rc = errno;
crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_rc_str(rc), rc);
rc = 0;
} else if ((s.st_mode & S_IFREG) != S_IFREG) {
unsigned char dtype;
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
dtype = a->d_type;
#else
switch (s.st_mode & S_IFMT) {
case S_IFREG: dtype = DT_REG; break;
case S_IFDIR: dtype = DT_DIR; break;
case S_IFCHR: dtype = DT_CHR; break;
case S_IFBLK: dtype = DT_BLK; break;
case S_IFLNK: dtype = DT_LNK; break;
case S_IFIFO: dtype = DT_FIFO; break;
case S_IFSOCK: dtype = DT_SOCK; break;
default: dtype = DT_UNKNOWN; break;
}
#endif
crm_trace("%s - wrong type (%d)", a->d_name, dtype);
} else if(strstr(a->d_name, "cib-") != a->d_name) {
crm_trace("%s - wrong prefix", a->d_name);
} else if (pcmk__ends_with_ext(a->d_name, ".sig")) {
crm_trace("%s - wrong suffix", a->d_name);
} else {
crm_debug("%s - candidate", a->d_name);
rc = 1;
}
free(a_path);
return rc;
}
static int cib_archive_sort(const struct dirent ** a, const struct dirent **b)
{
/* Order by creation date - most recently created file first */
int rc = 0;
struct stat buf;
time_t a_age = 0;
time_t b_age = 0;
char *a_path = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name);
char *b_path = crm_strdup_printf("%s/%s", cib_root, b[0]->d_name);
if(stat(a_path, &buf) == 0) {
a_age = buf.st_ctime;
}
if(stat(b_path, &buf) == 0) {
b_age = buf.st_ctime;
}
free(a_path);
free(b_path);
if(a_age > b_age) {
rc = 1;
} else if(a_age < b_age) {
rc = -1;
}
crm_trace("%s (%lu) vs. %s (%lu) : %d",
a[0]->d_name, (unsigned long)a_age,
b[0]->d_name, (unsigned long)b_age, rc);
return rc;
}
xmlNode *
readCibXmlFile(const char *dir, const char *file, gboolean discard_status)
{
struct dirent **namelist = NULL;
int lpc = 0;
char *sigfile = NULL;
char *sigfilepath = NULL;
char *filename = NULL;
const char *name = NULL;
const char *value = NULL;
const char *validation = NULL;
const char *use_valgrind = pcmk__env_option(PCMK__ENV_VALGRIND_ENABLED);
xmlNode *root = NULL;
xmlNode *status = NULL;
sigfile = crm_strdup_printf("%s.sig", file);
if (pcmk__daemon_can_write(dir, file) == FALSE
|| pcmk__daemon_can_write(dir, sigfile) == FALSE) {
cib_status = -EACCES;
return NULL;
}
filename = crm_strdup_printf("%s/%s", dir, file);
sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile);
free(sigfile);
cib_status = pcmk_ok;
root = retrieveCib(filename, sigfilepath);
free(filename);
free(sigfilepath);
if (root == NULL) {
crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root);
lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort);
if (lpc < 0) {
crm_err("scandir(%s) failed: %s", cib_root, pcmk_rc_str(errno));
}
}
while (root == NULL && lpc > 1) {
crm_debug("Testing %d candidates", lpc);
lpc--;
filename = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name);
sigfile = crm_strdup_printf("%s.sig", filename);
crm_info("Reading cluster configuration file %s (digest: %s)",
filename, sigfile);
if (cib_file_read_and_verify(filename, sigfile, &root) < 0) {
crm_warn("Continuing but %s will NOT be used.", filename);
} else {
crm_notice("Continuing with last valid configuration archive: %s", filename);
}
free(namelist[lpc]);
free(filename);
free(sigfile);
}
free(namelist);
if (root == NULL) {
root = createEmptyCib(0);
crm_warn("Continuing with an empty configuration.");
}
if (cib_writes_enabled && use_valgrind &&
(crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) {
cib_writes_enabled = FALSE;
crm_err("*** Disabling disk writes to avoid confusing Valgrind ***");
}
status = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL);
if (discard_status && status != NULL) {
// Strip out the PCMK_XE_STATUS section if there is one
free_xml(status);
status = NULL;
}
if (status == NULL) {
pcmk__xe_create(root, PCMK_XE_STATUS);
}
/* Do this before schema validation happens */
/* fill in some defaults */
name = PCMK_XA_ADMIN_EPOCH;
value = crm_element_value(root, name);
if (value == NULL) {
crm_warn("No value for %s was specified in the configuration.", name);
crm_warn("The recommended course of action is to shutdown,"
" run crm_verify and fix any errors it reports.");
crm_warn("We will default to zero and continue but may get"
" confused about which configuration to use if"
" multiple nodes are powered up at the same time.");
crm_xml_add_int(root, name, 0);
}
name = PCMK_XA_EPOCH;
value = crm_element_value(root, name);
if (value == NULL) {
crm_xml_add_int(root, name, 0);
}
name = PCMK_XA_NUM_UPDATES;
value = crm_element_value(root, name);
if (value == NULL) {
crm_xml_add_int(root, name, 0);
}
// Unset (DC should set appropriate value)
pcmk__xe_remove_attr(root, PCMK_XA_DC_UUID);
if (discard_status) {
crm_log_xml_trace(root, "[on-disk]");
}
validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
- if (validate_xml(root, NULL, TRUE) == FALSE) {
+ if (!pcmk__configured_schema_validates(root)) {
crm_err("CIB does not validate with %s",
pcmk__s(validation, "no schema specified"));
cib_status = -pcmk_err_schema_validation;
} else if (validation == NULL) {
pcmk__update_schema(&root, NULL, false, false);
validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
if (validation != NULL) {
crm_notice("Enabling %s validation on"
" the existing (sane) configuration", validation);
} else {
crm_err("CIB does not validate with any known schema");
cib_status = -pcmk_err_schema_validation;
}
}
return root;
}
gboolean
uninitializeCib(void)
{
xmlNode *tmp_cib = the_cib;
if (tmp_cib == NULL) {
crm_debug("The CIB has already been deallocated.");
return FALSE;
}
the_cib = NULL;
crm_debug("Deallocating the CIB.");
free_xml(tmp_cib);
crm_debug("The CIB has been deallocated.");
return TRUE;
}
/*
* This method will free the old CIB pointer on success and the new one
* on failure.
*/
int
activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
{
if (new_cib) {
xmlNode *saved_cib = the_cib;
CRM_ASSERT(new_cib != saved_cib);
the_cib = new_cib;
free_xml(saved_cib);
if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) {
crm_debug("Triggering CIB write for %s op", op);
mainloop_set_trigger(cib_writer);
}
return pcmk_ok;
}
crm_err("Ignoring invalid CIB");
if (the_cib) {
crm_warn("Reverting to last known CIB");
} else {
crm_crit("Could not write out new CIB and no saved version to revert to");
}
return -ENODATA;
}
static void
cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
{
const char *errmsg = "Could not write CIB to disk";
if ((exitcode != 0) && cib_writes_enabled) {
cib_writes_enabled = FALSE;
errmsg = "Disabling CIB disk writes after failure";
}
if ((signo == 0) && (exitcode == 0)) {
crm_trace("Disk write [%d] succeeded", (int) pid);
} else if (signo == 0) {
crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
} else {
crm_err("%s: process %d terminated with signal %d (%s)%s",
errmsg, (int) pid, signo, strsignal(signo),
(core? " and dumped core" : ""));
}
mainloop_trigger_complete(cib_writer);
}
int
write_cib_contents(gpointer p)
{
int exit_rc = pcmk_ok;
xmlNode *cib_local = NULL;
/* Make a copy of the CIB to write (possibly in a forked child) */
if (p) {
/* Synchronous write out */
cib_local = pcmk__xml_copy(NULL, p);
} else {
int pid = 0;
int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0);
/* Turn it off before the fork() to avoid:
* - 2 processes writing to the same shared mem
* - the child needing to disable it
* (which would close it from underneath the parent)
* This way, the shared mem files are already closed
*/
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
pid = fork();
if (pid < 0) {
crm_err("Disabling disk writes after fork failure: %s", pcmk_rc_str(errno));
cib_writes_enabled = FALSE;
return FALSE;
}
if (pid) {
/* Parent */
mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete);
if (bb_state == QB_LOG_STATE_ENABLED) {
/* Re-enable now that it it safe */
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
}
return -1; /* -1 means 'still work to do' */
}
/* Asynchronous write-out after a fork() */
/* In theory, we can scribble on the_cib here and not affect the parent,
* but let's be safe anyway.
*/
cib_local = pcmk__xml_copy(NULL, the_cib);
}
/* Write the CIB */
exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml");
/* A nonzero exit code will cause further writes to be disabled */
free_xml(cib_local);
if (p == NULL) {
crm_exit_t exit_code = CRM_EX_OK;
switch (exit_rc) {
case pcmk_ok:
exit_code = CRM_EX_OK;
break;
case pcmk_err_cib_modified:
exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest
break;
case pcmk_err_cib_backup: // Existing CIB couldn't be backed up
case pcmk_err_cib_save: // New CIB couldn't be saved
exit_code = CRM_EX_CANTCREAT;
break;
default:
exit_code = CRM_EX_ERROR;
break;
}
/* Use _exit() because exit() could affect the parent adversely */
_exit(exit_code);
}
return exit_rc;
}
diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c
index 58de8abd59..9c17938960 100644
--- a/daemons/based/based_messages.c
+++ b/daemons/based/based_messages.c
@@ -1,539 +1,539 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Maximum number of diffs to ignore while waiting for a resync */
#define MAX_DIFF_RETRY 5
bool based_is_primary = false;
xmlNode *the_cib = NULL;
int
cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
const char *host = crm_element_value(req, PCMK__XA_SRC);
*answer = NULL;
if (crm_element_value(req, PCMK__XA_CIB_ISREPLYTO) == NULL) {
crm_info("Peer %s is requesting to shut down", host);
return pcmk_ok;
}
if (cib_shutdown_flag == FALSE) {
crm_err("Peer %s mistakenly thinks we wanted to shut down", host);
return -EINVAL;
}
crm_info("Peer %s has acknowledged our shutdown request", host);
terminate_cib(__func__, 0);
return pcmk_ok;
}
// @COMPAT: Remove when PCMK__CIB_REQUEST_NOOP is removed
int
cib_process_noop(const char *op, int options, const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib,
xmlNode **answer)
{
crm_trace("Processing \"%s\" event", op);
*answer = NULL;
return pcmk_ok;
}
int
cib_process_readwrite(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing \"%s\" event", op);
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_IS_PRIMARY, pcmk__str_none)) {
if (based_is_primary) {
result = pcmk_ok;
} else {
result = -EPERM;
}
return result;
}
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_PRIMARY, pcmk__str_none)) {
if (!based_is_primary) {
crm_info("We are now in R/W mode");
based_is_primary = true;
} else {
crm_debug("We are still in R/W mode");
}
} else if (based_is_primary) {
crm_info("We are now in R/O mode");
based_is_primary = false;
}
return result;
}
/* Set to 1 when a sync is requested, incremented when a diff is ignored,
* reset to 0 when a sync is received
*/
static int sync_in_progress = 0;
void
send_sync_request(const char *host)
{
xmlNode *sync_me = pcmk__xe_create(NULL, "sync-me");
crm_node_t *peer = NULL;
crm_info("Requesting re-sync from %s", (host? host : "all peers"));
sync_in_progress = 1;
crm_xml_add(sync_me, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(sync_me, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SYNC_TO_ONE);
crm_xml_add(sync_me, PCMK__XA_CIB_DELEGATED_FROM,
stand_alone? "localhost" : crm_cluster->uname);
if (host != NULL) {
peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster);
}
send_cluster_message(peer, crm_msg_cib, sync_me, FALSE);
free_xml(sync_me);
}
int
cib_process_ping(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
const char *host = crm_element_value(req, PCMK__XA_SRC);
const char *seq = crm_element_value(req, PCMK__XA_CIB_PING_ID);
char *digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET);
xmlNode *wrapper = NULL;
crm_trace("Processing \"%s\" event %s from %s", op, seq, host);
*answer = pcmk__xe_create(NULL, PCMK__XE_PING_RESPONSE);
crm_xml_add(*answer, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
crm_xml_add(*answer, PCMK__XA_DIGEST, digest);
crm_xml_add(*answer, PCMK__XA_CIB_PING_ID, seq);
wrapper = pcmk__xe_create(*answer, PCMK__XE_CIB_CALLDATA);
if (the_cib != NULL) {
pcmk__if_tracing(
{
/* Append additional detail so the receiver can log the
* differences
*/
pcmk__xml_copy(wrapper, the_cib);
},
{
// Always include at least the version details
const char *name = (const char *) the_cib->name;
xmlNode *shallow = pcmk__xe_create(wrapper, name);
copy_in_properties(shallow, the_cib);
}
);
}
crm_info("Reporting our current digest to %s: %s for %s.%s.%s",
host, digest,
crm_element_value(existing_cib, PCMK_XA_ADMIN_EPOCH),
crm_element_value(existing_cib, PCMK_XA_EPOCH),
crm_element_value(existing_cib, PCMK_XA_NUM_UPDATES));
free(digest);
return pcmk_ok;
}
int
cib_process_sync(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
return sync_our_cib(req, TRUE);
}
int
cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int rc = pcmk_ok;
*answer = NULL;
if (crm_element_value(req, PCMK__XA_CIB_SCHEMA_MAX) != NULL) {
/* The originator of an upgrade request sends it to the DC, without
* PCMK__XA_CIB_SCHEMA_MAX. If an upgrade is needed, the DC
* re-broadcasts the request with PCMK__XA_CIB_SCHEMA_MAX, and each node
* performs the upgrade (and notifies its local clients) here.
*/
return cib_process_upgrade(
op, options, section, req, input, existing_cib, result_cib, answer);
} else {
xmlNode *scratch = pcmk__xml_copy(NULL, existing_cib);
const char *host = crm_element_value(req, PCMK__XA_SRC);
const char *original_schema = NULL;
const char *new_schema = NULL;
const char *client_id = crm_element_value(req, PCMK__XA_CIB_CLIENTID);
const char *call_opts = crm_element_value(req, PCMK__XA_CIB_CALLOPT);
const char *call_id = crm_element_value(req, PCMK__XA_CIB_CALLID);
crm_trace("Processing \"%s\" event", op);
original_schema = crm_element_value(existing_cib,
PCMK_XA_VALIDATE_WITH);
rc = pcmk__update_schema(&scratch, NULL, true, true);
rc = pcmk_rc2legacy(rc);
new_schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) {
xmlNode *up = pcmk__xe_create(NULL, __func__);
rc = pcmk_ok;
crm_notice("Upgrade request from %s verified", host);
crm_xml_add(up, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE);
crm_xml_add(up, PCMK__XA_CIB_SCHEMA_MAX, new_schema);
crm_xml_add(up, PCMK__XA_CIB_DELEGATED_FROM, host);
crm_xml_add(up, PCMK__XA_CIB_CLIENTID, client_id);
crm_xml_add(up, PCMK__XA_CIB_CALLOPT, call_opts);
crm_xml_add(up, PCMK__XA_CIB_CALLID, call_id);
if (cib_legacy_mode() && based_is_primary) {
rc = cib_process_upgrade(
op, options, section, up, input, existing_cib, result_cib, answer);
} else {
send_cluster_message(NULL, crm_msg_cib, up, FALSE);
}
free_xml(up);
} else if(rc == pcmk_ok) {
rc = -pcmk_err_schema_unchanged;
}
if (rc != pcmk_ok) {
// Notify originating peer so it can notify its local clients
crm_node_t *origin = NULL;
origin = pcmk__search_node_caches(0, host,
pcmk__node_search_cluster);
crm_info("Rejecting upgrade request from %s: %s "
CRM_XS " rc=%d peer=%s", host, pcmk_strerror(rc), rc,
(origin? origin->uname : "lost"));
if (origin) {
xmlNode *up = pcmk__xe_create(NULL, __func__);
crm_xml_add(up, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE);
crm_xml_add(up, PCMK__XA_CIB_DELEGATED_FROM, host);
crm_xml_add(up, PCMK__XA_CIB_ISREPLYTO, host);
crm_xml_add(up, PCMK__XA_CIB_CLIENTID, client_id);
crm_xml_add(up, PCMK__XA_CIB_CALLOPT, call_opts);
crm_xml_add(up, PCMK__XA_CIB_CALLID, call_id);
crm_xml_add_int(up, PCMK__XA_CIB_UPGRADE_RC, rc);
if (send_cluster_message(origin, crm_msg_cib, up, TRUE)
== FALSE) {
crm_warn("Could not send CIB upgrade result to %s", host);
}
free_xml(up);
}
}
free_xml(scratch);
}
return rc;
}
int
cib_process_sync_one(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
return sync_our_cib(req, FALSE);
}
int
cib_server_process_diff(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int rc = pcmk_ok;
if (sync_in_progress > MAX_DIFF_RETRY) {
/* Don't ignore diffs forever; the last request may have been lost.
* If the diff fails, we'll ask for another full resync.
*/
sync_in_progress = 0;
}
// The primary instance should never ignore a diff
if (sync_in_progress && !based_is_primary) {
int diff_add_updates = 0;
int diff_add_epoch = 0;
int diff_add_admin_epoch = 0;
int diff_del_updates = 0;
int diff_del_epoch = 0;
int diff_del_admin_epoch = 0;
cib_diff_version_details(input,
&diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates,
&diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);
sync_in_progress++;
crm_notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in progress)",
diff_del_admin_epoch, diff_del_epoch, diff_del_updates,
diff_add_admin_epoch, diff_add_epoch, diff_add_updates);
return -pcmk_err_diff_resync;
}
rc = cib_process_diff(op, options, section, req, input, existing_cib, result_cib, answer);
crm_trace("result: %s (%d), %s", pcmk_strerror(rc), rc,
(based_is_primary? "primary": "secondary"));
if ((rc == -pcmk_err_diff_resync) && !based_is_primary) {
free_xml(*result_cib);
*result_cib = NULL;
send_sync_request(NULL);
} else if (rc == -pcmk_err_diff_resync) {
rc = -pcmk_err_diff_failed;
if (options & cib_force_diff) {
crm_warn("Not requesting full refresh in R/W mode");
}
} else if ((rc != pcmk_ok) && !based_is_primary && cib_legacy_mode()) {
crm_warn("Requesting full CIB refresh because update failed: %s"
CRM_XS " rc=%d", pcmk_strerror(rc), rc);
pcmk__log_xml_patchset(LOG_INFO, input);
free_xml(*result_cib);
*result_cib = NULL;
send_sync_request(NULL);
}
return rc;
}
int
cib_process_replace_svr(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int rc =
cib_process_replace(op, options, section, req, input, existing_cib, result_cib, answer);
if ((rc == pcmk_ok) && pcmk__xe_is(input, PCMK_XE_CIB)) {
sync_in_progress = 0;
}
return rc;
}
// @COMPAT: Remove when PCMK__CIB_REQUEST_ABS_DELETE is removed
int
cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
return -EINVAL;
}
static xmlNode *
cib_msg_copy(xmlNode *msg)
{
static const char *field_list[] = {
PCMK__XA_T,
PCMK__XA_CIB_CLIENTID,
PCMK__XA_CIB_CALLOPT,
PCMK__XA_CIB_CALLID,
PCMK__XA_CIB_OP,
PCMK__XA_CIB_ISREPLYTO,
PCMK__XA_CIB_SECTION,
PCMK__XA_CIB_HOST,
PCMK__XA_CIB_RC,
PCMK__XA_CIB_DELEGATED_FROM,
PCMK__XA_CIB_OBJECT,
PCMK__XA_CIB_OBJECT_TYPE,
PCMK__XA_CIB_UPDATE,
PCMK__XA_CIB_CLIENTNAME,
PCMK__XA_CIB_USER,
PCMK__XA_CIB_NOTIFY_TYPE,
PCMK__XA_CIB_NOTIFY_ACTIVATE,
};
xmlNode *copy = pcmk__xe_create(NULL, PCMK__XE_COPY);
for (int lpc = 0; lpc < PCMK__NELEM(field_list); lpc++) {
const char *field = field_list[lpc];
const char *value = crm_element_value(msg, field);
if (value != NULL) {
crm_xml_add(copy, field, value);
}
}
return copy;
}
int
sync_our_cib(xmlNode * request, gboolean all)
{
int result = pcmk_ok;
char *digest = NULL;
const char *host = crm_element_value(request, PCMK__XA_SRC);
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
crm_node_t *peer = NULL;
xmlNode *replace_request = NULL;
xmlNode *wrapper = NULL;
CRM_CHECK(the_cib != NULL, return -EINVAL);
CRM_CHECK(all || (host != NULL), return -EINVAL);
crm_debug("Syncing CIB to %s", all ? "all peers" : host);
replace_request = cib_msg_copy(request);
if (host != NULL) {
crm_xml_add(replace_request, PCMK__XA_CIB_ISREPLYTO, host);
}
if (all) {
pcmk__xe_remove_attr(replace_request, PCMK__XA_CIB_HOST);
}
crm_xml_add(replace_request, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_REPLACE);
// @TODO Keep for tracing, or drop?
crm_xml_add(replace_request, PCMK__XA_ORIGINAL_CIB_OP, op);
pcmk__xe_set_bool_attr(replace_request, PCMK__XA_CIB_UPDATE, true);
crm_xml_add(replace_request, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET);
crm_xml_add(replace_request, PCMK__XA_DIGEST, digest);
wrapper = pcmk__xe_create(replace_request, PCMK__XE_CIB_CALLDATA);
pcmk__xml_copy(wrapper, the_cib);
if (!all) {
peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster);
}
if (!send_cluster_message(peer, crm_msg_cib, replace_request, FALSE)) {
result = -ENOTCONN;
}
free_xml(replace_request);
free(digest);
return result;
}
int
cib_process_commit_transaction(const char *op, int options, const char *section,
xmlNode *req, xmlNode *input,
xmlNode *existing_cib, xmlNode **result_cib,
xmlNode **answer)
{
/* On success, our caller will activate *result_cib locally, trigger a
* replace notification if appropriate, and sync *result_cib to all nodes.
* On failure, our caller will free *result_cib.
*/
int rc = pcmk_rc_ok;
const char *client_id = crm_element_value(req, PCMK__XA_CIB_CLIENTID);
const char *origin = crm_element_value(req, PCMK__XA_SRC);
pcmk__client_t *client = pcmk__find_client_by_id(client_id);
rc = based_commit_transaction(input, client, origin, result_cib);
if (rc != pcmk_rc_ok) {
char *source = based_transaction_source_str(client, origin);
crm_err("Could not commit transaction for %s: %s",
source, pcmk_rc_str(rc));
free(source);
}
return pcmk_rc2legacy(rc);
}
int
cib_process_schemas(const char *op, int options, const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib,
xmlNode **answer)
{
xmlNode *wrapper = NULL;
xmlNode *data = NULL;
const char *after_ver = NULL;
GList *schemas = NULL;
GList *already_included = NULL;
*answer = pcmk__xe_create(NULL, PCMK__XA_SCHEMAS);
wrapper = pcmk__xe_first_child(req, PCMK__XE_CIB_CALLDATA, NULL, NULL);
data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (data == NULL) {
crm_warn("No data specified in request");
return -EPROTO;
}
after_ver = crm_element_value(data, PCMK_XA_VERSION);
if (after_ver == NULL) {
crm_warn("No version specified in request");
return -EPROTO;
}
/* The client requested all schemas after the latest one we know about, which
* means the client is fully up-to-date. Return a properly formatted reply
* with no schemas.
*/
- if (pcmk__str_eq(after_ver, xml_latest_schema(), pcmk__str_none)) {
+ if (pcmk__str_eq(after_ver, pcmk__highest_schema_name(), pcmk__str_none)) {
return pcmk_ok;
}
schemas = pcmk__schema_files_later_than(after_ver);
for (GList *iter = schemas; iter != NULL; iter = iter->next) {
pcmk__build_schema_xml_node(*answer, iter->data, &already_included);
}
g_list_free_full(schemas, free);
g_list_free_full(already_included, free);
return pcmk_ok;
}
diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c
index 54756cc395..a81b15fd84 100644
--- a/daemons/execd/cts-exec-helper.c
+++ b/daemons/execd/cts-exec-helper.c
@@ -1,626 +1,626 @@
/*
* Copyright 2012-2024 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
#define SUMMARY "cts-exec-helper - inject commands into the Pacemaker executor and watch for events"
static int exec_call_id = 0;
static gboolean start_test(gpointer user_data);
static void try_connect(void);
static char *key = NULL;
static char *val = NULL;
static struct {
int verbose;
int quiet;
guint interval_ms;
int timeout;
int start_delay;
int cancel_call_id;
gboolean no_wait;
gboolean is_running;
gboolean no_connect;
int exec_call_opts;
const char *api_call;
const char *rsc_id;
const char *provider;
const char *class;
const char *type;
const char *action;
const char *listen;
gboolean use_tls;
lrmd_key_value_t *params;
} options;
static gboolean
interval_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
return pcmk_parse_interval_spec(optarg,
&options.interval_ms) == pcmk_rc_ok;
}
static gboolean
notify_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (pcmk__str_any_of(option_name, "--notify-orig", "-n", NULL)) {
options.exec_call_opts = lrmd_opt_notify_orig_only;
} else if (pcmk__str_any_of(option_name, "--notify-changes", "-o", NULL)) {
options.exec_call_opts = lrmd_opt_notify_changes_only;
}
return TRUE;
}
static gboolean
param_key_val_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (pcmk__str_any_of(option_name, "--param-key", "-k", NULL)) {
pcmk__str_update(&key, optarg);
} else if (pcmk__str_any_of(option_name, "--param-val", "-v", NULL)) {
pcmk__str_update(&val, optarg);
}
if (key != NULL && val != NULL) {
options.params = lrmd_key_value_add(options.params, key, val);
pcmk__str_update(&key, NULL);
pcmk__str_update(&val, NULL);
}
return TRUE;
}
static GOptionEntry basic_entries[] = {
{ "api-call", 'c', 0, G_OPTION_ARG_STRING, &options.api_call,
"Directly relates to executor API functions",
NULL },
{ "is-running", 'R', 0, G_OPTION_ARG_NONE, &options.is_running,
"Determine if a resource is registered and running",
NULL },
{ "listen", 'l', 0, G_OPTION_ARG_STRING, &options.listen,
"Listen for a specific event string",
NULL },
{ "no-wait", 'w', 0, G_OPTION_ARG_NONE, &options.no_wait,
"Make api call and do not wait for result",
NULL },
{ "notify-changes", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
"Only notify client changes to recurring operations",
NULL },
{ "notify-orig", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, notify_cb,
"Only notify this client of the results of an API action",
NULL },
{ "tls", 'S', 0, G_OPTION_ARG_NONE, &options.use_tls,
"Use TLS backend for local connection",
NULL },
{ NULL }
};
static GOptionEntry api_call_entries[] = {
{ "action", 'a', 0, G_OPTION_ARG_STRING, &options.action,
NULL, NULL },
{ "cancel-call-id", 'x', 0, G_OPTION_ARG_INT, &options.cancel_call_id,
NULL, NULL },
{ "class", 'C', 0, G_OPTION_ARG_STRING, &options.class,
NULL, NULL },
{ "interval", 'i', 0, G_OPTION_ARG_CALLBACK, interval_cb,
NULL, NULL },
{ "param-key", 'k', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
NULL, NULL },
{ "param-val", 'v', 0, G_OPTION_ARG_CALLBACK, param_key_val_cb,
NULL, NULL },
{ "provider", 'P', 0, G_OPTION_ARG_STRING, &options.provider,
NULL, NULL },
{ "rsc-id", 'r', 0, G_OPTION_ARG_STRING, &options.rsc_id,
NULL, NULL },
{ "start-delay", 's', 0, G_OPTION_ARG_INT, &options.start_delay,
NULL, NULL },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
NULL, NULL },
{ "type", 'T', 0, G_OPTION_ARG_STRING, &options.type,
NULL, NULL },
{ NULL }
};
static GMainLoop *mainloop = NULL;
static lrmd_t *lrmd_conn = NULL;
static char event_buf_v0[1024];
static crm_exit_t
test_exit(crm_exit_t exit_code)
{
lrmd_api_delete(lrmd_conn);
return crm_exit(exit_code);
}
#define print_result(fmt, args...) \
if (!options.quiet) { \
printf(fmt "\n" , ##args); \
}
#define report_event(event) \
snprintf(event_buf_v0, sizeof(event_buf_v0), "NEW_EVENT event_type:%s rsc_id:%s action:%s rc:%s op_status:%s", \
lrmd_event_type2str(event->type), \
event->rsc_id, \
event->op_type ? event->op_type : "none", \
services_ocf_exitcode_str(event->rc), \
pcmk_exec_status_str(event->op_status)); \
crm_info("%s", event_buf_v0);
static void
test_shutdown(int nsig)
{
lrmd_api_delete(lrmd_conn);
lrmd_conn = NULL;
}
static void
read_events(lrmd_event_data_t * event)
{
report_event(event);
if (options.listen) {
if (pcmk__str_eq(options.listen, event_buf_v0, pcmk__str_casei)) {
print_result("LISTEN EVENT SUCCESSFUL");
test_exit(CRM_EX_OK);
}
}
if (exec_call_id && (event->call_id == exec_call_id)) {
if (event->op_status == 0 && event->rc == 0) {
print_result("API-CALL SUCCESSFUL for 'exec'");
} else {
print_result("API-CALL FAILURE for 'exec', rc:%d lrmd_op_status:%s",
event->rc, pcmk_exec_status_str(event->op_status));
test_exit(CRM_EX_ERROR);
}
if (!options.listen) {
test_exit(CRM_EX_OK);
}
}
}
static gboolean
timeout_err(gpointer data)
{
print_result("LISTEN EVENT FAILURE - timeout occurred, never found");
test_exit(CRM_EX_TIMEOUT);
return FALSE;
}
static void
connection_events(lrmd_event_data_t * event)
{
int rc = event->connection_rc;
if (event->type != lrmd_event_connect) {
/* ignore */
return;
}
if (!rc) {
crm_info("Executor client connection established");
start_test(NULL);
return;
} else {
sleep(1);
try_connect();
crm_notice("Executor client connection failed");
}
}
static void
try_connect(void)
{
int tries = 10;
static int num_tries = 0;
int rc = 0;
lrmd_conn->cmds->set_callback(lrmd_conn, connection_events);
for (; num_tries < tries; num_tries++) {
rc = lrmd_conn->cmds->connect_async(lrmd_conn, crm_system_name, 3000);
if (!rc) {
return; /* we'll hear back in async callback */
}
sleep(1);
}
print_result("API CONNECTION FAILURE");
test_exit(CRM_EX_ERROR);
}
static gboolean
start_test(gpointer user_data)
{
int rc = 0;
if (!options.no_connect) {
if (!lrmd_conn->cmds->is_connected(lrmd_conn)) {
try_connect();
/* async connect -- this function will get called back into */
return 0;
}
}
lrmd_conn->cmds->set_callback(lrmd_conn, read_events);
if (options.timeout) {
g_timeout_add(options.timeout, timeout_err, NULL);
}
if (!options.api_call) {
return 0;
}
if (pcmk__str_eq(options.api_call, "exec", pcmk__str_casei)) {
rc = lrmd_conn->cmds->exec(lrmd_conn,
options.rsc_id,
options.action,
NULL,
options.interval_ms,
options.timeout,
options.start_delay,
options.exec_call_opts,
options.params);
if (rc > 0) {
exec_call_id = rc;
print_result("API-CALL 'exec' action pending, waiting on response");
}
} else if (pcmk__str_eq(options.api_call, "register_rsc", pcmk__str_casei)) {
rc = lrmd_conn->cmds->register_rsc(lrmd_conn,
options.rsc_id,
options.class, options.provider, options.type, 0);
} else if (pcmk__str_eq(options.api_call, "get_rsc_info", pcmk__str_casei)) {
lrmd_rsc_info_t *rsc_info;
rsc_info = lrmd_conn->cmds->get_rsc_info(lrmd_conn, options.rsc_id, 0);
if (rsc_info) {
print_result("RSC_INFO: id:%s class:%s provider:%s type:%s",
rsc_info->id, rsc_info->standard,
(rsc_info->provider? rsc_info->provider : ""),
rsc_info->type);
lrmd_free_rsc_info(rsc_info);
rc = pcmk_ok;
} else {
rc = -1;
}
} else if (pcmk__str_eq(options.api_call, "unregister_rsc", pcmk__str_casei)) {
rc = lrmd_conn->cmds->unregister_rsc(lrmd_conn, options.rsc_id, 0);
} else if (pcmk__str_eq(options.api_call, "cancel", pcmk__str_casei)) {
rc = lrmd_conn->cmds->cancel(lrmd_conn, options.rsc_id, options.action,
options.interval_ms);
} else if (pcmk__str_eq(options.api_call, "metadata", pcmk__str_casei)) {
char *output = NULL;
rc = lrmd_conn->cmds->get_metadata(lrmd_conn,
options.class,
options.provider, options.type, &output, 0);
if (rc == pcmk_ok) {
print_result("%s", output);
free(output);
}
} else if (pcmk__str_eq(options.api_call, "list_agents", pcmk__str_casei)) {
lrmd_list_t *list = NULL;
lrmd_list_t *iter = NULL;
rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, options.class, options.provider);
if (rc > 0) {
print_result("%d agents found", rc);
for (iter = list; iter != NULL; iter = iter->next) {
print_result("%s", iter->val);
}
lrmd_list_freeall(list);
rc = 0;
} else {
print_result("API_CALL FAILURE - no agents found");
rc = -1;
}
} else if (pcmk__str_eq(options.api_call, "list_ocf_providers", pcmk__str_casei)) {
lrmd_list_t *list = NULL;
lrmd_list_t *iter = NULL;
rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, options.type, &list);
if (rc > 0) {
print_result("%d providers found", rc);
for (iter = list; iter != NULL; iter = iter->next) {
print_result("%s", iter->val);
}
lrmd_list_freeall(list);
rc = 0;
} else {
print_result("API_CALL FAILURE - no providers found");
rc = -1;
}
} else if (pcmk__str_eq(options.api_call, "list_standards", pcmk__str_casei)) {
lrmd_list_t *list = NULL;
lrmd_list_t *iter = NULL;
rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);
if (rc > 0) {
print_result("%d standards found", rc);
for (iter = list; iter != NULL; iter = iter->next) {
print_result("%s", iter->val);
}
lrmd_list_freeall(list);
rc = 0;
} else {
print_result("API_CALL FAILURE - no providers found");
rc = -1;
}
} else if (pcmk__str_eq(options.api_call, "get_recurring_ops", pcmk__str_casei)) {
GList *op_list = NULL;
GList *op_item = NULL;
rc = lrmd_conn->cmds->get_recurring_ops(lrmd_conn, options.rsc_id, 0, 0,
&op_list);
for (op_item = op_list; op_item != NULL; op_item = op_item->next) {
lrmd_op_info_t *op_info = op_item->data;
print_result("RECURRING_OP: %s_%s_%s timeout=%sms",
op_info->rsc_id, op_info->action,
op_info->interval_ms_s, op_info->timeout_ms_s);
lrmd_free_op_info(op_info);
}
g_list_free(op_list);
} else if (options.api_call) {
print_result("API-CALL FAILURE unknown action '%s'", options.action);
test_exit(CRM_EX_ERROR);
}
if (rc < 0) {
print_result("API-CALL FAILURE for '%s' api_rc:%d",
options.api_call, rc);
test_exit(CRM_EX_ERROR);
}
if (options.api_call && rc == pcmk_ok) {
print_result("API-CALL SUCCESSFUL for '%s'", options.api_call);
if (!options.listen) {
test_exit(CRM_EX_OK);
}
}
if (options.no_wait) {
/* just make the call and exit regardless of anything else. */
test_exit(CRM_EX_OK);
}
return 0;
}
/*!
* \internal
* \brief Generate resource parameters from CIB if none explicitly given
*
* \return Standard Pacemaker return code
*/
static int
generate_params(void)
{
int rc = pcmk_rc_ok;
pcmk_scheduler_t *scheduler = NULL;
xmlNode *cib_xml_copy = NULL;
pcmk_resource_t *rsc = NULL;
GHashTable *params = NULL;
GHashTable *meta = NULL;
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
if (options.params != NULL) {
return pcmk_rc_ok; // User specified parameters explicitly
}
// Retrieve and update CIB
rc = cib__signon_query(NULL, NULL, &cib_xml_copy);
if (rc != pcmk_rc_ok) {
return rc;
}
- if (!cli_config_update(&cib_xml_copy, NULL, FALSE)) {
+ if (!pcmk__update_configured_schema(&cib_xml_copy, false)) {
crm_err("Could not update CIB");
return pcmk_rc_cib_corrupt;
}
// Calculate cluster status
scheduler = pe_new_working_set();
if (scheduler == NULL) {
crm_crit("Could not allocate scheduler data");
return ENOMEM;
}
pcmk__set_scheduler_flags(scheduler,
pcmk_sched_no_counts|pcmk_sched_no_compat);
scheduler->input = cib_xml_copy;
scheduler->now = crm_time_new(NULL);
cluster_status(scheduler);
// Find resource in CIB
rsc = pe_find_resource_with_flags(scheduler->resources, options.rsc_id,
pcmk_rsc_match_history
|pcmk_rsc_match_basename);
if (rsc == NULL) {
crm_err("Resource does not exist in config");
pe_free_working_set(scheduler);
return EINVAL;
}
// Add resource instance parameters to options.params
params = pe_rsc_params(rsc, NULL, scheduler);
if (params != NULL) {
g_hash_table_iter_init(&iter, params);
while (g_hash_table_iter_next(&iter, (gpointer *) &key,
(gpointer *) &value)) {
options.params = lrmd_key_value_add(options.params, key, value);
}
}
// Add resource meta-attributes to options.params
meta = pcmk__strkey_table(free, free);
get_meta_attributes(meta, rsc, NULL, scheduler);
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);
options.params = lrmd_key_value_add(options.params, crm_name, value);
free(crm_name);
}
g_hash_table_destroy(meta);
pe_free_working_set(scheduler);
return rc;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
context = pcmk__build_arg_context(args, NULL, group, NULL);
pcmk__add_main_args(context, basic_entries);
pcmk__add_arg_group(context, "api-call", "API Call Options:",
"Parameters for api-call option", api_call_entries);
return context;
}
int
main(int argc, char **argv)
{
GError *error = NULL;
crm_exit_t exit_code = CRM_EX_OK;
crm_trigger_t *trig = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
/* Typically we'd pass all the single character options that take an argument
* as the second parameter here (and there's a bunch of those in this tool).
* However, we control how this program is called so we can just not call it
* in a way where the preprocessing ever matters.
*/
gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
GOptionContext *context = build_arg_context(args, NULL);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
/* We have to use crm_log_init here to set up the logging because there's
* different handling for daemons vs. command line programs, and
* pcmk__cli_init_logging is set up to only handle the latter.
*/
crm_log_init(NULL, LOG_INFO, TRUE, (args->verbosity? TRUE : FALSE), argc,
argv, FALSE);
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
if (!options.listen && pcmk__strcase_any_of(options.api_call, "metadata", "list_agents",
"list_standards", "list_ocf_providers", NULL)) {
options.no_connect = TRUE;
}
if (options.is_running) {
int rc = pcmk_rc_ok;
if (options.rsc_id == NULL) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"--is-running requires --rsc-id");
goto done;
}
options.interval_ms = 0;
if (options.timeout == 0) {
options.timeout = 30000;
}
rc = generate_params();
if (rc != pcmk_rc_ok) {
exit_code = pcmk_rc2exitc(rc);
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Can not determine resource status: "
"unable to get parameters from CIB");
goto done;
}
options.api_call = "exec";
options.action = PCMK_ACTION_MONITOR;
options.exec_call_opts = lrmd_opt_notify_orig_only;
}
if (!options.api_call && !options.listen) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Must specify at least one of --api-call, --listen, "
"or --is-running");
goto done;
}
if (options.use_tls) {
lrmd_conn = lrmd_remote_api_new(NULL, "localhost", 0);
} else {
lrmd_conn = lrmd_api_new();
}
trig = mainloop_add_trigger(G_PRIORITY_HIGH, start_test, NULL);
mainloop_set_trigger(trig);
mainloop_add_signal(SIGTERM, test_shutdown);
crm_info("Starting");
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
free(key);
free(val);
pcmk__output_and_clear_error(&error, NULL);
return test_exit(exit_code);
}
diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c
index 1f31fae184..2cb8f323bc 100644
--- a/daemons/execd/remoted_schemas.c
+++ b/daemons/execd/remoted_schemas.c
@@ -1,284 +1,285 @@
/*
* Copyright 2023-2024 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 "pacemaker-execd.h"
static pid_t schema_fetch_pid = 0;
static int
rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb)
{
/* Don't delete PCMK__REMOTE_SCHEMA_DIR . */
if (ftwb->level == 0) {
return 0;
}
if (remove(pathname) != 0) {
int rc = errno;
crm_err("Could not remove %s: %s", pathname, pcmk_rc_str(rc));
return -1;
}
return 0;
}
static void
clean_up_extra_schema_files(void)
{
const char *remote_schema_dir = pcmk__remote_schema_dir();
struct stat sb;
int rc;
rc = stat(remote_schema_dir, &sb);
if (rc == -1) {
if (errno == ENOENT) {
/* If the directory doesn't exist, try to make it first. */
if (mkdir(remote_schema_dir, 0755) != 0) {
rc = errno;
crm_err("Could not create directory for schemas: %s",
pcmk_rc_str(rc));
}
} else {
rc = errno;
crm_err("Could not create directory for schemas: %s",
pcmk_rc_str(rc));
}
} else if (!S_ISDIR(sb.st_mode)) {
/* If something exists with the same name that's not a directory, that's
* an error.
*/
crm_err("%s already exists but is not a directory", remote_schema_dir);
} else {
/* It's a directory - clear it out so we can download potentially new
* schema files.
*/
rc = nftw(remote_schema_dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
if (rc != 0) {
crm_err("Could not remove %s: %s", remote_schema_dir, pcmk_rc_str(rc));
}
}
}
static void
write_extra_schema_file(xmlNode *xml, void *user_data)
{
const char *remote_schema_dir = pcmk__remote_schema_dir();
const char *file = NULL;
char *path = NULL;
int rc;
file = crm_element_value(xml, PCMK_XA_PATH);
if (file == NULL) {
crm_warn("No destination path given in schema request");
return;
}
path = crm_strdup_printf("%s/%s", remote_schema_dir, file);
/* The schema is a CDATA node, which is a child of the node. Traverse
* all children and look for the first CDATA child. There can't be more than
* one because we only have one file attribute on the parent.
*/
for (xmlNode *child = xml->children; child != NULL; child = child->next) {
FILE *stream = NULL;
if (child->type != XML_CDATA_SECTION_NODE) {
continue;
}
stream = fopen(path, "w+");
if (stream == NULL) {
crm_warn("Could not write schema file %s: %s", path, strerror(errno));
} else {
rc = fprintf(stream, "%s", child->content);
if (rc < 0) {
crm_warn("Could not write schema file %s: %s", path, strerror(errno));
}
fclose(stream);
}
break;
}
free(path);
}
static void
get_schema_files(void)
{
int rc = pcmk_rc_ok;
cib_t *cib = NULL;
xmlNode *reply;
cib = cib_new();
if (cib == NULL) {
_exit(ENOTCONN);
}
rc = cib->cmds->signon(cib, crm_system_name, cib_query);
if (rc != pcmk_ok) {
crm_err("Could not connect to the CIB manager: %s", pcmk_strerror(rc));
_exit(pcmk_rc2exitc(rc));
}
- rc = cib->cmds->fetch_schemas(cib, &reply, xml_latest_schema(), cib_sync_call);
+ rc = cib->cmds->fetch_schemas(cib, &reply, pcmk__highest_schema_name(),
+ cib_sync_call);
if (rc != pcmk_ok) {
crm_err("Could not get schema files: %s", pcmk_strerror(rc));
rc = pcmk_legacy2rc(rc);
} else if (reply->children != NULL) {
/* The returned document looks something like this:
*
*
*
*
*
*
*
*
* ...
*
*
*
* ...
*
*
*
*
* All the and tags are really just there for organizing
* the XML a little better. What we really care about are the nodes,
* and specifically the path attributes and the CDATA children (not shown)
* of each. We can use an xpath query to reach down and get all the
* nodes at once.
*
* If we already have the latest schema version, or we asked for one later
* than what the cluster supports, we'll get back an empty node,
* so all this will continue to work. It just won't do anything.
*/
crm_foreach_xpath_result(reply, "//" PCMK_XA_FILE,
write_extra_schema_file, NULL);
}
cib__clean_up_connection(&cib);
_exit(pcmk_rc2exitc(rc));
}
/* Load any additional schema files when the child is finished fetching and
* saving them to disk.
*/
static void
get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode)
{
const char *errmsg = "Could not load additional schema files";
if ((signo == 0) && (exitcode == 0)) {
const char *remote_schema_dir = pcmk__remote_schema_dir();
/* Don't just crm_schema_init here because that will load the base
* schemas again too. Instead just load the things we fetched.
*/
pcmk__load_schemas_from_dir(remote_schema_dir);
pcmk__sort_schemas();
crm_info("Fetching extra schema files completed successfully");
} else {
if (signo == 0) {
crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
} else {
crm_err("%s: process %d terminated with signal %d (%s)%s",
errmsg, (int) pid, signo, strsignal(signo),
(core? " and dumped core" : ""));
}
/* Clean up any incomplete schema data we might have been downloading when
* the process timed out or crashed. We don't need to do any extra cleanup
* because we never loaded the extra schemas, and we don't need to call
* crm_schema_init because that was called in remoted_request_cib_schema_files
* before this function.
*/
clean_up_extra_schema_files();
}
}
void
remoted_request_cib_schema_files(void)
{
pid_t pid;
int rc;
/* If a previous schema-fetch process is still running when we're called
* again, it's hung. Attempt to kill it before cleaning up the extra
* directory.
*/
if (schema_fetch_pid != 0) {
if (mainloop_child_kill(schema_fetch_pid) == FALSE) {
crm_warn("Unable to kill pre-existing schema-fetch process");
return;
}
schema_fetch_pid = 0;
}
/* Clean up any extra schema files we downloaded from a previous cluster
* connection. After the files are gone, we need to wipe them from
* known_schemas, but there's no opposite operation for add_schema().
*
* Instead, unload all the schemas. This means we'll also forget about all
- * the installed schemas as well, which means that xml_latest_schema() will
- * fail. So we need to load the base schemas right now.
+ * installed schemas as well, which means that pcmk__highest_schema_name()
+ * would fail. So we need to load the base schemas right now.
*/
clean_up_extra_schema_files();
crm_schema_cleanup();
crm_schema_init();
crm_info("Fetching extra schema files from cluster");
pid = fork();
switch (pid) {
case -1: {
rc = errno;
crm_warn("Could not spawn process to get schema files: %s", pcmk_rc_str(rc));
break;
}
case 0:
/* child */
get_schema_files();
break;
default:
/* parent */
schema_fetch_pid = pid;
mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL,
mainloop_leave_pid_group,
get_schema_files_complete);
break;
}
}
diff --git a/daemons/schedulerd/schedulerd_messages.c b/daemons/schedulerd/schedulerd_messages.c
index c28550aa9c..3bbd3e8bf9 100644
--- a/daemons/schedulerd/schedulerd_messages.c
+++ b/daemons/schedulerd/schedulerd_messages.c
@@ -1,340 +1,340 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "pacemaker-schedulerd.h"
static GHashTable *schedulerd_handlers = NULL;
static pcmk_scheduler_t *
init_working_set(void)
{
pcmk_scheduler_t *scheduler = pe_new_working_set();
pcmk__mem_assert(scheduler);
crm_config_error = FALSE;
crm_config_warning = FALSE;
was_processing_error = FALSE;
was_processing_warning = FALSE;
scheduler->priv = logger_out;
return scheduler;
}
static xmlNode *
handle_pecalc_request(pcmk__request_t *request)
{
static struct series_s {
const char *name;
const char *param;
/* Maximum number of inputs of this kind to save to disk.
* If -1, save all; if 0, save none.
*/
int wrap;
} series[] = {
{ "pe-error", PCMK_OPT_PE_ERROR_SERIES_MAX, -1 },
{ "pe-warn", PCMK_OPT_PE_WARN_SERIES_MAX, 5000 },
{ "pe-input", PCMK_OPT_PE_INPUT_SERIES_MAX, 4000 },
};
xmlNode *msg = request->xml;
xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CRM_XML, NULL, NULL);
xmlNode *xml_data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
static char *last_digest = NULL;
static char *filename = NULL;
unsigned int seq;
int series_id = 0;
int series_wrap = 0;
char *digest = NULL;
const char *value = NULL;
time_t execution_date = time(NULL);
xmlNode *converted = NULL;
xmlNode *reply = NULL;
bool is_repoke = false;
bool process = true;
pcmk_scheduler_t *scheduler = init_working_set();
pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE);
digest = calculate_xml_versioned_digest(xml_data, FALSE, FALSE,
CRM_FEATURE_SET);
converted = pcmk__xml_copy(NULL, xml_data);
- if (!cli_config_update(&converted, NULL, TRUE)) {
+ if (!pcmk__update_configured_schema(&converted, true)) {
scheduler->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH);
crm_xml_add_int(scheduler->graph, "transition_id", 0);
crm_xml_add_int(scheduler->graph, PCMK_OPT_CLUSTER_DELAY, 0);
process = false;
free(digest);
} else if (pcmk__str_eq(digest, last_digest, pcmk__str_casei)) {
is_repoke = true;
free(digest);
} else {
free(last_digest);
last_digest = digest;
}
if (process) {
pcmk__schedule_actions(converted,
pcmk_sched_no_counts
|pcmk_sched_no_compat
|pcmk_sched_show_utilization, scheduler);
}
// Get appropriate index into series[] array
if (was_processing_error || crm_config_error) {
series_id = 0;
} else if (was_processing_warning || crm_config_warning) {
series_id = 1;
} else {
series_id = 2;
}
value = pcmk__cluster_option(scheduler->config_hash,
series[series_id].param);
if ((value == NULL)
|| (pcmk__scan_min_int(value, &series_wrap, -1) != pcmk_rc_ok)) {
series_wrap = series[series_id].wrap;
}
if (pcmk__read_series_sequence(PE_STATE_DIR, series[series_id].name,
&seq) != pcmk_rc_ok) {
// @TODO maybe handle errors better ...
seq = 0;
}
crm_trace("Series %s: wrap=%d, seq=%u, pref=%s",
series[series_id].name, series_wrap, seq, value);
scheduler->input = NULL;
reply = create_reply(msg, scheduler->graph);
if (reply == NULL) {
pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
"Failed building ping reply for client %s",
pcmk__client_name(request->ipc_client));
goto done;
}
if (series_wrap == 0) { // Don't save any inputs of this kind
free(filename);
filename = NULL;
} else if (!is_repoke) { // Input changed, save to disk
free(filename);
filename = pcmk__series_filename(PE_STATE_DIR,
series[series_id].name, seq, true);
}
crm_xml_add(reply, PCMK__XA_CRM_TGRAPH_IN, filename);
crm_xml_add_int(reply, PCMK__XA_GRAPH_ERRORS, was_processing_error);
crm_xml_add_int(reply, PCMK__XA_GRAPH_WARNINGS, was_processing_warning);
crm_xml_add_int(reply, PCMK__XA_CONFIG_ERRORS, crm_config_error);
crm_xml_add_int(reply, PCMK__XA_CONFIG_WARNINGS, crm_config_warning);
pcmk__log_transition_summary(filename);
if (series_wrap == 0) {
crm_debug("Not saving input to disk (disabled by configuration)");
} else if (is_repoke) {
crm_info("Input has not changed since last time, not saving to disk");
} else {
unlink(filename);
crm_xml_add_ll(xml_data, PCMK_XA_EXECUTION_DATE,
(long long) execution_date);
pcmk__xml_write_file(xml_data, filename, true, NULL);
pcmk__write_series_sequence(PE_STATE_DIR, series[series_id].name,
++seq, series_wrap);
}
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
done:
free_xml(converted);
pe_free_working_set(scheduler);
return reply;
}
static xmlNode *
handle_unknown_request(pcmk__request_t *request)
{
pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
PCMK__XE_ACK, NULL, CRM_EX_INVALID_PARAM);
pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
"Unknown IPC request type '%s' (bug?)",
pcmk__client_name(request->ipc_client));
return NULL;
}
static xmlNode *
handle_hello_request(pcmk__request_t *request)
{
pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE);
crm_trace("Received IPC hello from %s", pcmk__client_name(request->ipc_client));
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
return NULL;
}
static void
schedulerd_register_handlers(void)
{
pcmk__server_command_t handlers[] = {
{ CRM_OP_HELLO, handle_hello_request },
{ CRM_OP_PECALC, handle_pecalc_request },
{ NULL, handle_unknown_request },
};
schedulerd_handlers = pcmk__register_handlers(handlers);
}
static int32_t
pe_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
{
crm_trace("Connection %p", c);
if (pcmk__new_client(c, uid, gid) == NULL) {
return -ENOMEM;
}
return 0;
}
static int32_t
pe_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size)
{
uint32_t id = 0;
uint32_t flags = 0;
xmlNode *msg = NULL;
pcmk__client_t *c = pcmk__find_client(qbc);
const char *sys_to = NULL;
CRM_CHECK(c != NULL, return 0);
if (schedulerd_handlers == NULL) {
schedulerd_register_handlers();
}
msg = pcmk__client_data2xml(c, data, &id, &flags);
if (msg == NULL) {
pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL, CRM_EX_PROTOCOL);
return 0;
}
sys_to = crm_element_value(msg, PCMK__XA_CRM_SYS_TO);
if (pcmk__str_eq(crm_element_value(msg, PCMK__XA_SUBT),
PCMK__VALUE_RESPONSE, pcmk__str_none)) {
pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL,
CRM_EX_INDETERMINATE);
crm_info("Ignoring IPC reply from %s", pcmk__client_name(c));
} else if (!pcmk__str_eq(sys_to, CRM_SYSTEM_PENGINE, pcmk__str_none)) {
pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL,
CRM_EX_INDETERMINATE);
crm_info("Ignoring invalid IPC message: to '%s' not "
CRM_SYSTEM_PENGINE, pcmk__s(sys_to, ""));
} else {
char *log_msg = NULL;
const char *reason = NULL;
xmlNode *reply = NULL;
pcmk__request_t request = {
.ipc_client = c,
.ipc_id = id,
.ipc_flags = flags,
.peer = NULL,
.xml = msg,
.call_options = 0,
.result = PCMK__UNKNOWN_RESULT,
};
request.op = crm_element_value_copy(request.xml, PCMK__XA_CRM_TASK);
CRM_CHECK(request.op != NULL, return 0);
reply = pcmk__process_request(&request, schedulerd_handlers);
if (reply != NULL) {
pcmk__ipc_send_xml(c, id, reply, crm_ipc_server_event);
free_xml(reply);
}
reason = request.result.exit_reason;
log_msg = crm_strdup_printf("Processed %s request from %s %s: %s%s%s%s",
request.op, pcmk__request_origin_type(&request),
pcmk__request_origin(&request),
pcmk_exec_status_str(request.result.execution_status),
(reason == NULL)? "" : " (",
(reason == NULL)? "" : reason,
(reason == NULL)? "" : ")");
if (!pcmk__result_ok(&request.result)) {
crm_warn("%s", log_msg);
} else {
crm_debug("%s", log_msg);
}
free(log_msg);
pcmk__reset_request(&request);
}
free_xml(msg);
return 0;
}
/* Error code means? */
static int32_t
pe_ipc_closed(qb_ipcs_connection_t * c)
{
pcmk__client_t *client = pcmk__find_client(c);
if (client == NULL) {
return 0;
}
crm_trace("Connection %p", c);
pcmk__free_client(client);
return 0;
}
static void
pe_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
pe_ipc_closed(c);
}
struct qb_ipcs_service_handlers ipc_callbacks = {
.connection_accept = pe_ipc_accept,
.connection_created = NULL,
.msg_process = pe_ipc_dispatch,
.connection_closed = pe_ipc_closed,
.connection_destroyed = pe_ipc_destroy
};
diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am
index 64adbf526a..f47503611a 100644
--- a/include/crm/common/Makefile.am
+++ b/include/crm/common/Makefile.am
@@ -1,54 +1,52 @@
#
# Copyright 2004-2024 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
MAINTAINERCLEANFILES = Makefile.in
headerdir=$(pkgincludedir)/crm/common
header_HEADERS = acl.h \
actions.h \
agents.h \
agents_compat.h \
cib.h \
ipc.h \
ipc_controld.h \
ipc_pacemakerd.h \
ipc_schedulerd.h \
iso8601.h \
logging.h \
logging_compat.h \
mainloop.h \
mainloop_compat.h \
nodes.h \
nodes_internal.h \
nvpair.h \
options.h \
output.h \
resources.h \
results.h \
results_compat.h \
roles.h \
rules.h \
scheduler.h \
scheduler_types.h \
- schemas.h \
- schemas_compat.h \
scores.h \
scores_compat.h \
tags.h \
tickets.h \
util.h \
util_compat.h \
xml.h \
xml_compat.h \
xml_io.h \
xml_io_compat.h \
xml_names.h
noinst_HEADERS = $(wildcard *internal.h)
diff --git a/include/crm/common/schemas.h b/include/crm/common/schemas.h
deleted file mode 100644
index ade23829ad..0000000000
--- a/include/crm/common/schemas.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2004-2024 the Pacemaker project contributors
- *
- * The version control history for this file may have further details.
- *
- * This source code is licensed under the GNU Lesser General Public License
- * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
- */
-
-#ifndef PCMK__CRM_COMMON_SCHEMAS__H
-# define PCMK__CRM_COMMON_SCHEMAS__H
-
-#include // gboolean
-#include // xmlNode
-
-gboolean validate_xml(xmlNode *xml_blob, const char *validation,
- gboolean to_logs);
-gboolean validate_xml_verbose(const xmlNode *xml_blob);
-
-int get_schema_version(const char *name);
-const char *get_schema_name(int version);
-const char *xml_latest_schema(void);
-gboolean cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs);
-
-#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
-#include
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // PCMK__CRM_COMMON_SCHEMAS__H
diff --git a/include/crm/common/schemas_compat.h b/include/crm/common/schemas_compat.h
deleted file mode 100644
index 39ff71cd89..0000000000
--- a/include/crm/common/schemas_compat.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2024 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 PCMK__CRM_COMMON_SCHEMAS_COMPAT__H
-#define PCMK__CRM_COMMON_SCHEMAS_COMPAT__H
-
-#include // xmlNode
-#include // gboolean
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * \file
- * \brief Deprecated Pacemaker XML schemas API
- * \ingroup core
- * \deprecated Do not include this header directly. The APIs in this header, and
- * the header itself, will be removed in a future release.
- */
-
-//! \deprecated Do not use
-int update_validation(xmlNode **xml_blob, int *best, int max,
- gboolean transform, gboolean to_logs);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // PCMK__CRM_COMMON_SCHEMAS_COMPAT__H
diff --git a/include/crm/common/schemas_internal.h b/include/crm/common/schemas_internal.h
index 4c398f7dcb..425b539b0f 100644
--- a/include/crm/common/schemas_internal.h
+++ b/include/crm/common/schemas_internal.h
@@ -1,34 +1,37 @@
/*
* Copyright 2006-2024 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 PCMK__SCHEMAS_INTERNAL__H
# define PCMK__SCHEMAS_INTERNAL__H
#include // GList, gboolean
#include // xmlNode, xmlRelaxNGValidityErrorFunc
void crm_schema_init(void);
void crm_schema_cleanup(void);
void pcmk__load_schemas_from_dir(const char *dir);
void pcmk__sort_schemas(void);
GList *pcmk__schema_files_later_than(const char *name);
void pcmk__build_schema_xml_node(xmlNode *parent, const char *name,
GList **already_included);
const char *pcmk__remote_schema_dir(void);
GList *pcmk__get_schema(const char *name);
+const char *pcmk__highest_schema_name(void);
int pcmk__cmp_schemas_by_name(const char *schema1_name,
const char *schema2_name);
-gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
- xmlRelaxNGValidityErrorFunc error_handler,
- void *error_handler_context);
+bool pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
+ xmlRelaxNGValidityErrorFunc error_handler,
+ void *error_handler_context);
+bool pcmk__configured_schema_validates(xmlNode *xml);
+bool pcmk__update_configured_schema(xmlNode **xml, bool to_logs);
int pcmk__update_schema(xmlNode **xml, const char *max_schema_name,
bool transform, bool to_logs);
#endif // PCMK__SCHEMAS_INTERNAL__H
diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
index 66c4682b95..28b349455f 100644
--- a/include/crm/common/xml.h
+++ b/include/crm/common/xml.h
@@ -1,130 +1,129 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_XML__H
# define PCMK__CRM_COMMON_XML__H
# include
# include
# include
# include
# include
# include
# include
# include
# include
# include
-# include
# include
# include
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Wrappers for and extensions to libxml2
* \ingroup core
*/
typedef const xmlChar *pcmkXmlStr;
/*
* \brief xmlCopyPropList ACLs-sensitive replacement expading i++ notation
*
* The gist is the same as with \c{xmlCopyPropList(target, src->properties)}.
* The function exits prematurely when any attribute cannot be copied for
* ACLs violation. Even without bailing out, the result can possibly be
* incosistent with expectations in that case, hence the caller shall,
* aposteriori, verify that no document-level-tracked denial was indicated
* with \c{xml_acl_denied(target)} and drop whole such intermediate object.
*
* \param[in,out] target Element to receive attributes from #src element
* \param[in] src Element carrying attributes to copy over to #target
*
* \note Original commit 1c632c506 sadly haven't stated which otherwise
* assumed behaviours of xmlCopyPropList were missing beyond otherwise
* custom extensions like said ACLs and "atomic increment" (that landed
* later on, anyway).
*/
void copy_in_properties(xmlNode *target, const xmlNode *src);
void expand_plus_plus(xmlNode * target, const char *name, const char *value);
/*
* Searching & Modifying
*/
xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
char *calculate_on_disk_digest(xmlNode * local_cib);
char *calculate_operation_digest(xmlNode * local_cib, const char *version);
char *calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
const char *version);
/*!
* \brief Initialize the CRM XML subsystem
*
* This method sets global XML settings and loads pacemaker schemas into the cache.
*/
void crm_xml_init(void);
void crm_xml_cleanup(void);
void pcmk_free_xml_subtree(xmlNode *xml);
void free_xml(xmlNode * child);
xmlNode *sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive);
xmlXPathObjectPtr xpath_search(const xmlNode *xml_top, const char *path);
void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
void (*helper)(xmlNode*, void*), void *user_data);
xmlNode *expand_idref(xmlNode * input, xmlNode * top);
void freeXpathObject(xmlXPathObjectPtr xpathObj);
xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
void dedupXpathResults(xmlXPathObjectPtr xpathObj);
static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
{
if(xpathObj == NULL || xpathObj->nodesetval == NULL) {
return 0;
}
return xpathObj->nodesetval->nodeNr;
}
bool xml_tracking_changes(xmlNode * xml);
bool xml_document_dirty(xmlNode *xml);
void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml);
void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml);
void xml_accept_changes(xmlNode * xml);
bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]);
xmlNode *xml_create_patchset(
int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
void crm_xml_sanitize_id(char *id);
void crm_xml_set_id(xmlNode *xml, const char *format, ...) G_GNUC_PRINTF(2, 3);
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include
#endif
#ifdef __cplusplus
}
#endif
#endif
diff --git a/include/crm/common/xml_compat.h b/include/crm/common/xml_compat.h
index d5d5779fa3..f657ea9af7 100644
--- a/include/crm/common/xml_compat.h
+++ b/include/crm/common/xml_compat.h
@@ -1,167 +1,190 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_XML_COMPAT__H
# define PCMK__CRM_COMMON_XML_COMPAT__H
#include // gboolean
#include // xmlNode
#include // crm_xml_add()
#include // PCMK_XE_CLONE
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Deprecated Pacemaker XML API
* \ingroup core
* \deprecated Do not include this header directly. The XML APIs in this
* header, and the header itself, will be removed in a future
* release.
*/
//! \deprecated Do not use (will be removed in a future release)
#define XML_PARANOIA_CHECKS 0
//! \deprecated This function will be removed in a future release
xmlDoc *getDocPtr(xmlNode *node);
//! \deprecated This function will be removed in a future release
int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child);
//! \deprecated This function will be removed in a future release
xmlNode *find_entity(xmlNode *parent, const char *node_name, const char *id);
//! \deprecated This function will be removed in a future release
char *xml_get_path(const xmlNode *xml);
//! \deprecated This function will be removed in a future release
void xml_log_changes(uint8_t level, const char *function, const xmlNode *xml);
//! \deprecated This function will be removed in a future release
void xml_log_patchset(uint8_t level, const char *function, const xmlNode *xml);
//! \deprecated Use xml_apply_patchset() instead
gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml);
//! \deprecated Do not use (will be removed in a future release)
void crm_destroy_xml(gpointer data);
//! \deprecated Check children member directly
gboolean xml_has_children(const xmlNode *root);
//! \deprecated Use crm_xml_add() with "true" or "false" instead
static inline const char *
crm_xml_add_boolean(xmlNode *node, const char *name, gboolean value)
{
return crm_xml_add(node, name, (value? "true" : "false"));
}
//! \deprecated Use name member directly
static inline const char *
crm_element_name(const xmlNode *xml)
{
return (xml == NULL)? NULL : (const char *) xml->name;
}
//! \deprecated Do not use
char *crm_xml_escape(const char *text);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *copy_xml(xmlNode *src_node);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *add_node_copy(xmlNode *new_parent, xmlNode *xml_node);
//! \deprecated Do not use
void purge_diff_markers(xmlNode *a_node);
//! \deprecated Do not use
xmlNode *diff_xml_object(xmlNode *left, xmlNode *right, gboolean suppress);
//! \deprecated Do not use
xmlNode *subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
gboolean full, gboolean *changed,
const char *marker);
//! \deprecated Do not use
gboolean can_prune_leaf(xmlNode *xml_node);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *create_xml_node(xmlNode *parent, const char *name);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *pcmk_create_xml_text_node(xmlNode *parent, const char *name,
const char *content);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *pcmk_create_html_node(xmlNode *parent, const char *element_name,
const char *id, const char *class_name,
const char *text);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *first_named_child(const xmlNode *parent, const char *name);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *find_xml_node(const xmlNode *root, const char *search_path,
gboolean must_find);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *crm_next_same_xml(const xmlNode *sibling);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
void xml_remove_prop(xmlNode *obj, const char *name);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
gboolean replace_xml_child(xmlNode *parent, xmlNode *child, xmlNode *update,
gboolean delete_only);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
gboolean update_xml_child(xmlNode *child, xmlNode *to_update);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
int find_xml_children(xmlNode **children, xmlNode *root, const char *tag,
const char *field, const char *value,
gboolean search_matches);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *get_xpath_object_relative(const char *xpath, xmlNode *xml_obj,
int error_level);
//! \deprecated Do not use
void fix_plus_plus_recursive(xmlNode *target);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
gboolean add_message_xml(xmlNode *msg, const char *field, xmlNode *xml);
//! \deprecated Do not use Pacemaker for general-purpose XML manipulation
xmlNode *get_message_xml(const xmlNode *msg, const char *field);
+//! \deprecated Do not use
+const char *xml_latest_schema(void);
+
+//! \deprecated Do not use
+const char *get_schema_name(int version);
+
+//! \deprecated Do not use
+int get_schema_version(const char *name);
+
+//! \deprecated Do not use
+int update_validation(xmlNode **xml_blob, int *best, int max,
+ gboolean transform, gboolean to_logs);
+
+//! \deprecated Do not use
+gboolean validate_xml(xmlNode *xml_blob, const char *validation,
+ gboolean to_logs);
+
+//! \deprecated Do not use
+gboolean validate_xml_verbose(const xmlNode *xml_blob);
+
+//! \deprecated Do not use
+gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs);
+
//! \deprecated Do not use
static inline const char *
crm_map_element_name(const xmlNode *xml)
{
if (xml == NULL) {
return NULL;
} else if (strcmp((const char *) xml->name, "master") == 0) {
// Can't use PCMK__XE_PROMOTABLE_LEGACY because it's internal
return PCMK_XE_CLONE;
} else {
return (const char *) xml->name;
}
}
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_XML_COMPAT__H
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index 4f6cdc2f79..24bd029406 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -1,1175 +1,1176 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-2024 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
#include
#include
#include
#include
#define CIB_SERIES "cib"
#define CIB_SERIES_MAX 100
#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
created with hard links
*/
#define CIB_LIVE_NAME CIB_SERIES ".xml"
// key: client ID (const char *) -> value: client (cib_t *)
static GHashTable *client_table = NULL;
enum cib_file_flags {
cib_file_flag_dirty = (1 << 0),
cib_file_flag_live = (1 << 1),
};
typedef struct cib_file_opaque_s {
char *id;
char *filename;
uint32_t flags; // Group of enum cib_file_flags
xmlNode *cib_xml;
} cib_file_opaque_t;
static int cib_file_process_commit_transaction(const char *op, int options,
const char *section,
xmlNode *req, xmlNode *input,
xmlNode *existing_cib,
xmlNode **result_cib,
xmlNode **answer);
/*!
* \internal
* \brief Add a CIB file client to client table
*
* \param[in] cib CIB client
*/
static void
register_client(const cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (client_table == NULL) {
client_table = pcmk__strkey_table(NULL, NULL);
}
g_hash_table_insert(client_table, private->id, (gpointer) cib);
}
/*!
* \internal
* \brief Remove a CIB file client from client table
*
* \param[in] cib CIB client
*/
static void
unregister_client(const cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (client_table == NULL) {
return;
}
g_hash_table_remove(client_table, private->id);
/* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged,
* instead of destroying the client table when there are no more clients.
*/
if (g_hash_table_size(client_table) == 0) {
g_hash_table_destroy(client_table);
client_table = NULL;
}
}
/*!
* \internal
* \brief Look up a CIB file client by its ID
*
* \param[in] client_id CIB client ID
*
* \return CIB client with matching ID if found, or \p NULL otherwise
*/
static cib_t *
get_client(const char *client_id)
{
if (client_table == NULL) {
return NULL;
}
return g_hash_table_lookup(client_table, (gpointer) client_id);
}
static const cib__op_fn_t cib_op_functions[] = {
[cib__op_apply_patch] = cib_process_diff,
[cib__op_bump] = cib_process_bump,
[cib__op_commit_transact] = cib_file_process_commit_transaction,
[cib__op_create] = cib_process_create,
[cib__op_delete] = cib_process_delete,
[cib__op_erase] = cib_process_erase,
[cib__op_modify] = cib_process_modify,
[cib__op_query] = cib_process_query,
[cib__op_replace] = cib_process_replace,
[cib__op_upgrade] = cib_process_upgrade,
};
/* cib_file_backup() and cib_file_write_with_digest() need to chown the
* written files only in limited circumstances, so these variables allow
* that to be indicated without affecting external callers
*/
static uid_t cib_file_owner = 0;
static uid_t cib_file_group = 0;
static gboolean cib_do_chown = FALSE;
#define cib_set_file_flags(cibfile, flags_to_set) do { \
(cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_set), \
#flags_to_set); \
} while (0)
#define cib_clear_file_flags(cibfile, flags_to_clear) do { \
(cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_clear), \
#flags_to_clear); \
} while (0)
/*!
* \internal
* \brief Get the function that performs a given CIB file operation
*
* \param[in] operation Operation whose function to look up
*
* \return Function that performs \p operation for a CIB file client
*/
static cib__op_fn_t
file_get_op_function(const cib__operation_t *operation)
{
enum cib__op_type type = operation->type;
CRM_ASSERT(type >= 0);
if (type >= PCMK__NELEM(cib_op_functions)) {
return NULL;
}
return cib_op_functions[type];
}
/*!
* \internal
* \brief Check whether a file is the live CIB
*
* \param[in] filename Name of file to check
*
* \return TRUE if file exists and its real path is same as live CIB's
*/
static gboolean
cib_file_is_live(const char *filename)
{
gboolean same = FALSE;
if (filename != NULL) {
// Canonicalize file names for true comparison
char *real_filename = NULL;
if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
char *real_livename = NULL;
if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
&real_livename) == pcmk_rc_ok) {
same = !strcmp(real_filename, real_livename);
free(real_livename);
}
free(real_filename);
}
}
return same;
}
static int
cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output)
{
int rc = pcmk_ok;
const cib__operation_t *operation = NULL;
cib__op_fn_t op_function = NULL;
int call_id = 0;
int call_options = cib_none;
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
const char *section = crm_element_value(request, PCMK__XA_CIB_SECTION);
xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA,
NULL, NULL);
xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
bool changed = false;
bool read_only = false;
xmlNode *result_cib = NULL;
xmlNode *cib_diff = NULL;
cib_file_opaque_t *private = cib->variant_opaque;
// We error checked these in callers
cib__get_operation(op, &operation);
op_function = file_get_op_function(operation);
crm_element_value_int(request, PCMK__XA_CIB_CALLID, &call_id);
crm_element_value_int(request, PCMK__XA_CIB_CALLOPT, &call_options);
read_only = !pcmk_is_set(operation->flags, cib__op_attr_modifies);
// Mirror the logic in prepare_input() in pacemaker-based
if ((section != NULL) && pcmk__xe_is(data, PCMK_XE_CIB)) {
data = pcmk_find_cib_element(data, section);
}
rc = cib_perform_op(cib, op, call_options, op_function, read_only, section,
request, data, true, &changed, &private->cib_xml,
&result_cib, &cib_diff, output);
if (pcmk_is_set(call_options, cib_transaction)) {
/* The rest of the logic applies only to the transaction as a whole, not
* to individual requests.
*/
goto done;
}
if (rc == -pcmk_err_schema_validation) {
- validate_xml_verbose(result_cib);
+ // Show validation errors to stderr
+ pcmk__validate_xml(result_cib, NULL, NULL, NULL);
} else if ((rc == pcmk_ok) && !read_only) {
pcmk__log_xml_patchset(LOG_DEBUG, cib_diff);
if (result_cib != private->cib_xml) {
free_xml(private->cib_xml);
private->cib_xml = result_cib;
}
cib_set_file_flags(private, cib_file_flag_dirty);
}
// Global operation callback (deprecated)
if (cib->op_callback != NULL) {
cib->op_callback(NULL, call_id, rc, *output);
}
done:
if ((result_cib != private->cib_xml) && (result_cib != *output)) {
free_xml(result_cib);
}
free_xml(cib_diff);
return rc;
}
static int
cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data,
xmlNode **output_data, int call_options,
const char *user_name)
{
int rc = pcmk_ok;
xmlNode *request = NULL;
xmlNode *output = NULL;
cib_file_opaque_t *private = cib->variant_opaque;
const cib__operation_t *operation = NULL;
crm_info("Handling %s operation for %s as %s",
pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"),
pcmk__s(user_name, "default user"));
if (output_data != NULL) {
*output_data = NULL;
}
if (cib->state == cib_disconnected) {
return -ENOTCONN;
}
rc = cib__get_operation(op, &operation);
rc = pcmk_rc2legacy(rc);
if (rc != pcmk_ok) {
// @COMPAT: At compatibility break, use rc directly
return -EPROTONOSUPPORT;
}
if (file_get_op_function(operation) == NULL) {
// @COMPAT: At compatibility break, use EOPNOTSUPP
crm_err("Operation %s is not supported by CIB file clients", op);
return -EPROTONOSUPPORT;
}
cib__set_call_options(call_options, "file operation", cib_no_mtime);
rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
NULL, &request);
if (rc != pcmk_ok) {
return rc;
}
crm_xml_add(request, PCMK_XE_ACL_TARGET, user_name);
crm_xml_add(request, PCMK__XA_CIB_CLIENTID, private->id);
if (pcmk_is_set(call_options, cib_transaction)) {
rc = cib__extend_transaction(cib, request);
goto done;
}
rc = cib_file_process_request(cib, request, &output);
if ((output_data != NULL) && (output != NULL)) {
if (output->doc == private->cib_xml->doc) {
*output_data = pcmk__xml_copy(NULL, output);
} else {
*output_data = output;
}
}
done:
if ((output != NULL)
&& (output->doc != private->cib_xml->doc)
&& ((output_data == NULL) || (output != *output_data))) {
free_xml(output);
}
free_xml(request);
return rc;
}
/*!
* \internal
* \brief Read CIB from disk and validate it against XML schema
*
* \param[in] filename Name of file to read CIB from
* \param[out] output Where to store the read CIB XML
*
* \return pcmk_ok on success,
* -ENXIO if file does not exist (or stat() otherwise fails), or
* -pcmk_err_schema_validation if XML doesn't parse or validate
* \note If filename is the live CIB, this will *not* verify its digest,
* though that functionality would be trivial to add here.
* Also, this will *not* verify that the file is writable,
* because some callers might not need to write.
*/
static int
load_file_cib(const char *filename, xmlNode **output)
{
struct stat buf;
xmlNode *root = NULL;
/* Ensure file is readable */
if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
return -ENXIO;
}
/* Parse XML from file */
root = pcmk__xml_read(filename);
if (root == NULL) {
return -pcmk_err_schema_validation;
}
/* Add a status section if not already present */
if (pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL) == NULL) {
pcmk__xe_create(root, PCMK_XE_STATUS);
}
/* Validate XML against its specified schema */
- if (validate_xml(root, NULL, TRUE) == FALSE) {
+ if (!pcmk__configured_schema_validates(root)) {
const char *schema = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
crm_err("CIB does not validate against %s, or that schema is unknown", schema);
free_xml(root);
return -pcmk_err_schema_validation;
}
/* Remember the parsed XML for later use */
*output = root;
return pcmk_ok;
}
static int
cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
if (private->filename == NULL) {
rc = -EINVAL;
} else {
rc = load_file_cib(private->filename, &private->cib_xml);
}
if (rc == pcmk_ok) {
crm_debug("Opened connection to local file '%s' for %s",
private->filename, name);
cib->state = cib_connected_command;
cib->type = cib_command;
register_client(cib);
} else {
crm_info("Connection to local file '%s' for %s (client %s) failed: %s",
private->filename, name, private->id, pcmk_strerror(rc));
}
return rc;
}
/*!
* \internal
* \brief Write out the in-memory CIB to a live CIB file
*
* \param[in] cib_root Root of XML tree to write
* \param[in,out] path Full path to file to write
*
* \return 0 on success, -1 on failure
*/
static int
cib_file_write_live(xmlNode *cib_root, char *path)
{
uid_t uid = geteuid();
struct passwd *daemon_pwent;
char *sep = strrchr(path, '/');
const char *cib_dirname, *cib_filename;
int rc = 0;
/* Get the desired uid/gid */
errno = 0;
daemon_pwent = getpwnam(CRM_DAEMON_USER);
if (daemon_pwent == NULL) {
crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
return -1;
}
/* If we're root, we can change the ownership;
* if we're daemon, anything we create will be OK;
* otherwise, block access so we don't create wrong owner
*/
if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
CRM_DAEMON_USER);
return 0;
}
/* fancy footwork to separate dirname from filename
* (we know the canonical name maps to the live CIB,
* but the given name might be relative, or symlinked)
*/
if (sep == NULL) { /* no directory component specified */
cib_dirname = "./";
cib_filename = path;
} else if (sep == path) { /* given name is in / */
cib_dirname = "/";
cib_filename = path + 1;
} else { /* typical case; split given name into parts */
*sep = '\0';
cib_dirname = path;
cib_filename = sep + 1;
}
/* if we're root, we want to update the file ownership */
if (uid == 0) {
cib_file_owner = daemon_pwent->pw_uid;
cib_file_group = daemon_pwent->pw_gid;
cib_do_chown = TRUE;
}
/* write the file */
if (cib_file_write_with_digest(cib_root, cib_dirname,
cib_filename) != pcmk_ok) {
rc = -1;
}
/* turn off file ownership changes, for other callers */
if (uid == 0) {
cib_do_chown = FALSE;
}
/* undo fancy stuff */
if ((sep != NULL) && (*sep == '\0')) {
*sep = '/';
}
return rc;
}
/*!
* \internal
* \brief Sign-off method for CIB file variants
*
* This will write the file to disk if needed, and free the in-memory CIB. If
* the file is the live CIB, it will compute and write a signature as well.
*
* \param[in,out] cib CIB object to sign off
*
* \return pcmk_ok on success, pcmk_err_generic on failure
* \todo This method should refuse to write the live CIB if the CIB manager is
* running.
*/
static int
cib_file_signoff(cib_t *cib)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
crm_debug("Disconnecting from the CIB manager");
cib->state = cib_disconnected;
cib->type = cib_no_connection;
unregister_client(cib);
cib->cmds->end_transaction(cib, false, cib_none);
/* If the in-memory CIB has been changed, write it to disk */
if (pcmk_is_set(private->flags, cib_file_flag_dirty)) {
/* If this is the live CIB, write it out with a digest */
if (pcmk_is_set(private->flags, cib_file_flag_live)) {
if (cib_file_write_live(private->cib_xml, private->filename) < 0) {
rc = pcmk_err_generic;
}
/* Otherwise, it's a simple write */
} else {
bool compress = pcmk__ends_with_ext(private->filename, ".bz2");
if (pcmk__xml_write_file(private->cib_xml, private->filename,
compress, NULL) != pcmk_rc_ok) {
rc = pcmk_err_generic;
}
}
if (rc == pcmk_ok) {
crm_info("Wrote CIB to %s", private->filename);
cib_clear_file_flags(private, cib_file_flag_dirty);
} else {
crm_err("Could not write CIB to %s", private->filename);
}
}
/* Free the in-memory CIB */
free_xml(private->cib_xml);
private->cib_xml = NULL;
return rc;
}
static int
cib_file_free(cib_t *cib)
{
int rc = pcmk_ok;
if (cib->state != cib_disconnected) {
rc = cib_file_signoff(cib);
}
if (rc == pcmk_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
free(private->id);
free(private->filename);
free(private);
free(cib->cmds);
free(cib->user);
free(cib);
} else {
fprintf(stderr, "Couldn't sign off: %d\n", rc);
}
return rc;
}
static int
cib_file_inputfd(cib_t *cib)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_set_connection_dnotify(cib_t *cib,
void (*dnotify) (gpointer user_data))
{
return -EPROTONOSUPPORT;
}
/*!
* \internal
* \brief Get the given CIB connection's unique client identifier
*
* \param[in] cib CIB connection
* \param[out] async_id If not \p NULL, where to store asynchronous client ID
* \param[out] sync_id If not \p NULL, where to store synchronous client ID
*
* \return Legacy Pacemaker return code
*
* \note This is the \p cib_file variant implementation of
* \p cib_api_operations_t:client_id().
*/
static int
cib_file_client_id(const cib_t *cib, const char **async_id,
const char **sync_id)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (async_id != NULL) {
*async_id = private->id;
}
if (sync_id != NULL) {
*sync_id = private->id;
}
return pcmk_ok;
}
cib_t *
cib_file_new(const char *cib_location)
{
cib_file_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
if (cib == NULL) {
return NULL;
}
private = calloc(1, sizeof(cib_file_opaque_t));
if (private == NULL) {
free(cib);
return NULL;
}
private->id = crm_generate_uuid();
cib->variant = cib_file;
cib->variant_opaque = private;
if (cib_location == NULL) {
cib_location = getenv("CIB_file");
CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible
}
private->flags = 0;
if (cib_file_is_live(cib_location)) {
cib_set_file_flags(private, cib_file_flag_live);
crm_trace("File %s detected as live CIB", cib_location);
}
private->filename = strdup(cib_location);
/* assign variant specific ops */
cib->delegate_fn = cib_file_perform_op_delegate;
cib->cmds->signon = cib_file_signon;
cib->cmds->signoff = cib_file_signoff;
cib->cmds->free = cib_file_free;
cib->cmds->inputfd = cib_file_inputfd; // Deprecated method
cib->cmds->register_notification = cib_file_register_notification;
cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
cib->cmds->client_id = cib_file_client_id;
return cib;
}
/*!
* \internal
* \brief Compare the calculated digest of an XML tree against a signature file
*
* \param[in] root Root of XML tree to compare
* \param[in] sigfile Name of signature file containing digest to compare
*
* \return TRUE if digests match or signature file does not exist, else FALSE
*/
static gboolean
cib_file_verify_digest(xmlNode *root, const char *sigfile)
{
gboolean passed = FALSE;
char *expected;
int rc = pcmk__file_contents(sigfile, &expected);
switch (rc) {
case pcmk_rc_ok:
if (expected == NULL) {
crm_err("On-disk digest at %s is empty", sigfile);
return FALSE;
}
break;
case ENOENT:
crm_warn("No on-disk digest present at %s", sigfile);
return TRUE;
default:
crm_err("Could not read on-disk digest from %s: %s",
sigfile, pcmk_rc_str(rc));
return FALSE;
}
passed = pcmk__verify_digest(root, expected);
free(expected);
return passed;
}
/*!
* \internal
* \brief Read an XML tree from a file and verify its digest
*
* \param[in] filename Name of XML file to read
* \param[in] sigfile Name of signature file containing digest to compare
* \param[out] root If non-NULL, will be set to pointer to parsed XML tree
*
* \return 0 if file was successfully read, parsed and verified, otherwise:
* -errno on stat() failure,
* -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
* -pcmk_err_cib_modified if digests do not match
* \note If root is non-NULL, it is the caller's responsibility to free *root on
* successful return.
*/
int
cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
{
int s_res;
struct stat buf;
char *local_sigfile = NULL;
xmlNode *local_root = NULL;
CRM_ASSERT(filename != NULL);
if (root) {
*root = NULL;
}
/* Verify that file exists and its size is nonzero */
s_res = stat(filename, &buf);
if (s_res < 0) {
crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
return -errno;
} else if (buf.st_size == 0) {
crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
return -pcmk_err_cib_corrupt;
}
/* Parse XML */
local_root = pcmk__xml_read(filename);
if (local_root == NULL) {
crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
return -pcmk_err_cib_corrupt;
}
/* If sigfile is not specified, use original file name plus .sig */
if (sigfile == NULL) {
sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename);
}
/* Verify that digests match */
if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
free(local_sigfile);
free_xml(local_root);
return -pcmk_err_cib_modified;
}
free(local_sigfile);
if (root) {
*root = local_root;
} else {
free_xml(local_root);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Back up a CIB
*
* \param[in] cib_dirname Directory containing CIB file and backups
* \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
*
* \return 0 on success, -1 on error
*/
static int
cib_file_backup(const char *cib_dirname, const char *cib_filename)
{
int rc = 0;
unsigned int seq;
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *cib_digest = crm_strdup_printf("%s.sig", cib_path);
char *backup_path;
char *backup_digest;
// Determine backup and digest file names
if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
&seq) != pcmk_rc_ok) {
// @TODO maybe handle errors better ...
seq = 0;
}
backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
CIB_SERIES_BZIP);
backup_digest = crm_strdup_printf("%s.sig", backup_path);
/* Remove the old backups if they exist */
unlink(backup_path);
unlink(backup_digest);
/* Back up the CIB, by hard-linking it to the backup name */
if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_path, backup_path);
rc = -1;
/* Back up the CIB signature similarly */
} else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_digest, backup_digest);
rc = -1;
/* Update the last counter and ensure everything is sync'd to media */
} else {
pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
CIB_SERIES_MAX);
if (cib_do_chown) {
int rc2;
if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
rc = -1;
}
if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
rc = -1;
}
rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
cib_file_owner, cib_file_group);
if (rc2 != pcmk_rc_ok) {
crm_err("Could not set owner of sequence file in %s: %s",
cib_dirname, pcmk_rc_str(rc2));
rc = -1;
}
}
pcmk__sync_directory(cib_dirname);
crm_info("Archived previous version as %s", backup_path);
}
free(cib_path);
free(cib_digest);
free(backup_path);
free(backup_digest);
return rc;
}
/*!
* \internal
* \brief Prepare CIB XML to be written to disk
*
* Set \c PCMK_XA_NUM_UPDATES to 0, set \c PCMK_XA_CIB_LAST_WRITTEN to the
* current timestamp, and strip out the status section.
*
* \param[in,out] root Root of CIB XML tree
*
* \return void
*/
static void
cib_file_prepare_xml(xmlNode *root)
{
xmlNode *cib_status_root = NULL;
/* Always write out with num_updates=0 and current last-written timestamp */
crm_xml_add(root, PCMK_XA_NUM_UPDATES, "0");
pcmk__xe_add_last_written(root);
/* Delete status section before writing to file, because
* we discard it on startup anyway, and users get confused by it */
cib_status_root = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL);
CRM_CHECK(cib_status_root != NULL, return);
free_xml(cib_status_root);
}
/*!
* \internal
* \brief Write CIB to disk, along with a signature file containing its digest
*
* \param[in,out] cib_root Root of XML tree to write
* \param[in] cib_dirname Directory containing CIB and signature files
* \param[in] cib_filename Name (relative to cib_dirname) of file to write
*
* \return pcmk_ok on success,
* pcmk_err_cib_modified if existing cib_filename doesn't match digest,
* pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
* or pcmk_err_cib_save if new cib_filename couldn't be saved
*/
int
cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
const char *cib_filename)
{
int exit_rc = pcmk_ok;
int rc, fd;
char *digest = NULL;
/* Detect CIB version for diagnostic purposes */
const char *epoch = crm_element_value(cib_root, PCMK_XA_EPOCH);
const char *admin_epoch = crm_element_value(cib_root, PCMK_XA_ADMIN_EPOCH);
/* Determine full CIB and signature pathnames */
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *digest_path = crm_strdup_printf("%s.sig", cib_path);
/* Create temporary file name patterns for writing out CIB and signature */
char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
/* Ensure the admin didn't modify the existing CIB underneath us */
crm_trace("Reading cluster configuration file %s", cib_path);
rc = cib_file_read_and_verify(cib_path, NULL, NULL);
if ((rc != pcmk_ok) && (rc != -ENOENT)) {
crm_err("%s was manually modified while the cluster was active!",
cib_path);
exit_rc = pcmk_err_cib_modified;
goto cleanup;
}
/* Back up the existing CIB */
if (cib_file_backup(cib_dirname, cib_filename) < 0) {
exit_rc = pcmk_err_cib_backup;
goto cleanup;
}
crm_debug("Writing CIB to disk");
umask(S_IWGRP | S_IWOTH | S_IROTH);
cib_file_prepare_xml(cib_root);
/* Write the CIB to a temporary file, so we can deploy (near) atomically */
fd = mkstemp(tmp_cib);
if (fd < 0) {
crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Protect the temporary file */
if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Write out the CIB */
if (pcmk__xml_write_fd(cib_root, tmp_cib, fd, false, NULL) != pcmk_rc_ok) {
crm_err("Changes couldn't be written to %s", tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Calculate CIB digest */
digest = calculate_on_disk_digest(cib_root);
CRM_ASSERT(digest != NULL);
crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
(admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
/* Write the CIB digest to a temporary file */
fd = mkstemp(tmp_digest);
if (fd < 0) {
crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
rc = pcmk__write_sync(fd, digest);
if (rc != pcmk_rc_ok) {
crm_err("Could not write digest to %s: %s",
tmp_digest, pcmk_rc_str(rc));
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
close(fd);
crm_debug("Wrote digest %s to disk", digest);
/* Verify that what we wrote is sane */
crm_info("Reading cluster configuration file %s (digest: %s)",
tmp_cib, tmp_digest);
rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
CRM_ASSERT(rc == 0);
/* Rename temporary files to live, and sync directory changes to media */
crm_debug("Activating %s", tmp_cib);
if (rename(tmp_cib, cib_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
exit_rc = pcmk_err_cib_save;
}
if (rename(tmp_digest, digest_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
digest_path);
exit_rc = pcmk_err_cib_save;
}
pcmk__sync_directory(cib_dirname);
cleanup:
free(cib_path);
free(digest_path);
free(digest);
free(tmp_digest);
free(tmp_cib);
return exit_rc;
}
/*!
* \internal
* \brief Process requests in a CIB transaction
*
* Stop when a request fails or when all requests have been processed.
*
* \param[in,out] cib CIB client
* \param[in,out] transaction CIB transaction
*
* \return Standard Pacemaker return code
*/
static int
cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction)
{
cib_file_opaque_t *private = cib->variant_opaque;
for (xmlNode *request = pcmk__xe_first_child(transaction,
PCMK__XE_CIB_COMMAND, NULL,
NULL);
request != NULL; request = pcmk__xe_next_same(request)) {
xmlNode *output = NULL;
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
int rc = cib_file_process_request(cib, request, &output);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Aborting transaction for CIB file client (%s) on file "
"'%s' due to failed %s request: %s",
private->id, private->filename, op, pcmk_rc_str(rc));
crm_log_xml_info(request, "Failed request");
return rc;
}
crm_trace("Applied %s request to transaction working CIB for CIB file "
"client (%s) on file '%s'",
op, private->id, private->filename);
crm_log_xml_trace(request, "Successful request");
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Commit a given CIB file client's transaction to a working CIB copy
*
* \param[in,out] cib CIB file client
* \param[in] transaction CIB transaction
* \param[in,out] result_cib Where to store result CIB
*
* \return Standard Pacemaker return code
*
* \note The caller is responsible for replacing the \p cib argument's
* \p private->cib_xml with \p result_cib on success, and for freeing
* \p result_cib using \p free_xml() on failure.
*/
static int
cib_file_commit_transaction(cib_t *cib, xmlNode *transaction,
xmlNode **result_cib)
{
int rc = pcmk_rc_ok;
cib_file_opaque_t *private = cib->variant_opaque;
xmlNode *saved_cib = private->cib_xml;
CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION),
return pcmk_rc_no_transaction);
/* *result_cib should be a copy of private->cib_xml (created by
* cib_perform_op()). If not, make a copy now. Change tracking isn't
* strictly required here because:
* * Each request in the transaction will have changes tracked and ACLs
* checked if appropriate.
* * cib_perform_op() will infer changes for the commit request at the end.
*/
CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml),
*result_cib = pcmk__xml_copy(NULL, private->cib_xml));
crm_trace("Committing transaction for CIB file client (%s) on file '%s' to "
"working CIB",
private->id, private->filename);
// Apply all changes to a working copy of the CIB
private->cib_xml = *result_cib;
rc = cib_file_process_transaction_requests(cib, transaction);
crm_trace("Transaction commit %s for CIB file client (%s) on file '%s'",
((rc == pcmk_rc_ok)? "succeeded" : "failed"),
private->id, private->filename);
/* Some request types (for example, erase) may have freed private->cib_xml
* (the working copy) and pointed it at a new XML object. In that case, it
* follows that *result_cib (the working copy) was freed.
*
* Point *result_cib at the updated working copy stored in private->cib_xml.
*/
*result_cib = private->cib_xml;
// Point private->cib_xml back to the unchanged original copy
private->cib_xml = saved_cib;
return rc;
}
static int
cib_file_process_commit_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer)
{
int rc = pcmk_rc_ok;
const char *client_id = crm_element_value(req, PCMK__XA_CIB_CLIENTID);
cib_t *cib = NULL;
CRM_CHECK(client_id != NULL, return -EINVAL);
cib = get_client(client_id);
CRM_CHECK(cib != NULL, return -EINVAL);
rc = cib_file_commit_transaction(cib, input, result_cib);
if (rc != pcmk_rc_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
crm_err("Could not commit transaction for CIB file client (%s) on "
"file '%s': %s",
private->id, private->filename, pcmk_rc_str(rc));
}
return pcmk_rc2legacy(rc);
}
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index 44077e5ae4..81aa12e6f2 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -1,1097 +1,1098 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-2024 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
#include
gboolean
cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
{
*epoch = -1;
*updates = -1;
*admin_epoch = -1;
if (cib == NULL) {
return FALSE;
} else {
crm_element_value_int(cib, PCMK_XA_EPOCH, epoch);
crm_element_value_int(cib, PCMK_XA_NUM_UPDATES, updates);
crm_element_value_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
}
return TRUE;
}
gboolean
cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
int *_admin_epoch, int *_epoch, int *_updates)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(diff, add, del);
*admin_epoch = add[0];
*epoch = add[1];
*updates = add[2];
*_admin_epoch = del[0];
*_epoch = del[1];
*_updates = del[2];
return TRUE;
}
/*!
* \internal
* \brief Get the XML patchset from a CIB diff notification
*
* \param[in] msg CIB diff notification
* \param[out] patchset Where to store XML patchset
*
* \return Standard Pacemaker return code
*/
int
cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
{
int rc = pcmk_err_generic;
xmlNode *wrapper = NULL;
CRM_ASSERT(patchset != NULL);
*patchset = NULL;
if (msg == NULL) {
crm_err("CIB diff notification received with no XML");
return ENOMSG;
}
if ((crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc) != 0)
|| (rc != pcmk_ok)) {
crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d",
pcmk_strerror(rc), rc);
crm_log_xml_debug(msg, "failed");
return pcmk_legacy2rc(rc);
}
wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL);
*patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (*patchset == NULL) {
crm_err("CIB diff notification received with no patchset");
return ENOMSG;
}
return pcmk_rc_ok;
}
#define XPATH_DIFF_V1 "//" PCMK__XE_CIB_UPDATE_RESULT "//" PCMK__XE_DIFF_ADDED
/*!
* \internal
* \brief Check whether a given CIB element was modified in a CIB patchset (v1)
*
* \param[in] patchset CIB XML patchset
* \param[in] element XML tag of CIB element to check (\c NULL is equivalent
* to \c PCMK_XE_CIB)
*
* \return \c true if \p element was modified, or \c false otherwise
*/
static bool
element_in_patchset_v1(const xmlNode *patchset, const char *element)
{
char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s",
pcmk__s(element, PCMK_XE_CIB));
xmlXPathObject *xpath_obj = xpath_search(patchset, xpath);
free(xpath);
if (xpath_obj == NULL) {
return false;
}
freeXpathObject(xpath_obj);
return true;
}
/*!
* \internal
* \brief Check whether a given CIB element was modified in a CIB patchset (v2)
*
* \param[in] patchset CIB XML patchset
* \param[in] element XML tag of CIB element to check (\c NULL is equivalent
* to \c PCMK_XE_CIB). Supported values include any CIB
* element supported by \c pcmk__cib_abs_xpath_for().
*
* \return \c true if \p element was modified, or \c false otherwise
*/
static bool
element_in_patchset_v2(const xmlNode *patchset, const char *element)
{
const char *element_xpath = pcmk__cib_abs_xpath_for(element);
const char *parent_xpath = pcmk_cib_parent_name_for(element);
char *element_regex = NULL;
bool rc = false;
CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
// Matches if and only if element_xpath is part of a changed path
element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
NULL, NULL);
change != NULL; change = pcmk__xe_next_same(change)) {
const char *op = crm_element_value(change, PCMK__XA_CIB_OP);
const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
// Change to an existing element
rc = true;
break;
}
if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
&& pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
&& pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
element)) {
// Newly added element
rc = true;
break;
}
}
free(element_regex);
return rc;
}
/*!
* \internal
* \brief Check whether a given CIB element was modified in a CIB patchset
*
* \param[in] patchset CIB XML patchset
* \param[in] element XML tag of CIB element to check (\c NULL is equivalent
* to \c PCMK_XE_CIB). Supported values include any CIB
* element supported by \c pcmk__cib_abs_xpath_for().
*
* \return \c true if \p element was modified, or \c false otherwise
*/
bool
cib__element_in_patchset(const xmlNode *patchset, const char *element)
{
int format = 1;
CRM_ASSERT(patchset != NULL);
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
return element_in_patchset_v1(patchset, element);
case 2:
return element_in_patchset_v2(patchset, element);
default:
crm_warn("Unknown patch format: %d", format);
return false;
}
}
/*!
* \brief Create XML for a new (empty) CIB
*
* \param[in] cib_epoch What to use as \c PCMK_XA_EPOCH CIB attribute
*
* \return Newly created XML for empty CIB
* \note It is the caller's responsibility to free the result with free_xml().
*/
xmlNode *
createEmptyCib(int cib_epoch)
{
xmlNode *cib_root = NULL, *config = NULL;
cib_root = pcmk__xe_create(NULL, PCMK_XE_CIB);
crm_xml_add(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
- crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, xml_latest_schema());
+ crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, pcmk__highest_schema_name());
crm_xml_add_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
crm_xml_add_int(cib_root, PCMK_XA_NUM_UPDATES, 0);
crm_xml_add_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
config = pcmk__xe_create(cib_root, PCMK_XE_CONFIGURATION);
pcmk__xe_create(cib_root, PCMK_XE_STATUS);
pcmk__xe_create(config, PCMK_XE_CRM_CONFIG);
pcmk__xe_create(config, PCMK_XE_NODES);
pcmk__xe_create(config, PCMK_XE_RESOURCES);
pcmk__xe_create(config, PCMK_XE_CONSTRAINTS);
#if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
{
xmlNode *rsc_defaults = pcmk__xe_create(config, PCMK_XE_RSC_DEFAULTS);
xmlNode *meta = pcmk__xe_create(rsc_defaults, PCMK_XE_META_ATTRIBUTES);
xmlNode *nvpair = pcmk__xe_create(meta, PCMK_XE_NVPAIR);
crm_xml_add(meta, PCMK_XA_ID, "build-resource-defaults");
crm_xml_add(nvpair, PCMK_XA_ID, "build-" PCMK_META_RESOURCE_STICKINESS);
crm_xml_add(nvpair, PCMK_XA_NAME, PCMK_META_RESOURCE_STICKINESS);
crm_xml_add_int(nvpair, PCMK_XA_VALUE,
PCMK__RESOURCE_STICKINESS_DEFAULT);
}
#endif
return cib_root;
}
static bool
cib_acl_enabled(xmlNode *xml, const char *user)
{
bool rc = FALSE;
if(pcmk_acl_required(user)) {
const char *value = NULL;
GHashTable *options = pcmk__strkey_table(free, free);
cib_read_config(options, xml);
value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL);
rc = crm_is_true(value);
g_hash_table_destroy(options);
}
crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
return rc;
}
/*!
* \internal
* \brief Determine whether to perform operations on a scratch copy of the CIB
*
* \param[in] op CIB operation
* \param[in] section CIB section
* \param[in] call_options CIB call options
*
* \return \p true if we should make a copy of the CIB, or \p false otherwise
*/
static bool
should_copy_cib(const char *op, const char *section, int call_options)
{
if (pcmk_is_set(call_options, cib_dryrun)) {
// cib_dryrun implies a scratch copy by definition; no side effects
return true;
}
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
/* Commit-transaction must make a copy for atomicity. We must revert to
* the original CIB if the entire transaction cannot be applied
* successfully.
*/
return true;
}
if (pcmk_is_set(call_options, cib_transaction)) {
/* If cib_transaction is set, then we're in the process of committing a
* transaction. The commit-transaction request already made a scratch
* copy, and we're accumulating changes in that copy.
*/
return false;
}
if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_none)) {
/* Copying large CIBs accounts for a huge percentage of our CIB usage,
* and this avoids some of it.
*
* @TODO: Is this safe? See discussion at
* https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
*/
return false;
}
// Default behavior is to operate on a scratch copy
return true;
}
int
cib_perform_op(cib_t *cib, const char *op, int call_options, cib__op_fn_t fn,
bool is_query, const char *section, xmlNode *req, xmlNode *input,
bool manage_counters, bool *config_changed, xmlNode **current_cib,
xmlNode **result_cib, xmlNode **diff, xmlNode **output)
{
int rc = pcmk_ok;
bool check_schema = true;
bool make_copy = true;
xmlNode *top = NULL;
xmlNode *scratch = NULL;
xmlNode *patchset_cib = NULL;
xmlNode *local_diff = NULL;
const char *user = crm_element_value(req, PCMK__XA_CIB_USER);
bool with_digest = false;
crm_trace("Begin %s%s%s op",
(pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
(is_query? "read-only " : ""), op);
CRM_CHECK(output != NULL, return -ENOMSG);
CRM_CHECK(current_cib != NULL, return -ENOMSG);
CRM_CHECK(result_cib != NULL, return -ENOMSG);
CRM_CHECK(config_changed != NULL, return -ENOMSG);
if(output) {
*output = NULL;
}
*result_cib = NULL;
*config_changed = false;
if (fn == NULL) {
return -EINVAL;
}
if (is_query) {
xmlNode *cib_ro = *current_cib;
xmlNode *cib_filtered = NULL;
if (cib_acl_enabled(cib_ro, user)
&& xml_acl_filtered_copy(user, *current_cib, *current_cib,
&cib_filtered)) {
if (cib_filtered == NULL) {
crm_debug("Pre-filtered the entire cib");
return -EACCES;
}
cib_ro = cib_filtered;
crm_log_xml_trace(cib_ro, "filtered");
}
rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
if(output == NULL || *output == NULL) {
/* nothing */
} else if(cib_filtered == *output) {
cib_filtered = NULL; /* Let them have this copy */
} else if (*output == *current_cib) {
/* They already know not to free it */
} else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
/* We're about to free the document of which *output is a part */
*output = pcmk__xml_copy(NULL, *output);
} else if ((*output)->doc == (*current_cib)->doc) {
/* Give them a copy they can free */
*output = pcmk__xml_copy(NULL, *output);
}
free_xml(cib_filtered);
return rc;
}
make_copy = should_copy_cib(op, section, call_options);
if (!make_copy) {
/* Conditional on v2 patch style */
scratch = *current_cib;
// Make a copy of the top-level element to store version details
top = pcmk__xe_create(NULL, (const char *) scratch->name);
copy_in_properties(top, scratch);
patchset_cib = top;
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
/* If scratch points to a new object now (for example, after an erase
* operation), then *current_cib should point to the same object.
*/
*current_cib = scratch;
} else {
scratch = pcmk__xml_copy(NULL, *current_cib);
patchset_cib = *current_cib;
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, *current_cib,
&scratch, output);
if ((scratch != NULL) && !xml_tracking_changes(scratch)) {
crm_trace("Inferring changes after %s op", op);
xml_track_changes(scratch, user, *current_cib,
cib_acl_enabled(*current_cib, user));
xml_calculate_changes(*current_cib, scratch);
}
CRM_CHECK(*current_cib != scratch, return -EINVAL);
}
xml_acl_disable(scratch); /* Allow the system to make any additional changes */
if (rc == pcmk_ok && scratch == NULL) {
rc = -EINVAL;
goto done;
} else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
crm_trace("ACL rejected part or all of the proposed changes");
rc = -EACCES;
goto done;
} else if (rc != pcmk_ok) {
goto done;
}
/* If the CIB is from a file, we don't need to check that the feature set is
* supported. All we care about in that case is the schema version, which
* is checked elsewhere.
*/
if (scratch && (cib == NULL || cib->variant != cib_file)) {
const char *new_version = crm_element_value(scratch, PCMK_XA_CRM_FEATURE_SET);
rc = pcmk__check_feature_set(new_version);
if (rc != pcmk_rc_ok) {
pcmk__config_err("Discarding update with feature set '%s' greater than our own '%s'",
new_version, CRM_FEATURE_SET);
rc = pcmk_rc2legacy(rc);
goto done;
}
}
if (patchset_cib != NULL) {
int old = 0;
int new = 0;
crm_element_value_int(scratch, PCMK_XA_ADMIN_EPOCH, &new);
crm_element_value_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
PCMK_XA_ADMIN_EPOCH, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
} else if (old == new) {
crm_element_value_int(scratch, PCMK_XA_EPOCH, &new);
crm_element_value_int(patchset_cib, PCMK_XA_EPOCH, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
PCMK_XA_EPOCH, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
}
}
}
crm_trace("Massaging CIB contents");
pcmk__strip_xml_text(scratch);
if (!make_copy) {
/* At this point, patchset_cib is just the PCMK_XE_CIB tag and its
* properties.
*
* The v1 format would barf on this, but we know the v2 patch
* format only needs it for the top-level version fields
*/
local_diff = xml_create_patchset(2, patchset_cib, scratch,
config_changed, manage_counters);
} else {
static time_t expires = 0;
time_t tm_now = time(NULL);
if (expires < tm_now) {
expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
with_digest = true;
}
local_diff = xml_create_patchset(0, patchset_cib, scratch,
config_changed, manage_counters);
}
pcmk__log_xml_changes(LOG_TRACE, scratch);
xml_accept_changes(scratch);
if(local_diff) {
patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
pcmk__log_xml_patchset(LOG_INFO, local_diff);
crm_log_xml_trace(local_diff, "raw patch");
}
if (make_copy && (local_diff != NULL)) {
// Original to compare against doesn't exist
pcmk__if_tracing(
{
// Validate the calculated patch set
int test_rc = pcmk_ok;
int format = 1;
xmlNode *cib_copy = pcmk__xml_copy(NULL, patchset_cib);
crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
test_rc = xml_apply_patchset(cib_copy, local_diff,
manage_counters);
if (test_rc != pcmk_ok) {
save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
save_xml_to_file(scratch, "PatchApply:actual", NULL);
save_xml_to_file(local_diff, "PatchApply:diff", NULL);
crm_err("v%d patchset error, patch failed to apply: %s "
"(%d)",
format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
test_rc);
}
free_xml(cib_copy);
},
{}
);
}
if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
/* Throttle the amount of costly validation we perform due to status updates
* a) we don't really care whats in the status section
* b) we don't validate any of its contents at the moment anyway
*/
check_schema = false;
}
/* === scratch must not be modified after this point ===
* Exceptions, anything in:
static filter_t filter[] = {
{ 0, PCMK_XA_CRM_DEBUG_ORIGIN },
{ 0, PCMK_XA_CIB_LAST_WRITTEN },
{ 0, PCMK_XA_UPDATE_ORIGIN },
{ 0, PCMK_XA_UPDATE_CLIENT },
{ 0, PCMK_XA_UPDATE_USER },
};
*/
if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
const char *schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
pcmk__xe_add_last_written(scratch);
/* Make values of origin, client, and user in scratch match
* the ones in req (if the schema allows the attributes)
*/
if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") >= 0) {
const char *origin = crm_element_value(req, PCMK__XA_SRC);
const char *client = crm_element_value(req,
PCMK__XA_CIB_CLIENTNAME);
if (origin != NULL) {
crm_xml_add(scratch, PCMK_XA_UPDATE_ORIGIN, origin);
} else {
pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_ORIGIN);
}
if (client != NULL) {
crm_xml_add(scratch, PCMK_XA_UPDATE_CLIENT, user);
} else {
pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_CLIENT);
}
if (user != NULL) {
crm_xml_add(scratch, PCMK_XA_UPDATE_USER, user);
} else {
pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_USER);
}
}
}
crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
- if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, true)) {
+ if ((rc == pcmk_ok) && check_schema
+ && !pcmk__configured_schema_validates(scratch)) {
const char *current_schema = crm_element_value(scratch,
PCMK_XA_VALIDATE_WITH);
crm_warn("Updated CIB does not validate against %s schema",
pcmk__s(current_schema, "unspecified"));
rc = -pcmk_err_schema_validation;
}
done:
*result_cib = scratch;
/* @TODO: This may not work correctly with !make_copy, since we don't
* keep the original CIB.
*/
if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
&& xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
if (*result_cib == NULL) {
crm_debug("Pre-filtered the entire cib result");
}
free_xml(scratch);
}
if(diff) {
*diff = local_diff;
} else {
free_xml(local_diff);
}
free_xml(top);
crm_trace("Done");
return rc;
}
int
cib__create_op(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data, int call_options,
const char *user_name, const char *client_name,
xmlNode **op_msg)
{
CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
*op_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
cib->call_id++;
if (cib->call_id < 1) {
cib->call_id = 1;
}
crm_xml_add(*op_msg, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(*op_msg, PCMK__XA_CIB_OP, op);
crm_xml_add(*op_msg, PCMK__XA_CIB_HOST, host);
crm_xml_add(*op_msg, PCMK__XA_CIB_SECTION, section);
crm_xml_add(*op_msg, PCMK__XA_CIB_USER, user_name);
crm_xml_add(*op_msg, PCMK__XA_CIB_CLIENTNAME, client_name);
crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLID, cib->call_id);
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLOPT, call_options);
if (data != NULL) {
xmlNode *wrapper = pcmk__xe_create(*op_msg, PCMK__XE_CIB_CALLDATA);
pcmk__xml_copy(wrapper, data);
}
if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
CRM_CHECK(pcmk_is_set(call_options, cib_scope_local),
free_xml(*op_msg); return -EPROTO);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Check whether a CIB request is supported in a transaction
*
* \param[in] request CIB request
*
* \return Standard Pacemaker return code
*/
static int
validate_transaction_request(const xmlNode *request)
{
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
const cib__operation_t *operation = NULL;
int rc = cib__get_operation(op, &operation);
if (rc != pcmk_rc_ok) {
// cib__get_operation() logs error
return rc;
}
if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
crm_err("Operation %s is not supported in CIB transactions", op);
return EOPNOTSUPP;
}
if (host != NULL) {
crm_err("Operation targeting a specific node (%s) is not supported in "
"a CIB transaction",
host);
return EOPNOTSUPP;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Append a CIB request to a CIB transaction
*
* \param[in,out] cib CIB client whose transaction to extend
* \param[in,out] request Request to add to transaction
*
* \return Legacy Pacemaker return code
*/
int
cib__extend_transaction(cib_t *cib, xmlNode *request)
{
int rc = pcmk_rc_ok;
CRM_ASSERT((cib != NULL) && (request != NULL));
rc = validate_transaction_request(request);
if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
rc = pcmk_rc_no_transaction;
}
if (rc == pcmk_rc_ok) {
pcmk__xml_copy(cib->transaction, request);
} else {
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
const char *client_id = NULL;
cib->cmds->client_id(cib, NULL, &client_id);
crm_err("Failed to add '%s' operation to transaction for client %s: %s",
op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
crm_log_xml_info(request, "failed");
}
return pcmk_rc2legacy(rc);
}
void
cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
{
xmlNode *output = NULL;
cib_callback_client_t *blob = NULL;
if (msg != NULL) {
xmlNode *wrapper = NULL;
crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
crm_element_value_int(msg, PCMK__XA_CIB_CALLID, &call_id);
wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_CALLDATA, NULL, NULL);
output = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
}
blob = cib__lookup_id(call_id);
if (blob == NULL) {
crm_trace("No callback found for call %d", call_id);
}
if (cib == NULL) {
crm_debug("No cib object supplied");
}
if (rc == -pcmk_err_diff_resync) {
/* This is an internal value that clients do not and should not care about */
rc = pcmk_ok;
}
if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
crm_trace("Invoking callback %s for call %d",
pcmk__s(blob->id, "without ID"), call_id);
blob->callback(msg, call_id, rc, output, blob->user_data);
} else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
crm_warn("CIB command failed: %s", pcmk_strerror(rc));
crm_log_xml_debug(msg, "Failed CIB Update");
}
/* This may free user_data, so do it after the callback */
if (blob) {
remove_cib_op_callback(call_id, FALSE);
}
if (cib && cib->op_callback != NULL) {
crm_trace("Invoking global callback for call %d", call_id);
cib->op_callback(msg, call_id, rc, output);
}
crm_trace("OP callback activated for %d", call_id);
}
void
cib_native_notify(gpointer data, gpointer user_data)
{
xmlNode *msg = user_data;
cib_notify_client_t *entry = data;
const char *event = NULL;
if (msg == NULL) {
crm_warn("Skipping callback - NULL message");
return;
}
event = crm_element_value(msg, PCMK__XA_SUBT);
if (entry == NULL) {
crm_warn("Skipping callback - NULL callback client");
return;
} else if (entry->callback == NULL) {
crm_warn("Skipping callback - NULL callback");
return;
} else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
return;
}
crm_trace("Invoking callback for %p/%s event...", entry, event);
entry->callback(event, msg);
crm_trace("Callback invoked...");
}
gboolean
cib_read_config(GHashTable * options, xmlNode * current_cib)
{
xmlNode *config = NULL;
crm_time_t *now = NULL;
if (options == NULL || current_cib == NULL) {
return FALSE;
}
now = crm_time_new(NULL);
g_hash_table_remove_all(options);
config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG);
if (config) {
pe_unpack_nvpairs(current_cib, config, PCMK_XE_CLUSTER_PROPERTY_SET,
NULL, options, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, TRUE,
now, NULL);
}
pcmk__validate_cluster_options(options);
crm_time_free(now);
return TRUE;
}
int
cib_internal_op(cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name)
{
int (*delegate) (cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name) =
cib->delegate_fn;
if(user_name == NULL) {
user_name = getenv("CIB_user");
}
return delegate(cib, op, host, section, data, output_data, call_options, user_name);
}
/*!
* \brief Apply a CIB update patch to a given CIB
*
* \param[in] event CIB update patch
* \param[in] input CIB to patch
* \param[out] output Resulting CIB after patch
* \param[in] level Log the patch at this log level (unless LOG_CRIT)
*
* \return Legacy Pacemaker return code
* \note sbd calls this function
*/
int
cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
int level)
{
int rc = pcmk_err_generic;
xmlNode *wrapper = NULL;
xmlNode *diff = NULL;
CRM_ASSERT(event);
CRM_ASSERT(input);
CRM_ASSERT(output);
crm_element_value_int(event, PCMK__XA_CIB_RC, &rc);
wrapper = pcmk__xe_first_child(event, PCMK__XE_CIB_UPDATE_RESULT, NULL,
NULL);
diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (rc < pcmk_ok || diff == NULL) {
return rc;
}
if (level > LOG_CRIT) {
pcmk__log_xml_patchset(level, diff);
}
if (input != NULL) {
rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
NULL);
if (rc != pcmk_ok) {
crm_debug("Update didn't apply: %s (%d) %p",
pcmk_strerror(rc), rc, *output);
if (rc == -pcmk_err_old_data) {
crm_trace("Masking error, we already have the supplied update");
return pcmk_ok;
}
free_xml(*output);
*output = NULL;
return rc;
}
}
return rc;
}
#define log_signon_query_err(out, fmt, args...) do { \
if (out != NULL) { \
out->err(out, fmt, ##args); \
} else { \
crm_err(fmt, ##args); \
} \
} while (0)
int
cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
{
int rc = pcmk_rc_ok;
cib_t *cib_conn = NULL;
CRM_ASSERT(cib_object != NULL);
if (cib == NULL) {
cib_conn = cib_new();
} else {
if (*cib == NULL) {
*cib = cib_new();
}
cib_conn = *cib;
}
if (cib_conn == NULL) {
return ENOMEM;
}
if (cib_conn->state == cib_disconnected) {
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
rc = pcmk_legacy2rc(rc);
}
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "Could not connect to the CIB: %s",
pcmk_rc_str(rc));
goto done;
}
if (out != NULL) {
out->transient(out, "Querying CIB...");
}
rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
cib_scope_local|cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
}
done:
if (cib == NULL) {
cib__clean_up_connection(&cib_conn);
}
if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
return pcmk_rc_no_input;
}
return rc;
}
int
cib__clean_up_connection(cib_t **cib)
{
int rc;
if (*cib == NULL) {
return pcmk_rc_ok;
}
rc = (*cib)->cmds->signoff(*cib);
cib_delete(*cib);
*cib = NULL;
return pcmk_legacy2rc(rc);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
xmlNode *
cib_get_generation(cib_t * cib)
{
xmlNode *the_cib = NULL;
xmlNode *generation = pcmk__xe_create(NULL, PCMK__XE_GENERATION_TUPLE);
cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
if (the_cib != NULL) {
copy_in_properties(generation, the_cib);
free_xml(the_cib);
}
return generation;
}
const char *
get_object_path(const char *object_type)
{
return pcmk_cib_xpath_for(object_type);
}
const char *
get_object_parent(const char *object_type)
{
return pcmk_cib_parent_name_for(object_type);
}
xmlNode *
get_object_root(const char *object_type, xmlNode *the_root)
{
return pcmk_find_cib_element(the_root, object_type);
}
const char *
cib_pref(GHashTable * options, const char *name)
{
return pcmk__cluster_option(options, name);
}
void
cib_metadata(void)
{
pcmk__output_t *out = NULL;
int rc = pcmk__output_new(&out, "text", NULL, NULL);
if (rc != pcmk_rc_ok) {
crm_err("Unable to output metadata: %s", pcmk_rc_str(rc));
return;
}
pcmk__daemon_metadata(out, "pacemaker-based",
"Cluster Information Base manager options",
"Cluster options used by Pacemaker's Cluster "
"Information Base manager",
pcmk__opt_based);
out->finish(out, CRM_EX_OK, true, NULL);
pcmk__output_free(out);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h
index e052599543..2036554fb3 100644
--- a/lib/common/crmcommon_private.h
+++ b/lib/common/crmcommon_private.h
@@ -1,419 +1,419 @@
/*
* Copyright 2018-2024 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 CRMCOMMON_PRIVATE__H
# define CRMCOMMON_PRIVATE__H
/* This header is for the sole use of libcrmcommon, so that functions can be
* declared with G_GNUC_INTERNAL for efficiency.
*/
#include // uint8_t, uint32_t
#include // bool
#include // size_t
#include // gchar, GList
#include // xmlNode, xmlAttr
#include // struct qb_ipc_response_header
// Decent chunk size for processing large amounts of data
#define PCMK__BUFFER_SIZE 4096
#if defined(PCMK__UNIT_TESTING)
#undef G_GNUC_INTERNAL
#define G_GNUC_INTERNAL
#endif
/* When deleting portions of an XML tree, we keep a record so we can know later
* (e.g. when checking differences) that something was deleted.
*/
typedef struct pcmk__deleted_xml_s {
gchar *path;
int position;
} pcmk__deleted_xml_t;
typedef struct xml_node_private_s {
uint32_t check;
uint32_t flags;
} xml_node_private_t;
typedef struct xml_doc_private_s {
uint32_t check;
uint32_t flags;
char *user;
GList *acls;
GList *deleted_objs; // List of pcmk__deleted_xml_t
} xml_doc_private_t;
// XML entity references
#define PCMK__XML_ENTITY_AMP "&"
#define PCMK__XML_ENTITY_GT ">"
#define PCMK__XML_ENTITY_LT "<"
#define PCMK__XML_ENTITY_QUOT """
//! libxml2 supports only XML version 1.0, at least as of libxml2-2.12.5
#define PCMK__XML_VERSION ((pcmkXmlStr) "1.0")
#define pcmk__set_xml_flags(xml_priv, flags_to_set) do { \
(xml_priv)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_NEVER, "XML", "XML node", (xml_priv)->flags, \
(flags_to_set), #flags_to_set); \
} while (0)
#define pcmk__clear_xml_flags(xml_priv, flags_to_clear) do { \
(xml_priv)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_NEVER, "XML", "XML node", (xml_priv)->flags, \
(flags_to_clear), #flags_to_clear); \
} while (0)
G_GNUC_INTERNAL
bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy);
G_GNUC_INTERNAL
void pcmk__xml_mark_created(xmlNode *xml);
G_GNUC_INTERNAL
int pcmk__xml_position(const xmlNode *xml,
enum xml_private_flags ignore_if_set);
G_GNUC_INTERNAL
xmlNode *pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle,
bool exact);
G_GNUC_INTERNAL
void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
bool as_diff);
G_GNUC_INTERNAL
xmlNode *pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment,
bool exact);
G_GNUC_INTERNAL
void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update);
G_GNUC_INTERNAL
void pcmk__free_acls(GList *acls);
G_GNUC_INTERNAL
void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user);
G_GNUC_INTERNAL
bool pcmk__is_user_in_group(const char *user, const char *group);
G_GNUC_INTERNAL
void pcmk__apply_acl(xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__apply_creation_acl(xmlNode *xml, bool check_top);
G_GNUC_INTERNAL
void pcmk__mark_xml_attr_dirty(xmlAttr *a);
G_GNUC_INTERNAL
bool pcmk__xa_filterable(const char *name);
G_GNUC_INTERNAL
void pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
G_GNUC_INTERNAL
void pcmk__mark_xml_node_dirty(xmlNode *xml);
G_GNUC_INTERNAL
bool pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data);
G_GNUC_INTERNAL
void pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer);
/*
* Date/times
*/
// For use with pcmk__add_time_from_xml()
enum pcmk__time_component {
pcmk__time_unknown,
pcmk__time_years,
pcmk__time_months,
pcmk__time_weeks,
pcmk__time_days,
pcmk__time_hours,
pcmk__time_minutes,
pcmk__time_seconds,
};
G_GNUC_INTERNAL
const char *pcmk__time_component_attr(enum pcmk__time_component component);
G_GNUC_INTERNAL
int pcmk__add_time_from_xml(crm_time_t *t, enum pcmk__time_component component,
const xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source);
/*
* IPC
*/
#define PCMK__IPC_VERSION 1
#define PCMK__CONTROLD_API_MAJOR "1"
#define PCMK__CONTROLD_API_MINOR "0"
// IPC behavior that varies by daemon
typedef struct pcmk__ipc_methods_s {
/*!
* \internal
* \brief Allocate any private data needed by daemon IPC
*
* \param[in,out] api IPC API connection
*
* \return Standard Pacemaker return code
*/
int (*new_data)(pcmk_ipc_api_t *api);
/*!
* \internal
* \brief Free any private data used by daemon IPC
*
* \param[in,out] api_data Data allocated by new_data() method
*/
void (*free_data)(void *api_data);
/*!
* \internal
* \brief Perform daemon-specific handling after successful connection
*
* Some daemons require clients to register before sending any other
* commands. The controller requires a CRM_OP_HELLO (with no reply), and
* the CIB manager, executor, and fencer require a CRM_OP_REGISTER (with a
* reply). Ideally this would be consistent across all daemons, but for now
* this allows each to do its own authorization.
*
* \param[in,out] api IPC API connection
*
* \return Standard Pacemaker return code
*/
int (*post_connect)(pcmk_ipc_api_t *api);
/*!
* \internal
* \brief Check whether an IPC request results in a reply
*
* \param[in,out] api IPC API connection
* \param[in] request IPC request XML
*
* \return true if request would result in an IPC reply, false otherwise
*/
bool (*reply_expected)(pcmk_ipc_api_t *api, const xmlNode *request);
/*!
* \internal
* \brief Perform daemon-specific handling of an IPC message
*
* \param[in,out] api IPC API connection
* \param[in,out] msg Message read from IPC connection
*
* \return true if more IPC reply messages should be expected
*/
bool (*dispatch)(pcmk_ipc_api_t *api, xmlNode *msg);
/*!
* \internal
* \brief Perform daemon-specific handling of an IPC disconnect
*
* \param[in,out] api IPC API connection
*/
void (*post_disconnect)(pcmk_ipc_api_t *api);
} pcmk__ipc_methods_t;
// Implementation of pcmk_ipc_api_t
struct pcmk_ipc_api_s {
enum pcmk_ipc_server server; // Daemon this IPC API instance is for
enum pcmk_ipc_dispatch dispatch_type; // How replies should be dispatched
size_t ipc_size_max; // maximum IPC buffer size
crm_ipc_t *ipc; // IPC connection
mainloop_io_t *mainloop_io; // If using mainloop, I/O source for IPC
bool free_on_disconnect; // Whether disconnect should free object
pcmk_ipc_callback_t cb; // Caller-registered callback (if any)
void *user_data; // Caller-registered data (if any)
void *api_data; // For daemon-specific use
pcmk__ipc_methods_t *cmds; // Behavior that varies by daemon
};
typedef struct pcmk__ipc_header_s {
struct qb_ipc_response_header qb;
uint32_t size_uncompressed;
uint32_t size_compressed;
uint32_t flags;
uint8_t version;
} pcmk__ipc_header_t;
G_GNUC_INTERNAL
int pcmk__send_ipc_request(pcmk_ipc_api_t *api, const xmlNode *request);
G_GNUC_INTERNAL
void pcmk__call_ipc_callback(pcmk_ipc_api_t *api,
enum pcmk_ipc_event event_type,
crm_exit_t status, void *event_data);
G_GNUC_INTERNAL
unsigned int pcmk__ipc_buffer_size(unsigned int max);
G_GNUC_INTERNAL
bool pcmk__valid_ipc_header(const pcmk__ipc_header_t *header);
G_GNUC_INTERNAL
pcmk__ipc_methods_t *pcmk__attrd_api_methods(void);
G_GNUC_INTERNAL
pcmk__ipc_methods_t *pcmk__controld_api_methods(void);
G_GNUC_INTERNAL
pcmk__ipc_methods_t *pcmk__pacemakerd_api_methods(void);
G_GNUC_INTERNAL
pcmk__ipc_methods_t *pcmk__schedulerd_api_methods(void);
/*
* Logging
*/
//! XML is newly created
#define PCMK__XML_PREFIX_CREATED "++"
//! XML has been deleted
#define PCMK__XML_PREFIX_DELETED "--"
//! XML has been modified
#define PCMK__XML_PREFIX_MODIFIED "+ "
//! XML has been moved
#define PCMK__XML_PREFIX_MOVED "+~"
/*
* Output
*/
G_GNUC_INTERNAL
int pcmk__bare_output_new(pcmk__output_t **out, const char *fmt_name,
const char *filename, char **argv);
G_GNUC_INTERNAL
void pcmk__register_option_messages(pcmk__output_t *out);
G_GNUC_INTERNAL
void pcmk__register_patchset_messages(pcmk__output_t *out);
G_GNUC_INTERNAL
bool pcmk__output_text_get_fancy(pcmk__output_t *out);
/*
* Rules
*/
// How node attribute values may be compared in rules
enum pcmk__comparison {
pcmk__comparison_unknown,
pcmk__comparison_defined,
pcmk__comparison_undefined,
pcmk__comparison_eq,
pcmk__comparison_ne,
pcmk__comparison_lt,
pcmk__comparison_lte,
pcmk__comparison_gt,
pcmk__comparison_gte,
};
// How node attribute values may be parsed in rules
enum pcmk__type {
pcmk__type_unknown,
pcmk__type_string,
pcmk__type_integer,
pcmk__type_number,
pcmk__type_version,
};
// Where to obtain reference value for a node attribute comparison
enum pcmk__reference_source {
pcmk__source_unknown,
pcmk__source_literal,
pcmk__source_instance_attrs,
pcmk__source_meta_attrs,
};
G_GNUC_INTERNAL
enum pcmk__comparison pcmk__parse_comparison(const char *op);
G_GNUC_INTERNAL
enum pcmk__type pcmk__parse_type(const char *type, enum pcmk__comparison op,
const char *value1, const char *value2);
G_GNUC_INTERNAL
enum pcmk__reference_source pcmk__parse_source(const char *source);
G_GNUC_INTERNAL
int pcmk__cmp_by_type(const char *value1, const char *value2,
enum pcmk__type type);
G_GNUC_INTERNAL
int pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
crm_time_t **end);
G_GNUC_INTERNAL
int pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now);
G_GNUC_INTERNAL
int pcmk__evaluate_attr_expression(const xmlNode *expression,
const pcmk_rule_input_t *rule_input);
G_GNUC_INTERNAL
int pcmk__evaluate_rsc_expression(const xmlNode *expr,
const pcmk_rule_input_t *rule_input);
G_GNUC_INTERNAL
int pcmk__evaluate_op_expression(const xmlNode *expr,
const pcmk_rule_input_t *rule_input);
/*
* Utils
*/
#define PCMK__PW_BUFFER_LEN 500
/*
* Schemas
*/
typedef struct {
unsigned char v[2];
} pcmk__schema_version_t;
enum pcmk__schema_validator {
pcmk__schema_validator_none,
pcmk__schema_validator_rng
};
typedef struct {
int schema_index;
char *name;
char *transform;
void *cache;
enum pcmk__schema_validator validator;
pcmk__schema_version_t version;
char *transform_enter;
bool transform_onleave;
} pcmk__schema_t;
G_GNUC_INTERNAL
-int pcmk__find_x_0_schema_index(void);
+GList *pcmk__find_x_0_schema(void);
#endif // CRMCOMMON_PRIVATE__H
diff --git a/lib/common/schemas.c b/lib/common/schemas.c
index a75f719e96..c5a515e728 100644
--- a/lib/common/schemas.c
+++ b/lib/common/schemas.c
@@ -1,1654 +1,1717 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU 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
#include /* PCMK__XML_LOG_BASE */
#include "crmcommon_private.h"
#define SCHEMA_ZERO { .v = { 0, 0 } }
#define schema_strdup_printf(prefix, version, suffix) \
crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
typedef struct {
xmlRelaxNGPtr rng;
xmlRelaxNGValidCtxtPtr valid;
xmlRelaxNGParserCtxtPtr parser;
} relaxng_ctx_cache_t;
static GList *known_schemas = NULL;
static bool silent_logging = FALSE;
static void G_GNUC_PRINTF(2, 3)
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (silent_logging == FALSE) {
/* XXX should not this enable dechunking as well? */
PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
}
va_end(ap);
}
static int
xml_latest_schema_index(void)
{
/* This function assumes that crm_schema_init() has been called beforehand,
* so we have at least three schemas (one real schema, the "pacemaker-next"
* schema, and the "none" schema).
*
* @COMPAT: pacemaker-next is deprecated since 2.1.5.
* Update this when we drop that schema.
*/
return g_list_length(known_schemas) - 3;
}
-/* Return the index of the most recent X.0 schema. */
-int
-pcmk__find_x_0_schema_index(void)
+/*!
+ * \internal
+ * \brief Return the schema entry of the highest-versioned schema
+ *
+ * \return Schema entry of highest-versioned schema (or NULL on error)
+ */
+static GList *
+get_highest_schema(void)
{
- /* We can't just use best to determine whether we've found the index
- * or not. What if we have a very long list of schemas all in the
- * same major version series? We'd return 0 for that, which means
- * we would still run this function every time.
+ /* The highest numerically versioned schema is the one before pacemaker-next
+ *
+ * @COMPAT pacemaker-next is deprecated since 2.1.5
*/
+ GList *entry = pcmk__get_schema("pacemaker-next");
+
+ CRM_ASSERT((entry != NULL) && (entry->prev != NULL));
+ return entry->prev;
+}
+
+/*!
+ * \internal
+ * \brief Return the name of the highest-versioned schema
+ *
+ * \return Name of highest-versioned schema (or NULL on error)
+ */
+const char *
+pcmk__highest_schema_name(void)
+{
+ GList *entry = get_highest_schema();
+
+ return ((pcmk__schema_t *)(entry->data))->name;
+}
+
+/*!
+ * \internal
+ * \brief Find first entry of highest major schema version series
+ *
+ * \return Schema entry of first schema with highest major version
+ */
+GList *
+pcmk__find_x_0_schema(void)
+{
#if defined(PCMK__UNIT_TESTING)
- /* If we're unit testing, these can't be static because they'll stick
- * around from one test run to the next. They need to be cleared out
+ /* If we're unit testing, this can't be static because it'll stick
+ * around from one test run to the next. It needs to be cleared out
* every time.
*/
- bool found = false;
- int best = 0;
+ GList *x_0_entry = NULL;
#else
- static bool found = false;
- static int best = 0;
+ static GList *x_0_entry = NULL;
#endif
- int i;
- GList *best_node = NULL;
- pcmk__schema_t *best_schema = NULL;
- if (found) {
- return best;
- }
+ pcmk__schema_t *highest_schema = NULL;
- CRM_ASSERT(known_schemas != NULL);
-
- /* Get the most recent schema so we can look at its version number. */
- best = xml_latest_schema_index();
- best_node = g_list_nth(known_schemas, best);
- best_schema = best_node->data;
-
- /* The "pacemaker-next" and "none" schemas are added to the real schemas,
- * so a list of length three actually only has one useful schema.
- *
- * @COMPAT pacemaker-next is deprecated since 2.1.5.
- * Update this when we drop that schema.
- */
- if (g_list_length(known_schemas) == 3) {
- goto done;
+ if (x_0_entry != NULL) {
+ return x_0_entry;
}
+ x_0_entry = get_highest_schema();
+ highest_schema = x_0_entry->data;
- /* Start comparing the list from the node before the best schema (there's
- * no point in comparing something to itself). Then, 'i' is an index
- * starting at the best schema and will always point at the node after
- * 'iter'. This makes it the value we want to return when we find what
- * we're looking for.
- */
- i = best;
-
- for (GList *iter = best_node->prev; iter != NULL; iter = iter->prev) {
+ for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
pcmk__schema_t *schema = iter->data;
/* We've found a schema in an older major version series. Return
* the index of the first one in the same major version series as
- * the best schema.
+ * the highest schema.
*/
- if (schema->version.v[0] < best_schema->version.v[0]) {
- best = i;
- goto done;
+ if (schema->version.v[0] < highest_schema->version.v[0]) {
+ x_0_entry = iter->next;
+ break;
+ }
/* We're out of list to examine. This probably means there was only
- * one major version series, so return index 0.
+ * one major version series, so return the first schema entry.
*/
- } else if (iter->prev == NULL) {
- best = 0;
- goto done;
+ if (iter->prev == NULL) {
+ x_0_entry = known_schemas->data;
+ break;
}
-
- i--;
}
-
-done:
- found = true;
- return best;
-}
-
-const char *
-xml_latest_schema(void)
-{
- return get_schema_name(xml_latest_schema_index());
+ return x_0_entry;
}
static inline bool
version_from_filename(const char *filename, pcmk__schema_version_t *version)
{
if (pcmk__ends_with(filename, ".rng")) {
return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2;
} else {
return sscanf(filename, "pacemaker-%hhu.%hhu", &(version->v[0]), &(version->v[1])) == 2;
}
}
static int
schema_filter(const struct dirent *a)
{
int rc = 0;
pcmk__schema_version_t version = SCHEMA_ZERO;
if (strstr(a->d_name, "pacemaker-") != a->d_name) {
/* crm_trace("%s - wrong prefix", a->d_name); */
} else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
/* crm_trace("%s - wrong suffix", a->d_name); */
} else if (!version_from_filename(a->d_name, &version)) {
/* crm_trace("%s - wrong format", a->d_name); */
} else {
/* crm_debug("%s - candidate", a->d_name); */
rc = 1;
}
return rc;
}
static int
schema_cmp(pcmk__schema_version_t a_version, pcmk__schema_version_t b_version)
{
for (int i = 0; i < 2; ++i) {
if (a_version.v[i] < b_version.v[i]) {
return -1;
} else if (a_version.v[i] > b_version.v[i]) {
return 1;
}
}
return 0;
}
static int
schema_cmp_directory(const struct dirent **a, const struct dirent **b)
{
pcmk__schema_version_t a_version = SCHEMA_ZERO;
pcmk__schema_version_t b_version = SCHEMA_ZERO;
if (!version_from_filename(a[0]->d_name, &a_version)
|| !version_from_filename(b[0]->d_name, &b_version)) {
// Shouldn't be possible, but makes static analysis happy
return 0;
}
return schema_cmp(a_version, b_version);
}
/*!
* \internal
* \brief Add given schema + auxiliary data to internal bookkeeping.
*
* \note When providing \p version, should not be called directly but
* through \c add_schema_by_version.
*/
static void
add_schema(enum pcmk__schema_validator validator, const pcmk__schema_version_t *version,
const char *name, const char *transform,
const char *transform_enter, bool transform_onleave)
{
pcmk__schema_t *schema = NULL;
schema = pcmk__assert_alloc(1, sizeof(pcmk__schema_t));
schema->validator = validator;
schema->version.v[0] = version->v[0];
schema->version.v[1] = version->v[1];
schema->transform_onleave = transform_onleave;
// schema->schema_index is set after all schemas are loaded and sorted
if (version->v[0] || version->v[1]) {
schema->name = schema_strdup_printf("pacemaker-", *version, "");
} else {
schema->name = pcmk__str_copy(name);
}
if (transform) {
schema->transform = pcmk__str_copy(transform);
}
if (transform_enter) {
schema->transform_enter = pcmk__str_copy(transform_enter);
}
known_schemas = g_list_prepend(known_schemas, schema);
}
/*!
* \internal
* \brief Add version-specified schema + auxiliary data to internal bookkeeping.
* \return Standard Pacemaker return value (the only possible values are
* \c ENOENT when no upgrade schema is associated, or \c pcmk_rc_ok otherwise.
*
* \note There's no reliance on the particular order of schemas entering here.
*
* \par A bit of theory
* We track 3 XSLT stylesheets that differ per usage:
* - "upgrade":
* . sparsely spread over the sequence of all available schemas,
* as they are only relevant when major version of the schema
* is getting bumped -- in that case, it MUST be set
* . name convention: upgrade-X.Y.xsl
* - "upgrade-enter":
* . may only accompany "upgrade" occurrence, but doesn't need to
* be present anytime such one is, i.e., it MAY not be set when
* "upgrade" is
* . name convention: upgrade-X.Y-enter.xsl,
* when not present: upgrade-enter.xsl
* - "upgrade-leave":
* . like "upgrade-enter", but SHOULD be present whenever
* "upgrade-enter" is (and vice versa, but that's only
* to prevent confusion based on observing the files,
* it would get ignored regardless)
* . name convention: (see "upgrade-enter")
*/
static int
add_schema_by_version(const pcmk__schema_version_t *version, bool transform_expected)
{
bool transform_onleave = FALSE;
int rc = pcmk_rc_ok;
struct stat s;
char *xslt = NULL,
*transform_upgrade = NULL,
*transform_enter = NULL;
/* prologue for further transform_expected handling */
if (transform_expected) {
/* check if there's suitable "upgrade" stylesheet */
transform_upgrade = schema_strdup_printf("upgrade-", *version, );
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_upgrade);
}
if (!transform_expected) {
/* jump directly to the end */
} else if (stat(xslt, &s) == 0) {
/* perhaps there's also a targeted "upgrade-enter" stylesheet */
transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
free(xslt);
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_enter);
if (stat(xslt, &s) != 0) {
/* or initially, at least a generic one */
crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
free(xslt);
free(transform_enter);
transform_enter = strdup("upgrade-enter");
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_enter);
if (stat(xslt, &s) != 0) {
crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
free(xslt);
xslt = NULL;
}
}
/* xslt contains full path to "upgrade-enter" stylesheet */
if (xslt != NULL) {
/* then there should be "upgrade-leave" counterpart (enter->leave) */
memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
transform_onleave = (stat(xslt, &s) == 0);
free(xslt);
} else {
free(transform_enter);
transform_enter = NULL;
}
} else {
crm_err("Upgrade transform %s not found", xslt);
free(xslt);
free(transform_upgrade);
transform_upgrade = NULL;
rc = ENOENT;
}
add_schema(pcmk__schema_validator_rng, version, NULL,
transform_upgrade, transform_enter, transform_onleave);
free(transform_upgrade);
free(transform_enter);
return rc;
}
static void
wrap_libxslt(bool finalize)
{
static xsltSecurityPrefsPtr secprefs;
int ret = 0;
/* security framework preferences */
if (!finalize) {
CRM_ASSERT(secprefs == NULL);
secprefs = xsltNewSecurityPrefs();
ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
xsltSecurityForbid);
if (ret != 0) {
return;
}
} else {
xsltFreeSecurityPrefs(secprefs);
secprefs = NULL;
}
/* cleanup only */
if (finalize) {
xsltCleanupGlobals();
}
}
void
pcmk__load_schemas_from_dir(const char *dir)
{
int lpc, max;
struct dirent **namelist = NULL;
max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
if (max < 0) {
crm_warn("Could not load schemas from %s: %s", dir, strerror(errno));
return;
}
for (lpc = 0; lpc < max; lpc++) {
bool transform_expected = false;
pcmk__schema_version_t version = SCHEMA_ZERO;
if (!version_from_filename(namelist[lpc]->d_name, &version)) {
// Shouldn't be possible, but makes static analysis happy
crm_warn("Skipping schema '%s': could not parse version",
namelist[lpc]->d_name);
continue;
}
if ((lpc + 1) < max) {
pcmk__schema_version_t next_version = SCHEMA_ZERO;
if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
&& (version.v[0] < next_version.v[0])) {
transform_expected = true;
}
}
if (add_schema_by_version(&version, transform_expected) != pcmk_rc_ok) {
break;
}
}
for (lpc = 0; lpc < max; lpc++) {
free(namelist[lpc]);
}
free(namelist);
}
static gint
schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
{
const pcmk__schema_t *schema_a = a;
const pcmk__schema_t *schema_b = b;
if (pcmk__str_eq(schema_a->name, "pacemaker-next", pcmk__str_none)) {
if (pcmk__str_eq(schema_b->name, PCMK_VALUE_NONE, pcmk__str_none)) {
return -1;
} else {
return 1;
}
} else if (pcmk__str_eq(schema_a->name, PCMK_VALUE_NONE, pcmk__str_none)) {
return 1;
} else if (pcmk__str_eq(schema_b->name, "pacemaker-next", pcmk__str_none)) {
return -1;
} else {
return schema_cmp(schema_a->version, schema_b->version);
}
}
/*!
* \internal
* \brief Sort the list of known schemas such that all pacemaker-X.Y are in
* version order, then pacemaker-next, then none
*
* This function should be called whenever additional schemas are loaded using
* pcmk__load_schemas_from_dir(), after the initial sets in crm_schema_init().
*/
void
pcmk__sort_schemas(void)
{
known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
}
/*!
* \internal
* \brief Load pacemaker schemas into cache
*
* \note This currently also serves as an entry point for the
* generic initialization of the libxslt library.
*/
void
crm_schema_init(void)
{
const char *remote_schema_dir = pcmk__remote_schema_dir();
char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
const pcmk__schema_version_t zero = SCHEMA_ZERO;
int schema_index = 0;
wrap_libxslt(false);
pcmk__load_schemas_from_dir(base);
pcmk__load_schemas_from_dir(remote_schema_dir);
// @COMPAT: Deprecated since 2.1.5
add_schema(pcmk__schema_validator_rng, &zero, "pacemaker-next",
NULL, NULL, FALSE);
add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE,
NULL, NULL, FALSE);
/* add_schema() prepends items to the list, so in the simple case, this just
* reverses the list. However if there were any remote schemas, sorting is
* necessary.
*/
pcmk__sort_schemas();
// Now set the schema indexes and log the final result
for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
pcmk__schema_t *schema = iter->data;
if (schema->transform == NULL) {
crm_debug("Loaded schema %d: %s", schema_index, schema->name);
} else {
crm_debug("Loaded schema %d: %s (upgrades with %s.xsl)",
schema_index, schema->name, schema->transform);
}
schema->schema_index = schema_index++;
}
}
-static gboolean
-validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file,
+static bool
+validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
+ void *error_handler_context, const char *relaxng_file,
relaxng_ctx_cache_t **cached_ctx)
{
int rc = 0;
- gboolean valid = TRUE;
+ bool valid = true;
relaxng_ctx_cache_t *ctx = NULL;
- CRM_CHECK(doc != NULL, return FALSE);
- CRM_CHECK(relaxng_file != NULL, return FALSE);
+ CRM_CHECK(doc != NULL, return false);
+ CRM_CHECK(relaxng_file != NULL, return false);
if (cached_ctx && *cached_ctx) {
ctx = *cached_ctx;
} else {
crm_debug("Creating RNG parser context");
ctx = pcmk__assert_alloc(1, sizeof(relaxng_ctx_cache_t));
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
if (error_handler) {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) error_handler,
(xmlRelaxNGValidityWarningFunc) error_handler,
error_handler_context);
} else {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
ctx->rng = xmlRelaxNGParse(ctx->parser);
CRM_CHECK(ctx->rng != NULL,
crm_err("Could not find/parse %s", relaxng_file);
goto cleanup);
ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
CRM_CHECK(ctx->valid != NULL, goto cleanup);
if (error_handler) {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) error_handler,
(xmlRelaxNGValidityWarningFunc) error_handler,
error_handler_context);
} else {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
}
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
- valid = FALSE;
+ valid = false;
} else if (rc < 0) {
crm_err("Internal libxml error during validation");
}
cleanup:
if (cached_ctx) {
*cached_ctx = ctx;
} else {
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
}
return valid;
}
static void
free_schema(gpointer data)
{
pcmk__schema_t *schema = data;
relaxng_ctx_cache_t *ctx = NULL;
switch (schema->validator) {
case pcmk__schema_validator_none: // not cached
break;
case pcmk__schema_validator_rng: // cached
ctx = (relaxng_ctx_cache_t *) schema->cache;
if (ctx == NULL) {
break;
}
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
schema->cache = NULL;
break;
}
free(schema->name);
free(schema->transform);
free(schema->transform_enter);
}
/*!
* \internal
* \brief Clean up global memory associated with XML schemas
*/
void
crm_schema_cleanup(void)
{
g_list_free_full(known_schemas, free_schema);
known_schemas = NULL;
wrap_libxslt(true);
}
/*!
* \internal
* \brief Get schema list entry corresponding to a schema name
*
* \param[in] name Name of schema to get
*
* \return Schema list entry corresponding to \p name, or NULL if unknown
*/
GList *
pcmk__get_schema(const char *name)
{
if (name == NULL) {
name = PCMK_VALUE_NONE;
}
for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
pcmk__schema_t *schema = iter->data;
if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
return iter;
}
}
return NULL;
}
/*!
* \internal
* \brief Compare two schema version numbers given the schema names
*
* \param[in] schema1 Name of first schema to compare
* \param[in] schema2 Name of second schema to compare
*
* \return Standard comparison result (negative integer if \p schema1 has the
* lower version number, positive integer if \p schema1 has the higher
* version number, of 0 if the version numbers are equal)
*/
int
pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
{
GList *entry1 = pcmk__get_schema(schema1_name);
GList *entry2 = pcmk__get_schema(schema2_name);
if (entry1 == NULL) {
return (entry2 == NULL)? 0 : -1;
} else if (entry2 == NULL) {
return 1;
} else {
pcmk__schema_t *schema1 = entry1->data;
pcmk__schema_t *schema2 = entry2->data;
return schema1->schema_index - schema2->schema_index;
}
}
-static gboolean
-validate_with(xmlNode *xml, pcmk__schema_t *schema, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
+static bool
+validate_with(xmlNode *xml, pcmk__schema_t *schema,
+ xmlRelaxNGValidityErrorFunc error_handler,
+ void *error_handler_context)
{
- gboolean valid = FALSE;
+ bool valid = false;
char *file = NULL;
relaxng_ctx_cache_t **cache = NULL;
if (schema == NULL) {
- return FALSE;
+ return false;
}
if (schema->validator == pcmk__schema_validator_none) {
- return TRUE;
+ return true;
}
if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
crm_warn("The pacemaker-next schema is deprecated and will be removed "
"in a future release.");
}
file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
schema->name);
crm_trace("Validating with %s (type=%d)",
pcmk__s(file, "missing schema"), schema->validator);
switch (schema->validator) {
case pcmk__schema_validator_rng:
cache = (relaxng_ctx_cache_t **) &(schema->cache);
valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
break;
default:
crm_err("Unknown validator type: %d", schema->validator);
break;
}
free(file);
return valid;
}
static bool
validate_with_silent(xmlNode *xml, pcmk__schema_t *schema)
{
bool rc, sl_backup = silent_logging;
silent_logging = TRUE;
rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
silent_logging = sl_backup;
return rc;
}
-static void
-dump_file(const char *filename)
-{
-
- FILE *fp = NULL;
- int ch, line = 0;
-
- CRM_CHECK(filename != NULL, return);
-
- fp = fopen(filename, "r");
- if (fp == NULL) {
- crm_perror(LOG_ERR, "Could not open %s for reading", filename);
- return;
- }
-
- fprintf(stderr, "%4d ", ++line);
- do {
- ch = getc(fp);
- if (ch == EOF) {
- putc('\n', stderr);
- break;
- } else if (ch == '\n') {
- fprintf(stderr, "\n%4d ", ++line);
- } else {
- putc(ch, stderr);
- }
- } while (1);
-
- fclose(fp);
-}
-
-gboolean
-validate_xml_verbose(const xmlNode *xml_blob)
-{
- int fd = 0;
- xmlDoc *doc = NULL;
- xmlNode *xml = NULL;
- gboolean rc = FALSE;
- char *filename = NULL;
-
- filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
-
- umask(S_IWGRP | S_IWOTH | S_IROTH);
- fd = mkstemp(filename);
- pcmk__xml_write_fd(xml_blob, filename, fd, false, NULL);
-
- dump_file(filename);
-
- doc = xmlReadFile(filename, NULL, 0);
- xml = xmlDocGetRootElement(doc);
- rc = validate_xml(xml, NULL, FALSE);
- free_xml(xml);
-
- unlink(filename);
- free(filename);
-
- return rc;
-}
-
-gboolean
-validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
-{
- return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
-}
-
-gboolean
+bool
pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
xmlRelaxNGValidityErrorFunc error_handler,
void *error_handler_context)
{
GList *entry = NULL;
pcmk__schema_t *schema = NULL;
- CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
+ CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false);
if (validation == NULL) {
validation = crm_element_value(xml_blob, PCMK_XA_VALIDATE_WITH);
}
if (validation == NULL) {
- bool valid = FALSE;
+ bool valid = false;
for (entry = known_schemas; entry != NULL; entry = entry->next) {
schema = entry->data;
if (validate_with(xml_blob, schema, NULL, NULL)) {
- valid = TRUE;
+ valid = true;
crm_xml_add(xml_blob, PCMK_XA_VALIDATE_WITH, schema->name);
crm_info("XML validated against %s", schema->name);
}
}
return valid;
}
entry = pcmk__get_schema(validation);
if (entry != NULL) {
schema = entry->data;
return validate_with(xml_blob, schema, error_handler,
error_handler_context);
}
crm_err("Unknown validator: %s", validation);
- return FALSE;
+ return false;
+}
+
+/*!
+ * \internal
+ * \brief Validate XML using its configured schema (and send errors to logs)
+ *
+ * \param[in] xml XML to validate
+ *
+ * \return true if XML validates, otherwise false
+ */
+bool
+pcmk__configured_schema_validates(xmlNode *xml)
+{
+ return pcmk__validate_xml(xml, NULL,
+ (xmlRelaxNGValidityErrorFunc) xml_log,
+ GUINT_TO_POINTER(LOG_ERR));
}
/* With this arrangement, an attempt to identify the message severity
as explicitly signalled directly from XSLT is performed in rather
a smart way (no reliance on formatting string + arguments being
always specified as ["%s", purposeful_string], as it can also be
["%s: %s", some_prefix, purposeful_string] etc. so every argument
pertaining %s specifier is investigated), and if such a mark found,
the respective level is determined and, when the messages are to go
to the native logs, the mark itself gets dropped
(by the means of string shift).
NOTE: whether the native logging is the right sink is decided per
the ctx parameter -- NULL denotes this case, otherwise it
carries a pointer to the numeric expression of the desired
target logging level (messages with higher level will be
suppressed)
NOTE: on some architectures, this string shift may not have any
effect, but that's an acceptable tradeoff
The logging level for not explicitly designated messages
(suspicious, likely internal errors or some runaways) is
LOG_WARNING.
*/
static void G_GNUC_PRINTF(2, 3)
cib_upgrade_err(void *ctx, const char *fmt, ...)
{
va_list ap, aq;
char *arg_cur;
bool found = FALSE;
const char *fmt_iter = fmt;
uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
const unsigned * log_level = (const unsigned *) ctx;
enum {
escan_seennothing,
escan_seenpercent,
} scan_state = escan_seennothing;
va_start(ap, fmt);
va_copy(aq, ap);
while (!found && *fmt_iter != '\0') {
/* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
switch (*fmt_iter++) {
case '%':
if (scan_state == escan_seennothing) {
scan_state = escan_seenpercent;
} else if (scan_state == escan_seenpercent) {
scan_state = escan_seennothing;
}
break;
case 's':
if (scan_state == escan_seenpercent) {
scan_state = escan_seennothing;
arg_cur = va_arg(aq, char *);
if (arg_cur != NULL) {
switch (arg_cur[0]) {
case 'W':
if (!strncmp(arg_cur, "WARNING: ",
sizeof("WARNING: ") - 1)) {
msg_log_level = LOG_WARNING;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
}
found = TRUE;
break;
case 'I':
if (!strncmp(arg_cur, "INFO: ",
sizeof("INFO: ") - 1)) {
msg_log_level = LOG_INFO;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
}
found = TRUE;
break;
case 'D':
if (!strncmp(arg_cur, "DEBUG: ",
sizeof("DEBUG: ") - 1)) {
msg_log_level = LOG_DEBUG;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
}
found = TRUE;
break;
}
}
}
break;
case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '*':
break;
case 'l':
case 'z':
case 't':
case 'j':
case 'd': case 'i':
case 'o':
case 'u':
case 'x': case 'X':
case 'e': case 'E':
case 'f': case 'F':
case 'g': case 'G':
case 'a': case 'A':
case 'c':
case 'p':
if (scan_state == escan_seenpercent) {
(void) va_arg(aq, void *); /* skip forward */
scan_state = escan_seennothing;
}
break;
default:
scan_state = escan_seennothing;
break;
}
}
if (log_level != NULL) {
/* intention of the following offset is:
cibadmin -V -> start showing INFO labelled messages */
if (*log_level + 4 >= msg_log_level) {
vfprintf(stderr, fmt, ap);
}
} else {
PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
}
va_end(aq);
va_end(ap);
}
/*!
* \internal
* \brief Apply a single XSL transformation to given XML
*
* \param[in] xml XML to transform
* \param[in] transform XSL name
* \param[in] to_logs If false, certain validation errors will be sent to
* stderr rather than logged
*
* \return Transformed XML on success, otherwise NULL
*/
static xmlNode *
apply_transformation(const xmlNode *xml, const char *transform,
gboolean to_logs)
{
char *xform = NULL;
xmlNode *out = NULL;
xmlDocPtr res = NULL;
xsltStylesheet *xslt = NULL;
xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform);
/* for capturing, e.g., what's emitted via */
if (to_logs) {
xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
} else {
xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
}
xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
CRM_CHECK(xslt != NULL, goto cleanup);
res = xsltApplyStylesheet(xslt, xml->doc, NULL);
CRM_CHECK(res != NULL, goto cleanup);
xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
out = xmlDocGetRootElement(res);
cleanup:
if (xslt) {
xsltFreeStylesheet(xslt);
}
free(xform);
return out;
}
/*!
* \internal
* \brief Perform all transformations needed to upgrade XML to next schema
*
* A schema upgrade can require up to three XSL transformations: an "enter"
* transform, the main upgrade transform, and a "leave" transform. Perform
* all needed transforms to upgrade given XML to the next schema.
*
* \param[in] original_xml XML to transform
* \param[in] schema_index Index of schema that successfully validates
* \p original_xml
* \param[in] to_logs If false, certain validation errors will be sent to
* stderr rather than logged
*
* \return XML result of schema transforms if successful, otherwise NULL
*/
static xmlNode *
apply_upgrade(const xmlNode *original_xml, int schema_index, gboolean to_logs)
{
pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
schema_index + 1);
bool transform_onleave = false;
char *transform_leave;
const xmlNode *xml = original_xml;
xmlNode *upgrade = NULL;
xmlNode *final = NULL;
xmlRelaxNGValidityErrorFunc error_handler = NULL;
CRM_ASSERT((schema != NULL) && (upgraded_schema != NULL));
if (to_logs) {
error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
}
transform_onleave = schema->transform_onleave;
if (schema->transform_enter != NULL) {
crm_debug("Upgrading schema from %s to %s: "
"applying pre-upgrade XSL transform %s",
schema->name, upgraded_schema->name, schema->transform_enter);
upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
if (upgrade == NULL) {
crm_warn("Pre-upgrade XSL transform %s failed, "
"will skip post-upgrade transform",
schema->transform_enter);
transform_onleave = FALSE;
} else {
xml = upgrade;
}
}
crm_debug("Upgrading schema from %s to %s: "
"applying upgrade XSL transform %s",
schema->name, upgraded_schema->name, schema->transform);
final = apply_transformation(xml, schema->transform, to_logs);
if (upgrade != xml) {
free_xml(upgrade);
upgrade = NULL;
}
if ((final != NULL) && transform_onleave) {
upgrade = final;
/* following condition ensured in add_schema_by_version */
CRM_ASSERT(schema->transform_enter != NULL);
transform_leave = strdup(schema->transform_enter);
/* enter -> leave */
memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
crm_debug("Upgrading schema from %s to %s: "
"applying post-upgrade XSL transform %s",
schema->name, upgraded_schema->name, transform_leave);
final = apply_transformation(upgrade, transform_leave, to_logs);
if (final == NULL) {
crm_warn("Ignoring failure of post-upgrade XSL transform %s",
transform_leave);
final = upgrade;
} else {
free_xml(upgrade);
}
free(transform_leave);
}
if (final == NULL) {
return NULL;
}
// Ensure result validates with its new schema
if (!validate_with(final, upgraded_schema, error_handler,
GUINT_TO_POINTER(LOG_ERR))) {
crm_err("Schema upgrade from %s to %s failed: "
"XSL transform %s produced an invalid configuration",
schema->name, upgraded_schema->name, schema->transform);
crm_log_xml_debug(final, "bad-transform-result");
free_xml(final);
return NULL;
}
crm_info("Schema upgrade from %s to %s succeeded",
schema->name, upgraded_schema->name);
return final;
}
-const char *
-get_schema_name(int version)
-{
- pcmk__schema_t *schema = g_list_nth_data(known_schemas, version);
-
- return (schema != NULL)? schema->name : "unknown";
-}
-
-int
-get_schema_version(const char *name)
-{
- int lpc = 0;
-
- if (name == NULL) {
- name = PCMK_VALUE_NONE;
- }
-
- for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
- pcmk__schema_t *schema = iter->data;
-
- if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
- return lpc;
- }
-
- lpc++;
- }
-
- return -1;
-}
-
/*!
* \internal
* \brief Get the schema list entry corresponding to XML configuration
*
* \param[in] xml CIB XML to check
*
* \return List entry of schema configured in \p xml
*/
static GList *
get_configured_schema(const xmlNode *xml)
{
const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
if (schema_name == NULL) {
return NULL;
}
return pcmk__get_schema(schema_name);
}
/*!
* \brief Update CIB XML to latest schema that validates it
*
* \param[in,out] xml XML to update (may be freed and replaced
* after being transformed)
* \param[in] max_schema_name If not NULL, do not update \p xml to any
* schema later than this one
* \param[in] transform If false, do not update \p xml to any schema
* that requires an XSL transform
* \param[in] to_logs If false, certain validation errors will be
* sent to stderr rather than logged
*
* \return Standard Pacemaker return code
*/
int
pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
bool to_logs)
{
int max_stable_schemas = xml_latest_schema_index();
int max_schema_index = 0;
int rc = pcmk_rc_ok;
GList *entry = NULL;
pcmk__schema_t *best_schema = NULL;
pcmk__schema_t *original_schema = NULL;
xmlRelaxNGValidityErrorFunc error_handler =
to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
return EINVAL);
if (max_schema_name != NULL) {
GList *max_entry = pcmk__get_schema(max_schema_name);
if (max_entry != NULL) {
pcmk__schema_t *max_schema = max_entry->data;
max_schema_index = max_schema->schema_index;
}
}
if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
max_schema_index = max_stable_schemas;
}
entry = get_configured_schema(*xml);
if (entry == NULL) {
entry = known_schemas;
} else {
original_schema = entry->data;
if (original_schema->schema_index >= max_schema_index) {
return pcmk_rc_ok;
}
}
for (; entry != NULL; entry = entry->next) {
pcmk__schema_t *current_schema = entry->data;
xmlNode *upgrade = NULL;
if (current_schema->schema_index > max_schema_index) {
break;
}
if (!validate_with(*xml, current_schema, error_handler,
GUINT_TO_POINTER(LOG_ERR))) {
crm_debug("Schema %s does not validate", current_schema->name);
if (best_schema != NULL) {
/* we've satisfied the validation, no need to check further */
break;
}
rc = pcmk_rc_schema_validation;
continue; // Try again with the next higher schema
}
crm_debug("Schema %s validates", current_schema->name);
rc = pcmk_rc_ok;
best_schema = current_schema;
if (current_schema->schema_index == max_schema_index) {
break; // No further transformations possible
}
if (!transform || (current_schema->transform == NULL)
|| validate_with_silent(*xml, entry->next->data)) {
/* The next schema either doesn't require a transform or validates
* successfully even without the transform. Skip the transform and
* try the next schema with the same XML.
*/
continue;
}
upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
if (upgrade == NULL) {
/* The transform failed, so this schema can't be used. Later
* schemas are unlikely to validate, but try anyway until we
* run out of options.
*/
rc = pcmk_rc_transform_failed;
} else {
best_schema = current_schema;
free_xml(*xml);
*xml = upgrade;
}
}
if (best_schema != NULL) {
if ((original_schema == NULL)
|| (best_schema->schema_index > original_schema->schema_index)) {
crm_info("%s the configuration schema to %s",
(transform? "Transformed" : "Upgraded"),
best_schema->name);
crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
}
}
return rc;
}
-gboolean
-cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
+/*!
+ * \internal
+ * \brief Update XML from its configured schema to the latest major series
+ *
+ * \param[in,out] xml XML to update
+ * \param[in] to_logs If false, certain validation errors will be
+ * sent to stderr rather than logged
+ *
+ * \return true if XML was successfully updated, otherwise false
+ */
+bool
+pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
{
- gboolean rc = TRUE;
+ bool rc = true;
char *original_schema_name = NULL;
- int version = 0;
- int orig_version = 0;
- int min_version = pcmk__find_x_0_schema_index();
+ const char *effective_original_name = "the first";
+ int orig_version = -1;
+ pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
+ GList *entry = NULL;
original_schema_name = crm_element_value_copy(*xml, PCMK_XA_VALIDATE_WITH);
- version = get_schema_version(original_schema_name);
- orig_version = version;
- if (version < min_version) {
+ entry = pcmk__get_schema(original_schema_name);
+ if (entry != NULL) {
+ pcmk__schema_t *original_schema = entry->data;
+
+ effective_original_name = original_schema->name;
+ orig_version = original_schema->schema_index;
+ }
+
+ if (orig_version < x_0_schema->schema_index) {
// Current configuration schema is not acceptable, try to update
xmlNode *converted = NULL;
const char *new_schema_name = NULL;
+ pcmk__schema_t *schema = NULL;
+ entry = NULL;
converted = pcmk__xml_copy(NULL, *xml);
if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
new_schema_name = crm_element_value(converted,
PCMK_XA_VALIDATE_WITH);
- version = get_schema_version(new_schema_name);
- } else {
- version = 0;
+ entry = pcmk__get_schema(new_schema_name);
}
+ schema = (entry == NULL)? NULL : entry->data;
- if (version < min_version) {
+ if ((schema == NULL)
+ || (schema->schema_index < x_0_schema->schema_index)) {
// Updated configuration schema is still not acceptable
- if (version < orig_version || orig_version == -1) {
+ if ((orig_version == -1) || (schema == NULL)
+ || (schema->schema_index < orig_version)) {
// We couldn't validate any schema at all
if (to_logs) {
pcmk__config_err("Cannot upgrade configuration (claiming "
"%s schema) to at least %s because it "
"does not validate with any schema from "
- "%s to %s",
+ "%s to the latest",
pcmk__s(original_schema_name, "no"),
- get_schema_name(min_version),
- get_schema_name(orig_version),
- xml_latest_schema());
+ x_0_schema->name, effective_original_name);
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
"%s schema) to at least %s because it "
"does not validate with any schema from "
- "%s to %s\n",
+ "%s to the latest\n",
pcmk__s(original_schema_name, "no"),
- get_schema_name(min_version),
- get_schema_name(orig_version),
- xml_latest_schema());
+ x_0_schema->name, effective_original_name);
}
} else {
// We updated configuration successfully, but still too low
if (to_logs) {
pcmk__config_err("Cannot upgrade configuration (claiming "
"%s schema) to at least %s because it "
"would not upgrade past %s",
pcmk__s(original_schema_name, "no"),
- get_schema_name(min_version),
+ x_0_schema->name,
pcmk__s(new_schema_name, "unspecified version"));
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
"%s schema) to at least %s because it "
"would not upgrade past %s\n",
pcmk__s(original_schema_name, "no"),
- get_schema_name(min_version),
+ x_0_schema->name,
pcmk__s(new_schema_name, "unspecified version"));
}
}
free_xml(converted);
converted = NULL;
- rc = FALSE;
+ rc = false;
} else {
// Updated configuration schema is acceptable
free_xml(*xml);
*xml = converted;
- if (version < xml_latest_schema_index()) {
+ if (schema->schema_index < xml_latest_schema_index()) {
if (to_logs) {
pcmk__config_warn("Configuration with %s schema was "
"internally upgraded to acceptable (but "
"not most recent) %s",
pcmk__s(original_schema_name, "no"),
- get_schema_name(version));
- }
- } else {
- if (to_logs) {
- crm_info("Configuration with %s schema was internally "
- "upgraded to latest version %s",
- pcmk__s(original_schema_name, "no"),
- get_schema_name(version));
+ schema->name);
}
+ } else if (to_logs) {
+ crm_info("Configuration with %s schema was internally "
+ "upgraded to latest version %s",
+ pcmk__s(original_schema_name, "no"),
+ schema->name);
}
}
- } else if (version >= get_schema_version(PCMK_VALUE_NONE)) {
- // Schema validation is disabled
- if (to_logs) {
- pcmk__config_warn("Schema validation of configuration is disabled "
- "(enabling is encouraged and prevents common "
- "misconfigurations)");
+ } else {
+ pcmk__schema_t *none_schema = NULL;
- } else {
- fprintf(stderr, "Schema validation of configuration is disabled "
- "(enabling is encouraged and prevents common "
- "misconfigurations)\n");
- }
- }
+ entry = pcmk__get_schema(PCMK_VALUE_NONE);
+ CRM_ASSERT((entry != NULL) && (entry->data != NULL));
+
+ none_schema = entry->data;
+ if (orig_version >= none_schema->schema_index) {
+ // Schema validation is disabled
+ if (to_logs) {
+ pcmk__config_warn("Schema validation of configuration is "
+ "disabled (enabling is encouraged and "
+ "prevents common misconfigurations)");
- if (best_version) {
- *best_version = version;
+ } else {
+ fprintf(stderr, "Schema validation of configuration is "
+ "disabled (enabling is encouraged and "
+ "prevents common misconfigurations)\n");
+ }
+ }
}
free(original_schema_name);
return rc;
}
/*!
* \internal
* \brief Return a list of all schema files and any associated XSLT files
* later than the given one
* \brief Return a list of all schema versions later than the given one
*
* \param[in] schema The schema to compare against (for example,
* "pacemaker-3.1.rng" or "pacemaker-3.1")
*
* \note The caller is responsible for freeing both the returned list and
* the elements of the list
*/
GList *
pcmk__schema_files_later_than(const char *name)
{
GList *lst = NULL;
pcmk__schema_version_t ver;
if (!version_from_filename(name, &ver)) {
return lst;
}
for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
iter != NULL; iter = iter->prev) {
pcmk__schema_t *schema = iter->data;
char *s = NULL;
if (schema_cmp(ver, schema->version) != -1) {
continue;
}
s = crm_strdup_printf("%s.rng", schema->name);
lst = g_list_prepend(lst, s);
if (schema->transform != NULL) {
char *xform = crm_strdup_printf("%s.xsl", schema->transform);
lst = g_list_prepend(lst, xform);
}
if (schema->transform_enter != NULL) {
char *enter = crm_strdup_printf("%s.xsl", schema->transform_enter);
lst = g_list_prepend(lst, enter);
if (schema->transform_onleave) {
int last_dash = strrchr(enter, '-') - enter;
char *leave = crm_strdup_printf("%.*s-leave.xsl", last_dash, enter);
lst = g_list_prepend(lst, leave);
}
}
}
return lst;
}
static void
append_href(xmlNode *xml, void *user_data)
{
GList **list = user_data;
char *href = crm_element_value_copy(xml, "href");
if (href == NULL) {
return;
}
*list = g_list_prepend(*list, href);
}
static void
external_refs_in_schema(GList **list, const char *contents)
{
/* local-name()= is needed to ignore the xmlns= setting at the top of
* the XML file. Otherwise, the xpath query will always return nothing.
*/
const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
xmlNode *xml = pcmk__xml_parse(contents);
crm_foreach_xpath_result(xml, search, append_href, list);
free_xml(xml);
}
static int
read_file_contents(const char *file, char **contents)
{
int rc = pcmk_rc_ok;
char *path = NULL;
if (pcmk__ends_with(file, ".rng")) {
path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
} else {
path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
}
rc = pcmk__file_contents(path, contents);
free(path);
return rc;
}
static void
add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
{
char *contents = NULL;
char *path = NULL;
xmlNode *file_node = NULL;
GList *includes = NULL;
int rc = pcmk_rc_ok;
/* If we already included this file, don't do so again. */
if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
return;
}
/* Ensure whatever file we were given has a suffix we know about. If not,
* just assume it's an RNG file.
*/
if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
path = crm_strdup_printf("%s.rng", file);
} else {
path = pcmk__str_copy(file);
}
rc = read_file_contents(path, &contents);
if (rc != pcmk_rc_ok || contents == NULL) {
crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
free(path);
return;
}
/* Create a new node with the contents of the file
* as a CDATA block underneath it.
*/
file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
crm_xml_add(file_node, PCMK_XA_PATH, path);
*already_included = g_list_prepend(*already_included, path);
xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
strlen(contents)));
/* Scan the file for any or nodes and build up
* a list of the files they reference.
*/
external_refs_in_schema(&includes, contents);
/* For each referenced file, recurse to add it (and potentially anything it
* references, ...) to the XML.
*/
for (GList *iter = includes; iter != NULL; iter = iter->next) {
add_schema_file_to_xml(parent, iter->data, already_included);
}
free(contents);
g_list_free_full(includes, free);
}
/*!
* \internal
* \brief Add an XML schema file and all the files it references as children
* of a given XML node
*
* \param[in,out] parent The parent XML node
* \param[in] name The schema version to compare against
* (for example, "pacemaker-3.1" or "pacemaker-3.1.rng")
* \param[in,out] already_included A list of names that have already been added
* to the parent node.
*
* \note The caller is responsible for freeing both the returned list and
* the elements of the list
*/
void
pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
{
/* First, create an unattached node to add all the schema files to as children. */
xmlNode *schema_node = pcmk__xe_create(NULL, PCMK__XA_SCHEMA);
crm_xml_add(schema_node, PCMK_XA_VERSION, name);
add_schema_file_to_xml(schema_node, name, already_included);
/* Then, if we actually added any children, attach the node to parent. If
* we did not add any children (for instance, name was invalid), this prevents
* us from returning a document with additional empty children.
*/
if (schema_node->children != NULL) {
xmlAddChild(parent, schema_node);
} else {
free_xml(schema_node);
}
}
/*!
* \internal
* \brief Return the directory containing any extra schema files that a
* Pacemaker Remote node fetched from the cluster
*/
const char *
pcmk__remote_schema_dir(void)
{
const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
if (pcmk__str_empty(dir)) {
return PCMK__REMOTE_SCHEMA_DIR;
}
return dir;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
-#include
+#include
+
+const char *
+xml_latest_schema(void)
+{
+ return pcmk__highest_schema_name();
+}
+
+const char *
+get_schema_name(int version)
+{
+ pcmk__schema_t *schema = g_list_nth_data(known_schemas, version);
+
+ return (schema != NULL)? schema->name : "unknown";
+}
+
+int
+get_schema_version(const char *name)
+{
+ int lpc = 0;
+
+ if (name == NULL) {
+ name = PCMK_VALUE_NONE;
+ }
+
+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
+ pcmk__schema_t *schema = iter->data;
+
+ if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
+ return lpc;
+ }
+
+ lpc++;
+ }
+
+ return -1;
+}
int
update_validation(xmlNode **xml, int *best, int max, gboolean transform,
gboolean to_logs)
{
int rc = pcmk__update_schema(xml, get_schema_name(max), transform, to_logs);
if ((best != NULL) && (xml != NULL) && (rc == pcmk_rc_ok)) {
const char *schema_name = crm_element_value(*xml,
PCMK_XA_VALIDATE_WITH);
GList *schema_entry = pcmk__get_schema(schema_name);
if (schema_entry != NULL) {
*best = ((pcmk__schema_t *)(schema_entry->data))->schema_index;
}
}
return pcmk_rc2legacy(rc);
}
+gboolean
+validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
+{
+ bool rc = pcmk__validate_xml(xml_blob, validation,
+ to_logs? (xmlRelaxNGValidityErrorFunc) xml_log : NULL,
+ GUINT_TO_POINTER(LOG_ERR));
+ return rc? TRUE : FALSE;
+}
+
+static void
+dump_file(const char *filename)
+{
+
+ FILE *fp = NULL;
+ int ch, line = 0;
+
+ CRM_CHECK(filename != NULL, return);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ crm_perror(LOG_ERR, "Could not open %s for reading", filename);
+ return;
+ }
+
+ fprintf(stderr, "%4d ", ++line);
+ do {
+ ch = getc(fp);
+ if (ch == EOF) {
+ putc('\n', stderr);
+ break;
+ } else if (ch == '\n') {
+ fprintf(stderr, "\n%4d ", ++line);
+ } else {
+ putc(ch, stderr);
+ }
+ } while (1);
+
+ fclose(fp);
+}
+
+gboolean
+validate_xml_verbose(const xmlNode *xml_blob)
+{
+ int fd = 0;
+ xmlDoc *doc = NULL;
+ xmlNode *xml = NULL;
+ gboolean rc = FALSE;
+ char *filename = NULL;
+
+ filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
+
+ umask(S_IWGRP | S_IWOTH | S_IROTH);
+ fd = mkstemp(filename);
+ pcmk__xml_write_fd(xml_blob, filename, fd, false, NULL);
+
+ dump_file(filename);
+
+ doc = xmlReadFile(filename, NULL, 0);
+ xml = xmlDocGetRootElement(doc);
+ rc = pcmk__validate_xml(xml, NULL, NULL, NULL);
+ free_xml(xml);
+
+ unlink(filename);
+ free(filename);
+
+ return rc? TRUE : FALSE;
+}
+
+gboolean
+cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
+{
+ bool rc = pcmk__update_configured_schema(xml, to_logs);
+
+ if (best_version != NULL) {
+ const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
+
+ if (name == NULL) {
+ *best_version = -1;
+ } else {
+ GList *entry = pcmk__get_schema(name);
+ pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
+
+ *best_version = (schema == NULL)? -1 : schema->schema_index;
+ }
+ }
+ return rc? TRUE: FALSE;
+}
+
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/tests/schemas/Makefile.am b/lib/common/tests/schemas/Makefile.am
index d13349568d..ba0f8054bc 100644
--- a/lib/common/tests/schemas/Makefile.am
+++ b/lib/common/tests/schemas/Makefile.am
@@ -1,90 +1,88 @@
#
# Copyright 2023-2024 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
include $(top_srcdir)/mk/tap.mk
include $(top_srcdir)/mk/unittest.mk
CFLAGS += -DPCMK__TEST_SCHEMA_DIR='"$(abs_builddir)/schemas"'
# Add "_test" to the end of all test program names to simplify .gitignore.
# These tests share a schema subdirectory
SHARED_SCHEMA_TESTS = pcmk__cmp_schemas_by_name_test \
crm_schema_init_test \
- get_schema_name_test \
- get_schema_version_test \
pcmk__build_schema_xml_node_test \
pcmk__get_schema_test \
pcmk__schema_files_later_than_test
# This test has its own schema directory
-FIND_X_0_SCHEMA_TEST = pcmk__find_x_0_schema_index_test
+FIND_X_0_SCHEMA_TEST = pcmk__find_x_0_schema_test
check_PROGRAMS = $(SHARED_SCHEMA_TESTS) $(FIND_X_0_SCHEMA_TEST)
TESTS = $(check_PROGRAMS)
$(SHARED_SCHEMA_TESTS): setup-schema-dir
$(FIND_X_0_SCHEMA_TEST): setup-find_x_0-schema-dir
# Set up a temporary schemas/ directory containing only some of the full set of
# pacemaker schema files. This lets us know exactly how many schemas are present,
# allowing us to write tests without having to make changes when new schemas are
# added.
#
# This directory contains the following:
#
# * pacemaker-next.rng - Used to verify that this sorts before all versions
# * upgrade-*.xsl - Required by various schema versions
# * pacemaker-[0-9]*.rng - We're only pulling in 15 schemas, which is enough
# to get everything through pacemaker-3.0.rng. This
# includes 2.10, needed so we can check that versions
# are compared as numbers instead of strings.
# * other RNG files - This catches everything except the pacemaker-*rng
# files. These files are included by the top-level
# pacemaker-*rng files, so we need them for tests.
# This will glob more than we need, but the extra ones
# won't get in the way.
LINK_FILES = $(abs_top_builddir)/xml/pacemaker-next.rng \
$(abs_top_builddir)/xml/upgrade-*.xsl
ROOT_RNGS = $(shell ls -1v $(abs_top_builddir)/xml/pacemaker-[0-9]*.rng | head -15)
INCLUDED_RNGS = $(shell ls -1 $(top_srcdir)/xml/*.rng | grep -v pacemaker-[0-9])
# Most tests share a common, read-only schema directory
.PHONY: setup-schema-dir
setup-schema-dir:
$(MKDIR_P) schemas
( cd schemas ; \
ln -sf $(LINK_FILES) . ; \
for f in $(ROOT_RNGS); do \
ln -sf $$f $$(basename $$f); \
done ; \
for f in $(INCLUDED_RNGS); do \
ln -sf ../$$f $$(basename $$f); \
done )
-# pcmk__find_x_0_schema_index_test moves schema files around, so it needs its
+# pcmk__find_x_0_schema_test moves schema files around, so it needs its
# own directory, otherwise other tests run in parallel could fail.
.PHONY: setup-find_x_0-schema-dir
setup-find_x_0-schema-dir:
$(MKDIR_P) schemas/find_x_0
( cd schemas/find_x_0 ; \
ln -sf $(LINK_FILES) . ; \
for f in $(ROOT_RNGS); do \
ln -sf $$f $$(basename $$f); \
done ; \
for f in $(INCLUDED_RNGS); do \
ln -sf ../$$f $$(basename $$f); \
done )
.PHONY: clean-local
clean-local:
-rm -rf schemas
diff --git a/lib/common/tests/schemas/crm_schema_init_test.c b/lib/common/tests/schemas/crm_schema_init_test.c
index f0a7054652..10fc6dab97 100644
--- a/lib/common/tests/schemas/crm_schema_init_test.c
+++ b/lib/common/tests/schemas/crm_schema_init_test.c
@@ -1,135 +1,151 @@
/*
* Copyright 2023-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
+#include "crmcommon_private.h"
static char *remote_schema_dir = NULL;
static int
symlink_schema(const char *tmpdir, const char *target_file, const char *link_file)
{
int rc = 0;
char *oldpath = NULL;
char *newpath = NULL;
oldpath = crm_strdup_printf("%s/%s", PCMK__TEST_SCHEMA_DIR, target_file);
newpath = crm_strdup_printf("%s/%s", tmpdir, link_file);
rc = symlink(oldpath, newpath);
free(oldpath);
free(newpath);
return rc;
}
static int
rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb)
{
return remove(pathname);
}
static int
rmtree(const char *dir)
{
return nftw(dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
}
static int
setup(void **state)
{
char *dir = NULL;
/* Create a directory to hold additional schema files. These don't need
* to be anything special - we can just copy existing schemas but give
* them new names.
*/
dir = crm_strdup_printf("%s/test-schemas.XXXXXX", pcmk__get_tmpdir());
remote_schema_dir = mkdtemp(dir);
if (remote_schema_dir == NULL) {
free(dir);
return -1;
}
/* Add new files to simulate a remote node not being up-to-date. We can't
* add a new major version here without also creating an XSL transform, and
* we can't add an older version (like 1.1 or 2.11 or something) because
* remotes will only ever ask for stuff newer than their newest.
*/
if (symlink_schema(dir, "pacemaker-3.0.rng", "pacemaker-3.1.rng") != 0) {
rmdir(dir);
free(dir);
return -1;
}
if (symlink_schema(dir, "pacemaker-3.0.rng", "pacemaker-3.2.rng") != 0) {
rmdir(dir);
free(dir);
return -1;
}
setenv("PCMK_remote_schema_directory", remote_schema_dir, 1);
setenv("PCMK_schema_directory", PCMK__TEST_SCHEMA_DIR, 1);
/* Do not call crm_schema_init here because that is the function we're
* testing. It needs to be called in each unit test. However, we can
* call crm_schema_cleanup in teardown().
*/
return 0;
}
static int
teardown(void **state)
{
int rc = 0;
char *f = NULL;
crm_schema_cleanup();
unsetenv("PCMK_remote_schema_directory");
unsetenv("PCMK_schema_directory");
rc = rmtree(remote_schema_dir);
free(remote_schema_dir);
free(f);
return rc;
}
+static void
+assert_schema(const char *schema_name, int schema_index)
+{
+ GList *entry = NULL;
+ pcmk__schema_t *schema = NULL;
+
+ entry = pcmk__get_schema(schema_name);
+ assert_non_null(entry);
+
+ schema = entry->data;
+ assert_non_null(schema);
+
+ assert_int_equal(schema_index, schema->schema_index);
+}
+
static void
extra_schema_files(void **state)
{
crm_schema_init();
/* Just iterate through the list of schemas and make sure everything
* (including the new schemas we loaded from a second directory) is in
* the right order.
*/
- assert_string_equal("pacemaker-1.0", get_schema_name(0));
- assert_string_equal("pacemaker-1.2", get_schema_name(1));
- assert_string_equal("pacemaker-2.0", get_schema_name(3));
- assert_string_equal("pacemaker-3.0", get_schema_name(14));
- assert_string_equal("pacemaker-3.1", get_schema_name(15));
- assert_string_equal("pacemaker-3.2", get_schema_name(16));
+ assert_schema("pacemaker-1.0", 0);
+ assert_schema("pacemaker-1.2", 1);
+ assert_schema("pacemaker-2.0", 3);
+ assert_schema("pacemaker-3.0", 14);
+ assert_schema("pacemaker-3.1", 15);
+ assert_schema("pacemaker-3.2", 16);
// @COMPAT pacemaker-next is deprecated since 2.1.5
- assert_string_equal("pacemaker-next", get_schema_name(17));
+ assert_schema("pacemaker-next", 17);
- assert_string_equal(PCMK_VALUE_NONE, get_schema_name(18));
+ assert_schema(PCMK_VALUE_NONE, 18);
}
PCMK__UNIT_TEST(setup, teardown,
cmocka_unit_test(extra_schema_files));
diff --git a/lib/common/tests/schemas/get_schema_name_test.c b/lib/common/tests/schemas/get_schema_name_test.c
deleted file mode 100644
index f1eb9fe0ef..0000000000
--- a/lib/common/tests/schemas/get_schema_name_test.c
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2023-2024 the Pacemaker project contributors
- *
- * The version control history for this file may have further details.
- *
- * This source code is licensed under the GNU General Public License version 2
- * or later (GPLv2+) WITHOUT ANY WARRANTY.
- */
-
-#include
-
-#include
-#include
-#include
-
-static int
-setup(void **state)
-{
- setenv("PCMK_schema_directory", PCMK__TEST_SCHEMA_DIR, 1);
- crm_schema_init();
- return 0;
-}
-
-static int
-teardown(void **state)
-{
- crm_schema_cleanup();
- unsetenv("PCMK_schema_directory");
- return 0;
-}
-
-static void
-bad_input(void **state)
-{
- assert_string_equal("unknown", get_schema_name(-1));
- assert_string_equal("unknown", get_schema_name(47000));
-}
-
-static void
-typical_usage(void **state)
-{
- assert_string_equal("pacemaker-1.0", get_schema_name(0));
- assert_string_equal("pacemaker-1.2", get_schema_name(1));
- assert_string_equal("pacemaker-2.0", get_schema_name(3));
- assert_string_equal("pacemaker-2.5", get_schema_name(8));
- assert_string_equal("pacemaker-3.0", get_schema_name(14));
-
- // @COMPAT pacemaker-next is deprecated since 2.1.5
- assert_string_equal("pacemaker-next", get_schema_name(15));
-
- assert_string_equal(PCMK_VALUE_NONE, get_schema_name(16));
-}
-
-PCMK__UNIT_TEST(setup, teardown,
- cmocka_unit_test(bad_input),
- cmocka_unit_test(typical_usage));
diff --git a/lib/common/tests/schemas/get_schema_version_test.c b/lib/common/tests/schemas/get_schema_version_test.c
deleted file mode 100644
index 863c6687ae..0000000000
--- a/lib/common/tests/schemas/get_schema_version_test.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2023-2024 the Pacemaker project contributors
- *
- * The version control history for this file may have further details.
- *
- * This source code is licensed under the GNU General Public License version 2
- * or later (GPLv2+) WITHOUT ANY WARRANTY.
- */
-
-#include
-
-#include
-#include
-#include
-
-static int
-setup(void **state)
-{
- setenv("PCMK_schema_directory", PCMK__TEST_SCHEMA_DIR, 1);
- crm_schema_init();
- return 0;
-}
-
-static int
-teardown(void **state)
-{
- crm_schema_cleanup();
- unsetenv("PCMK_schema_directory");
- return 0;
-}
-
-static void
-bad_input(void **state)
-{
- assert_int_equal(-1, get_schema_version(""));
- assert_int_equal(-1, get_schema_version("blahblah"));
- assert_int_equal(-1, get_schema_version("pacemaker-2.47"));
- assert_int_equal(-1, get_schema_version("pacemaker-47.0"));
-}
-
-static void
-typical_usage(void **state)
-{
- assert_int_equal(0, get_schema_version("pacemaker-1.0"));
- assert_int_equal(0, get_schema_version("PACEMAKER-1.0"));
- assert_int_equal(1, get_schema_version("pacemaker-1.2"));
- assert_int_equal(3, get_schema_version("pacemaker-2.0"));
- assert_int_equal(3, get_schema_version("pAcEmAkEr-2.0"));
- assert_int_equal(8, get_schema_version("pacemaker-2.5"));
- assert_int_equal(14, get_schema_version("pacemaker-3.0"));
- assert_int_equal(14, get_schema_version("paceMAKER-3.0"));
-
- // @COMPAT pacemaker-next is deprecated since 2.1.5
- assert_int_equal(15, get_schema_version("pacemaker-next"));
-
- assert_int_equal(16, get_schema_version("none"));
- assert_int_equal(16, get_schema_version("NONE"));
- assert_int_equal(16, get_schema_version(NULL)); // defaults to "none"
-}
-
-PCMK__UNIT_TEST(setup, teardown,
- cmocka_unit_test(bad_input),
- cmocka_unit_test(typical_usage));
diff --git a/lib/common/tests/schemas/pcmk__find_x_0_schema_index_test.c b/lib/common/tests/schemas/pcmk__find_x_0_schema_test.c
similarity index 82%
rename from lib/common/tests/schemas/pcmk__find_x_0_schema_index_test.c
rename to lib/common/tests/schemas/pcmk__find_x_0_schema_test.c
index 4d094f39f3..25ba0f3314 100644
--- a/lib/common/tests/schemas/pcmk__find_x_0_schema_index_test.c
+++ b/lib/common/tests/schemas/pcmk__find_x_0_schema_test.c
@@ -1,93 +1,100 @@
/*
* Copyright 2023-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include // NULL, rename()
#include // setenv(), unsetenv()
#include
#include
#include "crmcommon_private.h"
#define SCHEMA_PREFIX PCMK__TEST_SCHEMA_DIR "/find_x_0/pacemaker-"
static int
setup(void **state)
{
// Use a unique schema directory so we can move files around
setenv("PCMK_schema_directory", PCMK__TEST_SCHEMA_DIR "/find_x_0", 1);
return 0;
}
static int
teardown(void **state)
{
unsetenv("PCMK_schema_directory");
return 0;
}
+static void
+assert_schema_0(int schema_index, const char *schema_name)
+{
+ GList *entry = NULL;
+ pcmk__schema_t *schema = NULL;
+
+ entry = pcmk__find_x_0_schema();
+ assert_non_null(entry);
+
+ schema = entry->data;
+ assert_non_null(schema);
+
+ assert_int_equal(schema->schema_index, schema_index);
+ assert_string_equal(schema->name, schema_name);
+}
+
static void
last_is_0(void **state)
{
/* This loads all the schemas normally linked for unit testing, so we have
* many 1.x and 2.x schemas and a single pacemaker-3.0 schema at index 14.
*/
crm_schema_init();
-
- assert_int_equal(14, pcmk__find_x_0_schema_index());
- assert_string_equal(get_schema_name(14), "pacemaker-3.0");
-
+ assert_schema_0(14, "pacemaker-3.0");
crm_schema_cleanup();
}
static void
last_is_not_0(void **state)
{
/* Disable the pacemaker-3.0 schema, so we now should get pacemaker-2.0 at
* index 3.
*/
assert_int_equal(0, rename(SCHEMA_PREFIX "3.0.rng",
SCHEMA_PREFIX "3.0.bak"));
crm_schema_init();
-
- assert_int_equal(3, pcmk__find_x_0_schema_index());
- assert_string_equal(get_schema_name(3), "pacemaker-2.0");
-
+ assert_schema_0(3, "pacemaker-2.0");
assert_int_equal(0, rename(SCHEMA_PREFIX "3.0.bak",
SCHEMA_PREFIX "3.0.rng"));
crm_schema_cleanup();
}
static void
schema_0_missing(void **state)
{
/* Disable the pacemaker-3.0 and pacemaker-2.0 schemas, so we now should get
* pacemaker-2.1 at index 3.
*/
assert_int_equal(0, rename(SCHEMA_PREFIX "3.0.rng",
SCHEMA_PREFIX "3.0.bak"));
assert_int_equal(0, rename(SCHEMA_PREFIX "2.0.rng",
SCHEMA_PREFIX "2.0.bak"));
crm_schema_init();
-
- assert_int_equal(3, pcmk__find_x_0_schema_index());
- assert_string_equal(get_schema_name(3), "pacemaker-2.1");
-
+ assert_schema_0(3, "pacemaker-2.1");
assert_int_equal(0, rename(SCHEMA_PREFIX "2.0.bak",
SCHEMA_PREFIX "2.0.rng"));
assert_int_equal(0, rename(SCHEMA_PREFIX "3.0.bak",
SCHEMA_PREFIX "3.0.rng"));
crm_schema_cleanup();
}
PCMK__UNIT_TEST(setup, teardown,
cmocka_unit_test(last_is_0),
cmocka_unit_test(last_is_not_0),
cmocka_unit_test(schema_0_missing))
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index 88f86fb835..74dcbde7b6 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,1013 +1,1013 @@
/*
* Copyright 2021-2024 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 "libpacemaker_private.h"
static pcmk__output_t *out = NULL;
static cib_t *fake_cib = NULL;
static GList *fake_resource_list = NULL;
static const GList *fake_op_fail_list = NULL;
static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
const char *use_date);
/*!
* \internal
* \brief Create an action name for use in a dot graph
*
* \param[in] action Action to create name for
* \param[in] verbose If true, add action ID to name
*
* \return Newly allocated string with action name
* \note It is the caller's responsibility to free the result.
*/
static char *
create_action_name(const pcmk_action_t *action, bool verbose)
{
char *action_name = NULL;
const char *prefix = "";
const char *action_host = NULL;
const char *clone_name = NULL;
const char *task = action->task;
if (action->node != NULL) {
action_host = action->node->details->uname;
} else if (!pcmk_is_set(action->flags, pcmk_action_pseudo)) {
action_host = "";
}
if (pcmk__str_eq(action->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
prefix = "Cancel ";
task = action->cancel_task;
}
if (action->rsc != NULL) {
clone_name = action->rsc->clone_name;
}
if (clone_name != NULL) {
char *key = NULL;
guint interval_ms = 0;
if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
&interval_ms) != pcmk_rc_ok) {
interval_ms = 0;
}
if (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY,
PCMK_ACTION_NOTIFIED, NULL)) {
const char *n_type = g_hash_table_lookup(action->meta,
"notify_key_type");
const char *n_task = g_hash_table_lookup(action->meta,
"notify_key_operation");
CRM_ASSERT(n_type != NULL);
CRM_ASSERT(n_task != NULL);
key = pcmk__notify_key(clone_name, n_type, n_task);
} else {
key = pcmk__op_key(clone_name, task, interval_ms);
}
if (action_host != NULL) {
action_name = crm_strdup_printf("%s%s %s",
prefix, key, action_host);
} else {
action_name = crm_strdup_printf("%s%s", prefix, key);
}
free(key);
} else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
pcmk__str_none)) {
const char *op = g_hash_table_lookup(action->meta,
PCMK__META_STONITH_ACTION);
action_name = crm_strdup_printf("%s%s '%s' %s",
prefix, action->task, op, action_host);
} else if (action->rsc && action_host) {
action_name = crm_strdup_printf("%s%s %s",
prefix, action->uuid, action_host);
} else if (action_host) {
action_name = crm_strdup_printf("%s%s %s",
prefix, action->task, action_host);
} else {
action_name = crm_strdup_printf("%s", action->uuid);
}
if (verbose) {
char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
free(action_name);
action_name = with_id;
}
return action_name;
}
/*!
* \internal
* \brief Display the status of a cluster
*
* \param[in,out] scheduler Scheduler data
* \param[in] show_opts How to modify display (as pcmk_show_opt_e flags)
* \param[in] section_opts Sections to display (as pcmk_section_e flags)
* \param[in] title What to use as list title
* \param[in] print_spacer Whether to display a spacer first
*/
static void
print_cluster_status(pcmk_scheduler_t *scheduler, uint32_t show_opts,
uint32_t section_opts, const char *title,
bool print_spacer)
{
pcmk__output_t *out = scheduler->priv;
GList *all = NULL;
crm_exit_t stonith_rc = 0;
enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
section_opts |= pcmk_section_nodes | pcmk_section_resources;
show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
all = g_list_prepend(all, (gpointer) "*");
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
out->begin_list(out, NULL, NULL, "%s", title);
out->message(out, "cluster-status",
scheduler, state, stonith_rc, NULL,
pcmk__fence_history_none, section_opts, show_opts, NULL,
all, all);
out->end_list(out);
g_list_free(all);
}
/*!
* \internal
* \brief Display a summary of all actions scheduled in a transition
*
* \param[in,out] scheduler Scheduler data (fully scheduled)
* \param[in] print_spacer Whether to display a spacer first
*/
static void
print_transition_summary(pcmk_scheduler_t *scheduler, bool print_spacer)
{
pcmk__output_t *out = scheduler->priv;
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
out->begin_list(out, NULL, NULL, "Transition Summary");
pcmk__output_actions(scheduler);
out->end_list(out);
}
/*!
* \internal
* \brief Reset scheduler input, output, date, and flags
*
* \param[in,out] scheduler Scheduler data
* \param[in] input What to set as cluster input
* \param[in] out What to set as cluster output object
* \param[in] use_date What to set as cluster's current timestamp
* \param[in] flags Group of enum pcmk_scheduler_flags to set
*/
static void
reset(pcmk_scheduler_t *scheduler, xmlNodePtr input, pcmk__output_t *out,
const char *use_date, unsigned int flags)
{
scheduler->input = input;
scheduler->priv = out;
set_effective_date(scheduler, true, use_date);
if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
pcmk__set_scheduler_flags(scheduler, pcmk_sched_sanitized);
}
if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
pcmk__set_scheduler_flags(scheduler, pcmk_sched_output_scores);
}
if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
pcmk__set_scheduler_flags(scheduler, pcmk_sched_show_utilization);
}
}
/*!
* \brief Write out a file in dot(1) format describing the actions that will
* be taken by the scheduler in response to an input CIB file.
*
* \param[in,out] scheduler Scheduler data
* \param[in] dot_file The filename to write
* \param[in] all_actions Write all actions, even those that are optional
* or are on unmanaged resources
* \param[in] verbose Add extra information, such as action IDs, to the
* output
*
* \return Standard Pacemaker return code
*/
static int
write_sim_dotfile(pcmk_scheduler_t *scheduler, const char *dot_file,
bool all_actions, bool verbose)
{
GList *iter = NULL;
FILE *dot_strm = fopen(dot_file, "w");
if (dot_strm == NULL) {
return errno;
}
fprintf(dot_strm, " digraph \"g\" {\n");
for (iter = scheduler->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = (pcmk_action_t *) iter->data;
const char *style = "dashed";
const char *font = "black";
const char *color = "black";
char *action_name = create_action_name(action, verbose);
if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
font = "orange";
}
if (pcmk_is_set(action->flags, pcmk_action_added_to_graph)) {
style = PCMK__VALUE_BOLD;
color = "green";
} else if ((action->rsc != NULL)
&& !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)) {
color = "red";
font = "purple";
if (!all_actions) {
goto do_not_write;
}
} else if (pcmk_is_set(action->flags, pcmk_action_optional)) {
color = "blue";
if (!all_actions) {
goto do_not_write;
}
} else {
color = "red";
CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pcmk_action_runnable));
}
pcmk__set_action_flags(action, pcmk_action_added_to_graph);
fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
action_name, style, color, font);
do_not_write:
free(action_name);
}
for (iter = scheduler->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = (pcmk_action_t *) iter->data;
for (GList *before_iter = action->actions_before;
before_iter != NULL; before_iter = before_iter->next) {
pcmk__related_action_t *before = before_iter->data;
char *before_name = NULL;
char *after_name = NULL;
const char *style = "dashed";
bool optional = true;
if (before->state == pe_link_dumped) {
optional = false;
style = PCMK__VALUE_BOLD;
} else if ((uint32_t) before->type == pcmk__ar_none) {
continue;
} else if (pcmk_is_set(before->action->flags,
pcmk_action_added_to_graph)
&& pcmk_is_set(action->flags, pcmk_action_added_to_graph)
&& (uint32_t) before->type != pcmk__ar_if_on_same_node_or_target) {
optional = false;
}
if (all_actions || !optional) {
before_name = create_action_name(before->action, verbose);
after_name = create_action_name(action, verbose);
fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
before_name, after_name, style);
free(before_name);
free(after_name);
}
}
}
fprintf(dot_strm, "}\n");
fflush(dot_strm);
fclose(dot_strm);
return pcmk_rc_ok;
}
/*!
* \brief Profile the configuration updates and scheduler actions in a single
* CIB file, printing the profiling timings.
*
* \note \p scheduler->priv must have been set to a valid \p pcmk__output_t
* object before this function is called.
*
* \param[in] xml_file The CIB file to profile
* \param[in] repeat Number of times to run
* \param[in,out] scheduler Scheduler data
* \param[in] use_date The date to set the cluster's time to (may be NULL)
*/
static void
profile_file(const char *xml_file, long long repeat,
pcmk_scheduler_t *scheduler, const char *use_date)
{
pcmk__output_t *out = scheduler->priv;
xmlNode *cib_object = NULL;
clock_t start = 0;
clock_t end;
unsigned long long scheduler_flags = pcmk_sched_no_compat;
CRM_ASSERT(out != NULL);
cib_object = pcmk__xml_read(xml_file);
start = clock();
if (pcmk_find_cib_element(cib_object, PCMK_XE_STATUS) == NULL) {
pcmk__xe_create(cib_object, PCMK_XE_STATUS);
}
- if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
+ if (!pcmk__update_configured_schema(&cib_object, false)) {
free_xml(cib_object);
return;
}
- if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
+ if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) {
free_xml(cib_object);
return;
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) {
scheduler_flags |= pcmk_sched_output_scores;
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
scheduler_flags |= pcmk_sched_show_utilization;
}
for (int i = 0; i < repeat; ++i) {
xmlNode *input = cib_object;
if (repeat > 1) {
input = pcmk__xml_copy(NULL, cib_object);
}
scheduler->input = input;
set_effective_date(scheduler, false, use_date);
pcmk__schedule_actions(input, scheduler_flags, scheduler);
pe_reset_working_set(scheduler);
}
end = clock();
out->message(out, "profile", xml_file, start, end);
}
void
pcmk__profile_dir(const char *dir, long long repeat,
pcmk_scheduler_t *scheduler, const char *use_date)
{
pcmk__output_t *out = scheduler->priv;
struct dirent **namelist;
int file_num = scandir(dir, &namelist, 0, alphasort);
CRM_ASSERT(out != NULL);
if (file_num > 0) {
struct stat prop;
char buffer[FILENAME_MAX];
out->begin_list(out, NULL, NULL, "Timings");
while (file_num--) {
if ('.' == namelist[file_num]->d_name[0]) {
free(namelist[file_num]);
continue;
} else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
".xml")) {
free(namelist[file_num]);
continue;
}
snprintf(buffer, sizeof(buffer), "%s/%s",
dir, namelist[file_num]->d_name);
if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
profile_file(buffer, repeat, scheduler, use_date);
}
free(namelist[file_num]);
}
free(namelist);
out->end_list(out);
}
}
/*!
* \brief Set the date of the cluster, either to the value given by
* \p use_date, or to the \c PCMK_XA_EXECUTION_DATE value in the CIB.
*
* \note \p scheduler->priv must have been set to a valid \p pcmk__output_t
* object before this function is called.
*
* \param[in,out] scheduler Scheduler data
* \param[in] print_original If \p true, the \c PCMK_XA_EXECUTION_DATE
* should also be printed
* \param[in] use_date The date to set the cluster's time to
* (may be NULL)
*/
static void
set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
const char *use_date)
{
pcmk__output_t *out = scheduler->priv;
time_t original_date = 0;
CRM_ASSERT(out != NULL);
crm_element_value_epoch(scheduler->input, PCMK_XA_EXECUTION_DATE,
&original_date);
if (use_date) {
scheduler->now = crm_time_new(use_date);
out->info(out, "Setting effective cluster time: %s", use_date);
crm_time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->now,
crm_time_log_date | crm_time_log_timeofday);
} else if (original_date != 0) {
scheduler->now = pcmk__copy_timet(original_date);
if (print_original) {
char *when = crm_time_as_string(scheduler->now,
crm_time_log_date|crm_time_log_timeofday);
out->info(out, "Using the original execution date of: %s", when);
free(when);
}
}
}
/*!
* \internal
* \brief Simulate successfully executing a pseudo-action in a graph
*
* \param[in,out] graph Graph to update with pseudo-action result
* \param[in,out] action Pseudo-action to simulate executing
*
* \return Standard Pacemaker return code
*/
static int
simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
const char *task = crm_element_value(action->xml, PCMK__XA_OPERATION_KEY);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
out->message(out, "inject-pseudo-action", node, task);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Simulate executing a resource action in a graph
*
* \param[in,out] graph Graph to update with resource action result
* \param[in,out] action Resource action to simulate executing
*
* \return Standard Pacemaker return code
*/
static int
simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
int rc;
lrmd_event_data_t *op = NULL;
int target_outcome = PCMK_OCF_OK;
const char *rtype = NULL;
const char *rclass = NULL;
const char *resource = NULL;
const char *rprovider = NULL;
const char *resource_config_name = NULL;
const char *operation = crm_element_value(action->xml, PCMK_XA_OPERATION);
const char *target_rc_s = crm_meta_value(action->params,
PCMK__META_OP_TARGET_RC);
xmlNode *cib_node = NULL;
xmlNode *cib_resource = NULL;
xmlNode *action_rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE,
NULL, NULL);
char *node = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
char *uuid = NULL;
const char *router_node = crm_element_value(action->xml,
PCMK__XA_ROUTER_NODE);
// Certain actions don't need to be displayed or history entries
if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
crm_debug("No history injection for %s op on %s", operation, node);
goto done; // Confirm action and update graph
}
if (action_rsc == NULL) { // Shouldn't be possible
crm_log_xml_err(action->xml, "Bad");
free(node);
return EPROTO;
}
/* A resource might be known by different names in the configuration and in
* the action (for example, a clone instance). Grab the configuration name
* (which is preferred when writing history), and if necessary, the instance
* name.
*/
resource_config_name = crm_element_value(action_rsc, PCMK_XA_ID);
if (resource_config_name == NULL) { // Shouldn't be possible
crm_log_xml_err(action->xml, "No ID");
free(node);
return EPROTO;
}
resource = resource_config_name;
if (pe_find_resource(fake_resource_list, resource) == NULL) {
const char *longname = crm_element_value(action_rsc, PCMK__XA_LONG_ID);
if ((longname != NULL)
&& (pe_find_resource(fake_resource_list, longname) != NULL)) {
resource = longname;
}
}
// Certain actions need to be displayed but don't need history entries
if (pcmk__strcase_any_of(operation, PCMK_ACTION_DELETE,
PCMK_ACTION_META_DATA, NULL)) {
out->message(out, "inject-rsc-action", resource, operation, node,
(guint) 0);
goto done; // Confirm action and update graph
}
rclass = crm_element_value(action_rsc, PCMK_XA_CLASS);
rtype = crm_element_value(action_rsc, PCMK_XA_TYPE);
rprovider = crm_element_value(action_rsc, PCMK_XA_PROVIDER);
pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL,
cib_sync_call|cib_scope_local) == pcmk_ok);
// Ensure the action node is in the CIB
uuid = crm_element_value_copy(action->xml, PCMK__META_ON_NODE_UUID);
cib_node = pcmk__inject_node(fake_cib, node,
((router_node == NULL)? uuid: node));
free(uuid);
CRM_ASSERT(cib_node != NULL);
// Add a history entry for the action
cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
resource_config_name,
rclass, rtype, rprovider);
if (cib_resource == NULL) {
crm_err("Could not simulate action %d history for resource %s",
action->id, resource);
free(node);
free_xml(cib_node);
return EINVAL;
}
// Simulate and display an executor event for the action result
op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
target_outcome, "User-injected result");
out->message(out, "inject-rsc-action", resource, op->op_type, node,
op->interval_ms);
// Check whether action is in a list of desired simulated failures
for (const GList *iter = fake_op_fail_list;
iter != NULL; iter = iter->next) {
const char *spec = (const char *) iter->data;
char *key = NULL;
const char *match_name = NULL;
// Allow user to specify anonymous clone with or without instance number
key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
op->interval_ms, node);
if (strncasecmp(key, spec, strlen(key)) == 0) {
match_name = resource;
}
free(key);
// If not found, try the resource's name in the configuration
if ((match_name == NULL)
&& (strcmp(resource, resource_config_name) != 0)) {
key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
op->op_type, op->interval_ms, node);
if (strncasecmp(key, spec, strlen(key)) == 0) {
match_name = resource_config_name;
}
free(key);
}
if (match_name == NULL) {
continue; // This failed action entry doesn't match
}
// ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
if (rc != 1) {
out->err(out, "Invalid failed operation '%s' "
"(result code must be integer)", spec);
continue; // Keep checking other list entries
}
out->info(out, "Pretending action %d failed with rc=%d",
action->id, op->rc);
pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
graph->abort_priority = PCMK_SCORE_INFINITY;
pcmk__inject_failcount(out, fake_cib, cib_node, match_name, op->op_type,
op->interval_ms, op->rc);
break;
}
pcmk__inject_action_result(cib_resource, op, target_outcome);
lrmd_free_event(op);
rc = fake_cib->cmds->modify(fake_cib, PCMK_XE_STATUS, cib_node,
cib_sync_call|cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
done:
free(node);
free_xml(cib_node);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Simulate successfully executing a cluster action
*
* \param[in,out] graph Graph to update with action result
* \param[in,out] action Cluster action to simulate
*
* \return Standard Pacemaker return code
*/
static int
simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
const char *task = crm_element_value(action->xml, PCMK_XA_OPERATION);
xmlNode *rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
NULL);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
out->message(out, "inject-cluster-action", node, task, rsc);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Simulate successfully executing a fencing action
*
* \param[in,out] graph Graph to update with action result
* \param[in,out] action Fencing action to simulate
*
* \return Standard Pacemaker return code
*/
static int
simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *op = crm_meta_value(action->params, PCMK__META_STONITH_ACTION);
char *target = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
out->message(out, "inject-fencing-action", target, op);
if (!pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) {
int rc = pcmk_ok;
GString *xpath = g_string_sized_new(512);
// Set node state to offline
xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
false);
CRM_ASSERT(cib_node != NULL);
crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, __func__);
rc = fake_cib->cmds->replace(fake_cib, PCMK_XE_STATUS, cib_node,
cib_sync_call|cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
// Simulate controller clearing node's resource history and attributes
pcmk__g_strcat(xpath,
"//" PCMK__XE_NODE_STATE
"[@" PCMK_XA_UNAME "='", target, "']/" PCMK__XE_LRM,
NULL);
fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
cib_xpath|cib_sync_call|cib_scope_local);
g_string_truncate(xpath, 0);
pcmk__g_strcat(xpath,
"//" PCMK__XE_NODE_STATE
"[@" PCMK_XA_UNAME "='", target, "']"
"/" PCMK__XE_TRANSIENT_ATTRIBUTES, NULL);
fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
cib_xpath|cib_sync_call|cib_scope_local);
free_xml(cib_node);
g_string_free(xpath, TRUE);
}
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
pcmk__update_graph(graph, action);
free(target);
return pcmk_rc_ok;
}
enum pcmk__graph_status
pcmk__simulate_transition(pcmk_scheduler_t *scheduler, cib_t *cib,
const GList *op_fail_list)
{
pcmk__graph_t *transition = NULL;
enum pcmk__graph_status graph_rc;
pcmk__graph_functions_t simulation_fns = {
simulate_pseudo_action,
simulate_resource_action,
simulate_cluster_action,
simulate_fencing_action,
};
out = scheduler->priv;
fake_cib = cib;
fake_op_fail_list = op_fail_list;
if (!out->is_quiet(out)) {
out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
}
pcmk__set_graph_functions(&simulation_fns);
transition = pcmk__unpack_graph(scheduler->graph, crm_system_name);
pcmk__log_graph(LOG_DEBUG, transition);
fake_resource_list = scheduler->resources;
do {
graph_rc = pcmk__execute_graph(transition);
} while (graph_rc == pcmk__graph_active);
fake_resource_list = NULL;
if (graph_rc != pcmk__graph_complete) {
out->err(out, "Transition failed: %s",
pcmk__graph_status2text(graph_rc));
pcmk__log_graph(LOG_ERR, transition);
out->err(out, "An invalid transition was produced");
}
pcmk__free_graph(transition);
if (!out->is_quiet(out)) {
// If not quiet, we'll need the resulting CIB for later display
xmlNode *cib_object = NULL;
int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
cib_sync_call|cib_scope_local);
CRM_ASSERT(rc == pcmk_ok);
pe_reset_working_set(scheduler);
scheduler->input = cib_object;
out->end_list(out);
}
return graph_rc;
}
int
pcmk__simulate(pcmk_scheduler_t *scheduler, pcmk__output_t *out,
const pcmk_injections_t *injections, unsigned int flags,
uint32_t section_opts, const char *use_date,
const char *input_file, const char *graph_file,
const char *dot_file)
{
int printed = pcmk_rc_no_output;
int rc = pcmk_rc_ok;
xmlNodePtr input = NULL;
cib_t *cib = NULL;
rc = cib__signon_query(out, &cib, &input);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
reset(scheduler, input, out, use_date, flags);
cluster_status(scheduler);
if ((cib->variant == cib_native)
&& pcmk_is_set(section_opts, pcmk_section_times)) {
if (pcmk__our_nodename == NULL) {
// Currently used only in the times section
pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0);
}
scheduler->localhost = pcmk__our_nodename;
}
if (!out->is_quiet(out)) {
const bool show_pending = pcmk_is_set(flags, pcmk_sim_show_pending);
if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
printed = out->message(out, "maint-mode", scheduler->flags);
}
if (scheduler->disabled_resources || scheduler->blocked_resources) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
printed = out->info(out,
"%d of %d resource instances DISABLED and "
"%d BLOCKED from further action due to failure",
scheduler->disabled_resources,
scheduler->ninstances,
scheduler->blocked_resources);
}
/* Most formatted output headers use caps for each word, but this one
* only has the first word capitalized for compatibility with pcs.
*/
print_cluster_status(scheduler, (show_pending? pcmk_show_pending : 0),
section_opts, "Current cluster status",
(printed == pcmk_rc_ok));
printed = pcmk_rc_ok;
}
// If the user requested any injections, handle them
if ((injections->node_down != NULL)
|| (injections->node_fail != NULL)
|| (injections->node_up != NULL)
|| (injections->op_inject != NULL)
|| (injections->ticket_activate != NULL)
|| (injections->ticket_grant != NULL)
|| (injections->ticket_revoke != NULL)
|| (injections->ticket_standby != NULL)
|| (injections->watchdog != NULL)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
pcmk__inject_scheduler_input(scheduler, cib, injections);
printed = pcmk_rc_ok;
rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
if (rc != pcmk_rc_ok) {
rc = pcmk_legacy2rc(rc);
goto simulate_done;
}
cleanup_calculations(scheduler);
reset(scheduler, input, out, use_date, flags);
cluster_status(scheduler);
}
if (input_file != NULL) {
rc = pcmk__xml_write_file(input, input_file, false, NULL);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
}
if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
pcmk__output_t *logger_out = NULL;
unsigned long long scheduler_flags = pcmk_sched_no_compat;
if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) {
scheduler_flags |= pcmk_sched_output_scores;
}
if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
scheduler_flags |= pcmk_sched_show_utilization;
}
if (pcmk_all_flags_set(scheduler->flags,
pcmk_sched_output_scores
|pcmk_sched_show_utilization)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL,
"Assignment Scores and Utilization Information");
printed = pcmk_rc_ok;
} else if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL, "Assignment Scores");
printed = pcmk_rc_ok;
} else if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL, "Utilization Information");
printed = pcmk_rc_ok;
} else {
rc = pcmk__log_output_new(&logger_out);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
pe__register_messages(logger_out);
pcmk__register_lib_messages(logger_out);
scheduler->priv = logger_out;
}
pcmk__schedule_actions(input, scheduler_flags, scheduler);
if (logger_out == NULL) {
out->end_list(out);
} else {
logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
pcmk__output_free(logger_out);
scheduler->priv = out;
}
input = NULL; /* Don't try and free it twice */
if (graph_file != NULL) {
rc = pcmk__xml_write_file(scheduler->graph, graph_file, false,
NULL);
if (rc != pcmk_rc_ok) {
rc = pcmk_rc_graph_error;
goto simulate_done;
}
}
if (dot_file != NULL) {
rc = write_sim_dotfile(scheduler, dot_file,
pcmk_is_set(flags, pcmk_sim_all_actions),
pcmk_is_set(flags, pcmk_sim_verbose));
if (rc != pcmk_rc_ok) {
rc = pcmk_rc_dot_error;
goto simulate_done;
}
}
if (!out->is_quiet(out)) {
print_transition_summary(scheduler, printed == pcmk_rc_ok);
}
}
rc = pcmk_rc_ok;
if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
goto simulate_done;
}
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
if (pcmk__simulate_transition(scheduler, cib, injections->op_fail)
!= pcmk__graph_complete) {
rc = pcmk_rc_invalid_transition;
}
if (out->is_quiet(out)) {
goto simulate_done;
}
set_effective_date(scheduler, true, use_date);
if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
pcmk__set_scheduler_flags(scheduler, pcmk_sched_output_scores);
}
if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
pcmk__set_scheduler_flags(scheduler, pcmk_sched_show_utilization);
}
cluster_status(scheduler);
print_cluster_status(scheduler, 0, section_opts, "Revised Cluster Status",
true);
simulate_done:
cib__clean_up_connection(&cib);
return rc;
}
int
pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler,
const pcmk_injections_t *injections, unsigned int flags,
unsigned int section_opts, const char *use_date,
const char *input_file, const char *graph_file,
const char *dot_file)
{
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
pe__register_messages(out);
pcmk__register_lib_messages(out);
rc = pcmk__simulate(scheduler, out, injections, flags, section_opts,
use_date, input_file, graph_file, dot_file);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
return rc;
}
diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c
index 875b34523e..0be054d6c9 100644
--- a/lib/pacemaker/pcmk_status.c
+++ b/lib/pacemaker/pcmk_status.c
@@ -1,381 +1,381 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // stonith__register_messages()
#include
#include
static stonith_t *
fencing_connect(void)
{
stonith_t *st = stonith_api_new();
int rc = pcmk_rc_ok;
if (st == NULL) {
return NULL;
}
rc = st->cmds->connect(st, crm_system_name, NULL);
if (rc == pcmk_rc_ok) {
return st;
} else {
stonith_api_delete(st);
return NULL;
}
}
/*!
* \internal
* \brief Output the cluster status given a fencer and CIB connection
*
* \param[in,out] out Output object
* \param[in,out] stonith Fencer connection
* \param[in,out] cib CIB connection
* \param[in] current_cib Current CIB XML
* \param[in] pcmkd_state \p pacemakerd state
* \param[in] fence_history How much of the fencing history to output
* \param[in] show Group of \p pcmk_section_e flags
* \param[in] show_opts Group of \p pcmk_show_opt_e flags
* \param[in] only_node If a node name or tag, include only the
* matching node(s) (if any) in the output.
* If \p "*" or \p NULL, include all nodes
* in the output.
* \param[in] only_rsc If a resource ID or tag, include only the
* matching resource(s) (if any) in the
* output. If \p "*" or \p NULL, include all
* resources in the output.
* \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID
* \param[in] simple_output Whether to use a simple output format.
* Note: This is for use by \p crm_mon only
* and is planned to be deprecated.
*
* \return Standard Pacemaker return code
*/
int
pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib,
xmlNode *current_cib,
enum pcmk_pacemakerd_state pcmkd_state,
enum pcmk__fence_history fence_history,
uint32_t show, uint32_t show_opts,
const char *only_node, const char *only_rsc,
const char *neg_location_prefix, bool simple_output)
{
xmlNode *cib_copy = pcmk__xml_copy(NULL, current_cib);
stonith_history_t *stonith_history = NULL;
int history_rc = 0;
pcmk_scheduler_t *scheduler = NULL;
GList *unames = NULL;
GList *resources = NULL;
int rc = pcmk_rc_ok;
- if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
+ if (!pcmk__update_configured_schema(&cib_copy, false)) {
cib__clean_up_connection(&cib);
free_xml(cib_copy);
rc = pcmk_rc_schema_validation;
out->err(out, "Upgrade failed: %s", pcmk_rc_str(rc));
return rc;
}
/* get the stonith-history if there is evidence we need it */
if (fence_history != pcmk__fence_history_none) {
history_rc = pcmk__get_fencing_history(stonith, &stonith_history,
fence_history);
}
scheduler = pe_new_working_set();
pcmk__mem_assert(scheduler);
pcmk__set_scheduler_flags(scheduler, pcmk_sched_no_compat);
scheduler->input = cib_copy;
scheduler->priv = out;
cluster_status(scheduler);
if ((cib->variant == cib_native) && pcmk_is_set(show, pcmk_section_times)) {
if (pcmk__our_nodename == NULL) {
// Currently used only in the times section
pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0);
}
scheduler->localhost = pcmk__our_nodename;
}
/* Unpack constraints if any section will need them
* (tickets may be referenced in constraints but not granted yet,
* and bans need negative location constraints) */
if (pcmk_is_set(show, pcmk_section_bans)
|| pcmk_is_set(show, pcmk_section_tickets)) {
pcmk__unpack_constraints(scheduler);
}
unames = pe__build_node_name_list(scheduler, only_node);
resources = pe__build_rsc_list(scheduler, only_rsc);
/* Always print DC if NULL. */
if (scheduler->dc_node == NULL) {
show |= pcmk_section_dc;
}
if (simple_output) {
rc = pcmk__output_simple_status(out, scheduler);
} else {
out->message(out, "cluster-status",
scheduler, pcmkd_state, pcmk_rc2exitc(history_rc),
stonith_history, fence_history, show, show_opts,
neg_location_prefix, unames, resources);
}
g_list_free_full(unames, free);
g_list_free_full(resources, free);
stonith_history_free(stonith_history);
stonith_history = NULL;
pe_free_working_set(scheduler);
return rc;
}
int
pcmk_status(xmlNodePtr *xml)
{
cib_t *cib = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
uint32_t show_opts = pcmk_show_pending
|pcmk_show_inactive_rscs
|pcmk_show_timing;
cib = cib_new();
if (cib == NULL) {
return pcmk_rc_cib_corrupt;
}
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
cib_delete(cib);
return rc;
}
pcmk__register_lib_messages(out);
pe__register_messages(out);
stonith__register_messages(out);
rc = pcmk__status(out, cib, pcmk__fence_history_full, pcmk_section_all,
show_opts, NULL, NULL, NULL, false, 0);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
cib_delete(cib);
return rc;
}
/*!
* \internal
* \brief Query and output the cluster status
*
* The operation is considered a success if we're able to get the \p pacemakerd
* state. If possible, we'll also try to connect to the fencer and CIB and
* output their respective status information.
*
* \param[in,out] out Output object
* \param[in,out] cib CIB connection
* \param[in] fence_history How much of the fencing history to output
* \param[in] show Group of \p pcmk_section_e flags
* \param[in] show_opts Group of \p pcmk_show_opt_e flags
* \param[in] only_node If a node name or tag, include only the
* matching node(s) (if any) in the output.
* If \p "*" or \p NULL, include all nodes
* in the output.
* \param[in] only_rsc If a resource ID or tag, include only the
* matching resource(s) (if any) in the
* output. If \p "*" or \p NULL, include all
* resources in the output.
* \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID
* \param[in] simple_output Whether to use a simple output format.
* Note: This is for use by \p crm_mon only
* and is planned to be deprecated.
* \param[in] timeout_ms How long to wait for a reply from the
* \p pacemakerd API. If 0,
* \p pcmk_ipc_dispatch_sync will be used.
* If positive, \p pcmk_ipc_dispatch_main
* will be used, and a new mainloop will be
* created for this purpose (freed before
* return).
*
* \return Standard Pacemaker return code
*/
int
pcmk__status(pcmk__output_t *out, cib_t *cib,
enum pcmk__fence_history fence_history, uint32_t show,
uint32_t show_opts, const char *only_node, const char *only_rsc,
const char *neg_location_prefix, bool simple_output,
unsigned int timeout_ms)
{
xmlNode *current_cib = NULL;
int rc = pcmk_rc_ok;
stonith_t *stonith = NULL;
enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid;
time_t last_updated = 0;
if (cib == NULL) {
return ENOTCONN;
}
if (cib->variant == cib_native) {
rc = pcmk__pacemakerd_status(out, crm_system_name, timeout_ms, false,
&pcmkd_state);
if (rc != pcmk_rc_ok) {
return rc;
}
last_updated = time(NULL);
switch (pcmkd_state) {
case pcmk_pacemakerd_state_running:
case pcmk_pacemakerd_state_shutting_down:
case pcmk_pacemakerd_state_remote:
/* Fencer and CIB may still be available while shutting down or
* running on a Pacemaker Remote node
*/
break;
default:
// Fencer and CIB are definitely unavailable
out->message(out, "pacemakerd-health",
NULL, pcmkd_state, NULL, last_updated);
return rc;
}
if (fence_history != pcmk__fence_history_none) {
stonith = fencing_connect();
}
}
rc = cib__signon_query(out, &cib, ¤t_cib);
if (rc != pcmk_rc_ok) {
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
// Invalid at this point means we didn't query the pcmkd state
out->message(out, "pacemakerd-health",
NULL, pcmkd_state, NULL, last_updated);
}
goto done;
}
rc = pcmk__output_cluster_status(out, stonith, cib, current_cib,
pcmkd_state, fence_history, show,
show_opts, only_node, only_rsc,
neg_location_prefix, simple_output);
if (rc != pcmk_rc_ok) {
out->err(out, "Error outputting status info from the fencer or CIB");
}
done:
stonith_api_delete(stonith);
free_xml(current_cib);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Output cluster status in Nagios Plugin format
*
* \param[in,out] out Output object
* \param[in] scheduler Scheduler data
*
* \return Standard Pacemaker return code
* \note This is for a deprecated crm_mon option and should be called only for
* that.
*/
int
pcmk__output_simple_status(pcmk__output_t *out,
const pcmk_scheduler_t *scheduler)
{
int nodes_online = 0;
int nodes_standby = 0;
int nodes_maint = 0;
GString *offline_nodes = NULL;
bool no_dc = false;
bool offline = false;
bool has_warnings = false;
if (scheduler->dc_node == NULL) {
has_warnings = true;
no_dc = true;
}
for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) {
pcmk_node_t *node = (pcmk_node_t *) iter->data;
if (node->details->standby && node->details->online) {
nodes_standby++;
} else if (node->details->maintenance && node->details->online) {
nodes_maint++;
} else if (node->details->online) {
nodes_online++;
} else {
pcmk__add_word(&offline_nodes, 1024, "offline node:");
pcmk__add_word(&offline_nodes, 0, pcmk__node_name(node));
has_warnings = true;
offline = true;
}
}
if (has_warnings) {
out->info(out, "CLUSTER WARN: %s%s%s",
no_dc ? "No DC" : "",
no_dc && offline ? ", " : "",
(offline? (const char *) offline_nodes->str : ""));
if (offline_nodes != NULL) {
g_string_free(offline_nodes, TRUE);
}
} else {
char *nodes_standby_s = NULL;
char *nodes_maint_s = NULL;
if (nodes_standby > 0) {
nodes_standby_s = crm_strdup_printf(", %d standby node%s",
nodes_standby,
pcmk__plural_s(nodes_standby));
}
if (nodes_maint > 0) {
nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
nodes_maint,
pcmk__plural_s(nodes_maint));
}
out->info(out, "CLUSTER OK: %d node%s online%s%s, "
"%d resource instance%s configured",
nodes_online, pcmk__plural_s(nodes_online),
nodes_standby_s != NULL ? nodes_standby_s : "",
nodes_maint_s != NULL ? nodes_maint_s : "",
scheduler->ninstances, pcmk__plural_s(scheduler->ninstances));
free(nodes_standby_s);
free(nodes_maint_s);
}
if (has_warnings) {
return pcmk_rc_error;
} else {
return pcmk_rc_ok;
}
/* coverity[leaked_storage] False positive */
}
diff --git a/lib/pacemaker/pcmk_verify.c b/lib/pacemaker/pcmk_verify.c
index f124e2da9d..e51f844e3f 100644
--- a/lib/pacemaker/pcmk_verify.c
+++ b/lib/pacemaker/pcmk_verify.c
@@ -1,151 +1,152 @@
/*
* Copyright 2023-2024 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 "libpacemaker_private.h"
int
pcmk__parse_cib(pcmk__output_t *out, const char *cib_source, xmlNodePtr *cib_object)
{
// @COMPAT Take an enum for cib_source instead of trying to figure it out?
const char *first = cib_source;
if (cib_source == NULL) {
crm_info("Reading XML from: live cluster");
return cib__signon_query(out, NULL, cib_object);
}
while (isspace(*first)) {
first++;
}
if (*first == '<') {
*cib_object = pcmk__xml_parse(cib_source);
} else {
*cib_object = pcmk__xml_read(cib_source);
}
return (*cib_object == NULL)? ENODATA : pcmk_rc_ok;
}
int
pcmk__verify(pcmk_scheduler_t *scheduler, pcmk__output_t *out, xmlNode *cib_object)
{
int rc = pcmk_rc_ok;
xmlNode *status = NULL;
xmlNode *cib_object_copy = NULL;
if (!pcmk__xe_is(cib_object, PCMK_XE_CIB)) {
rc = EBADMSG;
out->err(out, "This tool can only check complete configurations (i.e. those starting with ).");
goto verify_done;
}
status = pcmk_find_cib_element(cib_object, PCMK_XE_STATUS);
if (status == NULL) {
pcmk__xe_create(cib_object, PCMK_XE_STATUS);
}
- if (pcmk__validate_xml(cib_object, NULL, (xmlRelaxNGValidityErrorFunc) out->err, out) == FALSE) {
+ if (!pcmk__validate_xml(cib_object, NULL,
+ (xmlRelaxNGValidityErrorFunc) out->err, out)) {
crm_config_error = TRUE;
rc = pcmk_rc_schema_validation;
goto verify_done;
- } else if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
+ } else if (!pcmk__update_configured_schema(&cib_object, false)) {
crm_config_error = TRUE;
out->err(out, "The cluster will NOT be able to use this configuration.\n"
"Please manually update the configuration to conform to the %s syntax.",
- xml_latest_schema());
+ pcmk__highest_schema_name());
rc = pcmk_rc_schema_validation;
goto verify_done;
}
/* Process the configuration to set crm_config_error/crm_config_warning.
*
* @TODO Some parts of the configuration are unpacked only when needed (for
* example, action configuration), so we aren't necessarily checking those.
*/
if (cib_object != NULL) {
unsigned long long flags = pcmk_sched_no_counts|pcmk_sched_no_compat;
if (status == NULL) {
// No status available, so do minimal checks
flags |= pcmk_sched_validate_only;
}
cib_object_copy = pcmk__xml_copy(NULL, cib_object);
/* The scheduler takes ownership of the XML object and potentially
* frees it later. We want the caller of pcmk__verify to retain
* ownership of the passed-in XML object, hence we pass in a copy
* to the scheduler.
*/
pcmk__schedule_actions(cib_object_copy, flags, scheduler);
}
verify_done:
if (crm_config_error) {
rc = pcmk_rc_schema_validation;
pcmk__config_err("CIB did not pass schema validation");
} else if (crm_config_warning) {
rc = pcmk_rc_schema_validation;
}
return rc;
}
int
pcmk_verify(xmlNodePtr *xml, const char *cib_source)
{
pcmk_scheduler_t *scheduler = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
xmlNode *cib_object = NULL;
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
pe__register_messages(out);
pcmk__register_lib_messages(out);
rc = pcmk__parse_cib(out, cib_source, &cib_object);
if (rc != pcmk_rc_ok) {
out->err(out, "Couldn't parse input");
goto done;
}
scheduler = pe_new_working_set();
if (scheduler == NULL) {
rc = errno;
out->err(out, "Couldn't allocate scheduler data: %s", pcmk_rc_str(rc));
goto done;
}
scheduler->priv = out;
rc = pcmk__verify(scheduler, out, cib_object);
done:
pe_free_working_set(scheduler);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
free_xml(cib_object);
return rc;
}
diff --git a/tools/cibadmin.c b/tools/cibadmin.c
index 1e7dcbfdf7..b6767da6b1 100644
--- a/tools/cibadmin.c
+++ b/tools/cibadmin.c
@@ -1,953 +1,954 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define SUMMARY "query and edit the Pacemaker configuration"
#define INDENT " "
enum cibadmin_section_type {
cibadmin_section_all = 0,
cibadmin_section_scope,
cibadmin_section_xpath,
};
static int request_id = 0;
static cib_t *the_cib = NULL;
static GMainLoop *mainloop = NULL;
static crm_exit_t exit_code = CRM_EX_OK;
static struct {
const char *cib_action;
int cmd_options;
enum cibadmin_section_type section_type;
char *cib_section;
char *validate_with;
gint message_timeout_sec;
enum pcmk__acl_render_how acl_render_mode;
gchar *cib_user;
gchar *dest_node;
gchar *input_file;
gchar *input_xml;
gboolean input_stdin;
bool delete_all;
gboolean allow_create;
gboolean force;
gboolean get_node_path;
gboolean local;
gboolean no_children;
gboolean sync_call;
/* @COMPAT: For "-!" version option. Not advertised nor marked as
* deprecated, but accepted.
*/
gboolean extended_version;
//! \deprecated
gboolean no_bcast;
} options;
int do_init(void);
static int do_work(xmlNode *input, xmlNode **output);
void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
void *user_data);
static void
print_xml_output(xmlNode * xml)
{
if (!xml) {
return;
} else if (xml->type != XML_ELEMENT_NODE) {
return;
}
if (pcmk_is_set(options.cmd_options, cib_xpath_address)) {
const char *id = crm_element_value(xml, PCMK_XA_ID);
if (pcmk__xe_is(xml, PCMK__XE_XPATH_QUERY)) {
xmlNode *child = NULL;
for (child = xml->children; child; child = child->next) {
print_xml_output(child);
}
} else if (id) {
printf("%s\n", id);
}
} else {
GString *buf = g_string_sized_new(1024);
pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buf, 0);
fprintf(stdout, "%s", buf->str);
g_string_free(buf, TRUE);
}
}
// Upgrade requested but already at latest schema
static void
report_schema_unchanged(void)
{
const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged);
crm_info("Upgrade unnecessary: %s\n", err);
printf("Upgrade unnecessary: %s\n", err);
exit_code = CRM_EX_OK;
}
/*!
* \internal
* \brief Check whether the current CIB action is dangerous
* \return true if \p options.cib_action is dangerous, or false otherwise
*/
static inline bool
cib_action_is_dangerous(void)
{
return options.no_bcast || options.delete_all
|| pcmk__str_any_of(options.cib_action,
PCMK__CIB_REQUEST_UPGRADE,
PCMK__CIB_REQUEST_ERASE,
NULL);
}
/*!
* \internal
* \brief Determine whether the given CIB scope is valid for \p cibadmin
*
* \param[in] scope Scope to validate
*
* \return true if \p scope is valid, or false otherwise
* \note An invalid scope applies the operation to the entire CIB.
*/
static inline bool
scope_is_valid(const char *scope)
{
return pcmk__str_any_of(scope,
PCMK_XE_CONFIGURATION,
PCMK_XE_NODES,
PCMK_XE_RESOURCES,
PCMK_XE_CONSTRAINTS,
PCMK_XE_CRM_CONFIG,
PCMK_XE_RSC_DEFAULTS,
PCMK_XE_OP_DEFAULTS,
PCMK_XE_ACLS,
PCMK_XE_FENCING_TOPOLOGY,
PCMK_XE_TAGS,
PCMK_XE_ALERTS,
PCMK_XE_STATUS,
NULL);
}
static gboolean
command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
GError **error)
{
options.delete_all = false;
if (pcmk__str_any_of(option_name, "-u", "--upgrade", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_UPGRADE;
} else if (pcmk__str_any_of(option_name, "-Q", "--query", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_QUERY;
} else if (pcmk__str_any_of(option_name, "-E", "--erase", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_ERASE;
} else if (pcmk__str_any_of(option_name, "-B", "--bump", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_BUMP;
} else if (pcmk__str_any_of(option_name, "-C", "--create", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_CREATE;
} else if (pcmk__str_any_of(option_name, "-M", "--modify", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_MODIFY;
} else if (pcmk__str_any_of(option_name, "-P", "--patch", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_APPLY_PATCH;
} else if (pcmk__str_any_of(option_name, "-R", "--replace", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_REPLACE;
} else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_DELETE;
} else if (pcmk__str_any_of(option_name, "-d", "--delete-all", NULL)) {
options.cib_action = PCMK__CIB_REQUEST_DELETE;
options.delete_all = true;
} else if (pcmk__str_any_of(option_name, "-a", "--empty", NULL)) {
options.cib_action = "empty";
pcmk__str_update(&options.validate_with, optarg);
} else if (pcmk__str_any_of(option_name, "-5", "--md5-sum", NULL)) {
options.cib_action = "md5-sum";
} else if (pcmk__str_any_of(option_name, "-6", "--md5-sum-versioned",
NULL)) {
options.cib_action = "md5-sum-versioned";
} else {
// Should be impossible
return FALSE;
}
return TRUE;
}
static gboolean
show_access_cb(const gchar *option_name, const gchar *optarg, gpointer data,
GError **error)
{
if (pcmk__str_eq(optarg, "auto", pcmk__str_null_matches)) {
options.acl_render_mode = pcmk__acl_render_default;
} else if (g_strcmp0(optarg, "namespace") == 0) {
options.acl_render_mode = pcmk__acl_render_namespace;
} else if (g_strcmp0(optarg, "text") == 0) {
options.acl_render_mode = pcmk__acl_render_text;
} else if (g_strcmp0(optarg, "color") == 0) {
options.acl_render_mode = pcmk__acl_render_color;
} else {
g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
"Invalid value '%s' for option '%s'",
optarg, option_name);
return FALSE;
}
return TRUE;
}
static gboolean
section_cb(const gchar *option_name, const gchar *optarg, gpointer data,
GError **error)
{
if (pcmk__str_any_of(option_name, "-o", "--scope", NULL)) {
options.section_type = cibadmin_section_scope;
} else if (pcmk__str_any_of(option_name, "-A", "--xpath", NULL)) {
options.section_type = cibadmin_section_xpath;
} else {
// Should be impossible
return FALSE;
}
pcmk__str_update(&options.cib_section, optarg);
return TRUE;
}
static GOptionEntry command_entries[] = {
{ "upgrade", 'u', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Upgrade the configuration to the latest syntax", NULL },
{ "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Query the contents of the CIB", NULL },
{ "erase", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Erase the contents of the whole CIB", NULL },
{ "bump", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Increase the CIB's epoch value by 1", NULL },
{ "create", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Create an object in the CIB (will fail if object already exists)",
NULL },
{ "modify", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Find object somewhere in CIB's XML tree and update it (fails if object "
"does not exist unless -c is also specified)",
NULL },
{ "patch", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Supply an update in the form of an XML diff (see crm_diff(8))", NULL },
{ "replace", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Recursively replace an object in the CIB", NULL },
{ "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Delete first object matching supplied criteria (for example, "
"<" PCMK_XE_OP " " PCMK_XA_ID "=\"rsc1_op1\" "
PCMK_XA_NAME "=\"monitor\"/>).\n"
INDENT "The XML element name and all attributes must match in order for "
"the element to be deleted.",
NULL },
{ "delete-all", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
command_cb,
"When used with --xpath, remove all matching objects in the "
"configuration instead of just the first one",
NULL },
{ "empty", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
command_cb,
"Output an empty CIB. Accepts an optional schema name argument to use as "
"the " PCMK_XA_VALIDATE_WITH " value.\n"
INDENT "If no schema is given, the latest will be used.",
"[schema]" },
{ "md5-sum", '5', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
"Calculate the on-disk CIB digest", NULL },
{ "md5-sum-versioned", '6', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
command_cb, "Calculate an on-the-wire versioned CIB digest", NULL },
{ NULL }
};
static GOptionEntry data_entries[] = {
/* @COMPAT: These arguments should be last-wins. We can have an enum option
* that stores the input type, along with a single string option that stores
* the XML string for --xml-text, filename for --xml-file, or NULL for
* --xml-pipe.
*/
{ "xml-text", 'X', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
&options.input_xml, "Retrieve XML from the supplied string", "value" },
{ "xml-file", 'x', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME,
&options.input_file, "Retrieve XML from the named file", "value" },
{ "xml-pipe", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&options.input_stdin, "Retrieve XML from stdin", NULL },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
"Force the action to be performed", NULL },
{ "timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT,
&options.message_timeout_sec,
"Time (in seconds) to wait before declaring the operation failed",
"value" },
{ "user", 'U', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.cib_user,
"Run the command with permissions of the named user (valid only for the "
"root and " CRM_DAEMON_USER " accounts)", "value" },
{ "sync-call", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&options.sync_call, "Wait for call to complete before returning", NULL },
{ "local", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.local,
"Command takes effect locally (should be used only for queries)", NULL },
{ "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
"Limit scope of operation to specific section of CIB\n"
INDENT "Valid values: " PCMK_XE_CONFIGURATION ", " PCMK_XE_NODES
", " PCMK_XE_RESOURCES ", " PCMK_XE_CONSTRAINTS
", " PCMK_XE_CRM_CONFIG ", " PCMK_XE_RSC_DEFAULTS ",\n"
INDENT " " PCMK_XE_OP_DEFAULTS ", " PCMK_XE_ACLS
", " PCMK_XE_FENCING_TOPOLOGY ", " PCMK_XE_TAGS ", " PCMK_XE_ALERTS
", " PCMK_XE_STATUS "\n"
INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
"appear takes effect",
"value" },
{ "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
"A valid XPath to use instead of --scope/-o\n"
INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
"appear takes effect",
"value" },
{ "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&options.get_node_path,
"When performing XPath queries, return paths of any matches found\n"
INDENT "(for example, "
"\"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
"/" PCMK_XE_RESOURCES "/" PCMK_XE_CLONE
"[@" PCMK_XA_ID "='dummy-clone']"
"/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='dummy']\")",
NULL },
{ "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
show_access_cb,
"Whether to use syntax highlighting for ACLs (with -Q/--query and "
"-U/--user)\n"
INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, "
"default for non-terminal),\n"
INDENT " 'namespace', or 'auto' (use default value)\n"
INDENT "Default value: 'auto'",
"[value]" },
{ "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&options.allow_create,
"(Advanced) Allow target of --modify/-M to be created if it does not "
"exist",
NULL },
{ "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&options.no_children,
"(Advanced) When querying an object, do not include its children in the "
"result",
NULL },
{ "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node,
"(Advanced) Send command to the specified host", "value" },
// @COMPAT: Deprecated
{ "no-bcast", 'b', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
&options.no_bcast, "deprecated", NULL },
// @COMPAT: Deprecated
{ "host", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
&options.dest_node, "deprecated", NULL },
{ NULL }
};
static GOptionContext *
build_arg_context(pcmk__common_args_t *args)
{
const char *desc = NULL;
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
// @COMPAT: Deprecated
{ "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
&options.extended_version, "deprecated", NULL },
{ NULL }
};
desc = "Examples:\n\n"
"Query the configuration from the local node:\n\n"
"\t# cibadmin --query --local\n\n"
"Query just the cluster options configuration:\n\n"
"\t# cibadmin --query --scope " PCMK_XE_CRM_CONFIG "\n\n"
"Query all '" PCMK_META_TARGET_ROLE "' settings:\n\n"
"\t# cibadmin --query --xpath "
"\"//" PCMK_XE_NVPAIR
"[@" PCMK_XA_NAME "='" PCMK_META_TARGET_ROLE"']\"\n\n"
"Remove all '" PCMK_META_IS_MANAGED "' settings:\n\n"
"\t# cibadmin --delete-all --xpath "
"\"//" PCMK_XE_NVPAIR
"[@" PCMK_XA_NAME "='" PCMK_META_IS_MANAGED "']\"\n\n"
"Remove the resource named 'old':\n\n"
"\t# cibadmin --delete --xml-text "
"'<" PCMK_XE_PRIMITIVE " " PCMK_XA_ID "=\"old\"/>'\n\n"
"Remove all resources from the configuration:\n\n"
"\t# cibadmin --replace --scope " PCMK_XE_RESOURCES
" --xml-text '<" PCMK_XE_RESOURCES "/>'\n\n"
"Replace complete configuration with contents of "
"$HOME/pacemaker.xml:\n\n"
"\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n"
"Replace " PCMK_XE_CONSTRAINTS " section of configuration with "
"contents of $HOME/constraints.xml:\n\n"
"\t# cibadmin --replace --scope " PCMK_XE_CONSTRAINTS
" --xml-file $HOME/constraints.xml\n\n"
"Increase configuration version to prevent old configurations from "
"being loaded accidentally:\n\n"
"\t# cibadmin --modify --xml-text "
"'<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH
"=\"" PCMK_XA_ADMIN_EPOCH "++\"/>'\n\n"
"Edit the configuration with your favorite $EDITOR:\n\n"
"\t# cibadmin --query > $HOME/local.xml\n\n"
"\t# $EDITOR $HOME/local.xml\n\n"
"\t# cibadmin --replace --xml-file $HOME/local.xml\n\n"
"Assuming terminal, render configuration in color (green for "
"writable, blue for readable, red for\n"
"denied) to visualize permissions for user tony:\n\n"
"\t# cibadmin --show-access=color --query --user tony | less -r\n\n"
"SEE ALSO:\n"
" crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n";
context = pcmk__build_arg_context(args, NULL, NULL, "");
g_option_context_set_description(context, desc);
pcmk__add_main_args(context, extra_prog_entries);
pcmk__add_arg_group(context, "commands", "Commands:", "Show command help",
command_entries);
pcmk__add_arg_group(context, "data", "Data:", "Show data help",
data_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
int rc = pcmk_rc_ok;
const char *source = NULL;
xmlNode *output = NULL;
xmlNode *input = NULL;
gchar *acl_cred = NULL;
GError *error = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx");
GOptionContext *context = build_arg_context(args);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
if (g_strv_length(processed_args) > 1) {
gchar *help = g_option_context_get_help(context, TRUE, NULL);
GString *extra = g_string_sized_new(128);
for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
if (extra->len > 0) {
g_string_append_c(extra, ' ');
}
g_string_append(extra, processed_args[lpc]);
}
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"non-option ARGV-elements: %s\n\n%s", extra->str, help);
g_free(help);
g_string_free(extra, TRUE);
goto done;
}
if (args->version || options.extended_version) {
g_strfreev(processed_args);
pcmk__free_arg_context(context);
/* FIXME: When cibadmin is converted to use formatted output, this can
* be replaced by out->version with the appropriate boolean flag.
*
* options.extended_version is deprecated and will be removed in a
* future release.
*/
pcmk__cli_help(options.extended_version? '!' : 'v');
}
/* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like
*
* (func@file:line) error: CIB failures
*
* In cibadmin we explicitly output the XML portion without the prefixes. So
* we default to LOG_CRIT.
*/
pcmk__cli_init_logging("cibadmin", 0);
set_crm_log_level(LOG_CRIT);
if (args->verbosity > 0) {
cib__set_call_options(options.cmd_options, crm_system_name,
cib_verbose);
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
}
if (options.cib_action == NULL) {
// @COMPAT: Create a default command if other tools have one
gchar *help = g_option_context_get_help(context, TRUE, NULL);
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Must specify a command option\n\n%s", help);
g_free(help);
goto done;
}
if (strcmp(options.cib_action, "empty") == 0) {
// Output an empty CIB
GString *buf = g_string_sized_new(1024);
output = createEmptyCib(1);
crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
pcmk__xml_string(output, pcmk__xml_fmt_pretty, buf, 0);
fprintf(stdout, "%s", buf->str);
g_string_free(buf, TRUE);
goto done;
}
if (cib_action_is_dangerous() && !options.force) {
exit_code = CRM_EX_UNSAFE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"The supplied command is considered dangerous. To prevent "
"accidental destruction of the cluster, the --force flag "
"is required in order to proceed.");
goto done;
}
if (options.message_timeout_sec < 1) {
// Set default timeout
options.message_timeout_sec = 30;
}
if (options.section_type == cibadmin_section_xpath) {
// Enable getting section by XPath
cib__set_call_options(options.cmd_options, crm_system_name,
cib_xpath);
} else if (options.section_type == cibadmin_section_scope) {
if (!scope_is_valid(options.cib_section)) {
// @COMPAT: Consider requiring --force to proceed
fprintf(stderr,
"Invalid value '%s' for '--scope'. Operation will apply "
"to the entire CIB.\n", options.cib_section);
}
}
if (options.allow_create) {
// Allow target of --modify/-M to be created if it does not exist
cib__set_call_options(options.cmd_options, crm_system_name,
cib_can_create);
}
if (options.delete_all) {
// With cibadmin_section_xpath, remove all matching objects
cib__set_call_options(options.cmd_options, crm_system_name,
cib_multiple);
}
if (options.get_node_path) {
/* Enable getting node path of XPath query matches.
* Meaningful only if options.section_type == cibadmin_section_xpath.
*/
cib__set_call_options(options.cmd_options, crm_system_name,
cib_xpath_address);
}
if (options.local) {
// Configure command to take effect only locally
cib__set_call_options(options.cmd_options, crm_system_name,
cib_scope_local);
}
// @COMPAT: Deprecated option
if (options.no_bcast) {
// Configure command to take effect only locally and not to broadcast
cib__set_call_options(options.cmd_options, crm_system_name,
cib_inhibit_bcast|cib_scope_local);
}
if (options.no_children) {
// When querying an object, don't include its children in the result
cib__set_call_options(options.cmd_options, crm_system_name,
cib_no_children);
}
if (options.sync_call
|| (options.acl_render_mode != pcmk__acl_render_none)) {
/* Wait for call to complete before returning.
*
* The ACL render modes work only with sync calls due to differences in
* output handling between sync/async. It shouldn't matter to the user
* whether the call is synchronous; for a CIB query, we have to wait for
* the result in order to display it in any case.
*/
cib__set_call_options(options.cmd_options, crm_system_name,
cib_sync_call);
}
if (options.input_file != NULL) {
input = pcmk__xml_read(options.input_file);
source = options.input_file;
} else if (options.input_xml != NULL) {
input = pcmk__xml_parse(options.input_xml);
source = "input string";
} else if (options.input_stdin) {
input = pcmk__xml_read(NULL);
source = "STDIN";
} else if (options.acl_render_mode != pcmk__acl_render_none) {
char *username = pcmk__uid2username(geteuid());
bool required = pcmk_acl_required(username);
free(username);
if (required) {
if (options.force) {
fprintf(stderr, "The supplied command can provide skewed"
" result since it is run under user that also"
" gets guarded per ACLs on their own right."
" Continuing since --force flag was"
" provided.\n");
} else {
exit_code = CRM_EX_UNSAFE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"The supplied command can provide skewed result "
"since it is run under user that also gets guarded "
"per ACLs in their own right. To accept the risk "
"of such a possible distortion (without even "
"knowing it at this time), use the --force flag.");
goto done;
}
}
if (options.cib_user == NULL) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"The supplied command requires -U user specified.");
goto done;
}
/* We already stopped/warned ACL-controlled users about consequences.
*
* Note: acl_cred takes ownership of options.cib_user here.
* options.cib_user is set to NULL so that the CIB is obtained as the
* user running the cibadmin command. The CIB must be obtained as a user
* with full permissions in order to show the CIB correctly annotated
* for the options.cib_user's permissions.
*/
acl_cred = options.cib_user;
options.cib_user = NULL;
}
if (input != NULL) {
crm_log_xml_debug(input, "[admin input]");
} else if (source != NULL) {
exit_code = CRM_EX_CONFIG;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Couldn't parse input from %s.", source);
goto done;
}
if (pcmk__str_eq(options.cib_action, "md5-sum", pcmk__str_casei)) {
char *digest = NULL;
if (input == NULL) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Please supply XML to process with -X, -x, or -p");
goto done;
}
digest = calculate_on_disk_digest(input);
fprintf(stderr, "Digest: ");
fprintf(stdout, "%s\n", pcmk__s(digest, ""));
free(digest);
goto done;
} else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) {
char *digest = NULL;
const char *version = NULL;
if (input == NULL) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Please supply XML to process with -X, -x, or -p");
goto done;
}
version = crm_element_value(input, PCMK_XA_CRM_FEATURE_SET);
digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version);
fprintf(stderr, "Versioned (%s) digest: ", version);
fprintf(stdout, "%s\n", pcmk__s(digest, ""));
free(digest);
goto done;
}
rc = do_init();
if (rc != pcmk_ok) {
rc = pcmk_legacy2rc(rc);
exit_code = pcmk_rc2exitc(rc);
crm_err("Init failed, could not perform requested operations: %s",
pcmk_rc_str(rc));
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Init failed, could not perform requested operations: %s",
pcmk_rc_str(rc));
goto done;
}
rc = do_work(input, &output);
if (rc > 0) {
/* wait for the reply by creating a mainloop and running it until
* the callbacks are invoked...
*/
request_id = rc;
the_cib->cmds->register_callback(the_cib, request_id,
options.message_timeout_sec, FALSE,
NULL, "cibadmin_op_callback",
cibadmin_op_callback);
mainloop = g_main_loop_new(NULL, FALSE);
crm_trace("%s waiting for reply from the local CIB", crm_system_name);
crm_info("Starting mainloop");
g_main_loop_run(mainloop);
} else if ((rc == -pcmk_err_schema_unchanged)
&& (strcmp(options.cib_action,
PCMK__CIB_REQUEST_UPGRADE) == 0)) {
report_schema_unchanged();
} else if (rc < 0) {
rc = pcmk_legacy2rc(rc);
crm_err("Call failed: %s", pcmk_rc_str(rc));
fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
if (rc == pcmk_rc_schema_validation) {
if (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0) {
xmlNode *obj = NULL;
if (the_cib->cmds->query(the_cib, NULL, &obj,
options.cmd_options) == pcmk_ok) {
pcmk__update_schema(&obj, NULL, true, false);
}
free_xml(obj);
} else if (output) {
- validate_xml_verbose(output);
+ // Show validation errors to stderr
+ pcmk__validate_xml(output, NULL, NULL, NULL);
}
}
exit_code = pcmk_rc2exitc(rc);
}
if ((output != NULL)
&& (options.acl_render_mode != pcmk__acl_render_none)) {
xmlDoc *acl_evaled_doc;
rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
if (rc == pcmk_rc_ok) {
xmlChar *rendered = NULL;
rc = pcmk__acl_evaled_render(acl_evaled_doc,
options.acl_render_mode, &rendered);
if (rc != pcmk_rc_ok) {
exit_code = CRM_EX_CONFIG;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Could not render evaluated access: %s",
pcmk_rc_str(rc));
goto done;
}
printf("%s\n", (char *) rendered);
free(rendered);
} else {
exit_code = CRM_EX_CONFIG;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Could not evaluate access per request (%s, error: %s)",
acl_cred, pcmk_rc_str(rc));
goto done;
}
} else if (output != NULL) {
print_xml_output(output);
}
crm_trace("%s exiting normally", crm_system_name);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
g_free(options.cib_user);
g_free(options.dest_node);
g_free(options.input_file);
g_free(options.input_xml);
free(options.cib_section);
free(options.validate_with);
g_free(acl_cred);
free_xml(input);
free_xml(output);
rc = cib__clean_up_connection(&the_cib);
if (exit_code == CRM_EX_OK) {
exit_code = pcmk_rc2exitc(rc);
}
pcmk__output_and_clear_error(&error, NULL);
crm_exit(exit_code);
}
static int
do_work(xmlNode *input, xmlNode **output)
{
/* construct the request */
the_cib->call_timeout = options.message_timeout_sec;
if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0)
&& pcmk__xe_is(input, PCMK_XE_CIB)) {
xmlNode *status = pcmk_find_cib_element(input, PCMK_XE_STATUS);
if (status == NULL) {
pcmk__xe_create(input, PCMK_XE_STATUS);
}
}
crm_trace("Passing \"%s\" to variant_op...", options.cib_action);
return cib_internal_op(the_cib, options.cib_action, options.dest_node,
options.cib_section, input, output,
options.cmd_options, options.cib_user);
}
int
do_init(void)
{
int rc = pcmk_ok;
the_cib = cib_new();
rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
if (rc != pcmk_ok) {
crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
fprintf(stderr, "Could not connect to the CIB: %s\n",
pcmk_strerror(rc));
}
return rc;
}
void
cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
rc = pcmk_legacy2rc(rc);
exit_code = pcmk_rc2exitc(rc);
if (rc == pcmk_rc_schema_unchanged) {
report_schema_unchanged();
} else if (rc != pcmk_rc_ok) {
crm_warn("Call %s failed: %s " CRM_XS " rc=%d",
options.cib_action, pcmk_rc_str(rc), rc);
fprintf(stderr, "Call %s failed: %s\n",
options.cib_action, pcmk_rc_str(rc));
print_xml_output(output);
} else if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_QUERY) == 0)
&& (output == NULL)) {
crm_err("Query returned no output");
crm_log_xml_err(msg, "no output");
} else if (output == NULL) {
crm_info("Call passed");
} else {
crm_info("Call passed");
print_xml_output(output);
}
if (call_id == request_id) {
g_main_loop_quit(mainloop);
} else {
crm_info("Message was not the response we were looking for (%d vs. %d)",
call_id, request_id);
}
}
diff --git a/tools/crm_resource_runtime.c b/tools/crm_resource_runtime.c
index e92eb617db..eed2c601e4 100644
--- a/tools/crm_resource_runtime.c
+++ b/tools/crm_resource_runtime.c
@@ -1,2282 +1,2283 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static GList *
build_node_info_list(const pcmk_resource_t *rsc)
{
GList *retval = NULL;
for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
for (const GList *iter2 = child->running_on;
iter2 != NULL; iter2 = iter2->next) {
const pcmk_node_t *node = (const pcmk_node_t *) iter2->data;
node_info_t *ni = pcmk__assert_alloc(1, sizeof(node_info_t));
ni->node_name = node->details->uname;
ni->promoted = pcmk_is_set(rsc->flags, pcmk_rsc_promotable) &&
child->fns->state(child, TRUE) == pcmk_role_promoted;
retval = g_list_prepend(retval, ni);
}
}
return retval;
}
GList *
cli_resource_search(pcmk_resource_t *rsc, const char *requested_name,
pcmk_scheduler_t *scheduler)
{
GList *retval = NULL;
const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
if (pcmk__is_clone(rsc)) {
retval = build_node_info_list(rsc);
/* The anonymous clone children's common ID is supplied */
} else if (pcmk__is_clone(parent)
&& !pcmk_is_set(rsc->flags, pcmk_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)) {
retval = build_node_info_list(parent);
} else if (rsc->running_on != NULL) {
for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
pcmk_node_t *node = (pcmk_node_t *) iter->data;
node_info_t *ni = pcmk__assert_alloc(1, sizeof(node_info_t));
ni->node_name = node->details->uname;
ni->promoted = (rsc->fns->state(rsc, TRUE) == pcmk_role_promoted);
retval = g_list_prepend(retval, ni);
}
}
return retval;
}
// \return Standard Pacemaker return code
static int
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 rc = pcmk_rc_ok;
xmlNode *xml_search = NULL;
GString *xpath = NULL;
const char *xpath_base = NULL;
if(value) {
*value = NULL;
}
if(the_cib == NULL) {
return ENOTCONN;
}
xpath_base = pcmk_cib_xpath_for(PCMK_XE_RESOURCES);
if (xpath_base == NULL) {
crm_err(PCMK_XE_RESOURCES " CIB element not known (bug?)");
return ENOMSG;
}
xpath = g_string_sized_new(1024);
pcmk__g_strcat(xpath,
xpath_base, "//*[@" PCMK_XA_ID "=\"", rsc, "\"]", NULL);
if (attr_set_type != NULL) {
pcmk__g_strcat(xpath, "/", attr_set_type, NULL);
if (set_name != NULL) {
pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "=\"", set_name, "\"]",
NULL);
}
}
g_string_append(xpath, "//" PCMK_XE_NVPAIR);
if (attr_id != NULL && attr_name!= NULL) {
pcmk__g_strcat(xpath,
"[@" PCMK_XA_ID "='", attr_id, "' "
"and @" PCMK_XA_NAME "='", attr_name, "']", NULL);
} else if (attr_id != NULL) {
pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", attr_id, "']", NULL);
} else if (attr_name != NULL) {
pcmk__g_strcat(xpath, "[@" PCMK_XA_NAME "='", attr_name, "']", NULL);
}
rc = the_cib->cmds->query(the_cib, (const char *) xpath->str, &xml_search,
cib_sync_call | cib_scope_local | cib_xpath);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
goto done;
}
crm_log_xml_debug(xml_search, "Match");
if (xml_search->children != NULL) {
rc = ENOTUNIQ;
pcmk__warn_multiple_name_matches(out, xml_search, attr_name);
out->spacer(out);
} else if(value) {
pcmk__str_update(value, crm_element_value(xml_search, attr));
}
done:
g_string_free(xpath, TRUE);
free_xml(xml_search);
return rc;
}
/* PRIVATE. Use the find_matching_attr_resources instead. */
static void
find_matching_attr_resources_recursive(pcmk__output_t *out,
GList /* */ **result,
pcmk_resource_t *rsc, const char * attr_set,
const char * attr_set_type, const char * attr_id,
const char * attr_name, cib_t * cib, int depth)
{
int rc = pcmk_rc_ok;
char *lookup_id = clone_strip(rsc->id);
/* visit the children */
for(GList *gIter = rsc->children; gIter; gIter = gIter->next) {
find_matching_attr_resources_recursive(out, result,
(pcmk_resource_t *) gIter->data,
attr_set, attr_set_type, attr_id,
attr_name, cib, depth+1);
/* do it only once for clones */
if (rsc->variant == pcmk_rsc_variant_clone) {
break;
}
}
rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, NULL);
/* 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(lookup_id);
}
/* The result is a linearized pre-ordered tree of resources. */
static GList/**/ *
find_matching_attr_resources(pcmk__output_t *out, pcmk_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)
{
int rc = pcmk_rc_ok;
char *lookup_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 != NULL)
&& (rsc->parent->variant == pcmk_rsc_variant_clone)) {
int rc = find_resource_attr(out, cib, PCMK_XA_ID, rsc_id, attr_set_type,
attr_set, attr_id, attr_name, NULL);
if(rc != pcmk_rc_ok) {
rsc = rsc->parent;
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 != NULL)
&& (rsc->variant == pcmk_rsc_variant_clone)) {
pcmk_resource_t *child = rsc->children->data;
if (child->variant == pcmk_rsc_variant_primitive) {
lookup_id = clone_strip(child->id); /* Could be a cloned group! */
rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id,
attr_set_type, attr_set, attr_id, attr_name,
NULL);
if(rc == pcmk_rc_ok) {
rsc = child;
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(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(out, &result, rsc, attr_set,
attr_set_type, attr_id, attr_name,
cib, 0);
return result;
}
// \return Standard Pacemaker return code
int
cli_resource_update_attribute(pcmk_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, gboolean force)
{
pcmk__output_t *out = rsc->cluster->priv;
int rc = pcmk_rc_ok;
char *found_attr_id = NULL;
GList/**/ *resources = NULL;
const char *top_id = pe__const_top_resource(rsc, false)->id;
if ((attr_id == NULL) && !force) {
find_resource_attr(out, cib, PCMK_XA_ID, top_id, NULL, NULL, NULL,
attr_name, NULL);
}
if (pcmk__str_eq(attr_set_type, PCMK_XE_INSTANCE_ATTRIBUTES,
pcmk__str_casei)) {
if (!force) {
rc = find_resource_attr(out, cib, PCMK_XA_ID, top_id,
PCMK_XE_META_ATTRIBUTES, attr_set, attr_id,
attr_name, &found_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)",
top_id, attr_name, found_attr_id);
out->err(out,
" Delete '%s' first or use the force option "
"to override", found_attr_id);
}
free(found_attr_id);
if (rc == pcmk_rc_ok) {
return ENOTUNIQ;
}
}
resources = g_list_append(resources, rsc);
} else if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
crm_xml_add(rsc->xml, attr_name, attr_value);
CRM_ASSERT(cib != NULL);
rc = cib->cmds->replace(cib, PCMK_XE_RESOURCES, rsc->xml, cib_options);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
out->info(out, "Set attribute: " PCMK_XA_NAME "=%s value=%s",
attr_name, attr_value);
}
return rc;
} else {
resources = find_matching_attr_resources(out, rsc, requested_name,
attr_set, attr_set_type,
attr_id, attr_name, cib,
"update", force);
}
/* If the user specified attr_set or attr_id, the intent is to modify a
* single resource, which will be the last item in the list.
*/
if ((attr_set != NULL) || (attr_id != NULL)) {
GList *last = g_list_last(resources);
resources = g_list_remove_link(resources, last);
g_list_free(resources);
resources = last;
}
for (GList *iter = resources; iter != NULL; iter = iter->next) {
char *lookup_id = NULL;
char *local_attr_set = NULL;
const char *rsc_attr_id = attr_id;
const char *rsc_attr_set = attr_set;
xmlNode *xml_top = NULL;
xmlNode *xml_obj = NULL;
found_attr_id = NULL;
rsc = (pcmk_resource_t *) iter->data;
lookup_id = clone_strip(rsc->id); /* Could be a cloned group! */
rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, &found_attr_id);
switch (rc) {
case pcmk_rc_ok:
crm_debug("Found a match for " PCMK_XA_NAME "='%s': "
PCMK_XA_ID "='%s'", attr_name, found_attr_id);
rsc_attr_id = found_attr_id;
break;
case ENXIO:
if (rsc_attr_set == NULL) {
local_attr_set = crm_strdup_printf("%s-%s", lookup_id,
attr_set_type);
rsc_attr_set = local_attr_set;
}
if (rsc_attr_id == NULL) {
found_attr_id = crm_strdup_printf("%s-%s",
rsc_attr_set, attr_name);
rsc_attr_id = found_attr_id;
}
xml_top = pcmk__xe_create(NULL, (const char *) rsc->xml->name);
crm_xml_add(xml_top, PCMK_XA_ID, lookup_id);
xml_obj = pcmk__xe_create(xml_top, attr_set_type);
crm_xml_add(xml_obj, PCMK_XA_ID, rsc_attr_set);
break;
default:
free(lookup_id);
free(found_attr_id);
g_list_free(resources);
return rc;
}
xml_obj = crm_create_nvpair_xml(xml_obj, rsc_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, PCMK_XE_RESOURCES, xml_top, cib_options);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
out->info(out, "Set '%s' option: "
PCMK_XA_ID "=%s%s%s%s%s value=%s",
lookup_id, found_attr_id,
((rsc_attr_set == NULL)? "" : " set="),
pcmk__s(rsc_attr_set, ""),
((attr_name == NULL)? "" : " " PCMK_XA_NAME "="),
pcmk__s(attr_name, ""), attr_value);
}
free_xml(xml_top);
free(lookup_id);
free(found_attr_id);
free(local_attr_set);
if (recursive
&& pcmk__str_eq(attr_set_type, PCMK_XE_META_ATTRIBUTES,
pcmk__str_casei)) {
GList *lpc = NULL;
static bool need_init = true;
if (need_init) {
need_init = false;
pcmk__unpack_constraints(rsc->cluster);
pe__clear_resource_flags_on_all(rsc->cluster,
pcmk_rsc_detect_loop);
}
/* We want to set the attribute only on resources explicitly
* colocated with this one, so we use rsc->rsc_cons_lhs directly
* rather than the with_this_colocations() method.
*/
pcmk__set_rsc_flags(rsc, pcmk_rsc_detect_loop);
for (lpc = rsc->rsc_cons_lhs; lpc != NULL; lpc = lpc->next) {
pcmk__colocation_t *cons = (pcmk__colocation_t *) lpc->data;
crm_debug("Checking %s %d", cons->id, cons->score);
if (!pcmk_is_set(cons->dependent->flags, pcmk_rsc_detect_loop)
&& (cons->score > 0)) {
crm_debug("Setting %s=%s for dependent resource %s",
attr_name, attr_value, cons->dependent->id);
cli_resource_update_attribute(cons->dependent,
cons->dependent->id, NULL,
attr_set_type, NULL,
attr_name, attr_value,
recursive, cib, cib_options,
force);
}
}
}
}
g_list_free(resources);
return rc;
}
// \return Standard Pacemaker return code
int
cli_resource_delete_attribute(pcmk_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, gboolean force)
{
pcmk__output_t *out = rsc->cluster->priv;
int rc = pcmk_rc_ok;
GList/**/ *resources = NULL;
if ((attr_id == NULL) && !force) {
find_resource_attr(out, cib, PCMK_XA_ID,
pe__const_top_resource(rsc, false)->id, NULL,
NULL, NULL, attr_name, NULL);
}
if (pcmk__str_eq(attr_set_type, PCMK_XE_META_ATTRIBUTES, pcmk__str_casei)) {
resources = find_matching_attr_resources(out, rsc, requested_name,
attr_set, attr_set_type,
attr_id, attr_name, cib,
"delete", force);
} else if (pcmk__str_eq(attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) {
pcmk__xe_remove_attr(rsc->xml, attr_name);
CRM_ASSERT(cib != NULL);
rc = cib->cmds->replace(cib, PCMK_XE_RESOURCES, rsc->xml, cib_options);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
out->info(out, "Deleted attribute: %s", attr_name);
}
return rc;
} else {
resources = g_list_append(resources, rsc);
}
for (GList *iter = resources; iter != NULL; iter = iter->next) {
char *lookup_id = NULL;
xmlNode *xml_obj = NULL;
char *found_attr_id = NULL;
const char *rsc_attr_id = attr_id;
rsc = (pcmk_resource_t *) iter->data;
lookup_id = clone_strip(rsc->id);
rc = find_resource_attr(out, cib, PCMK_XA_ID, lookup_id, attr_set_type,
attr_set, attr_id, attr_name, &found_attr_id);
switch (rc) {
case pcmk_rc_ok:
break;
case ENXIO:
free(lookup_id);
rc = pcmk_rc_ok;
continue;
default:
free(lookup_id);
g_list_free(resources);
return rc;
}
if (rsc_attr_id == NULL) {
rsc_attr_id = found_attr_id;
}
xml_obj = crm_create_nvpair_xml(NULL, rsc_attr_id, attr_name, NULL);
crm_log_xml_debug(xml_obj, "Delete");
CRM_ASSERT(cib);
rc = cib->cmds->remove(cib, PCMK_XE_RESOURCES, xml_obj, cib_options);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
out->info(out, "Deleted '%s' option: " PCMK_XA_ID "=%s%s%s%s%s",
lookup_id, found_attr_id,
((attr_set == NULL)? "" : " set="),
pcmk__s(attr_set, ""),
((attr_name == NULL)? "" : " " PCMK_XA_NAME "="),
pcmk__s(attr_name, ""));
}
free(lookup_id);
free_xml(xml_obj);
free(found_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,
pcmk_scheduler_t *scheduler)
{
pcmk__output_t *out = scheduler->priv;
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;
pcmk_resource_t *rsc = pe_find_resource(scheduler->resources, rsc_id);
if (rsc == NULL) {
out->err(out, "Resource %s not found", rsc_id);
return ENXIO;
} else if (rsc->variant != pcmk_rsc_variant_primitive) {
out->err(out, "We can only process primitive resources, not %s", rsc_id);
return EINVAL;
}
rsc_class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
rsc_provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
rsc_type = crm_element_value(rsc->xml, PCMK_XA_TYPE);
if ((rsc_class == NULL) || (rsc_type == NULL)) {
out->err(out, "Resource %s does not have a class and type", rsc_id);
return EINVAL;
}
{
pcmk_node_t *node = pe_find_node(scheduler->nodes, host_uname);
if (node == NULL) {
out->err(out, "Node %s not found", host_uname);
return pcmk_rc_node_unknown;
}
if (!(node->details->online)) {
if (do_fail_resource) {
out->err(out, "Node %s is not online", host_uname);
return ENOTCONN;
} else {
cib_only = true;
}
}
if (!cib_only && pcmk__is_pacemaker_remote_node(node)) {
node = pcmk__current_node(node->details->remote_rsc);
if (node == NULL) {
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(const pcmk_resource_t *rsc)
{
const char *name = (rsc->clone_name? rsc->clone_name : rsc->id);
if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
return strdup(name);
}
return 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, pcmk_scheduler_t *scheduler)
{
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, scheduler);
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__output_t *out, pcmk_ipc_api_t *controld_api,
const char *node_name, const char *rsc_id, const char *operation,
const char *interval_spec, pcmk_scheduler_t *scheduler)
{
int rc = pcmk_rc_ok;
const char *failed_value = NULL;
const char *failed_id = NULL;
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 = pcmk__strkey_table(NULL, NULL);
// Normalize interval to milliseconds for comparison to history entry
if (operation) {
guint interval_ms = 0U;
pcmk_parse_interval_spec(interval_spec, &interval_ms);
interval_ms_s = crm_strdup_printf("%u", interval_ms);
}
for (xmlNode *xml_op = pcmk__xe_first_child(scheduler->failed, NULL, NULL,
NULL);
xml_op != NULL; xml_op = pcmk__xe_next(xml_op)) {
failed_id = crm_element_value(xml_op, PCMK__XA_RSC_ID);
if (failed_id == NULL) {
// Malformed history entry, should never happen
continue;
}
// No resource specified means all resources match
if (rsc_id) {
pcmk_resource_t *fail_rsc = NULL;
fail_rsc = pe_find_resource_with_flags(scheduler->resources,
failed_id,
pcmk_rsc_match_history
|pcmk_rsc_match_anon_basename);
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, PCMK_XA_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, PCMK_XA_OPERATION);
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, PCMK_META_INTERVAL);
if (!pcmk__str_eq(interval_ms_s, failed_value, pcmk__str_casei)) {
continue;
}
}
g_hash_table_add(rscs, (gpointer) failed_id);
}
free(interval_ms_s);
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, scheduler);
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(const pcmk_resource_t *rsc, const char *operation,
const char *interval_spec, const pcmk_node_t *node)
{
int rc = pcmk_rc_ok;
int attr_options = pcmk__node_attr_none;
char *rsc_name = rsc_fail_name(rsc);
if (pcmk__is_pacemaker_remote_node(node)) {
attr_options |= pcmk__node_attr_remote;
}
rc = pcmk__attrd_api_clear_failures(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,
const pcmk_resource_t *rsc, const char *operation,
const char *interval_spec, bool just_failures,
pcmk_scheduler_t *scheduler, gboolean force)
{
pcmk__output_t *out = scheduler->priv;
int rc = pcmk_rc_ok;
pcmk_node_t *node = NULL;
if (rsc == NULL) {
return ENXIO;
} else if (rsc->children) {
for (const GList *lpc = rsc->children; lpc != NULL; lpc = lpc->next) {
const pcmk_resource_t *child = (const pcmk_resource_t *) lpc->data;
rc = cli_resource_delete(controld_api, host_uname, child, operation,
interval_spec, just_failures, scheduler,
force);
if (rc != pcmk_rc_ok) {
return rc;
}
}
return pcmk_rc_ok;
} else if (host_uname == NULL) {
GList *lpc = NULL;
GList *nodes = g_hash_table_get_values(rsc->known_on);
if(nodes == NULL && force) {
nodes = pcmk__copy_node_list(scheduler->nodes, false);
} else if(nodes == NULL && rsc->exclusive_discover) {
GHashTableIter iter;
pcmk_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 = (pcmk_node_t *) lpc->data;
if (node->details->online) {
rc = cli_resource_delete(controld_api, node->details->uname, rsc,
operation, interval_spec, just_failures,
scheduler, force);
}
if (rc != pcmk_rc_ok) {
g_list_free(nodes);
return rc;
}
}
g_list_free(nodes);
return pcmk_rc_ok;
}
node = pe_find_node(scheduler->nodes, host_uname);
if (node == NULL) {
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) {
out->err(out, "Unable to clean up %s because resource discovery disabled on %s",
rsc->id, host_uname);
return EOPNOTSUPP;
}
if (controld_api == NULL) {
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) {
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(out, controld_api, host_uname, rsc->id, operation,
interval_spec, scheduler);
} else {
rc = clear_rsc_history(controld_api, host_uname, rsc->id, scheduler);
}
if (rc != pcmk_rc_ok) {
out->err(out, "Cleaned %s failures on %s, but unable to clean history: %s",
rsc->id, host_uname, pcmk_rc_str(rc));
} else {
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,
pcmk_scheduler_t *scheduler)
{
pcmk__output_t *out = scheduler->priv;
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) {
out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
display_name);
return rc;
}
if (node_name) {
pcmk_node_t *node = pe_find_node(scheduler->nodes, node_name);
if (node == NULL) {
out->err(out, "Unknown node: %s", node_name);
return ENXIO;
}
if (pcmk__is_pacemaker_remote_node(node)) {
attr_options |= pcmk__node_attr_remote;
}
}
rc = pcmk__attrd_api_clear_failures(NULL, node_name, NULL, operation,
interval_spec, NULL, attr_options);
if (rc != pcmk_rc_ok) {
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(out, controld_api, node_name, NULL,
operation, interval_spec, scheduler);
if (rc != pcmk_rc_ok) {
out->err(out, "Cleaned all resource failures on %s, but unable to clean history: %s",
node_name, pcmk_rc_str(rc));
return rc;
}
} else {
for (GList *iter = scheduler->nodes; iter; iter = iter->next) {
pcmk_node_t *node = (pcmk_node_t *) iter->data;
rc = clear_rsc_failures(out, controld_api, node->details->uname, NULL,
operation, interval_spec, scheduler);
if (rc != pcmk_rc_ok) {
out->err(out, "Cleaned all resource failures on all nodes, but unable to clean history: %s",
pcmk_rc_str(rc));
return rc;
}
}
}
out->info(out, "Cleaned up all resources on %s", display_name);
return rc;
}
static void
check_role(resource_checks_t *checks)
{
const char *role_s = g_hash_table_lookup(checks->rsc->meta,
PCMK_META_TARGET_ROLE);
if (role_s == NULL) {
return;
}
switch (pcmk_parse_role(role_s)) {
case pcmk_role_stopped:
checks->flags |= rsc_remain_stopped;
break;
case pcmk_role_unpromoted:
if (pcmk_is_set(pe__const_top_resource(checks->rsc, false)->flags,
pcmk_rsc_promotable)) {
checks->flags |= rsc_unpromotable;
}
break;
default:
break;
}
}
static void
check_managed(resource_checks_t *checks)
{
const char *managed_s = g_hash_table_lookup(checks->rsc->meta,
PCMK_META_IS_MANAGED);
if ((managed_s != NULL) && !crm_is_true(managed_s)) {
checks->flags |= rsc_unmanaged;
}
}
static void
check_locked(resource_checks_t *checks)
{
if (checks->rsc->lock_node != NULL) {
checks->flags |= rsc_locked;
checks->lock_node = checks->rsc->lock_node->details->uname;
}
}
static bool
node_is_unhealthy(pcmk_node_t *node)
{
switch (pe__health_strategy(node->details->data_set)) {
case pcmk__health_strategy_none:
break;
case pcmk__health_strategy_no_red:
if (pe__node_health(node) < 0) {
return true;
}
break;
case pcmk__health_strategy_only_green:
if (pe__node_health(node) <= 0) {
return true;
}
break;
case pcmk__health_strategy_progressive:
case pcmk__health_strategy_custom:
/* @TODO These are finite scores, possibly with rules, and possibly
* combining with other scores, so attributing these as a cause is
* nontrivial.
*/
break;
}
return false;
}
static void
check_node_health(resource_checks_t *checks, pcmk_node_t *node)
{
if (node == NULL) {
GHashTableIter iter;
bool allowed = false;
bool all_nodes_unhealthy = true;
g_hash_table_iter_init(&iter, checks->rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
allowed = true;
if (!node_is_unhealthy(node)) {
all_nodes_unhealthy = false;
break;
}
}
if (allowed && all_nodes_unhealthy) {
checks->flags |= rsc_node_health;
}
} else if (node_is_unhealthy(node)) {
checks->flags |= rsc_node_health;
}
}
int
cli_resource_check(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node)
{
resource_checks_t checks = { .rsc = rsc };
check_role(&checks);
check_managed(&checks);
check_locked(&checks);
check_node_health(&checks, node);
return out->message(out, "resource-check-list", &checks);
}
// \return Standard Pacemaker return code
int
cli_resource_fail(pcmk_ipc_api_t *controld_api, const char *host_uname,
const char *rsc_id, pcmk_scheduler_t *scheduler)
{
crm_notice("Failing %s on %s", rsc_id, host_uname);
return send_lrm_rsc_op(controld_api, true, host_uname, rsc_id, scheduler);
}
static GHashTable *
generate_resource_params(pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_scheduler_t *scheduler)
{
GHashTable *params = NULL;
GHashTable *meta = NULL;
GHashTable *combined = NULL;
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
combined = pcmk__strkey_table(free, free);
params = pe_rsc_params(rsc, node, scheduler);
if (params != NULL) {
g_hash_table_iter_init(&iter, params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
pcmk__insert_dup(combined, key, value);
}
}
meta = pcmk__strkey_table(free, free);
get_meta_attributes(meta, rsc, NULL, scheduler);
if (meta != 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;
}
bool resource_is_running_on(pcmk_resource_t *rsc, const char *host)
{
bool found = true;
GList *hIter = NULL;
GList *hosts = NULL;
if (rsc == NULL) {
return false;
}
rsc->fns->location(rsc, &hosts, TRUE);
for (hIter = hosts; host != NULL && hIter != NULL; hIter = hIter->next) {
pcmk_node_t *node = (pcmk_node_t *) hIter->data;
if (pcmk__strcase_any_of(host, node->details->uname, node->details->id, NULL)) {
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) {
pcmk_resource_t *rsc = (pcmk_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 == pcmk_rsc_variant_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(pcmk__output_t *out, GList *items, const char *tag)
{
GList *item = NULL;
for (item = items; item != NULL; item = item->next) {
out->info(out, "%s%s", tag, (const char *)item->data);
}
}
/*!
* \internal
* \brief Upgrade XML to latest schema version and use it as scheduler input
*
* This also updates the scheduler timestamp to the current time.
*
* \param[in,out] scheduler Scheduler data to update
* \param[in,out] xml XML to use as input
*
* \return Standard Pacemaker return code
* \note On success, caller is responsible for freeing memory allocated for
* scheduler->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.
+ * \todo This follows the example of other callers of
+ * pcmk__update_configured_schema() and returns ENOKEY ("Required key not
+ * available") if that fails, but perhaps pcmk_rc_schema_validation would
+ * be better in that case.
*/
int
update_scheduler_input(pcmk_scheduler_t *scheduler, xmlNode **xml)
{
- if (cli_config_update(xml, NULL, FALSE) == FALSE) {
+ if (!pcmk__update_configured_schema(xml, false)) {
return ENOKEY;
}
scheduler->input = *xml;
scheduler->now = crm_time_new(NULL);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Update scheduler XML input based on a CIB query
*
* \param[in] scheduler Scheduler data 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
* scheduler->input and scheduler->now.
*/
static int
update_scheduler_input_to_cib(pcmk__output_t *out, pcmk_scheduler_t *scheduler,
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) {
out->err(out, "Could not obtain the current CIB: %s (%d)", pcmk_rc_str(rc), rc);
return rc;
}
rc = update_scheduler_input(scheduler, &cib_xml_copy);
if (rc != pcmk_rc_ok) {
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, pcmk_scheduler_t *scheduler, bool simulate)
{
char *pid = NULL;
char *shadow_file = NULL;
cib_t *shadow_cib = NULL;
int rc = pcmk_rc_ok;
pcmk__output_t *out = scheduler->priv;
pe_reset_working_set(scheduler);
pcmk__set_scheduler_flags(scheduler,
pcmk_sched_no_counts|pcmk_sched_no_compat);
rc = update_scheduler_input_to_cib(out, scheduler, cib);
if (rc != pcmk_rc_ok) {
return rc;
}
if(simulate) {
bool prev_quiet = false;
pid = pcmk__getpid_s();
shadow_cib = cib_shadow_new(pid);
shadow_file = get_shadow_file(pid);
if (shadow_cib == NULL) {
out->err(out, "Could not create shadow cib: '%s'", pid);
rc = ENXIO;
goto done;
}
rc = pcmk__xml_write_file(scheduler->input, shadow_file, false, NULL);
if (rc != pcmk_rc_ok) {
out->err(out, "Could not populate shadow cib: %s", pcmk_rc_str(rc));
goto done;
}
rc = shadow_cib->cmds->signon(shadow_cib, crm_system_name, cib_command);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
out->err(out, "Could not connect to shadow cib: %s",
pcmk_rc_str(rc));
goto done;
}
pcmk__schedule_actions(scheduler->input,
pcmk_sched_no_counts|pcmk_sched_no_compat,
scheduler);
prev_quiet = out->is_quiet(out);
out->quiet = true;
pcmk__simulate_transition(scheduler, shadow_cib, NULL);
out->quiet = prev_quiet;
rc = update_dataset(shadow_cib, scheduler, false);
} else {
cluster_status(scheduler);
}
done:
// Do not free scheduler->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;
}
/*!
* \internal
* \brief Find the maximum stop timeout of a resource and its children (if any)
*
* \param[in,out] rsc Resource to get timeout for
*
* \return Maximum stop timeout for \p rsc (in milliseconds)
*/
static guint
max_rsc_stop_timeout(pcmk_resource_t *rsc)
{
long long result_ll;
guint max_delay = 0;
xmlNode *config = NULL;
GHashTable *meta = NULL;
if (rsc == NULL) {
return 0;
}
// If resource is collective, use maximum of its children's stop timeouts
if (rsc->children != NULL) {
for (GList *iter = rsc->children; iter; iter = iter->next) {
pcmk_resource_t *child = iter->data;
guint delay = max_rsc_stop_timeout(child);
if (delay > max_delay) {
pcmk__rsc_trace(rsc,
"Maximum stop timeout for %s is now %s "
"due to %s", rsc->id,
pcmk__readable_interval(delay), child->id);
max_delay = delay;
}
}
return max_delay;
}
// Get resource's stop action configuration from CIB
config = pcmk__find_action_config(rsc, PCMK_ACTION_STOP, 0, true);
/* Get configured timeout for stop action (fully evaluated for rules,
* defaults, etc.).
*
* @TODO This currently ignores node (which might matter for rules)
*/
meta = pcmk__unpack_action_meta(rsc, NULL, PCMK_ACTION_STOP, 0, config);
if ((pcmk__scan_ll(g_hash_table_lookup(meta, PCMK_META_TIMEOUT),
&result_ll, -1LL) == pcmk_rc_ok) && (result_ll >= 0)) {
max_delay = (guint) QB_MIN(result_ll, UINT_MAX);
}
g_hash_table_destroy(meta);
return max_delay;
}
/*!
* \internal
* \brief Find a reasonable waiting time for stopping any one resource in a list
*
* \param[in,out] scheduler Scheduler data
* \param[in] resources List of names of resources that will be stopped
*
* \return Rough estimate of a reasonable time to wait (in seconds) to stop any
* one resource in \p resources
* \note This estimate is very rough, simply the maximum stop timeout of all
* given resources and their children, plus a small fudge factor. It does
* not account for children that must be stopped in sequence, action
* throttling, or any demotions needed. It checks the stop timeout, even
* if the resources in question are actually being started.
*/
static guint
wait_time_estimate(pcmk_scheduler_t *scheduler, const GList *resources)
{
guint max_delay = 0U;
// Find maximum stop timeout in milliseconds
for (const GList *item = resources; item != NULL; item = item->next) {
pcmk_resource_t *rsc = pe_find_resource(scheduler->resources,
(const char *) item->data);
guint delay = max_rsc_stop_timeout(rsc);
if (delay > max_delay) {
pcmk__rsc_trace(rsc,
"Wait time is now %s due to %s",
pcmk__readable_interval(delay), rsc->id);
max_delay = delay;
}
}
return (max_delay / 1000U) + 5U;
}
#define waiting_for_starts(d, r, h) ((d != NULL) || \
(!resource_is_running_on((r), (h))))
/*!
* \internal
* \brief Restart a resource (on a particular host if requested).
*
* \param[in,out] out Output object
* \param[in,out] rsc The resource to restart
* \param[in] node Node to restart resource on (NULL for all)
* \param[in] move_lifetime If not NULL, how long constraint should
* remain in effect (as ISO 8601 string)
* \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, it will be calculated based on
* the resource timeout)
* \param[in,out] cib Connection to the CIB manager
* \param[in] cib_options Group of enum cib_call_options flags to
* use with CIB calls
* \param[in] promoted_role_only If true, limit to promoted instances
* \param[in] force If true, apply only to requested instance
* if part of a collective resource
*
* \return Standard Pacemaker return code (exits on certain failures)
*/
int
cli_resource_restart(pcmk__output_t *out, pcmk_resource_t *rsc,
const pcmk_node_t *node, const char *move_lifetime,
guint 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;
guint step_timeout_s = 0;
guint sleep_interval = 2U;
guint timeout = timeout_ms / 1000U;
bool stop_via_ban = false;
char *rsc_id = NULL;
char *lookup_id = NULL;
char *orig_target_role = NULL;
GList *list_delta = NULL;
GList *target_active = NULL;
GList *current_active = NULL;
GList *restart_target_active = NULL;
pcmk_scheduler_t *scheduler = NULL;
pcmk_resource_t *parent = uber_parent(rsc);
bool running = false;
const char *id = rsc->clone_name ? rsc->clone_name : rsc->id;
const char *host = node ? node->details->uname : NULL;
/* If the implicit resource or primitive resource of a bundle is given, operate on the
* bundle itself instead.
*/
if (pcmk__is_bundled(rsc)) {
rsc = parent->parent;
}
running = resource_is_running_on(rsc, host);
if (pcmk__is_clone(parent) && !running) {
if (pcmk__is_unique_clone(parent)) {
lookup_id = strdup(rsc->id);
} else {
lookup_id = clone_strip(rsc->id);
}
rsc = parent->fns->find_rsc(parent, lookup_id, node,
pcmk_rsc_match_basename
|pcmk_rsc_match_current_node);
free(lookup_id);
running = resource_is_running_on(rsc, host);
}
if (!running) {
if (host) {
out->err(out, "%s is not running on %s and so cannot be restarted", id, host);
} else {
out->err(out, "%s is not running anywhere and so cannot be restarted", id);
}
return ENXIO;
}
if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
out->err(out, "Unmanaged resources cannot be restarted.");
return EAGAIN;
}
rsc_id = strdup(rsc->id);
if (pcmk__is_unique_clone(parent)) {
lookup_id = strdup(rsc->id);
} else {
lookup_id = clone_strip(rsc->id);
}
if (host) {
if (pcmk__is_clone(rsc) || pe_bundle_replicas(rsc)) {
stop_via_ban = true;
} else if (pcmk__is_clone(parent)) {
stop_via_ban = true;
free(lookup_id);
lookup_id = strdup(parent->id);
}
}
/*
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)
*/
scheduler = pe_new_working_set();
if (scheduler == NULL) {
rc = errno;
out->err(out, "Could not allocate scheduler data: %s", pcmk_rc_str(rc));
goto done;
}
scheduler->priv = out;
rc = update_dataset(cib, scheduler, false);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not get new resource list: %s (%d)", pcmk_rc_str(rc), rc);
goto done;
}
restart_target_active = get_active_resources(host, scheduler->resources);
current_active = get_active_resources(host, scheduler->resources);
dump_list(current_active, "Origin");
if (stop_via_ban) {
/* Stop the clone or bundle instance by banning it from the host */
out->quiet = true;
rc = cli_resource_ban(out, lookup_id, host, move_lifetime, cib,
cib_options, promoted_role_only,
PCMK_ROLE_PROMOTED);
} else {
/* Stop the resource by setting PCMK_META_TARGET_ROLE to Stopped.
* Remember any existing PCMK_META_TARGET_ROLE so we can restore it
* later (though it only makes any difference if it's Unpromoted).
*/
find_resource_attr(out, cib, PCMK_XA_VALUE, lookup_id, NULL, NULL, NULL,
PCMK_META_TARGET_ROLE, &orig_target_role);
rc = cli_resource_update_attribute(rsc, rsc_id, NULL,
PCMK_XE_META_ATTRIBUTES, NULL,
PCMK_META_TARGET_ROLE,
PCMK_ACTION_STOPPED, FALSE, cib,
cib_options, force);
}
if(rc != pcmk_rc_ok) {
out->err(out, "Could not set " PCMK_META_TARGET_ROLE " for %s: %s (%d)",
rsc_id, pcmk_rc_str(rc), rc);
if (current_active != NULL) {
g_list_free_full(current_active, free);
current_active = NULL;
}
if (restart_target_active != NULL) {
g_list_free_full(restart_target_active, free);
restart_target_active = NULL;
}
goto done;
}
rc = update_dataset(cib, scheduler, true);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not determine which resources would be stopped");
goto failure;
}
target_active = get_active_resources(host, scheduler->resources);
dump_list(target_active, "Target");
list_delta = pcmk__subtract_lists(current_active, target_active, (GCompareFunc) strcmp);
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 = wait_time_estimate(scheduler, 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("%us remaining", timeout);
}
rc = update_dataset(cib, scheduler, FALSE);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not determine which resources were stopped");
goto failure;
}
if (current_active != NULL) {
g_list_free_full(current_active, free);
}
current_active = get_active_resources(host, scheduler->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 */
out->err(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(lookup_id, host, NULL, cib, cib_options, true, force);
} else if (orig_target_role) {
rc = cli_resource_update_attribute(rsc, rsc_id, NULL,
PCMK_XE_META_ATTRIBUTES, NULL,
PCMK_META_TARGET_ROLE,
orig_target_role, FALSE, cib,
cib_options, force);
free(orig_target_role);
orig_target_role = NULL;
} else {
rc = cli_resource_delete_attribute(rsc, rsc_id, NULL,
PCMK_XE_META_ATTRIBUTES, NULL,
PCMK_META_TARGET_ROLE, cib,
cib_options, force);
}
if(rc != pcmk_rc_ok) {
out->err(out,
"Could not unset " PCMK_META_TARGET_ROLE " for %s: %s (%d)",
rsc_id, pcmk_rc_str(rc), rc);
goto done;
}
if (target_active != NULL) {
g_list_free_full(target_active, free);
}
target_active = restart_target_active;
list_delta = pcmk__subtract_lists(target_active, current_active, (GCompareFunc) strcmp);
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 = wait_time_estimate(scheduler, 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, scheduler, false);
if(rc != pcmk_rc_ok) {
out->err(out, "Could not determine which resources were started");
goto failure;
}
/* It's OK if dependent resources moved to a different node,
* so we check active resources on all nodes.
*/
if (current_active != NULL) {
g_list_free_full(current_active, free);
}
current_active = get_active_resources(NULL, scheduler->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 */
out->err(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(lookup_id, host, NULL, cib, cib_options, true, force);
} else if (orig_target_role) {
cli_resource_update_attribute(rsc, rsc_id, NULL,
PCMK_XE_META_ATTRIBUTES, NULL,
PCMK_META_TARGET_ROLE, orig_target_role,
FALSE, cib, cib_options, force);
free(orig_target_role);
} else {
cli_resource_delete_attribute(rsc, rsc_id, NULL,
PCMK_XE_META_ATTRIBUTES, NULL,
PCMK_META_TARGET_ROLE, cib, cib_options,
force);
}
done:
if (list_delta != NULL) {
g_list_free(list_delta);
}
if (current_active != NULL) {
g_list_free_full(current_active, free);
}
if (target_active != NULL && (target_active != restart_target_active)) {
g_list_free_full(target_active, free);
}
if (restart_target_active != NULL) {
g_list_free_full(restart_target_active, free);
}
free(rsc_id);
free(lookup_id);
pe_free_working_set(scheduler);
return rc;
}
static inline bool
action_is_pending(const pcmk_action_t *action)
{
if (pcmk_any_flags_set(action->flags,
pcmk_action_optional|pcmk_action_pseudo)
|| !pcmk_is_set(action->flags, pcmk_action_runnable)
|| pcmk__str_eq(PCMK_ACTION_NOTIFY, action->task, pcmk__str_casei)) {
return false;
}
return true;
}
/*!
* \internal
* \brief Check whether 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, otherwise false
*/
static bool
actions_are_pending(const GList *actions)
{
for (const GList *action = actions; action != NULL; action = action->next) {
const pcmk_action_t *a = (const pcmk_action_t *) action->data;
if (action_is_pending(a)) {
crm_notice("Waiting for %s (flags=%#.8x)", a->uuid, a->flags);
return true;
}
}
return false;
}
static void
print_pending_actions(pcmk__output_t *out, GList *actions)
{
GList *action;
out->info(out, "Pending actions:");
for (action = actions; action != NULL; action = action->next) {
pcmk_action_t *a = (pcmk_action_t *) action->data;
if (!action_is_pending(a)) {
continue;
}
if (a->node) {
out->info(out, "\tAction %d: %s\ton %s",
a->id, a->uuid, pcmk__node_name(a->node));
} 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,out] out Output object
* \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,out] cib Connection to the CIB manager
*
* \return Standard Pacemaker return code
*/
int
wait_till_stable(pcmk__output_t *out, guint timeout_ms, cib_t * cib)
{
pcmk_scheduler_t *scheduler = NULL;
xmlXPathObjectPtr search;
int rc = pcmk_rc_ok;
bool pending_unknown_state_resources;
time_t expire_time = time(NULL);
time_t time_diff;
bool printed_version_warning = out->is_quiet(out); // i.e. don't print if quiet
char *xpath = NULL;
if (timeout_ms == 0) {
expire_time += WAIT_DEFAULT_TIMEOUT_S;
} else {
expire_time += (timeout_ms + 999) / 1000;
}
scheduler = pe_new_working_set();
if (scheduler == NULL) {
return ENOMEM;
}
xpath = crm_strdup_printf("/" PCMK_XE_CIB "/" PCMK_XE_STATUS
"/" PCMK__XE_NODE_STATE "/" PCMK__XE_LRM
"/" PCMK__XE_LRM_RESOURCES
"/" PCMK__XE_LRM_RESOURCE
"/" PCMK__XE_LRM_RSC_OP
"[@" PCMK__XA_RC_CODE "='%d']",
PCMK_OCF_UNKNOWN);
do {
/* Abort if timeout is reached */
time_diff = expire_time - time(NULL);
if (time_diff <= 0) {
print_pending_actions(out, scheduler->actions);
rc = ETIME;
break;
}
crm_info("Waiting up to %lld seconds for cluster actions to complete",
(long long) time_diff);
if (rc == pcmk_rc_ok) { /* this avoids sleep on first loop iteration */
sleep(WAIT_SLEEP_S);
}
/* Get latest transition graph */
pe_reset_working_set(scheduler);
rc = update_scheduler_input_to_cib(out, scheduler, cib);
if (rc != pcmk_rc_ok) {
break;
}
pcmk__schedule_actions(scheduler->input,
pcmk_sched_no_counts|pcmk_sched_no_compat,
scheduler);
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(scheduler->config_hash,
PCMK_OPT_DC_VERSION);
if (!pcmk__str_eq(dc_version, PACEMAKER_VERSION "-" BUILD_VERSION, pcmk__str_casei)) {
out->info(out, "warning: wait option may not work properly in "
"mixed-version cluster");
printed_version_warning = true;
}
}
search = xpath_search(scheduler->input, xpath);
pending_unknown_state_resources = (numXpathResults(search) > 0);
freeXpathObject(search);
} while (actions_are_pending(scheduler->actions) || pending_unknown_state_resources);
pe_free_working_set(scheduler);
free(xpath);
return rc;
}
static const char *
get_action(const char *rsc_action) {
const char *action = NULL;
if (pcmk__str_eq(rsc_action, "validate", pcmk__str_casei)) {
action = PCMK_ACTION_VALIDATE_ALL;
} else if (pcmk__str_eq(rsc_action, "force-check", pcmk__str_casei)) {
action = PCMK_ACTION_MONITOR;
} else if (pcmk__strcase_any_of(rsc_action, "force-start", "force-stop",
"force-demote", "force-promote", NULL)) {
action = rsc_action+6;
} else {
action = rsc_action;
}
return action;
}
/*!
* \brief Set up environment variables as expected by resource agents
*
* When the cluster executes resource agents, it adds certain environment
* variables (directly or via resource meta-attributes) expected by some
* resource agents. Add the essential ones that many resource agents expect, so
* the behavior is the same for command-line execution.
*
* \param[in,out] params Resource parameters that will be passed to agent
* \param[in] timeout_ms Action timeout (in milliseconds)
* \param[in] check_level OCF check level
* \param[in] verbosity Verbosity level
*/
static void
set_agent_environment(GHashTable *params, guint timeout_ms, int check_level,
int verbosity)
{
g_hash_table_insert(params, crm_meta_name(PCMK_META_TIMEOUT),
crm_strdup_printf("%u", timeout_ms));
pcmk__insert_dup(params, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
if (check_level >= 0) {
char *level = crm_strdup_printf("%d", check_level);
setenv("OCF_CHECK_LEVEL", level, 1);
free(level);
}
pcmk__set_env_option(PCMK__ENV_DEBUG, ((verbosity > 0)? "1" : "0"), true);
if (verbosity > 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);
}
/*!
* \internal
* \brief Apply command-line overrides to resource parameters
*
* \param[in,out] params Parameters to be passed to agent
* \param[in] overrides Parameters to override (or NULL if none)
*/
static void
apply_overrides(GHashTable *params, GHashTable *overrides)
{
if (overrides != NULL) {
GHashTableIter iter;
char *name = NULL;
char *value = NULL;
g_hash_table_iter_init(&iter, overrides);
while (g_hash_table_iter_next(&iter, (gpointer *) &name,
(gpointer *) &value)) {
pcmk__insert_dup(params, name, value);
}
}
}
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,
guint timeout_ms, int resource_verbose,
gboolean force, int check_level)
{
const char *class = rsc_class;
const char *action = get_action(rsc_action);
crm_exit_t exit_code = CRM_EX_OK;
svc_action_t *op = NULL;
// If no timeout was provided, use the same default as the cluster
if (timeout_ms == 0U) {
timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
set_agent_environment(params, timeout_ms, check_level, resource_verbose);
apply_overrides(params, override_hash);
op = services__create_resource_action(rsc_name? rsc_name : "test",
rsc_class, rsc_prov, rsc_type, action,
0, QB_MIN(timeout_ms, INT_MAX),
params, 0);
if (op == NULL) {
out->err(out, "Could not execute %s using %s%s%s:%s: %s",
action, rsc_class, (rsc_prov? ":" : ""),
(rsc_prov? rsc_prov : ""), rsc_type, strerror(ENOMEM));
g_hash_table_destroy(params);
return CRM_EX_OSERR;
}
if (pcmk__str_eq(rsc_class, PCMK_RESOURCE_CLASS_SERVICE, pcmk__str_casei)) {
class = resources_find_service_class(rsc_type);
}
if (!pcmk__strcase_any_of(class, PCMK_RESOURCE_CLASS_OCF,
PCMK_RESOURCE_CLASS_LSB, NULL)) {
services__format_result(op, CRM_EX_UNIMPLEMENT_FEATURE, PCMK_EXEC_ERROR,
"Manual execution of the %s standard is "
"unsupported", pcmk__s(class, "unspecified"));
}
if (op->rc != PCMK_OCF_UNKNOWN) {
exit_code = op->rc;
goto done;
}
services_action_sync(op);
// Map results to OCF codes for consistent reporting to user
{
enum ocf_exitcode ocf_code = services_result2ocf(class, action, op->rc);
// Cast variable instead of function return to keep compilers happy
exit_code = (crm_exit_t) ocf_code;
}
done:
out->message(out, "resource-agent-action", resource_verbose, rsc_class,
rsc_prov, rsc_type, rsc_name, rsc_action, override_hash,
exit_code, op->status, services__exit_reason(op),
op->stdout_data, op->stderr_data);
services_action_free(op);
return exit_code;
}
/*!
* \internal
* \brief Get the timeout the cluster would use for an action
*
* \param[in] rsc Resource that action is for
* \param[in] action Name of action
*/
static guint
get_action_timeout(pcmk_resource_t *rsc, const char *action)
{
long long timeout_ms = -1LL;
xmlNode *op = pcmk__find_action_config(rsc, action, 0, true);
GHashTable *meta = pcmk__unpack_action_meta(rsc, NULL, action, 0, op);
if ((pcmk__scan_ll(g_hash_table_lookup(meta, PCMK_META_TIMEOUT),
&timeout_ms, -1LL) != pcmk_rc_ok)
|| (timeout_ms <= 0LL)) {
timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
g_hash_table_destroy(meta);
return (guint) QB_MIN(timeout_ms, UINT_MAX);
}
crm_exit_t
cli_resource_execute(pcmk_resource_t *rsc, const char *requested_name,
const char *rsc_action, GHashTable *override_hash,
guint timeout_ms, cib_t *cib, pcmk_scheduler_t *scheduler,
int resource_verbose, gboolean force, int check_level)
{
pcmk__output_t *out = scheduler->priv;
crm_exit_t exit_code = CRM_EX_OK;
const char *rid = NULL;
const char *rtype = NULL;
const char *rprov = NULL;
const char *rclass = NULL;
GHashTable *params = NULL;
if (pcmk__strcase_any_of(rsc_action, "force-start", "force-demote",
"force-promote", NULL)) {
if (pcmk__is_clone(rsc)) {
GList *nodes = cli_resource_search(rsc, requested_name, scheduler);
if(nodes != NULL && force == FALSE) {
out->err(out, "It is not safe to %s %s here: the cluster claims it is already active",
rsc_action, rsc->id);
out->err(out,
"Try setting "
PCMK_META_TARGET_ROLE "=" PCMK_ROLE_STOPPED
" first or specifying the force option");
return CRM_EX_UNSAFE;
}
g_list_free_full(nodes, free);
}
}
if (pcmk__is_clone(rsc)) {
/* Grab the first child resource in the hope it's not a group */
rsc = rsc->children->data;
}
if (rsc->variant == pcmk_rsc_variant_group) {
out->err(out, "Sorry, the %s option doesn't support group resources", rsc_action);
return CRM_EX_UNIMPLEMENT_FEATURE;
} else if (pcmk__is_bundled(rsc)) {
out->err(out, "Sorry, the %s option doesn't support bundled resources", rsc_action);
return CRM_EX_UNIMPLEMENT_FEATURE;
}
rclass = crm_element_value(rsc->xml, PCMK_XA_CLASS);
rprov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
rtype = crm_element_value(rsc->xml, PCMK_XA_TYPE);
params = generate_resource_params(rsc, NULL /* @TODO use local node */,
scheduler);
if (timeout_ms == 0U) {
timeout_ms = get_action_timeout(rsc, get_action(rsc_action));
}
rid = pcmk__is_anonymous_clone(rsc->parent)? requested_name : rsc->id;
exit_code = cli_resource_execute_from_params(out, rid, rclass, rprov, rtype, rsc_action,
params, override_hash, timeout_ms,
resource_verbose, force, check_level);
return exit_code;
}
// \return Standard Pacemaker return code
int
cli_resource_move(const pcmk_resource_t *rsc, const char *rsc_id,
const char *host_name, const char *move_lifetime, cib_t *cib,
int cib_options, pcmk_scheduler_t *scheduler,
gboolean promoted_role_only, gboolean force)
{
pcmk__output_t *out = scheduler->priv;
int rc = pcmk_rc_ok;
unsigned int count = 0;
pcmk_node_t *current = NULL;
pcmk_node_t *dest = pe_find_node(scheduler->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, pcmk_rsc_promotable)) {
const pcmk_resource_t *p = pe__const_top_resource(rsc, false);
if (pcmk_is_set(p->flags, pcmk_rsc_promotable)) {
out->info(out, "Using parent '%s' for move instead of '%s'.", rsc->id, rsc_id);
rsc_id = p->id;
rsc = p;
} else {
out->info(out, "Ignoring --promoted option: %s is not promotable",
rsc_id);
promoted_role_only = FALSE;
}
}
current = pe__find_active_requires(rsc, &count);
if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
unsigned int promoted_count = 0;
pcmk_node_t *promoted_node = NULL;
for (const GList *iter = rsc->children; iter; iter = iter->next) {
const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
enum rsc_role_e child_role = child->fns->state(child, TRUE);
if (child_role == pcmk_role_promoted) {
rsc = child;
promoted_node = pcmk__current_node(child);
promoted_count++;
}
}
if (promoted_role_only || (promoted_count != 0)) {
count = promoted_count;
current = promoted_node;
}
}
if (count > 1) {
if (pcmk__is_clone(rsc)) {
current = NULL;
} else {
return pcmk_rc_multiple;
}
}
if (pcmk__same_node(current, dest)) {
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",
pcmk__node_name(dest));
} else {
return pcmk_rc_already;
}
}
/* Clear any previous prefer constraints across all nodes. */
cli_resource_clear(rsc_id, NULL, scheduler->nodes, cib, cib_options, false,
force);
/* Clear any previous ban constraints on 'dest'. */
cli_resource_clear(rsc_id, dest->details->uname, scheduler->nodes, cib,
cib_options, TRUE, force);
/* Record an explicit preference for 'dest' */
rc = cli_resource_prefer(out, rsc_id, dest->details->uname, move_lifetime,
cib, cib_options, promoted_role_only,
PCMK_ROLE_PROMOTED);
crm_trace("%s%s now prefers %s%s",
rsc->id, (promoted_role_only? " (promoted)" : ""),
pcmk__node_name(dest), 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) {
/* Ban the original location if possible */
if(current) {
(void)cli_resource_ban(out, rsc_id, current->details->uname, move_lifetime,
cib, cib_options, promoted_role_only,
PCMK_ROLE_PROMOTED);
} else if(count > 1) {
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, pcmk__node_name(dest));
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;
}
diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c
index d503b4cc84..55490c3fac 100644
--- a/tools/crm_simulate.c
+++ b/tools/crm_simulate.c
@@ -1,586 +1,586 @@
/*
* Copyright 2009-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include