summaryrefslogtreecommitdiffstats
path: root/lib/common/options.c
blob: ba64959c8a57a1fcc09384ccfc89ff1d333a1b32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
/*
 * 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 _GNU_SOURCE
#  define _GNU_SOURCE
#endif

#include <crm_internal.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <crm/crm.h>
#include <crm/common/xml.h>

void
pcmk__cli_help(char cmd)
{
    if (cmd == 'v' || cmd == '$') {
        printf("Pacemaker %s\n", PACEMAKER_VERSION);
        printf("Written by Andrew Beekhof and "
               "the Pacemaker project contributors\n");

    } else if (cmd == '!') {
        printf("Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
    }

    crm_exit(CRM_EX_OK);
    while(1); // above does not return
}


/*
 * Option metadata
 */

static const pcmk__cluster_option_t cluster_options[] = {
    /* name, old name, type, allowed values,
     * default value, validator,
     * flags,
     * short description,
     * long description
     */
    {
        PCMK_OPT_DC_VERSION, NULL, PCMK_VALUE_VERSION, NULL,
        NULL, NULL,
        pcmk__opt_controld|pcmk__opt_generated,
        N_("Pacemaker version on cluster node elected Designated Controller "
            "(DC)"),
        N_("Includes a hash which identifies the exact revision the code was "
            "built from. Used for diagnostic purposes."),
    },
    {
        PCMK_OPT_CLUSTER_INFRASTRUCTURE, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_controld|pcmk__opt_generated,
        N_("The messaging layer on which Pacemaker is currently running"),
        N_("Used for informational and diagnostic purposes."),
    },
    {
        PCMK_OPT_CLUSTER_NAME, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_controld,
        N_("An arbitrary name for the cluster"),
        N_("This optional value is mostly for users' convenience as desired "
            "in administration, but may also be used in Pacemaker "
            "configuration rules via the #cluster-name node attribute, and "
            "by higher-level tools and resource agents."),
    },
    {
        PCMK_OPT_DC_DEADTIME, NULL, PCMK_VALUE_DURATION, NULL,
        "20s", pcmk__valid_interval_spec,
        pcmk__opt_controld,
        N_("How long to wait for a response from other nodes during start-up"),
        N_("The optimal value will depend on the speed and load of your "
            "network and the type of switches used."),
    },
    {
        PCMK_OPT_CLUSTER_RECHECK_INTERVAL, NULL, PCMK_VALUE_DURATION, NULL,
        "15min", pcmk__valid_interval_spec,
        pcmk__opt_controld,
        N_("Polling interval to recheck cluster state and evaluate rules "
            "with date specifications"),
        N_("Pacemaker is primarily event-driven, and looks ahead to know when "
            "to recheck cluster state for failure-timeout settings and most "
            "time-based rules. However, it will also recheck the cluster after "
            "this amount of inactivity, to evaluate rules with date "
            "specifications and serve as a fail-safe for certain types of "
            "scheduler bugs. A value of 0 disables polling. A positive value "
            "sets an interval in seconds, unless other units are specified "
            "(for example, \"5min\")."),
    },
    {
        PCMK_OPT_FENCE_REACTION, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_STOP ", " PCMK_VALUE_PANIC,
        PCMK_VALUE_STOP, NULL,
        pcmk__opt_controld,
        N_("How a cluster node should react if notified of its own fencing"),
        N_("A cluster node may receive notification of a \"succeeded\" "
            "fencing that targeted it if fencing is misconfigured, or if "
            "fabric fencing is in use that doesn't cut cluster communication. "
            "Use \"stop\" to attempt to immediately stop Pacemaker and stay "
            "stopped, or \"panic\" to attempt to immediately reboot the local "
            "node, falling back to stop on failure."),
    },
    {
        PCMK_OPT_ELECTION_TIMEOUT, NULL, PCMK_VALUE_DURATION, NULL,
        "2min", pcmk__valid_interval_spec,
        pcmk__opt_controld|pcmk__opt_advanced,
        N_("Declare an election failed if it is not decided within this much "
            "time. If you need to adjust this value, it probably indicates "
            "the presence of a bug."),
        NULL,
    },
    {
        PCMK_OPT_SHUTDOWN_ESCALATION, NULL, PCMK_VALUE_DURATION, NULL,
        "20min", pcmk__valid_interval_spec,
        pcmk__opt_controld|pcmk__opt_advanced,
        N_("Exit immediately if shutdown does not complete within this much "
            "time. If you need to adjust this value, it probably indicates "
            "the presence of a bug."),
        NULL,
    },
    {
        PCMK_OPT_JOIN_INTEGRATION_TIMEOUT, "crmd-integration-timeout",
            PCMK_VALUE_DURATION, NULL,
        "3min", pcmk__valid_interval_spec,
        pcmk__opt_controld|pcmk__opt_advanced,
        N_("If you need to adjust this value, it probably indicates "
            "the presence of a bug."),
        NULL,
    },
    {
        PCMK_OPT_JOIN_FINALIZATION_TIMEOUT, "crmd-finalization-timeout",
            PCMK_VALUE_DURATION, NULL,
        "30min", pcmk__valid_interval_spec,
        pcmk__opt_controld|pcmk__opt_advanced,
        N_("If you need to adjust this value, it probably indicates "
            "the presence of a bug."),
        NULL,
    },
    {
        PCMK_OPT_TRANSITION_DELAY, "crmd-transition-delay", PCMK_VALUE_DURATION,
            NULL,
        "0s", pcmk__valid_interval_spec,
        pcmk__opt_controld|pcmk__opt_advanced,
        N_("Enabling this option will slow down cluster recovery under all "
            "conditions"),
        N_("Delay cluster recovery for this much time to allow for additional "
            "events to occur. Useful if your configuration is sensitive to "
            "the order in which ping updates arrive."),
    },
    {
        PCMK_OPT_NO_QUORUM_POLICY, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_STOP ", " PCMK_VALUE_FREEZE ", " PCMK_VALUE_IGNORE
                ", " PCMK_VALUE_DEMOTE ", " PCMK_VALUE_FENCE_LEGACY,
        PCMK_VALUE_STOP, pcmk__valid_no_quorum_policy,
        pcmk__opt_schedulerd,
        N_("What to do when the cluster does not have quorum"),
        NULL,
    },
    {
        PCMK_OPT_SHUTDOWN_LOCK, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether to lock resources to a cleanly shut down node"),
        N_("When true, resources active on a node when it is cleanly shut down "
            "are kept \"locked\" to that node (not allowed to run elsewhere) "
            "until they start again on that node after it rejoins (or for at "
            "most shutdown-lock-limit, if set). Stonith resources and "
            "Pacemaker Remote connections are never locked. Clone and bundle "
            "instances and the promoted role of promotable clones are "
            "currently never locked, though support could be added in a future "
            "release."),
    },
    {
        PCMK_OPT_SHUTDOWN_LOCK_LIMIT, NULL, PCMK_VALUE_DURATION, NULL,
        "0", pcmk__valid_interval_spec,
        pcmk__opt_schedulerd,
        N_("Do not lock resources to a cleanly shut down node longer than "
           "this"),
        N_("If shutdown-lock is true and this is set to a nonzero time "
            "duration, shutdown locks will expire after this much time has "
            "passed since the shutdown was initiated, even if the node has not "
            "rejoined."),
    },
    {
        PCMK_OPT_ENABLE_ACL, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, pcmk__valid_boolean,
        pcmk__opt_based,
        N_("Enable Access Control Lists (ACLs) for the CIB"),
        NULL,
    },
    {
        PCMK_OPT_SYMMETRIC_CLUSTER, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether resources can run on any node by default"),
        NULL,
    },
    {
        PCMK_OPT_MAINTENANCE_MODE, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether the cluster should refrain from monitoring, starting, and "
            "stopping resources"),
        NULL,
    },
    {
        PCMK_OPT_START_FAILURE_IS_FATAL, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether a start failure should prevent a resource from being "
            "recovered on the same node"),
        N_("When true, the cluster will immediately ban a resource from a node "
            "if it fails to start there. When false, the cluster will instead "
            "check the resource's fail count against its migration-threshold.")
    },
    {
        PCMK_OPT_ENABLE_STARTUP_PROBES, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether the cluster should check for active resources during "
            "start-up"),
        NULL,
    },

    // Fencing-related options
    {
        PCMK_OPT_STONITH_ENABLED, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd|pcmk__opt_advanced,
        N_("Whether nodes may be fenced as part of recovery"),
        N_("If false, unresponsive nodes are immediately assumed to be "
            "harmless, and resources that were active on them may be recovered "
            "elsewhere. This can result in a \"split-brain\" situation, "
            "potentially leading to data loss and/or service unavailability."),
    },
    {
        PCMK_OPT_STONITH_ACTION, NULL, PCMK_VALUE_SELECT,
            PCMK_ACTION_REBOOT ", " PCMK_ACTION_OFF ", " PCMK__ACTION_POWEROFF,
        PCMK_ACTION_REBOOT, pcmk__is_fencing_action,
        pcmk__opt_schedulerd,
        N_("Action to send to fence device when a node needs to be fenced "
            "(\"poweroff\" is a deprecated alias for \"off\")"),
        NULL,
    },
    {
        PCMK_OPT_STONITH_TIMEOUT, NULL, PCMK_VALUE_DURATION, NULL,
        "60s", pcmk__valid_interval_spec,
        pcmk__opt_schedulerd,
        N_("How long to wait for on, off, and reboot fence actions to complete "
            "by default"),
        NULL,
    },
    {
        PCMK_OPT_HAVE_WATCHDOG, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, pcmk__valid_boolean,
        pcmk__opt_schedulerd|pcmk__opt_generated,
        N_("Whether watchdog integration is enabled"),
        N_("This is set automatically by the cluster according to whether SBD "
            "is detected to be in use. User-configured values are ignored. "
            "The value `true` is meaningful if diskless SBD is used and "
            "`stonith-watchdog-timeout` is nonzero. In that case, if fencing "
            "is required, watchdog-based self-fencing will be performed via "
            "SBD without requiring a fencing resource explicitly configured."),
    },
    {
        /* @COMPAT Currently, unparsable values default to -1 (auto-calculate),
         * while missing values default to 0 (disable). All values are accepted
         * (unless the controller finds that the value conflicts with the
         * SBD_WATCHDOG_TIMEOUT).
         *
         * At a compatibility break: properly validate as a timeout, let
         * either negative values or a particular string like "auto" mean auto-
         * calculate, and use 0 as the single default for when the option either
         * is unset or fails to validate.
         */
        PCMK_OPT_STONITH_WATCHDOG_TIMEOUT, NULL, PCMK_VALUE_TIMEOUT, NULL,
        "0", NULL,
        pcmk__opt_controld,
        N_("How long before nodes can be assumed to be safely down when "
           "watchdog-based self-fencing via SBD is in use"),
        N_("If this is set to a positive value, lost nodes are assumed to "
           "achieve self-fencing using watchdog-based SBD within this much "
           "time. This does not require a fencing resource to be explicitly "
           "configured, though a fence_watchdog resource can be configured, to "
           "limit use to specific nodes. If this is set to 0 (the default), "
           "the cluster will never assume watchdog-based self-fencing. If this "
           "is set to a negative value, the cluster will use twice the local "
           "value of the `SBD_WATCHDOG_TIMEOUT` environment variable if that "
           "is positive, or otherwise treat this as 0. WARNING: When used, "
           "this timeout must be larger than `SBD_WATCHDOG_TIMEOUT` on all "
           "nodes that use watchdog-based SBD, and Pacemaker will refuse to "
           "start on any of those nodes where this is not true for the local "
           "value or SBD is not active. When this is set to a negative value, "
           "`SBD_WATCHDOG_TIMEOUT` must be set to the same value on all nodes "
           "that use SBD, otherwise data corruption or loss could occur."),
    },
    {
        PCMK_OPT_STONITH_MAX_ATTEMPTS, NULL, PCMK_VALUE_SCORE, NULL,
        "10", pcmk__valid_positive_int,
        pcmk__opt_controld,
        N_("How many times fencing can fail before it will no longer be "
            "immediately re-attempted on a target"),
        NULL,
    },
    {
        PCMK_OPT_CONCURRENT_FENCING, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK__CONCURRENT_FENCING_DEFAULT, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Allow performing fencing operations in parallel"),
        NULL,
    },
    {
        PCMK_OPT_STARTUP_FENCING, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd|pcmk__opt_advanced,
        N_("Whether to fence unseen nodes at start-up"),
        N_("Setting this to false may lead to a \"split-brain\" situation, "
            "potentially leading to data loss and/or service unavailability."),
    },
    {
        PCMK_OPT_PRIORITY_FENCING_DELAY, NULL, PCMK_VALUE_DURATION, NULL,
        "0", pcmk__valid_interval_spec,
        pcmk__opt_schedulerd,
        N_("Apply fencing delay targeting the lost nodes with the highest "
            "total resource priority"),
        N_("Apply specified delay for the fencings that are targeting the lost "
            "nodes with the highest total resource priority in case we don't "
            "have the majority of the nodes in our cluster partition, so that "
            "the more significant nodes potentially win any fencing match, "
            "which is especially meaningful under split-brain of 2-node "
            "cluster. A promoted resource instance takes the base priority + 1 "
            "on calculation if the base priority is not 0. Any static/random "
            "delays that are introduced by `pcmk_delay_base/max` configured "
            "for the corresponding fencing resources will be added to this "
            "delay. This delay should be significantly greater than, safely "
            "twice, the maximum `pcmk_delay_base/max`. By default, priority "
            "fencing delay is disabled."),
    },
    {
        PCMK_OPT_NODE_PENDING_TIMEOUT, NULL, PCMK_VALUE_DURATION, NULL,
        "0", pcmk__valid_interval_spec,
        pcmk__opt_schedulerd,
        N_("How long to wait for a node that has joined the cluster to join "
           "the controller process group"),
        N_("Fence nodes that do not join the controller process group within "
           "this much time after joining the cluster, to allow the cluster "
           "to continue managing resources. A value of 0 means never fence "
           "pending nodes. Setting the value to 2h means fence nodes after "
           "2 hours."),
    },
    {
        PCMK_OPT_CLUSTER_DELAY, NULL, PCMK_VALUE_DURATION, NULL,
        "60s", pcmk__valid_interval_spec,
        pcmk__opt_schedulerd,
        N_("Maximum time for node-to-node communication"),
        N_("The node elected Designated Controller (DC) will consider an action "
            "failed if it does not get a response from the node executing the "
            "action within this time (after considering the action's own "
            "timeout). The \"correct\" value will depend on the speed and "
            "load of your network and cluster nodes.")
    },

    // Limits
    {
        PCMK_OPT_LOAD_THRESHOLD, NULL, PCMK_VALUE_PERCENTAGE, NULL,
        "80%", pcmk__valid_percentage,
        pcmk__opt_controld,
        N_("Maximum amount of system load that should be used by cluster "
            "nodes"),
        N_("The cluster will slow down its recovery process when the amount of "
            "system resources used (currently CPU) approaches this limit"),
    },
    {
        PCMK_OPT_NODE_ACTION_LIMIT, NULL, PCMK_VALUE_INTEGER, NULL,
        "0", pcmk__valid_int,
        pcmk__opt_controld,
        N_("Maximum number of jobs that can be scheduled per node (defaults to "
            "2x cores)"),
        NULL,
    },
    {
        PCMK_OPT_BATCH_LIMIT, NULL, PCMK_VALUE_INTEGER, NULL,
        "0", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("Maximum number of jobs that the cluster may execute in parallel "
            "across all nodes"),
        N_("The \"correct\" value will depend on the speed and load of your "
            "network and cluster nodes. If set to 0, the cluster will "
            "impose a dynamically calculated limit when any node has a "
            "high load."),
    },
    {
        PCMK_OPT_MIGRATION_LIMIT, NULL, PCMK_VALUE_INTEGER, NULL,
        "-1", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The number of live migration actions that the cluster is allowed "
            "to execute in parallel on a node (-1 means no limit)"),
        NULL,
    },
    {
        /* @TODO This is actually ignored if not strictly positive. We should
         * overhaul value types in Pacemaker Explained. There are lots of
         * inaccurate ranges (assumptions of 32-bit width, "nonnegative" when
         * positive is required, etc.).
         *
         * Maybe a single integer type with the allowed range specified would be
         * better.
         *
         * Drop the PCMK_VALUE_NONNEGATIVE_INTEGER constant if we do this before
         * a release.
         */
        PCMK_OPT_CLUSTER_IPC_LIMIT, NULL, PCMK_VALUE_NONNEGATIVE_INTEGER, NULL,
        "500", pcmk__valid_positive_int,
        pcmk__opt_based,
        N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
        N_("Raise this if log has \"Evicting client\" messages for cluster "
            "daemon PIDs (a good value is the number of resources in the "
            "cluster multiplied by the number of nodes)."),
    },

    // Orphans and stopping
    {
        PCMK_OPT_STOP_ALL_RESOURCES, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether the cluster should stop all active resources"),
        NULL,
    },
    {
        PCMK_OPT_STOP_ORPHAN_RESOURCES, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether to stop resources that were removed from the "
            "configuration"),
        NULL,
    },
    {
        PCMK_OPT_STOP_ORPHAN_ACTIONS, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, pcmk__valid_boolean,
        pcmk__opt_schedulerd,
        N_("Whether to cancel recurring actions removed from the "
            "configuration"),
        NULL,
    },
    {
        PCMK__OPT_REMOVE_AFTER_STOP, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, pcmk__valid_boolean,
        pcmk__opt_schedulerd|pcmk__opt_deprecated,
        N_("Whether to remove stopped resources from the executor"),
        N_("Values other than default are poorly tested and potentially "
            "dangerous."),
    },

    // Storing inputs
    {
        PCMK_OPT_PE_ERROR_SERIES_MAX, NULL, PCMK_VALUE_INTEGER, NULL,
        "-1", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The number of scheduler inputs resulting in errors to save"),
        N_("Zero to disable, -1 to store unlimited."),
    },
    {
        PCMK_OPT_PE_WARN_SERIES_MAX, NULL, PCMK_VALUE_INTEGER, NULL,
        "5000", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The number of scheduler inputs resulting in warnings to save"),
        N_("Zero to disable, -1 to store unlimited."),
    },
    {
        PCMK_OPT_PE_INPUT_SERIES_MAX, NULL, PCMK_VALUE_INTEGER, NULL,
        "4000", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The number of scheduler inputs without errors or warnings to save"),
        N_("Zero to disable, -1 to store unlimited."),
    },

    // Node health
    {
        PCMK_OPT_NODE_HEALTH_STRATEGY, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_NONE ", " PCMK_VALUE_MIGRATE_ON_RED ", "
                PCMK_VALUE_ONLY_GREEN ", " PCMK_VALUE_PROGRESSIVE ", "
                PCMK_VALUE_CUSTOM,
        PCMK_VALUE_NONE, pcmk__validate_health_strategy,
        pcmk__opt_schedulerd,
        N_("How cluster should react to node health attributes"),
        N_("Requires external entities to create node attributes (named with "
            "the prefix \"#health\") with values \"red\", \"yellow\", or "
            "\"green\".")
    },
    {
        PCMK_OPT_NODE_HEALTH_BASE, NULL, PCMK_VALUE_SCORE, NULL,
        "0", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("Base health score assigned to a node"),
        N_("Only used when \"node-health-strategy\" is set to "
            "\"progressive\"."),
    },
    {
        PCMK_OPT_NODE_HEALTH_GREEN, NULL, PCMK_VALUE_SCORE, NULL,
        "0", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The score to use for a node health attribute whose value is "
            "\"green\""),
        N_("Only used when \"node-health-strategy\" is set to \"custom\" or "
            "\"progressive\"."),
    },
    {
        PCMK_OPT_NODE_HEALTH_YELLOW, NULL, PCMK_VALUE_SCORE, NULL,
        "0", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The score to use for a node health attribute whose value is "
            "\"yellow\""),
        N_("Only used when \"node-health-strategy\" is set to \"custom\" or "
            "\"progressive\"."),
    },
    {
        PCMK_OPT_NODE_HEALTH_RED, NULL, PCMK_VALUE_SCORE, NULL,
        "-INFINITY", pcmk__valid_int,
        pcmk__opt_schedulerd,
        N_("The score to use for a node health attribute whose value is "
            "\"red\""),
        N_("Only used when \"node-health-strategy\" is set to \"custom\" or "
            "\"progressive\".")
    },

    // Placement strategy
    {
        PCMK_OPT_PLACEMENT_STRATEGY, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_DEFAULT ", " PCMK_VALUE_UTILIZATION ", "
                PCMK_VALUE_MINIMAL ", " PCMK_VALUE_BALANCED,
        PCMK_VALUE_DEFAULT, pcmk__valid_placement_strategy,
        pcmk__opt_schedulerd,
        N_("How the cluster should allocate resources to nodes"),
        NULL,
    },

    { NULL, },
};

static const pcmk__cluster_option_t fencing_params[] = {
    /* name, old name, type, allowed values,
     * default value, validator,
     * flags,
     * short description,
     * long description
     */
    {
        PCMK_STONITH_HOST_ARGUMENT, NULL, PCMK_VALUE_STRING, NULL,
        "port", NULL,
        pcmk__opt_advanced,
        N_("An alternate parameter to supply instead of 'port'"),
        N_("Some devices do not support the standard 'port' parameter or may "
            "provide additional ones. Use this to specify an alternate, device-"
            "specific, parameter that should indicate the machine to be "
            "fenced. A value of \"none\" can be used to tell the cluster not "
            "to supply any additional parameters."),
    },
    {
        PCMK_STONITH_HOST_MAP, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("A mapping of node names to port numbers for devices that do not "
            "support node names."),
        N_("For example, \"node1:1;node2:2,3\" would tell the cluster to use "
            "port 1 for node1 and ports 2 and 3 for node2."),
    },
    {
        PCMK_STONITH_HOST_LIST, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("Nodes targeted by this device"),
        N_("Comma-separated list of nodes that can be targeted by this device "
           "(for example, \"node1,node2,node3\"). If pcmk_host_check is "
           "\"static-list\", either this or pcmk_host_map must be set."),
    },
    {
        PCMK_STONITH_HOST_CHECK, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_DYNAMIC_LIST ", " PCMK_VALUE_STATIC_LIST ", "
            PCMK_VALUE_STATUS ", " PCMK_VALUE_NONE,
        NULL, NULL,
        pcmk__opt_none,
        N_("How to determine which nodes can be targeted by the device"),
        N_("Use \"dynamic-list\" to query the device via the 'list' command; "
            "\"static-list\" to check the pcmk_host_list attribute; "
            "\"status\" to query the device via the 'status' command; or "
            "\"none\" to assume every device can fence every node. "
            "The default value is \"static-list\" if pcmk_host_map or "
            "pcmk_host_list is set; otherwise \"dynamic-list\" if the device "
            "supports the list operation; otherwise \"status\" if the device "
            "supports the status operation; otherwise \"none\""),
    },
    {
        PCMK_STONITH_DELAY_MAX, NULL, PCMK_VALUE_DURATION, NULL,
        "0s", NULL,
        pcmk__opt_none,
        N_("Enable a delay of no more than the time specified before executing "
            "fencing actions."),
        N_("Enable a delay of no more than the time specified before executing "
            "fencing actions. Pacemaker derives the overall delay by taking "
            "the value of pcmk_delay_base and adding a random delay value such "
            "that the sum is kept below this maximum."),
    },
    {
        PCMK_STONITH_DELAY_BASE, NULL, PCMK_VALUE_STRING, NULL,
        "0s", NULL,
        pcmk__opt_none,
        N_("Enable a base delay for fencing actions and specify base delay "
            "value."),
        N_("This enables a static delay for fencing actions, which can help "
            "avoid \"death matches\" where two nodes try to fence each other "
            "at the same time. If pcmk_delay_max is also used, a random delay "
            "will be added such that the total delay is kept below that value. "
            "This can be set to a single time value to apply to any node "
            "targeted by this device (useful if a separate device is "
            "configured for each target), or to a node map (for example, "
            "\"node1:1s;node2:5\") to set a different value for each target."),
    },
    {
        PCMK_STONITH_ACTION_LIMIT, NULL, PCMK_VALUE_INTEGER, NULL,
        "1", NULL,
        pcmk__opt_none,
        N_("The maximum number of actions can be performed in parallel on this "
            "device"),
        N_("Cluster property concurrent-fencing=\"true\" needs to be "
            "configured first. Then use this to specify the maximum number of "
            "actions can be performed in parallel on this device. A value of "
            "-1 means an unlimited number of actions can be performed in "
            "parallel."),
    },
    {
        "pcmk_reboot_action", NULL, PCMK_VALUE_STRING, NULL,
        PCMK_ACTION_REBOOT, NULL,
        pcmk__opt_advanced,
        N_("An alternate command to run instead of 'reboot'"),
        N_("Some devices do not support the standard commands or may provide "
            "additional ones. Use this to specify an alternate, device-"
            "specific, command that implements the 'reboot' action."),
    },
    {
        "pcmk_reboot_timeout", NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_advanced,
        N_("Specify an alternate timeout to use for 'reboot' actions instead "
            "of stonith-timeout"),
        N_("Some devices need much more/less time to complete than normal. "
            "Use this to specify an alternate, device-specific, timeout for "
            "'reboot' actions."),
    },
    {
        "pcmk_reboot_retries", NULL, PCMK_VALUE_INTEGER, NULL,
        "2", NULL,
        pcmk__opt_advanced,
        N_("The maximum number of times to try the 'reboot' command within the "
            "timeout period"),
        N_("Some devices do not support multiple connections. Operations may "
            "\"fail\" if the device is busy with another task. In that case, "
            "Pacemaker will automatically retry the operation if there is time "
            "remaining. Use this option to alter the number of times Pacemaker "
            "tries a 'reboot' action before giving up."),
    },
    {
        "pcmk_off_action", NULL, PCMK_VALUE_STRING, NULL,
        PCMK_ACTION_OFF, NULL,
        pcmk__opt_advanced,
        N_("An alternate command to run instead of 'off'"),
        N_("Some devices do not support the standard commands or may provide "
            "additional ones. Use this to specify an alternate, device-"
            "specific, command that implements the 'off' action."),
    },
    {
        "pcmk_off_timeout", NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_advanced,
        N_("Specify an alternate timeout to use for 'off' actions instead of "
            "stonith-timeout"),
        N_("Some devices need much more/less time to complete than normal. "
            "Use this to specify an alternate, device-specific, timeout for "
            "'off' actions."),
    },
    {
        "pcmk_off_retries", NULL, PCMK_VALUE_INTEGER, NULL,
        "2", NULL,
        pcmk__opt_advanced,
        N_("The maximum number of times to try the 'off' command within the "
            "timeout period"),
        N_("Some devices do not support multiple connections. Operations may "
            "\"fail\" if the device is busy with another task. In that case, "
            "Pacemaker will automatically retry the operation if there is time "
            "remaining. Use this option to alter the number of times Pacemaker "
            "tries a 'off' action before giving up."),
    },
    {
        "pcmk_on_action", NULL, PCMK_VALUE_STRING, NULL,
        PCMK_ACTION_ON, NULL,
        pcmk__opt_advanced,
        N_("An alternate command to run instead of 'on'"),
        N_("Some devices do not support the standard commands or may provide "
            "additional ones. Use this to specify an alternate, device-"
            "specific, command that implements the 'on' action."),
    },
    {
        "pcmk_on_timeout", NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_advanced,
        N_("Specify an alternate timeout to use for 'on' actions instead of "
            "stonith-timeout"),
        N_("Some devices need much more/less time to complete than normal. "
            "Use this to specify an alternate, device-specific, timeout for "
            "'on' actions."),
    },
    {
        "pcmk_on_retries", NULL, PCMK_VALUE_INTEGER, NULL,
        "2", NULL,
        pcmk__opt_advanced,
        N_("The maximum number of times to try the 'on' command within the "
            "timeout period"),
        N_("Some devices do not support multiple connections. Operations may "
            "\"fail\" if the device is busy with another task. In that case, "
            "Pacemaker will automatically retry the operation if there is time "
            "remaining. Use this option to alter the number of times Pacemaker "
            "tries a 'on' action before giving up."),
    },
    {
        "pcmk_list_action", NULL, PCMK_VALUE_STRING, NULL,
        PCMK_ACTION_LIST, NULL,
        pcmk__opt_advanced,
        N_("An alternate command to run instead of 'list'"),
        N_("Some devices do not support the standard commands or may provide "
            "additional ones. Use this to specify an alternate, device-"
            "specific, command that implements the 'list' action."),
    },
    {
        "pcmk_list_timeout", NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_advanced,
        N_("Specify an alternate timeout to use for 'list' actions instead of "
            "stonith-timeout"),
        N_("Some devices need much more/less time to complete than normal. "
            "Use this to specify an alternate, device-specific, timeout for "
            "'list' actions."),
    },
    {
        "pcmk_list_retries", NULL, PCMK_VALUE_INTEGER, NULL,
        "2", NULL,
        pcmk__opt_advanced,
        N_("The maximum number of times to try the 'list' command within the "
            "timeout period"),
        N_("Some devices do not support multiple connections. Operations may "
            "\"fail\" if the device is busy with another task. In that case, "
            "Pacemaker will automatically retry the operation if there is time "
            "remaining. Use this option to alter the number of times Pacemaker "
            "tries a 'list' action before giving up."),
    },
    {
        "pcmk_monitor_action", NULL, PCMK_VALUE_STRING, NULL,
        PCMK_ACTION_MONITOR, NULL,
        pcmk__opt_advanced,
        N_("An alternate command to run instead of 'monitor'"),
        N_("Some devices do not support the standard commands or may provide "
            "additional ones. Use this to specify an alternate, device-"
            "specific, command that implements the 'monitor' action."),
    },
    {
        "pcmk_monitor_timeout", NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_advanced,
        N_("Specify an alternate timeout to use for 'monitor' actions instead "
            "of stonith-timeout"),
        N_("Some devices need much more/less time to complete than normal. "
            "Use this to specify an alternate, device-specific, timeout for "
            "'monitor' actions."),
    },
    {
        "pcmk_monitor_retries", NULL, PCMK_VALUE_INTEGER, NULL,
        "2", NULL,
        pcmk__opt_advanced,
        N_("The maximum number of times to try the 'monitor' command within "
            "the timeout period"),
        N_("Some devices do not support multiple connections. Operations may "
            "\"fail\" if the device is busy with another task. In that case, "
            "Pacemaker will automatically retry the operation if there is time "
            "remaining. Use this option to alter the number of times Pacemaker "
            "tries a 'monitor' action before giving up."),
    },
    {
        "pcmk_status_action", NULL, PCMK_VALUE_STRING, NULL,
        PCMK_ACTION_STATUS, NULL,
        pcmk__opt_advanced,
        N_("An alternate command to run instead of 'status'"),
        N_("Some devices do not support the standard commands or may provide "
            "additional ones. Use this to specify an alternate, device-"
            "specific, command that implements the 'status' action."),
    },
    {
        "pcmk_status_timeout", NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_advanced,
        N_("Specify an alternate timeout to use for 'status' actions instead "
            "of stonith-timeout"),
        N_("Some devices need much more/less time to complete than normal. "
            "Use this to specify an alternate, device-specific, timeout for "
            "'status' actions."),
    },
    {
        "pcmk_status_retries", NULL, PCMK_VALUE_INTEGER, NULL,
        "2", NULL,
        pcmk__opt_advanced,
        N_("The maximum number of times to try the 'status' command within "
            "the timeout period"),
        N_("Some devices do not support multiple connections. Operations may "
            "\"fail\" if the device is busy with another task. In that case, "
            "Pacemaker will automatically retry the operation if there is time "
            "remaining. Use this option to alter the number of times Pacemaker "
            "tries a 'status' action before giving up."),
    },

    { NULL, },
};

static const pcmk__cluster_option_t primitive_meta[] = {
    /* name, old name, type, allowed values,
     * default value, validator,
     * flags,
     * short description,
     * long description
     */
    {
        PCMK_META_PRIORITY, NULL, PCMK_VALUE_SCORE, NULL,
        "0", NULL,
        pcmk__opt_none,
        N_("Resource assignment priority"),
        N_("If not all resources can be active, the cluster will stop "
            "lower-priority resources in order to keep higher-priority ones "
            "active."),
    },
    {
        PCMK_META_CRITICAL, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, NULL,
        pcmk__opt_none,
        N_("Default value for influence in colocation constraints"),
        N_("Use this value as the default for influence in all colocation "
            "constraints involving this resource, as well as in the implicit "
            "colocation constraints created if this resource is in a group."),
    },
    {
        PCMK_META_TARGET_ROLE, NULL, PCMK_VALUE_SELECT,
            PCMK_ROLE_STOPPED ", " PCMK_ROLE_STARTED ", "
            PCMK_ROLE_UNPROMOTED ", " PCMK_ROLE_PROMOTED,
        PCMK_ROLE_STARTED, NULL,
        pcmk__opt_none,
        N_("State the cluster should attempt to keep this resource in"),
        N_("\"Stopped\" forces the resource to be stopped. "
            "\"Started\" allows the resource to be started (and in the case of "
            "promotable clone resources, promoted if appropriate). "
            "\"Unpromoted\" allows the resource to be started, but only in the "
            "unpromoted role if the resource is promotable. "
            "\"Promoted\" is equivalent to \"Started\"."),
    },
    {
        PCMK_META_IS_MANAGED, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, NULL,
        pcmk__opt_none,
        N_("Whether the cluster is allowed to actively change the resource's "
            "state"),
        N_("If false, the cluster will not start, stop, promote, or demote the "
            "resource on any node. Recurring actions for the resource are "
            "unaffected. If true, a true value for the maintenance-mode "
            "cluster option, the maintenance node attribute, or the "
            "maintenance resource meta-attribute overrides this."),
    },
    {
        PCMK_META_MAINTENANCE, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, NULL,
        pcmk__opt_none,
        N_("If true, the cluster will not schedule any actions involving the "
            "resource"),
        N_("If true, the cluster will not start, stop, promote, or demote the "
            "resource on any node, and will pause any recurring monitors "
            "(except those specifying role as \"Stopped\"). If false, a true "
            "value for the maintenance-mode cluster option or maintenance node "
            "attribute overrides this."),
    },
    {
        PCMK_META_RESOURCE_STICKINESS, NULL, PCMK_VALUE_SCORE, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("Score to add to the current node when a resource is already "
            "active"),
        N_("Score to add to the current node when a resource is already "
            "active. This allows running resources to stay where they are, "
            "even if they would be placed elsewhere if they were being started "
            "from a stopped state. "
            "The default is 1 for individual clone instances, and 0 for all "
            "other resources."),
    },
    {
        PCMK_META_REQUIRES, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_NOTHING ", " PCMK_VALUE_QUORUM ", "
            PCMK_VALUE_FENCING ", " PCMK_VALUE_UNFENCING,
        NULL, NULL,
        pcmk__opt_none,
        N_("Conditions under which the resource can be started"),
        N_("Conditions under which the resource can be started. "
            "\"nothing\" means the cluster can always start this resource. "
            "\"quorum\" means the cluster can start this resource only if a "
            "majority of the configured nodes are active. "
            "\"fencing\" means the cluster can start this resource only if a "
            "majority of the configured nodes are active and any failed or "
            "unknown nodes have been fenced. "
            "\"unfencing\" means the cluster can start this resource only if "
            "a majority of the configured nodes are active and any failed or "
            "unknown nodes have been fenced, and only on nodes that have been "
            "unfenced. "
            "The default is \"quorum\" for resources with a class of stonith; "
            "otherwise, \"unfencing\" if unfencing is active in the cluster; "
            "otherwise, \"fencing\" if the stonith-enabled cluster option is "
            "true; "
            "otherwise, \"quorum\"."),
    },
    {
        PCMK_META_MIGRATION_THRESHOLD, NULL, PCMK_VALUE_SCORE, NULL,
        PCMK_VALUE_INFINITY, NULL,
        pcmk__opt_none,
        N_("Number of failures on a node before the resource becomes "
            "ineligible to run there."),
        N_("Number of failures that may occur for this resource on a node, "
            "before that node is marked ineligible to host this resource. A "
            "value of 0 indicates that this feature is disabled (the node will "
            "never be marked ineligible). By contrast, the cluster treats "
            "\"INFINITY\" (the default) as a very large but finite number. "
            "This option has an effect only if the failed operation specifies "
            "its on-fail attribute as \"restart\" (the default), and "
            "additionally for failed start operations, if the "
            "start-failure-is-fatal cluster property is set to false."),
    },
    {
        PCMK_META_FAILURE_TIMEOUT, NULL, PCMK_VALUE_DURATION, NULL,
        "0", NULL,
        pcmk__opt_none,
        N_("Number of seconds before acting as if a failure had not occurred"),
        N_("Number of seconds after a failed action for this resource before "
            "acting as if the failure had not occurred, and potentially "
            "allowing the resource back to the node on which it failed. "
            "A value of 0 indicates that this feature is disabled."),
    },
    {
        PCMK_META_MULTIPLE_ACTIVE, NULL, PCMK_VALUE_SELECT,
            PCMK_VALUE_BLOCK ", " PCMK_VALUE_STOP_ONLY ", "
            PCMK_VALUE_STOP_START ", " PCMK_VALUE_STOP_UNEXPECTED,
        PCMK_VALUE_STOP_START, NULL,
        pcmk__opt_none,
        N_("What to do if the cluster finds the resource active on more than "
            "one node"),
        N_("What to do if the cluster finds the resource active on more than "
            "one node. "
            "\"block\" means to mark the resource as unmanaged. "
            "\"stop_only\" means to stop all active instances of this resource "
            "and leave them stopped. "
            "\"stop_start\" means to stop all active instances of this "
            "resource and start the resource in one location only. "
            "\"stop_unexpected\" means to stop all active instances of this "
            "resource except where the resource should be active. (This should "
            "be used only when extra instances are not expected to disrupt "
            "existing instances, and the resource agent's monitor of an "
            "existing instance is capable of detecting any problems that could "
            "be caused. Note that any resources ordered after this one will "
            "still need to be restarted.)"),
    },
    {
        PCMK_META_ALLOW_MIGRATE, NULL, PCMK_VALUE_BOOLEAN, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("Whether the cluster should try to \"live migrate\" this resource "
            "when it needs to be moved"),
        N_("Whether the cluster should try to \"live migrate\" this resource "
            "when it needs to be moved. "
            "The default is true for ocf:pacemaker:remote resources, and false "
            "otherwise."),
    },
    {
        PCMK_META_ALLOW_UNHEALTHY_NODES, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_FALSE, NULL,
        pcmk__opt_none,
        N_("Whether the resource should be allowed to run on a node even if "
            "the node's health score would otherwise prevent it"),
        NULL,
    },
    {
        PCMK_META_CONTAINER_ATTRIBUTE_TARGET, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("Where to check user-defined node attributes"),
        N_("Whether to check user-defined node attributes on the physical host "
            "where a container is running or on the local node. This is "
            "usually set for a bundle resource and inherited by the bundle's "
            "primitive resource. "
            "A value of \"host\" means to check user-defined node attributes "
            "on the underlying physical host. Any other value means to check "
            "user-defined node attributes on the local node (for a bundled "
            "primitive resource, this is the bundle node)."),
    },
    {
        PCMK_META_REMOTE_NODE, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("Name of the Pacemaker Remote guest node this resource is "
            "associated with, if any"),
        N_("Name of the Pacemaker Remote guest node this resource is "
            "associated with, if any. If specified, this both enables the "
            "resource as a guest node and defines the unique name used to "
            "identify the guest node. The guest must be configured to run the "
            "Pacemaker Remote daemon when it is started. "
            "WARNING: This value cannot overlap with any resource or node "
            "IDs."),
    },
    {
        PCMK_META_REMOTE_ADDR, NULL, PCMK_VALUE_STRING, NULL,
        NULL, NULL,
        pcmk__opt_none,
        N_("If remote-node is specified, the IP address or hostname used to "
            "connect to the guest via Pacemaker Remote"),
        N_("If remote-node is specified, the IP address or hostname used to "
            "connect to the guest via Pacemaker Remote. The Pacemaker Remote "
            "daemon on the guest must be configured to accept connections on "
            "this address. "
            "The default is the value of the remote-node meta-attribute."),
    },
    {
        PCMK_META_REMOTE_PORT, NULL, PCMK_VALUE_PORT, NULL,
        "3121", NULL,
        pcmk__opt_none,
        N_("If remote-node is specified, port on the guest used for its "
            "Pacemaker Remote connection"),
        N_("If remote-node is specified, the port on the guest used for its "
            "Pacemaker Remote connection. The Pacemaker Remote daemon on the "
            "guest must be configured to listen on this port."),
    },
    {
        PCMK_META_REMOTE_CONNECT_TIMEOUT, NULL, PCMK_VALUE_TIMEOUT, NULL,
        "60s", NULL,
        pcmk__opt_none,
        N_("If remote-node is specified, how long before a pending Pacemaker "
            "Remote guest connection times out."),
        NULL,
    },
    {
        PCMK_META_REMOTE_ALLOW_MIGRATE, NULL, PCMK_VALUE_BOOLEAN, NULL,
        PCMK_VALUE_TRUE, NULL,
        pcmk__opt_none,
        N_("If remote-node is specified, this acts as the allow-migrate "
            "meta-attribute for the implicit remote connection resource "
            "(ocf:pacemaker:remote)."),
        NULL,
    },

    { NULL, },
};

/*
 * Environment variable option handling
 */

/*!
 * \internal
 * \brief Get the value of a Pacemaker environment variable option
 *
 * If an environment variable option is set, with either a PCMK_ or (for
 * backward compatibility) HA_ prefix, log and return the value.
 *
 * \param[in] option  Environment variable name (without prefix)
 *
 * \return Value of environment variable option, or NULL in case of
 *         option name too long or value not found
 */
const char *
pcmk__env_option(const char *option)
{
    const char *const prefixes[] = {"PCMK_", "HA_"};
    char env_name[NAME_MAX];
    const char *value = NULL;

    CRM_CHECK(!pcmk__str_empty(option), return NULL);

    for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
        int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);

        if (rv < 0) {
            crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
                    strerror(errno));
            return NULL;
        }

        if (rv >= sizeof(env_name)) {
            crm_trace("\"%s%s\" is too long", prefixes[i], option);
            continue;
        }

        value = getenv(env_name);
        if (value != NULL) {
            crm_trace("Found %s = %s", env_name, value);
            return value;
        }
    }

    crm_trace("Nothing found for %s", option);
    return NULL;
}

/*!
 * \brief Set or unset a Pacemaker environment variable option
 *
 * Set an environment variable option with a \c "PCMK_" prefix and optionally
 * an \c "HA_" prefix for backward compatibility.
 *
 * \param[in] option  Environment variable name (without prefix)
 * \param[in] value   New value (or NULL to unset)
 * \param[in] compat  If false and \p value is not \c NULL, set only
 *                    \c "PCMK_<option>"; otherwise, set (or unset) both
 *                    \c "PCMK_<option>" and \c "HA_<option>"
 *
 * \note \p compat is ignored when \p value is \c NULL. A \c NULL \p value
 *       means we're unsetting \p option. \c pcmk__get_env_option() checks for
 *       both prefixes, so we want to clear them both.
 */
void
pcmk__set_env_option(const char *option, const char *value, bool compat)
{
    // @COMPAT Drop support for "HA_" options eventually
    const char *const prefixes[] = {"PCMK_", "HA_"};
    char env_name[NAME_MAX];

    CRM_CHECK(!pcmk__str_empty(option) && (strchr(option, '=') == NULL),
              return);

    for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
        int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);

        if (rv < 0) {
            crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
                    strerror(errno));
            return;
        }

        if (rv >= sizeof(env_name)) {
            crm_trace("\"%s%s\" is too long", prefixes[i], option);
            continue;
        }

        if (value != NULL) {
            crm_trace("Setting %s to %s", env_name, value);
            rv = setenv(env_name, value, 1);
        } else {
            crm_trace("Unsetting %s", env_name);
            rv = unsetenv(env_name);
        }

        if (rv < 0) {
            crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un",
                    env_name, strerror(errno));
        }

        if (!compat && (value != NULL)) {
            // For set, don't proceed to HA_<option> unless compat is enabled
            break;
        }
    }
}

/*!
 * \internal
 * \brief Check whether Pacemaker environment variable option is enabled
 *
 * Given a Pacemaker environment variable option that can either be boolean
 * or a list of daemon names, return true if the option is enabled for a given
 * daemon.
 *
 * \param[in] daemon   Daemon name (can be NULL)
 * \param[in] option   Pacemaker environment variable name
 *
 * \return true if variable is enabled for daemon, otherwise false
 */
bool
pcmk__env_option_enabled(const char *daemon, const char *option)
{
    const char *value = pcmk__env_option(option);

    return (value != NULL)
        && (crm_is_true(value)
            || ((daemon != NULL) && (strstr(value, daemon) != NULL)));
}


/*
 * Cluster option handling
 */

/*!
 * \internal
 * \brief Check whether a string represents a valid interval specification
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid interval specification, or \c false
 *         otherwise
 */
bool
pcmk__valid_interval_spec(const char *value)
{
    return pcmk_parse_interval_spec(value, NULL) == pcmk_rc_ok;
}

/*!
 * \internal
 * \brief Check whether a string represents a valid boolean value
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid boolean value, or \c false otherwise
 */
bool
pcmk__valid_boolean(const char *value)
{
    return crm_str_to_boolean(value, NULL) == 1;
}

/*!
 * \internal
 * \brief Check whether a string represents a valid integer
 *
 * Valid values include \c INFINITY, \c -INFINITY, and all 64-bit integers.
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid integer, or \c false otherwise
 */
bool
pcmk__valid_int(const char *value)
{
    return (value != NULL)
           && (pcmk_str_is_infinity(value)
               || pcmk_str_is_minus_infinity(value)
               || (pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok));
}

/*!
 * \internal
 * \brief Check whether a string represents a valid positive integer
 *
 * Valid values include \c INFINITY and all 64-bit positive integers.
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid positive integer, or \c false
 *         otherwise
 */
bool
pcmk__valid_positive_int(const char *value)
{
    long long num = 0LL;

    return pcmk_str_is_infinity(value)
           || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok)
               && (num > 0));
}

/*!
 * \internal
 * \brief Check whether a string represents a valid
 *        \c PCMK__OPT_NO_QUORUM_POLICY value
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid \c PCMK__OPT_NO_QUORUM_POLICY value,
 *         or \c false otherwise
 */
bool
pcmk__valid_no_quorum_policy(const char *value)
{
    return pcmk__strcase_any_of(value,
                                PCMK_VALUE_STOP, PCMK_VALUE_FREEZE,
                                PCMK_VALUE_IGNORE, PCMK_VALUE_DEMOTE,
                                PCMK_VALUE_FENCE_LEGACY, NULL);
}

/*!
 * \internal
 * \brief Check whether a string represents a valid percentage
 *
 * Valid values include long integers, with an optional trailing string
 * beginning with '%'.
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid percentage value, or \c false
 *         otherwise
 */
bool
pcmk__valid_percentage(const char *value)
{
    char *end = NULL;
    float number = strtof(value, &end);

    return ((end == NULL) || (end[0] == '%')) && (number >= 0);
}

/*!
 * \internal
 * \brief Check whether a string represents a valid placement strategy
 *
 * \param[in] value  String to validate
 *
 * \return \c true if \p value is a valid placement strategy, or \c false
 *         otherwise
 */
bool
pcmk__valid_placement_strategy(const char *value)
{
    return pcmk__strcase_any_of(value,
                                PCMK_VALUE_DEFAULT, PCMK_VALUE_UTILIZATION,
                                PCMK_VALUE_MINIMAL, PCMK_VALUE_BALANCED, NULL);
}

/*!
 * \internal
 * \brief Check a table of configured options for a particular option
 *
 * \param[in,out] table   Name/value pairs for configured options
 * \param[in]     option  Option to look up
 *
 * \return Option value (from supplied options table or default value)
 */
static const char *
cluster_option_value(GHashTable *table, const pcmk__cluster_option_t *option)
{
    const char *value = NULL;

    CRM_ASSERT((option != NULL) && (option->name != NULL));

    if (table != NULL) {
        value = g_hash_table_lookup(table, option->name);

        if ((value == NULL) && (option->alt_name != NULL)) {
            value = g_hash_table_lookup(table, option->alt_name);
            if (value != NULL) {
                pcmk__config_warn("Support for legacy name '%s' for cluster "
                                  "option '%s' is deprecated and will be "
                                  "removed in a future release",
                                  option->alt_name, option->name);

                // Inserting copy with current name ensures we only warn once
                pcmk__insert_dup(table, option->name, value);
            }
        }

        if ((value != NULL) && (option->is_valid != NULL)
            && !option->is_valid(value)) {

            pcmk__config_err("Using default value for cluster option '%s' "
                             "because '%s' is invalid", option->name, value);
            value = NULL;
        }

        if (value != NULL) {
            return value;
        }
    }

    // No value found, use default
    value = option->default_value;

    if (value == NULL) {
        crm_trace("No value or default provided for cluster option '%s'",
                  option->name);
        return NULL;
    }

    CRM_CHECK((option->is_valid == NULL) || option->is_valid(value),
              crm_err("Bug: default value for cluster option '%s' is invalid",
                      option->name);
              return NULL);

    crm_trace("Using default value '%s' for cluster option '%s'",
              value, option->name);
    if (table != NULL) {
        pcmk__insert_dup(table, option->name, value);
    }
    return value;
}

/*!
 * \internal
 * \brief Get the value of a cluster option
 *
 * \param[in,out] options  Name/value pairs for configured options
 * \param[in]     name     (Primary) option name to look for
 *
 * \return Option value
 */
const char *
pcmk__cluster_option(GHashTable *options, const char *name)
{
    for (const pcmk__cluster_option_t *option = cluster_options;
         option->name != NULL; option++) {

        if (pcmk__str_eq(name, option->name, pcmk__str_casei)) {
            return cluster_option_value(options, option);
        }
    }
    CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
    return NULL;
}

/*!
 * \internal
 * \brief Output cluster option metadata as OCF-like XML
 *
 * \param[in,out] out         Output object
 * \param[in]     name        Fake resource agent name for the option list
 * \param[in]     desc_short  Short description of the option list
 * \param[in]     desc_long   Long description of the option list
 * \param[in]     filter      Group of <tt>enum pcmk__opt_flags</tt>; output an
 *                            option only if its \c flags member has all these
 *                            flags set
 * \param[in]     all         If \c true, output all options; otherwise, exclude
 *                            advanced and deprecated options unless
 *                            \c pcmk__opt_advanced and \c pcmk__opt_deprecated
 *                            flags (respectively) are set in \p filter. This is
 *                            always treated as true for XML output objects.
 *
 * \return Standard Pacemaker return code
 */
int
pcmk__output_cluster_options(pcmk__output_t *out, const char *name,
                             const char *desc_short, const char *desc_long,
                             uint32_t filter, bool all)
{
    return out->message(out, "option-list", name, desc_short, desc_long, filter,
                        cluster_options, all);
}

/*!
 * \internal
 * \brief Output primitive resource meta-attributes as OCF-like XML
 *
 * \param[in,out] out         Output object
 * \param[in]     name        Fake resource agent name for the option list
 * \param[in]     desc_short  Short description of the option list
 * \param[in]     desc_long   Long description of the option list
 * \param[in]     all         If \c true, output all options; otherwise, exclude
 *                            advanced and deprecated options. This is always
 *                            treated as true for XML output objects.
 *
 * \return Standard Pacemaker return code
 */
int
pcmk__output_primitive_meta(pcmk__output_t *out, const char *name,
                            const char *desc_short, const char *desc_long,
                            bool all)
{
    return out->message(out, "option-list", name, desc_short, desc_long,
                        pcmk__opt_none, primitive_meta, all);
}

/*!
 * \internal
 * \brief Output fence device common parameter metadata as OCF-like XML
 *
 * These are parameters that are available for all fencing resources, regardless
 * of type. They are processed by Pacemaker, rather than by the fence agent or
 * the fencing library.
 *
 * \param[in,out] out         Output object
 * \param[in]     name        Fake resource agent name for the option list
 * \param[in]     desc_short  Short description of the option list
 * \param[in]     desc_long   Long description of the option list
 * \param[in]     all         If \c true, output all options; otherwise, exclude
 *                            advanced and deprecated options. This is always
 *                            treated as true for XML output objects.
 *
 * \return Standard Pacemaker return code
 */
int
pcmk__output_fencing_params(pcmk__output_t *out, const char *name,
                          const char *desc_short, const char *desc_long,
                          bool all)
{
    return out->message(out, "option-list", name, desc_short, desc_long,
                        pcmk__opt_none, fencing_params, all);
}

/*!
 * \internal
 * \brief Output a list of cluster options for a daemon
 *
 * \brief[in,out] out         Output object
 * \brief[in]     name        Daemon name
 * \brief[in]     desc_short  Short description of the option list
 * \brief[in]     desc_long   Long description of the option list
 * \brief[in]     filter      <tt>enum pcmk__opt_flags</tt> flag corresponding
 *                            to daemon
 *
 * \return Standard Pacemaker return code
 */
int
pcmk__daemon_metadata(pcmk__output_t *out, const char *name,
                      const char *desc_short, const char *desc_long,
                      enum pcmk__opt_flags filter)
{
    // @COMPAT Drop this function when we drop daemon metadata
    pcmk__output_t *tmp_out = NULL;
    xmlNode *top = NULL;
    const xmlNode *metadata = NULL;
    GString *metadata_s = NULL;

    int rc = pcmk__output_new(&tmp_out, "xml", "/dev/null", NULL);

    if (rc != pcmk_rc_ok) {
        return rc;
    }

    pcmk__output_set_legacy_xml(tmp_out);

    if (filter == pcmk__opt_fencing) {
        pcmk__output_fencing_params(tmp_out, name, desc_short, desc_long, true);
    } else {
        pcmk__output_cluster_options(tmp_out, name, desc_short, desc_long,
                                     (uint32_t) filter, true);
    }

    tmp_out->finish(tmp_out, CRM_EX_OK, false, (void **) &top);
    metadata = pcmk__xe_first_child(top, PCMK_XE_RESOURCE_AGENT, NULL, NULL);

    metadata_s = g_string_sized_new(16384);
    pcmk__xml_string(metadata, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text,
                     metadata_s, 0);

    out->output_xml(out, PCMK_XE_METADATA, metadata_s->str);

    pcmk__output_free(tmp_out);
    free_xml(top);
    g_string_free(metadata_s, TRUE);
    return pcmk_rc_ok;
}

void
pcmk__validate_cluster_options(GHashTable *options)
{
    for (const pcmk__cluster_option_t *option = cluster_options;
         option->name != NULL; option++) {

        cluster_option_value(options, option);
    }
}