diff --git a/include/crm/common/results.h b/include/crm/common/results.h index dd754c0932..b7b2cbcd0a 100644 --- a/include/crm/common/results.h +++ b/include/crm/common/results.h @@ -1,238 +1,239 @@ /* * Copyright 2012-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM_RESULTS__H # define CRM_RESULTS__H #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Function and executable result codes * \ingroup core */ // Lifted from config.h /* The _Noreturn keyword of C11. */ #ifndef _Noreturn # if (defined __cplusplus \ && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \ || (defined _MSC_VER && 1900 <= _MSC_VER))) # define _Noreturn [[noreturn]] # elif ((!defined __cplusplus || defined __clang__) \ && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \ || 4 < __GNUC__ + (7 <= __GNUC_MINOR__))) /* _Noreturn works as-is. */ # elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C # define _Noreturn __attribute__ ((__noreturn__)) # elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0) # define _Noreturn __declspec (noreturn) # else # define _Noreturn # endif #endif # define CRM_ASSERT(expr) do { \ if(__unlikely((expr) == FALSE)) { \ crm_abort(__FILE__, __FUNCTION__, __LINE__, #expr, TRUE, FALSE); \ abort(); /* Redundant but it makes static analyzers happy */ \ } \ } while(0) /* * Function return codes * * Most Pacemaker API functions return an integer return code. There are two * alternative interpretations. The legacy interpration is that the absolute * value of the return code is either a system error number or a custom * pcmk_err_* number. This is less than ideal because system error numbers are * constrained only to the positive int range, so there's the possibility * (though not noticed in the wild) that system errors and custom errors could * collide. The new intepretation is that negative values are from the pcmk_rc_e * enum, and positive values are system error numbers. Both use 0 for success. * * For system error codes, see: * - /usr/include/asm-generic/errno.h * - /usr/include/asm-generic/errno-base.h */ // Legacy custom return codes for Pacemaker API functions (deprecated) # define pcmk_ok 0 # define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */ # define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */ # define pcmk_err_generic 201 # define pcmk_err_no_quorum 202 # define pcmk_err_schema_validation 203 # define pcmk_err_transform_failed 204 # define pcmk_err_old_data 205 # define pcmk_err_diff_failed 206 # define pcmk_err_diff_resync 207 # define pcmk_err_cib_modified 208 # define pcmk_err_cib_backup 209 # define pcmk_err_cib_save 210 # define pcmk_err_schema_unchanged 211 # define pcmk_err_cib_corrupt 212 # define pcmk_err_multiple 213 # define pcmk_err_node_unknown 214 # define pcmk_err_already 215 /* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */ #ifdef __hppa__ # define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */ # define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */ #else # define pcmk_err_bad_nvpair 216 # define pcmk_err_unknown_format 217 #endif /*! * \enum pcmk_rc_e * \brief Return codes for Pacemaker API functions * * Any Pacemaker API function documented as returning a "standard Pacemaker * return code" will return pcmk_rc_ok (0) on success, and one of this * enumeration's other (negative) values or a (positive) system error number * otherwise. The custom codes are at -1001 and lower, so that the caller may * use -1 through -1000 for their own custom values if desired. While generally * referred to as "errors", nonzero values simply indicate a result, which might * or might not be an error depending on the calling context. */ enum pcmk_rc_e { /* When adding new values, use consecutively lower numbers, update the array * in lib/common/results.c, and test with crm_error. */ + pcmk_rc_no_input = -1027, pcmk_rc_no_output = -1026, pcmk_rc_after_range = -1025, pcmk_rc_within_range = -1024, pcmk_rc_before_range = -1023, pcmk_rc_undetermined = -1022, pcmk_rc_op_unsatisfied = -1021, pcmk_rc_ipc_pid_only = -1020, pcmk_rc_ipc_unresponsive = -1019, pcmk_rc_ipc_unauthorized = -1018, pcmk_rc_no_quorum = -1017, pcmk_rc_schema_validation = -1016, pcmk_rc_schema_unchanged = -1015, pcmk_rc_transform_failed = -1014, pcmk_rc_old_data = -1013, pcmk_rc_diff_failed = -1012, pcmk_rc_diff_resync = -1011, pcmk_rc_cib_modified = -1010, pcmk_rc_cib_backup = -1009, pcmk_rc_cib_save = -1008, pcmk_rc_cib_corrupt = -1007, pcmk_rc_multiple = -1006, pcmk_rc_node_unknown = -1005, pcmk_rc_already = -1004, pcmk_rc_bad_nvpair = -1003, pcmk_rc_unknown_format = -1002, // Developers: Use a more specific code than pcmk_rc_error whenever possible pcmk_rc_error = -1001, // Values -1 through -1000 reserved for caller use pcmk_rc_ok = 0 // Positive values reserved for system error numbers }; /* * Exit status codes * * We want well-specified (i.e. OS-invariant) exit status codes for our daemons * and applications so they can be relied on by callers. (Function return codes * and errno's do not make good exit statuses.) * * The only hard rule is that exit statuses must be between 0 and 255; all else * is convention. Universally, 0 is success, and 1 is generic error (excluding * OSes we don't support -- for example, OpenVMS considers 1 success!). * * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for * application use. OCF adds 8-9 and 189-199. * * sysexits.h was an attempt to give additional meanings, but never really * caught on. It uses 0 and 64-78. * * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command * found but not executable", 127 is "command not found", 128 + n is * "interrupted by signal n"). * * tldp.org recommends 64-113 for application use. * * We try to overlap with the above conventions when practical. */ typedef enum crm_exit_e { // Common convention CRM_EX_OK = 0, CRM_EX_ERROR = 1, // LSB + OCF CRM_EX_INVALID_PARAM = 2, CRM_EX_UNIMPLEMENT_FEATURE = 3, CRM_EX_INSUFFICIENT_PRIV = 4, CRM_EX_NOT_INSTALLED = 5, CRM_EX_NOT_CONFIGURED = 6, CRM_EX_NOT_RUNNING = 7, // sysexits.h CRM_EX_USAGE = 64, // command line usage error CRM_EX_DATAERR = 65, // user-supplied data incorrect CRM_EX_NOINPUT = 66, // input file not available CRM_EX_NOUSER = 67, // user does not exist CRM_EX_NOHOST = 68, // host unknown CRM_EX_UNAVAILABLE = 69, // needed service unavailable CRM_EX_SOFTWARE = 70, // internal software bug CRM_EX_OSERR = 71, // external (OS/environmental) problem CRM_EX_OSFILE = 72, // system file not usable CRM_EX_CANTCREAT = 73, // file couldn't be created CRM_EX_IOERR = 74, // file I/O error CRM_EX_TEMPFAIL = 75, // try again CRM_EX_PROTOCOL = 76, // protocol violated CRM_EX_NOPERM = 77, // non-file permission issue CRM_EX_CONFIG = 78, // misconfiguration // Custom CRM_EX_FATAL = 100, // do not respawn CRM_EX_PANIC = 101, // panic the local host CRM_EX_DISCONNECT = 102, // lost connection to something CRM_EX_OLD = 103, // update older than existing config CRM_EX_DIGEST = 104, // digest comparison failed CRM_EX_NOSUCH = 105, // requested item does not exist CRM_EX_QUORUM = 106, // local partition does not have quorum CRM_EX_UNSAFE = 107, // requires --force or new conditions CRM_EX_EXISTS = 108, // requested item already exists CRM_EX_MULTIPLE = 109, // requested item has multiple matches CRM_EX_EXPIRED = 110, // requested item has expired CRM_EX_NOT_YET_IN_EFFECT = 111, // requested item is not in effect CRM_EX_INDETERMINATE = 112, // could not determine status CRM_EX_UNSATISFIED = 113, // requested item does not satisfy constraints // Other CRM_EX_TIMEOUT = 124, // convention from timeout(1) CRM_EX_MAX = 255, // ensure crm_exit_t can hold this } crm_exit_t; const char *pcmk_rc_name(int rc); const char *pcmk_rc_str(int rc); crm_exit_t pcmk_rc2exitc(int rc); int pcmk_rc2legacy(int rc); int pcmk_legacy2rc(int legacy_rc); const char *pcmk_strerror(int rc); const char *pcmk_errorname(int rc); const char *bz2_strerror(int rc); crm_exit_t crm_errno2exit(int rc); const char *crm_exit_name(crm_exit_t exit_code); const char *crm_exit_str(crm_exit_t exit_code); _Noreturn crm_exit_t crm_exit(crm_exit_t rc); #ifdef __cplusplus } #endif #endif diff --git a/lib/common/results.c b/lib/common/results.c index df28f857e4..5b83a8499f 100644 --- a/lib/common/results.c +++ b/lib/common/results.c @@ -1,767 +1,774 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include // @COMPAT Legacy function return codes //! \deprecated Use standard return codes and pcmk_rc_name() instead const char * pcmk_errorname(int rc) { rc = abs(rc); switch (rc) { case pcmk_err_generic: return "pcmk_err_generic"; case pcmk_err_no_quorum: return "pcmk_err_no_quorum"; case pcmk_err_schema_validation: return "pcmk_err_schema_validation"; case pcmk_err_transform_failed: return "pcmk_err_transform_failed"; case pcmk_err_old_data: return "pcmk_err_old_data"; case pcmk_err_diff_failed: return "pcmk_err_diff_failed"; case pcmk_err_diff_resync: return "pcmk_err_diff_resync"; case pcmk_err_cib_modified: return "pcmk_err_cib_modified"; case pcmk_err_cib_backup: return "pcmk_err_cib_backup"; case pcmk_err_cib_save: return "pcmk_err_cib_save"; case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt"; case pcmk_err_multiple: return "pcmk_err_multiple"; case pcmk_err_node_unknown: return "pcmk_err_node_unknown"; case pcmk_err_already: return "pcmk_err_already"; case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair"; case pcmk_err_unknown_format: return "pcmk_err_unknown_format"; default: return pcmk_rc_name(rc); // system errno } } //! \deprecated Use standard return codes and pcmk_rc_str() instead const char * pcmk_strerror(int rc) { if (rc == 0) { return "OK"; } rc = abs(rc); // Of course rc > 0 ... unless someone passed INT_MIN as rc if ((rc > 0) && (rc < PCMK_ERROR_OFFSET)) { return strerror(rc); } switch (rc) { case pcmk_err_generic: return "Generic Pacemaker error"; case pcmk_err_no_quorum: return "Operation requires quorum"; case pcmk_err_schema_validation: return "Update does not conform to the configured schema"; case pcmk_err_transform_failed: return "Schema transform failed"; case pcmk_err_old_data: return "Update was older than existing configuration"; case pcmk_err_diff_failed: return "Application of an update diff failed"; case pcmk_err_diff_resync: return "Application of an update diff failed, requesting a full refresh"; case pcmk_err_cib_modified: return "The on-disk configuration was manually modified"; case pcmk_err_cib_backup: return "Could not archive the previous configuration"; case pcmk_err_cib_save: return "Could not save the new configuration to disk"; case pcmk_err_cib_corrupt: return "Could not parse on-disk configuration"; case pcmk_err_multiple: return "Resource active on multiple nodes"; case pcmk_err_node_unknown: return "Node not found"; case pcmk_err_already: return "Situation already as requested"; case pcmk_err_bad_nvpair: return "Bad name/value pair given"; case pcmk_err_schema_unchanged: return "Schema is already the latest available"; case pcmk_err_unknown_format: return "Unknown output format"; /* The following cases will only be hit on systems for which they are non-standard */ /* coverity[dead_error_condition] False positive on non-Linux */ case ENOTUNIQ: return "Name not unique on network"; /* coverity[dead_error_condition] False positive on non-Linux */ case ECOMM: return "Communication error on send"; /* coverity[dead_error_condition] False positive on non-Linux */ case ELIBACC: return "Can not access a needed shared library"; /* coverity[dead_error_condition] False positive on non-Linux */ case EREMOTEIO: return "Remote I/O error"; /* coverity[dead_error_condition] False positive on non-Linux */ case EUNATCH: return "Protocol driver not attached"; /* coverity[dead_error_condition] False positive on non-Linux */ case ENOKEY: return "Required key not available"; } crm_err("Unknown error code: %d", rc); return "Unknown error"; } // Standard Pacemaker API return codes /* This array is used only for nonzero values of pcmk_rc_e. Its values must be * kept in the exact reverse order of the enum value numbering (i.e. add new * values to the end of the array). */ static struct pcmk__rc_info { const char *name; const char *desc; int legacy_rc; } pcmk__rcs[] = { { "pcmk_rc_error", "Error", -pcmk_err_generic, }, { "pcmk_rc_unknown_format", "Unknown output format", -pcmk_err_unknown_format, }, { "pcmk_rc_bad_nvpair", "Bad name/value pair given", -pcmk_err_bad_nvpair, }, { "pcmk_rc_already", "Already in requested state", -pcmk_err_already, }, { "pcmk_rc_node_unknown", "Node not found", -pcmk_err_node_unknown, }, { "pcmk_rc_multiple", "Resource active on multiple nodes", -pcmk_err_multiple, }, { "pcmk_rc_cib_corrupt", "Could not parse on-disk configuration", -pcmk_err_cib_corrupt, }, { "pcmk_rc_cib_save", "Could not save new configuration to disk", -pcmk_err_cib_save, }, { "pcmk_rc_cib_backup", "Could not archive previous configuration", -pcmk_err_cib_backup, }, { "pcmk_rc_cib_modified", "On-disk configuration was manually modified", -pcmk_err_cib_modified, }, { "pcmk_rc_diff_resync", "Application of update diff failed, requesting full refresh", -pcmk_err_diff_resync, }, { "pcmk_rc_diff_failed", "Application of update diff failed", -pcmk_err_diff_failed, }, { "pcmk_rc_old_data", "Update was older than existing configuration", -pcmk_err_old_data, }, { "pcmk_rc_transform_failed", "Schema transform failed", -pcmk_err_transform_failed, }, { "pcmk_rc_schema_unchanged", "Schema is already the latest available", -pcmk_err_schema_unchanged, }, { "pcmk_rc_schema_validation", "Update does not conform to the configured schema", -pcmk_err_schema_validation, }, { "pcmk_rc_no_quorum", "Operation requires quorum", -pcmk_err_no_quorum, }, { "pcmk_rc_ipc_pid_only", "IPC server process is active but not accepting connections", -pcmk_err_generic, }, { "pcmk_rc_ipc_unresponsive", "IPC server is unresponsive", -pcmk_err_generic, }, { "pcmk_rc_ipc_unauthorized", "IPC server is blocked by unauthorized process", -pcmk_err_generic, }, { "pcmk_rc_op_unsatisifed", "Not applicable under current conditions", -pcmk_err_generic, }, { "pcmk_rc_undetermined", "Result undetermined", -pcmk_err_generic, }, { "pcmk_rc_before_range", "Result occurs before given range", -pcmk_err_generic, }, { "pcmk_rc_within_range", "Result occurs within given range", -pcmk_err_generic, }, { "pcmk_rc_after_range", "Result occurs after given range", -pcmk_err_generic, }, { "pcmk_rc_no_output", "Output message produced no output", -pcmk_err_generic, + }, + { "pcmk_rc_no_input", + "Input file not available", + -pcmk_err_generic, } }; #define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info)) /*! * \brief Get a return code constant name as a string * * \param[in] rc Integer return code to convert * * \return String of constant name corresponding to rc */ const char * pcmk_rc_name(int rc) { if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) { return pcmk__rcs[pcmk_rc_error - rc].name; } switch (rc) { case pcmk_rc_ok: return "pcmk_rc_ok"; case E2BIG: return "E2BIG"; case EACCES: return "EACCES"; case EADDRINUSE: return "EADDRINUSE"; case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EAGAIN: return "EAGAIN"; case EALREADY: return "EALREADY"; case EBADF: return "EBADF"; case EBADMSG: return "EBADMSG"; case EBUSY: return "EBUSY"; case ECANCELED: return "ECANCELED"; case ECHILD: return "ECHILD"; case ECOMM: return "ECOMM"; case ECONNABORTED: return "ECONNABORTED"; case ECONNREFUSED: return "ECONNREFUSED"; case ECONNRESET: return "ECONNRESET"; /* case EDEADLK: return "EDEADLK"; */ case EDESTADDRREQ: return "EDESTADDRREQ"; case EDOM: return "EDOM"; case EDQUOT: return "EDQUOT"; case EEXIST: return "EEXIST"; case EFAULT: return "EFAULT"; case EFBIG: return "EFBIG"; case EHOSTDOWN: return "EHOSTDOWN"; case EHOSTUNREACH: return "EHOSTUNREACH"; case EIDRM: return "EIDRM"; case EILSEQ: return "EILSEQ"; case EINPROGRESS: return "EINPROGRESS"; case EINTR: return "EINTR"; case EINVAL: return "EINVAL"; case EIO: return "EIO"; case EISCONN: return "EISCONN"; case EISDIR: return "EISDIR"; case ELIBACC: return "ELIBACC"; case ELOOP: return "ELOOP"; case EMFILE: return "EMFILE"; case EMLINK: return "EMLINK"; case EMSGSIZE: return "EMSGSIZE"; #ifdef EMULTIHOP // Not available on OpenBSD case EMULTIHOP: return "EMULTIHOP"; #endif case ENAMETOOLONG: return "ENAMETOOLONG"; case ENETDOWN: return "ENETDOWN"; case ENETRESET: return "ENETRESET"; case ENETUNREACH: return "ENETUNREACH"; case ENFILE: return "ENFILE"; case ENOBUFS: return "ENOBUFS"; case ENODATA: return "ENODATA"; case ENODEV: return "ENODEV"; case ENOENT: return "ENOENT"; case ENOEXEC: return "ENOEXEC"; case ENOKEY: return "ENOKEY"; case ENOLCK: return "ENOLCK"; #ifdef ENOLINK // Not available on OpenBSD case ENOLINK: return "ENOLINK"; #endif case ENOMEM: return "ENOMEM"; case ENOMSG: return "ENOMSG"; case ENOPROTOOPT: return "ENOPROTOOPT"; case ENOSPC: return "ENOSPC"; case ENOSR: return "ENOSR"; case ENOSTR: return "ENOSTR"; case ENOSYS: return "ENOSYS"; case ENOTBLK: return "ENOTBLK"; case ENOTCONN: return "ENOTCONN"; case ENOTDIR: return "ENOTDIR"; case ENOTEMPTY: return "ENOTEMPTY"; case ENOTSOCK: return "ENOTSOCK"; #if ENOTSUP != EOPNOTSUPP case ENOTSUP: return "ENOTSUP"; #endif case ENOTTY: return "ENOTTY"; case ENOTUNIQ: return "ENOTUNIQ"; case ENXIO: return "ENXIO"; case EOPNOTSUPP: return "EOPNOTSUPP"; case EOVERFLOW: return "EOVERFLOW"; case EPERM: return "EPERM"; case EPFNOSUPPORT: return "EPFNOSUPPORT"; case EPIPE: return "EPIPE"; case EPROTO: return "EPROTO"; case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; case EPROTOTYPE: return "EPROTOTYPE"; case ERANGE: return "ERANGE"; case EREMOTE: return "EREMOTE"; case EREMOTEIO: return "EREMOTEIO"; case EROFS: return "EROFS"; case ESHUTDOWN: return "ESHUTDOWN"; case ESPIPE: return "ESPIPE"; case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; case ESRCH: return "ESRCH"; case ESTALE: return "ESTALE"; case ETIME: return "ETIME"; case ETIMEDOUT: return "ETIMEDOUT"; case ETXTBSY: return "ETXTBSY"; case EUNATCH: return "EUNATCH"; case EUSERS: return "EUSERS"; /* case EWOULDBLOCK: return "EWOULDBLOCK"; */ case EXDEV: return "EXDEV"; #ifdef EBADE // Not available on OS X case EBADE: return "EBADE"; case EBADFD: return "EBADFD"; case EBADSLT: return "EBADSLT"; case EDEADLOCK: return "EDEADLOCK"; case EBADR: return "EBADR"; case EBADRQC: return "EBADRQC"; case ECHRNG: return "ECHRNG"; #ifdef EISNAM // Not available on OS X, Illumos, Solaris case EISNAM: return "EISNAM"; case EKEYEXPIRED: return "EKEYEXPIRED"; case EKEYREJECTED: return "EKEYREJECTED"; case EKEYREVOKED: return "EKEYREVOKED"; #endif case EL2HLT: return "EL2HLT"; case EL2NSYNC: return "EL2NSYNC"; case EL3HLT: return "EL3HLT"; case EL3RST: return "EL3RST"; case ELIBBAD: return "ELIBBAD"; case ELIBMAX: return "ELIBMAX"; case ELIBSCN: return "ELIBSCN"; case ELIBEXEC: return "ELIBEXEC"; #ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris case ENOMEDIUM: return "ENOMEDIUM"; case EMEDIUMTYPE: return "EMEDIUMTYPE"; #endif case ENONET: return "ENONET"; case ENOPKG: return "ENOPKG"; case EREMCHG: return "EREMCHG"; case ERESTART: return "ERESTART"; case ESTRPIPE: return "ESTRPIPE"; #ifdef EUCLEAN // Not available on OS X, Illumos, Solaris case EUCLEAN: return "EUCLEAN"; #endif case EXFULL: return "EXFULL"; #endif // EBADE default: return "Unknown"; } } /*! * \brief Get a user-friendly description of a return code * * \param[in] rc Integer return code to convert * * \return String description of rc */ const char * pcmk_rc_str(int rc) { if (rc == pcmk_rc_ok) { return "OK"; } if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) { return pcmk__rcs[pcmk_rc_error - rc].desc; } if (rc < 0) { return "Unknown error"; } return strerror(rc); } // This returns negative values for errors //! \deprecated Use standard return codes instead int pcmk_rc2legacy(int rc) { if (rc >= 0) { return -rc; // OK or system errno } if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) { return pcmk__rcs[pcmk_rc_error - rc].legacy_rc; } return -pcmk_err_generic; } //! \deprecated Use standard return codes instead int pcmk_legacy2rc(int legacy_rc) { legacy_rc = abs(legacy_rc); switch (legacy_rc) { case pcmk_err_no_quorum: return pcmk_rc_no_quorum; case pcmk_err_schema_validation: return pcmk_rc_schema_validation; case pcmk_err_schema_unchanged: return pcmk_rc_schema_unchanged; case pcmk_err_transform_failed: return pcmk_rc_transform_failed; case pcmk_err_old_data: return pcmk_rc_old_data; case pcmk_err_diff_failed: return pcmk_rc_diff_failed; case pcmk_err_diff_resync: return pcmk_rc_diff_resync; case pcmk_err_cib_modified: return pcmk_rc_cib_modified; case pcmk_err_cib_backup: return pcmk_rc_cib_backup; case pcmk_err_cib_save: return pcmk_rc_cib_save; case pcmk_err_cib_corrupt: return pcmk_rc_cib_corrupt; case pcmk_err_multiple: return pcmk_rc_multiple; case pcmk_err_node_unknown: return pcmk_rc_node_unknown; case pcmk_err_already: return pcmk_rc_already; case pcmk_err_bad_nvpair: return pcmk_rc_bad_nvpair; case pcmk_err_unknown_format: return pcmk_rc_unknown_format; case pcmk_err_generic: return pcmk_rc_error; case pcmk_ok: return pcmk_rc_ok; default: return legacy_rc; // system errno } } // Exit status codes const char * crm_exit_name(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "CRM_EX_OK"; case CRM_EX_ERROR: return "CRM_EX_ERROR"; case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM"; case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE"; case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV"; case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED"; case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED"; case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING"; case CRM_EX_USAGE: return "CRM_EX_USAGE"; case CRM_EX_DATAERR: return "CRM_EX_DATAERR"; case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT"; case CRM_EX_NOUSER: return "CRM_EX_NOUSER"; case CRM_EX_NOHOST: return "CRM_EX_NOHOST"; case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE"; case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE"; case CRM_EX_OSERR: return "CRM_EX_OSERR"; case CRM_EX_OSFILE: return "CRM_EX_OSFILE"; case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT"; case CRM_EX_IOERR: return "CRM_EX_IOERR"; case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL"; case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL"; case CRM_EX_NOPERM: return "CRM_EX_NOPERM"; case CRM_EX_CONFIG: return "CRM_EX_CONFIG"; case CRM_EX_FATAL: return "CRM_EX_FATAL"; case CRM_EX_PANIC: return "CRM_EX_PANIC"; case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT"; case CRM_EX_DIGEST: return "CRM_EX_DIGEST"; case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH"; case CRM_EX_QUORUM: return "CRM_EX_QUORUM"; case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE"; case CRM_EX_EXISTS: return "CRM_EX_EXISTS"; case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE"; case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED"; case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT"; case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE"; case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED"; case CRM_EX_OLD: return "CRM_EX_OLD"; case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT"; case CRM_EX_MAX: return "CRM_EX_UNKNOWN"; } return "CRM_EX_UNKNOWN"; } const char * crm_exit_str(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "OK"; case CRM_EX_ERROR: return "Error occurred"; case CRM_EX_INVALID_PARAM: return "Invalid parameter"; case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented"; case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges"; case CRM_EX_NOT_INSTALLED: return "Not installed"; case CRM_EX_NOT_CONFIGURED: return "Not configured"; case CRM_EX_NOT_RUNNING: return "Not running"; case CRM_EX_USAGE: return "Incorrect usage"; case CRM_EX_DATAERR: return "Invalid data given"; case CRM_EX_NOINPUT: return "Input file not available"; case CRM_EX_NOUSER: return "User does not exist"; case CRM_EX_NOHOST: return "Host does not exist"; case CRM_EX_UNAVAILABLE: return "Necessary service unavailable"; case CRM_EX_SOFTWARE: return "Internal software bug"; case CRM_EX_OSERR: return "Operating system error occurred"; case CRM_EX_OSFILE: return "System file not available"; case CRM_EX_CANTCREAT: return "Cannot create output file"; case CRM_EX_IOERR: return "I/O error occurred"; case CRM_EX_TEMPFAIL: return "Temporary failure, try again"; case CRM_EX_PROTOCOL: return "Protocol violated"; case CRM_EX_NOPERM: return "Insufficient privileges"; case CRM_EX_CONFIG: return "Invalid configuration"; case CRM_EX_FATAL: return "Fatal error occurred, will not respawn"; case CRM_EX_PANIC: return "System panic required"; case CRM_EX_DISCONNECT: return "Not connected"; case CRM_EX_DIGEST: return "Digest mismatch"; case CRM_EX_NOSUCH: return "No such object"; case CRM_EX_QUORUM: return "Quorum required"; case CRM_EX_UNSAFE: return "Operation not safe"; case CRM_EX_EXISTS: return "Requested item already exists"; case CRM_EX_MULTIPLE: return "Multiple items match request"; case CRM_EX_EXPIRED: return "Requested item has expired"; case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect"; case CRM_EX_INDETERMINATE: return "Could not determine status"; case CRM_EX_UNSATISFIED: return "Not applicable under current conditions"; case CRM_EX_OLD: return "Update was older than existing configuration"; case CRM_EX_TIMEOUT: return "Timeout occurred"; case CRM_EX_MAX: return "Error occurred"; } if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) { return "Interrupted by signal"; } return "Unknown exit status"; } //! \deprecated Use standard return codes and pcmk_rc2exitc() instead crm_exit_t crm_errno2exit(int rc) { rc = abs(rc); // Convenience for functions that return -errno switch (rc) { case pcmk_ok: return CRM_EX_OK; case pcmk_err_no_quorum: return CRM_EX_QUORUM; case pcmk_err_old_data: return CRM_EX_OLD; case pcmk_err_schema_validation: case pcmk_err_transform_failed: return CRM_EX_CONFIG; case pcmk_err_bad_nvpair: return CRM_EX_INVALID_PARAM; case pcmk_err_already: return CRM_EX_EXISTS; case pcmk_err_multiple: return CRM_EX_MULTIPLE; case pcmk_err_node_unknown: case pcmk_err_unknown_format: return CRM_EX_NOSUCH; default: return pcmk_rc2exitc(rc); // system errno } } /*! * \brief Map a function return code to the most similar exit code * * \param[in] rc Function return code * * \return Most similar exit code */ crm_exit_t pcmk_rc2exitc(int rc) { switch (rc) { case pcmk_rc_ok: return CRM_EX_OK; case pcmk_rc_no_quorum: return CRM_EX_QUORUM; case pcmk_rc_old_data: return CRM_EX_OLD; case pcmk_rc_schema_validation: case pcmk_rc_transform_failed: return CRM_EX_CONFIG; case pcmk_rc_bad_nvpair: return CRM_EX_INVALID_PARAM; case EACCES: return CRM_EX_INSUFFICIENT_PRIV; case EBADF: case EINVAL: case EFAULT: case ENOSYS: case EOVERFLOW: return CRM_EX_SOFTWARE; case EBADMSG: case EMSGSIZE: case ENOMSG: case ENOPROTOOPT: case EPROTO: case EPROTONOSUPPORT: case EPROTOTYPE: return CRM_EX_PROTOCOL; case ECOMM: case ENOMEM: return CRM_EX_OSERR; case ECONNABORTED: case ECONNREFUSED: case ECONNRESET: case ENOTCONN: return CRM_EX_DISCONNECT; case EEXIST: case pcmk_rc_already: return CRM_EX_EXISTS; case EIO: case pcmk_rc_no_output: return CRM_EX_IOERR; case ENOTSUP: #if EOPNOTSUPP != ENOTSUP case EOPNOTSUPP: #endif return CRM_EX_UNIMPLEMENT_FEATURE; case ENOTUNIQ: case pcmk_rc_multiple: return CRM_EX_MULTIPLE; case ENXIO: case pcmk_rc_node_unknown: case pcmk_rc_unknown_format: return CRM_EX_NOSUCH; case ETIME: case ETIMEDOUT: return CRM_EX_TIMEOUT; case EAGAIN: case EBUSY: return CRM_EX_UNSATISFIED; case pcmk_rc_before_range: return CRM_EX_NOT_YET_IN_EFFECT; case pcmk_rc_after_range: return CRM_EX_EXPIRED; case pcmk_rc_undetermined: return CRM_EX_INDETERMINATE; case pcmk_rc_op_unsatisfied: return CRM_EX_UNSATISFIED; case pcmk_rc_within_range: return CRM_EX_OK; + case pcmk_rc_no_input: + return CRM_EX_NOINPUT; + default: return CRM_EX_ERROR; } } // Other functions const char * bz2_strerror(int rc) { // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17 switch (rc) { case BZ_OK: case BZ_RUN_OK: case BZ_FLUSH_OK: case BZ_FINISH_OK: case BZ_STREAM_END: return "Ok"; case BZ_CONFIG_ERROR: return "libbz2 has been improperly compiled on your platform"; case BZ_SEQUENCE_ERROR: return "library functions called in the wrong order"; case BZ_PARAM_ERROR: return "parameter is out of range or otherwise incorrect"; case BZ_MEM_ERROR: return "memory allocation failed"; case BZ_DATA_ERROR: return "data integrity error is detected during decompression"; case BZ_DATA_ERROR_MAGIC: return "the compressed stream does not start with the correct magic bytes"; case BZ_IO_ERROR: return "error reading or writing in the compressed file"; case BZ_UNEXPECTED_EOF: return "compressed file finishes before the logical end of stream is detected"; case BZ_OUTBUFF_FULL: return "output data will not fit into the buffer provided"; } return "Unknown error"; } crm_exit_t crm_exit(crm_exit_t rc) { /* A compiler could theoretically use any type for crm_exit_t, but an int * should always hold it, so cast to int to keep static analysis happy. */ if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) { rc = CRM_EX_ERROR; } mainloop_cleanup(); crm_xml_cleanup(); pcmk__cli_option_cleanup(); if (crm_system_name) { crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc); free(crm_system_name); } else { crm_trace("Exiting with status %d", rc); } qb_log_fini(); // Don't log anything after this point exit(rc); } diff --git a/tools/Makefile.am b/tools/Makefile.am index e5c44e8d55..e3486b6570 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,166 +1,167 @@ # # Copyright 2004-2019 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/common.mk if BUILD_SYSTEMD systemdsystemunit_DATA = crm_mon.service endif noinst_HEADERS = crm_mon.h crm_resource.h crm_resource_controller.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common report.collector sbin_SCRIPTS = crm_report crm_standby crm_master crm_failcount if BUILD_CIBSECRETS sbin_SCRIPTS += cibsecret endif noinst_SCRIPTS = pcmk_simtimes EXTRA_DIST = crm_diff.8.inc \ crm_mon.sysconfig \ crm_mon.8.inc \ crm_node.8.inc \ crm_rule.8.inc \ + crm_simulate.8.inc \ fix-manpages \ stonith_admin.8.inc sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin if BUILD_SERVICELOG sbin_PROGRAMS += notifyServicelogEvent endif if BUILD_OPENIPMI_SERVICELOG sbin_PROGRAMS += ipmiservicelogd endif ## SOURCES # A few tools are just thin wrappers around crm_attribute. # This makes their help get updated when crm_attribute changes # (see mk/common.mk). MAN8DEPS = crm_attribute crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_print.c crm_mon_runtime.c crm_mon_xml.c crm_mon_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(CURSESLIBS) crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c \ crm_resource_ban.c \ crm_resource_controller.c \ crm_resource_print.c \ crm_resource_runtime.c crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/common/libcrmcommon.la if BUILD_SERVICELOG notifyServicelogEvent_SOURCES = notifyServicelogEvent.c notifyServicelogEvent_CFLAGS = $(SERVICELOG_CFLAGS) notifyServicelogEvent_LDADD = $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS) endif if BUILD_OPENIPMI_SERVICELOG ipmiservicelogd_SOURCES = ipmiservicelogd.c ipmiservicelogd_CFLAGS = $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS) ipmiservicelogd_LDFLAGS = $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS) endif CLEANFILES = $(man8_MANS) diff --git a/tools/crm_simulate.8.inc b/tools/crm_simulate.8.inc new file mode 100644 index 0000000000..8f37f5ba44 --- /dev/null +++ b/tools/crm_simulate.8.inc @@ -0,0 +1,8 @@ +[synopsis] +crm_simulate [options] + +/response to events/ +.SH OPTIONS + +/only essential output/ +.SH OPERATION SPECIFICATION diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c index 60a2d70938..dc908ae244 100644 --- a/tools/crm_simulate.c +++ b/tools/crm_simulate.c @@ -1,1109 +1,1138 @@ /* * Copyright 2009-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include +#define SUMMARY "crm_simulate - simulate a Pacemaker cluster's response to events" + +/* show_scores and show_utilization can't be added to this struct. They + * actually come from include/pcmki/pcmki_scheduler.h where they are + * defined as extern. + */ +struct { + gboolean all_actions; + char *dot_file; + char *graph_file; + gchar *input_file; + guint modified; + GListPtr node_up; + GListPtr node_down; + GListPtr node_fail; + GListPtr op_fail; + GListPtr op_inject; + gchar *output_file; + gboolean print_pending; + gboolean process; + char *quorum; + long long repeat; + gboolean simulate; + gboolean store; + gchar *test_dir; + GListPtr ticket_grant; + GListPtr ticket_revoke; + GListPtr ticket_standby; + GListPtr ticket_activate; + char *use_date; + char *watchdog; + char *xml_file; +} options = { + .print_pending = TRUE, + .repeat = 1 +}; + cib_t *global_cib = NULL; -GListPtr op_fail = NULL; bool action_numbers = FALSE; gboolean quiet = FALSE; -gboolean print_pending = TRUE; char *temp_shadow = NULL; extern gboolean bringing_nodes_online; #define quiet_log(fmt, args...) do { \ if(quiet == FALSE) { \ printf(fmt , ##args); \ } \ } while(0) -char *use_date = NULL; +#define INDENT " " + +static gboolean +in_place_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.store = TRUE; + options.process = TRUE; + options.simulate = TRUE; + return TRUE; +} + +static gboolean +live_check_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.xml_file) { + free(options.xml_file); + } + + options.xml_file = NULL; + return TRUE; +} + +static gboolean +node_down_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.node_down = g_list_append(options.node_down, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +node_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.node_fail = g_list_append(options.node_fail, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +node_up_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + bringing_nodes_online = TRUE; + options.node_up = g_list_append(options.node_up, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +op_fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.process = TRUE; + options.simulate = TRUE; + options.op_fail = g_list_append(options.op_fail, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +op_inject_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.op_inject = g_list_append(options.op_inject, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +quorum_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.quorum) { + free(options.quorum); + } + + options.modified++; + options.quorum = strdup(optarg); + return TRUE; +} + +static gboolean +save_dotfile_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.dot_file) { + free(options.dot_file); + } + + options.process = TRUE; + options.dot_file = strdup(optarg); + return TRUE; +} + +static gboolean +save_graph_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.graph_file) { + free(options.graph_file); + } + + options.process = TRUE; + options.graph_file = strdup(optarg); + return TRUE; +} + +static gboolean +show_scores_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.process = TRUE; + show_scores = TRUE; + return TRUE; +} + +static gboolean +simulate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.process = TRUE; + options.simulate = TRUE; + return TRUE; +} + +static gboolean +ticket_activate_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.ticket_activate = g_list_append(options.ticket_activate, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +ticket_grant_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.ticket_grant = g_list_append(options.ticket_grant, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +ticket_revoke_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.ticket_revoke = g_list_append(options.ticket_revoke, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +ticket_standby_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.modified++; + options.ticket_standby = g_list_append(options.ticket_standby, (gchar *) g_strdup(optarg)); + return TRUE; +} + +static gboolean +utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.process = TRUE; + show_utilization = TRUE; + return TRUE; +} + +static gboolean +watchdog_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.watchdog) { + free(options.watchdog); + } + + options.modified++; + options.watchdog = strdup(optarg); + return TRUE; +} + +static gboolean +xml_pipe_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.xml_file) { + free(options.xml_file); + } + + options.xml_file = strdup("-"); + return TRUE; +} + +static GOptionEntry operation_entries[] = { + { "run", 'R', 0, G_OPTION_ARG_NONE, &options.process, + "Determine cluster's response to the given configuration and status", + NULL }, + { "simulate", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, simulate_cb, + "Simulate transition's execution and display resulting cluster status", + NULL }, + { "in-place", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, in_place_cb, + "Simulate transition's execution and store result back to input file", + NULL }, + { "show-scores", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_scores_cb, + "Show allocation scores", + NULL }, + { "show-utilization", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb, + "Show utilization information", + NULL }, + { "profile", 'P', 0, G_OPTION_ARG_FILENAME, &options.test_dir, + "Run all tests in the named directory to create profiling data", + NULL }, + { "repeat", 'N', 0, G_OPTION_ARG_INT, &options.repeat, + "With --profile, repeat each test N times and print timings", + "N" }, + { "pending", 'j', 0, G_OPTION_ARG_NONE, &options.print_pending, + "Display pending state if 'record-pending' is enabled", + NULL }, + + { NULL } +}; + +static GOptionEntry synthetic_entries[] = { + { "node-up", 'u', 0, G_OPTION_ARG_CALLBACK, node_up_cb, + "Bring a node online", + "NODE" }, + { "node-down", 'd', 0, G_OPTION_ARG_CALLBACK, node_down_cb, + "Take a node offline", + "NODE" }, + { "node-fail", 'f', 0, G_OPTION_ARG_CALLBACK, node_fail_cb, + "Mark a node as failed", + "NODE" }, + { "op-inject", 'i', 0, G_OPTION_ARG_CALLBACK, op_inject_cb, + "Generate a failure for the cluster to react to in the simulation.\n" + INDENT "See `Operation Specification` help for more information.", + "OPSPEC" }, + { "op-fail", 'F', 0, G_OPTION_ARG_CALLBACK, op_fail_cb, + "If the specified task occurs during the simulation, have it fail with return code ${rc}.\n" + INDENT "The transition will normally stop at the failed action.\n" + INDENT "Save the result with --save-output and re-run with --xml-file.\n" + INDENT "See `Operation Specification` help for more information.", + "OPSPEC" }, + { "set-datetime", 't', 0, G_OPTION_ARG_STRING, &options.use_date, + "Set date/time (ISO 8601 format, see https://en.wikipedia.org/wiki/ISO_8601)", + "DATETIME" }, + { "quorum", 'q', 0, G_OPTION_ARG_CALLBACK, quorum_cb, + "Specify a value for quorum", + "QUORUM" }, + { "watchdog", 'w', 0, G_OPTION_ARG_CALLBACK, watchdog_cb, + "Assume a watchdog device is active", + "DEVICE" }, + { "ticket-grant", 'g', 0, G_OPTION_ARG_CALLBACK, ticket_grant_cb, + "Grant a ticket", + "TICKET" }, + { "ticket-revoke", 'r', 0, G_OPTION_ARG_CALLBACK, ticket_revoke_cb, + "Revoke a ticket", + "TICKET" }, + { "ticket-standby", 'b', 0, G_OPTION_ARG_CALLBACK, ticket_standby_cb, + "Make a ticket standby", + "TICKET" }, + { "ticket-activate", 'e', 0, G_OPTION_ARG_CALLBACK, ticket_activate_cb, + "Activate a ticket", + "TICKET" }, + + { NULL } +}; + +static GOptionEntry output_entries[] = { + { "save-input", 'I', 0, G_OPTION_ARG_FILENAME, &options.input_file, + "Save the input configuration to the named file", + "FILE" }, + { "save-output", 'O', 0, G_OPTION_ARG_FILENAME, &options.output_file, + "Save the output configuration to the named file", + "FILE" }, + { "save-graph", 'G', 0, G_OPTION_ARG_CALLBACK, save_graph_cb, + "Save the transition graph (XML format) to the named file", + "FILE" }, + { "save-dotfile", 'D', 0, G_OPTION_ARG_CALLBACK, save_dotfile_cb, + "Save the transition graph (DOT format) to the named file", + "FILE" }, + { "all-actions", 'a', 0, G_OPTION_ARG_NONE, &options.all_actions, + "Display all possible actions in DOT graph (even if not part of transition)", + NULL }, + + { NULL } +}; + +static GOptionEntry source_entries[] = { + { "live-check", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, live_check_cb, + "Connect to CIB mamager and use the current CIB contents as input", + NULL }, + { "xml-file", 'x', 0, G_OPTION_ARG_FILENAME, &options.xml_file, + "Retrieve XML from the named file", + "FILE" }, + { "xml-pipe", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, xml_pipe_cb, + "Retrieve XML from stdin", + NULL }, + + { NULL } +}; static void -get_date(pe_working_set_t *data_set, bool print_original) +get_date(pe_working_set_t *data_set, bool print_original, char *use_date) { time_t original_date = 0; crm_element_value_epoch(data_set->input, "execution-date", &original_date); if (use_date) { data_set->now = crm_time_new(use_date); quiet_log(" + Setting effective cluster time: %s", use_date); crm_time_log(LOG_NOTICE, "Pretending 'now' is", data_set->now, crm_time_log_date | crm_time_log_timeofday); } else if (original_date) { data_set->now = crm_time_new(NULL); crm_time_set_timet(data_set->now, &original_date); if (print_original) { char *when = crm_time_as_string(data_set->now, crm_time_log_date|crm_time_log_timeofday); printf("Using the original execution date of: %s\n", when); free(when); } } } static void print_cluster_status(pe_working_set_t * data_set, long options) { char *online_nodes = NULL; char *online_remote_nodes = NULL; char *online_guest_nodes = NULL; char *offline_nodes = NULL; char *offline_remote_nodes = NULL; GListPtr gIter = NULL; for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; const char *node_mode = NULL; char *node_name = NULL; if (pe__is_guest_node(node)) { node_name = crm_strdup_printf("%s:%s", node->details->uname, node->details->remote_rsc->container->id); } else { node_name = crm_strdup_printf("%s", node->details->uname); } if (node->details->unclean) { if (node->details->online && node->details->unclean) { node_mode = "UNCLEAN (online)"; } else if (node->details->pending) { node_mode = "UNCLEAN (pending)"; } else { node_mode = "UNCLEAN (offline)"; } } else if (node->details->pending) { node_mode = "pending"; } else if (node->details->standby_onfail && node->details->online) { node_mode = "standby (on-fail)"; } else if (node->details->standby) { if (node->details->online) { node_mode = "standby"; } else { node_mode = "OFFLINE (standby)"; } } else if (node->details->maintenance) { if (node->details->online) { node_mode = "maintenance"; } else { node_mode = "OFFLINE (maintenance)"; } } else if (node->details->online) { if (pe__is_guest_node(node)) { online_guest_nodes = pcmk__add_word(online_guest_nodes, node_name); } else if (pe__is_remote_node(node)) { online_remote_nodes = pcmk__add_word(online_remote_nodes, node_name); } else { online_nodes = pcmk__add_word(online_nodes, node_name); } free(node_name); continue; } else { if (pe__is_remote_node(node)) { offline_remote_nodes = pcmk__add_word(offline_remote_nodes, node_name); } else if (pe__is_guest_node(node)) { /* ignore offline container nodes */ } else { offline_nodes = pcmk__add_word(offline_nodes, node_name); } free(node_name); continue; } if (pe__is_guest_node(node)) { printf("GuestNode %s: %s\n", node_name, node_mode); } else if (pe__is_remote_node(node)) { printf("RemoteNode %s: %s\n", node_name, node_mode); } else if (safe_str_eq(node->details->uname, node->details->id)) { printf("Node %s: %s\n", node_name, node_mode); } else { printf("Node %s (%s): %s\n", node_name, node->details->id, node_mode); } free(node_name); } if (online_nodes) { printf("Online: [%s ]\n", online_nodes); free(online_nodes); } if (offline_nodes) { printf("OFFLINE: [%s ]\n", offline_nodes); free(offline_nodes); } if (online_remote_nodes) { printf("RemoteOnline: [%s ]\n", online_remote_nodes); free(online_remote_nodes); } if (offline_remote_nodes) { printf("RemoteOFFLINE: [%s ]\n", offline_remote_nodes); free(offline_remote_nodes); } if (online_guest_nodes) { printf("GuestOnline: [%s ]\n", online_guest_nodes); free(online_guest_nodes); } fprintf(stdout, "\n"); for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; if (is_set(rsc->flags, pe_rsc_orphan) && rsc->role == RSC_ROLE_STOPPED) { continue; } rsc->fns->print(rsc, NULL, pe_print_printf | options, stdout); } fprintf(stdout, "\n"); } static char * create_action_name(pe_action_t *action) { 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) { action_host = action->node->details->uname; } else if (is_not_set(action->flags, pe_action_pseudo)) { action_host = ""; } if (safe_str_eq(action->task, RSC_CANCEL)) { prefix = "Cancel "; task = action->cancel_task; } if (action->rsc && action->rsc->clone_name) { clone_name = action->rsc->clone_name; } if (clone_name) { char *key = NULL; guint interval_ms = 0; if (pcmk__guint_from_hash(action->meta, XML_LRM_ATTR_INTERVAL_MS, 0, &interval_ms) != pcmk_rc_ok) { interval_ms = 0; } if (safe_str_eq(action->task, RSC_NOTIFY) || safe_str_eq(action->task, RSC_NOTIFIED)) { 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) { 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 (safe_str_eq(action->task, CRM_OP_FENCE)) { const char *op = g_hash_table_lookup(action->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 (action_numbers) { // i.e. verbose char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id); free(action_name); action_name = with_id; } return action_name; } -static void -create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions) +static bool +create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions, + GError **error) { GListPtr gIter = NULL; FILE *dot_strm = fopen(dot_file, "w"); if (dot_strm == NULL) { - crm_perror(LOG_ERR, "Could not open %s for writing", dot_file); - return; + g_set_error(error, G_OPTION_ERROR, pcmk_rc2exitc(errno), + "Could not open %s for writing: %s", dot_file, + pcmk_rc_str(errno)); + return false; } fprintf(dot_strm, " digraph \"g\" {\n"); for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->data; const char *style = "dashed"; const char *font = "black"; const char *color = "black"; char *action_name = create_action_name(action); crm_trace("Action %d: %s %s %p", action->id, action_name, action->uuid, action); if (is_set(action->flags, pe_action_pseudo)) { font = "orange"; } if (is_set(action->flags, pe_action_dumped)) { style = "bold"; color = "green"; } else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) { color = "red"; font = "purple"; if (all_actions == FALSE) { goto do_not_write; } } else if (is_set(action->flags, pe_action_optional)) { color = "blue"; if (all_actions == FALSE) { goto do_not_write; } } else { color = "red"; CRM_CHECK(is_set(action->flags, pe_action_runnable) == FALSE,; ); } set_bit(action->flags, pe_action_dumped); crm_trace("\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]", action_name, style, color, font); fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n", action_name, style, color, font); do_not_write: free(action_name); } for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->data; GListPtr gIter2 = NULL; for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) { pe_action_wrapper_t *before = (pe_action_wrapper_t *) gIter2->data; char *before_name = NULL; char *after_name = NULL; const char *style = "dashed"; gboolean optional = TRUE; if (before->state == pe_link_dumped) { optional = FALSE; style = "bold"; } else if (is_set(action->flags, pe_action_pseudo) && (before->type & pe_order_stonith_stop)) { continue; } else if (before->type == pe_order_none) { continue; } else if (is_set(before->action->flags, pe_action_dumped) && is_set(action->flags, pe_action_dumped) && before->type != pe_order_load) { optional = FALSE; } if (all_actions || optional == FALSE) { before_name = create_action_name(before->action); after_name = create_action_name(action); crm_trace("\"%s\" -> \"%s\" [ style = %s]", before_name, after_name, style); 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 true; } -static void -setup_input(const char *input, const char *output) +static int +setup_input(const char *input, const char *output, GError **error) { - int rc = pcmk_ok; + int rc = pcmk_rc_ok; cib_t *cib_conn = NULL; xmlNode *cib_object = NULL; char *local_output = NULL; if (input == NULL) { /* Use live CIB */ cib_conn = cib_new(); rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); + rc = pcmk_legacy2rc(rc); - if (rc == pcmk_ok) { + if (rc == pcmk_rc_ok) { rc = cib_conn->cmds->query(cib_conn, NULL, &cib_object, cib_scope_local | cib_sync_call); } cib_conn->cmds->signoff(cib_conn); cib_delete(cib_conn); cib_conn = NULL; - if (rc != pcmk_ok) { - fprintf(stderr, "Live CIB query failed: %s (%d)\n", pcmk_strerror(rc), rc); - crm_exit(crm_errno2exit(rc)); + if (rc != pcmk_rc_ok) { + rc = pcmk_legacy2rc(rc); + g_set_error(error, G_OPTION_ERROR, pcmk_rc2exitc(rc), + "Live CIB query failed: %s (%d)", pcmk_rc_str(rc), rc); + return rc; } else if (cib_object == NULL) { - fprintf(stderr, "Live CIB query failed: empty result\n"); - crm_exit(CRM_EX_NOINPUT); + g_set_error(error, G_OPTION_ERROR, CRM_EX_NOINPUT, + "Live CIB query failed: empty result"); + return pcmk_rc_no_input; } } else if (safe_str_eq(input, "-")) { cib_object = filename2xml(NULL); } else { cib_object = filename2xml(input); } if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) { create_xml_node(cib_object, XML_CIB_TAG_STATUS); } if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) { free_xml(cib_object); - crm_exit(CRM_EX_CONFIG); + return pcmk_rc_transform_failed; } if (validate_xml(cib_object, NULL, FALSE) != TRUE) { free_xml(cib_object); - crm_exit(CRM_EX_CONFIG); + return pcmk_rc_schema_validation; } if (output == NULL) { char *pid = pcmk__getpid_s(); local_output = get_shadow_file(pid); temp_shadow = strdup(local_output); output = local_output; free(pid); } rc = write_xml_file(cib_object, output, FALSE); free_xml(cib_object); cib_object = NULL; if (rc < 0) { - fprintf(stderr, "Could not create '%s': %s\n", - output, pcmk_strerror(rc)); - crm_exit(CRM_EX_CANTCREAT); + rc = pcmk_legacy2rc(rc); + g_set_error(error, G_OPTION_ERROR, CRM_EX_CANTCREAT, + "Could not create '%s': %s", output, pcmk_rc_str(rc)); + return rc; + } else { + setenv("CIB_file", output, 1); + free(local_output); + return pcmk_rc_ok; } - setenv("CIB_file", output, 1); - free(local_output); } - -static pcmk__cli_option_t long_options[] = { - // long option, argument type, storage, short option, description, flags - { - "help", no_argument, NULL, '?', - "\tThis text", pcmk__option_default - }, - { - "version", no_argument, NULL, '$', - "\tVersion information", pcmk__option_default - }, - { - "quiet", no_argument, NULL, 'Q', - "\tDisplay only essential output", pcmk__option_default - }, - { - "verbose", no_argument, NULL, 'V', - "\tIncrease debug output", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nOperations:", pcmk__option_default - }, - { - "run", no_argument, NULL, 'R', - "\tDetermine cluster's response to the given configuration and status", - pcmk__option_default - }, - { - "simulate", no_argument, NULL, 'S', - "Simulate transition's execution and display resulting cluster status", - pcmk__option_default - }, - { - "in-place", no_argument, NULL, 'X', - "Simulate transition's execution and store result back to input file", - pcmk__option_default - }, - { - "show-scores", no_argument, NULL, 's', - "Show allocation scores", pcmk__option_default - }, - { - "show-utilization", no_argument, NULL, 'U', - "Show utilization information", pcmk__option_default - }, - { - "profile", required_argument, NULL, 'P', - "Run all tests in the named directory to create profiling data", - pcmk__option_default - }, - { - "repeat", required_argument, NULL, 'N', - "With --profile, repeat each test N times and print timings", - pcmk__option_default - }, - { - "pending", no_argument, NULL, 'j', - "\tDisplay pending state if 'record-pending' is enabled", - pcmk__option_hidden - }, - { - "-spacer-", no_argument, NULL, '-', - "\nSynthetic Cluster Events:", pcmk__option_default - }, - { - "node-up", required_argument, NULL, 'u', - "\tBring a node online", pcmk__option_default - }, - { - "node-down", required_argument, NULL, 'd', - "\tTake a node offline", pcmk__option_default - }, - { - "node-fail", required_argument, NULL, 'f', - "\tMark a node as failed", pcmk__option_default - }, - { - "op-inject", required_argument, NULL, 'i', - "\tGenerate a failure for the cluster to react to in the simulation", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\tValue is of the form " - "${resource}_${task}_${interval_in_ms}@${node}=${rc}.", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\t(for example, memcached_monitor_20000@bart.example.com=7)", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\tFor more information on OCF return codes, refer to: " - "https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/" - "2.0/html/Pacemaker_Administration/s-ocf-return-codes.html", - pcmk__option_default - }, - { - "op-fail", required_argument, NULL, 'F', - "\tIf the specified task occurs during the simulation, have it fail " - "with return code ${rc}", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\tValue is of the form " - "${resource}_${task}_${interval_in_ms}@${node}=${rc}.", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\t(for example, memcached_stop_0@bart.example.com=1)\n", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\tThe transition will normally stop at the failed action. Save " - "the result with --save-output and re-run with --xml-file", - pcmk__option_default - }, - { "set-datetime", required_argument, NULL, 't', - "Set date/time (ISO 8601 format, see " - "https://en.wikipedia.org/wiki/ISO_8601)", - pcmk__option_default - }, - { - "quorum", required_argument, NULL, 'q', - "\tSpecify a value for quorum", pcmk__option_default - }, - { - "watchdog", required_argument, NULL, 'w', - "\tAssume a watchdog device is active", pcmk__option_default - }, - { - "ticket-grant", required_argument, NULL, 'g', - "Grant a ticket", pcmk__option_default - }, - { - "ticket-revoke", required_argument, NULL, 'r', - "Revoke a ticket", pcmk__option_default - }, - { - "ticket-standby", required_argument, NULL, 'b', - "Make a ticket standby", pcmk__option_default - }, - { - "ticket-activate", required_argument, NULL, 'e', - "Activate a ticket", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nOutput Options:", pcmk__option_default - }, - { - "save-input", required_argument, NULL, 'I', - "\tSave the input configuration to the named file", pcmk__option_default - }, - { - "save-output", required_argument, NULL, 'O', - "Save the output configuration to the named file", pcmk__option_default - }, - { - "save-graph", required_argument, NULL, 'G', - "\tSave the transition graph (XML format) to the named file", - pcmk__option_default - }, - { - "save-dotfile", required_argument, NULL, 'D', - "Save the transition graph (DOT format) to the named file", - pcmk__option_default - }, - { - "all-actions", no_argument, NULL, 'a', - "\tDisplay all possible actions in DOT graph (even if not part " - "of transition)", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nData Source:", pcmk__option_default - }, - { - "live-check", no_argument, NULL, 'L', - "\tConnect to CIB mamager and use the current CIB contents as input", - pcmk__option_default - }, - { - "xml-file", required_argument, NULL, 'x', - "\tRetrieve XML from the named file", pcmk__option_default - }, - { - "xml-pipe", no_argument, NULL, 'p', - "\tRetrieve XML from stdin", pcmk__option_default - }, - - { - "-spacer-", no_argument, NULL, '-', - "\nExamples:\n", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "Pretend a recurring monitor action found memcached stopped on node " - "fred.example.com and, during recovery, that the memcached stop " - "action failed", - pcmk__option_paragraph - }, - { - "-spacer-", no_argument, NULL, '-', - " crm_simulate -LS --op-inject " - "memcached:0_monitor_20000@bart.example.com=7 " - "--op-fail memcached:0_stop_0@fred.example.com=1 " - "--save-output /tmp/memcached-test.xml", - pcmk__option_example - }, - { - "-spacer-", no_argument, NULL, '-', - "Now see what the reaction to the stop failure would be", - pcmk__option_paragraph - }, - { - "-spacer-", no_argument, NULL, '-', - " crm_simulate -S --xml-file /tmp/memcached-test.xml", - pcmk__option_example - }, - { 0, 0, 0, 0 } -}; - static void -profile_one(const char *xml_file, long long repeat, pe_working_set_t *data_set) +profile_one(const char *xml_file, long long repeat, pe_working_set_t *data_set, char *use_date) { xmlNode *cib_object = NULL; clock_t start = 0; printf("* Testing %s ...", xml_file); fflush(stdout); cib_object = filename2xml(xml_file); start = clock(); if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) { create_xml_node(cib_object, XML_CIB_TAG_STATUS); } if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) { free_xml(cib_object); return; } if (validate_xml(cib_object, NULL, FALSE) != TRUE) { free_xml(cib_object); return; } for (int i = 0; i < repeat; ++i) { xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object); data_set->input = input; - get_date(data_set, false); + get_date(data_set, false, use_date); pcmk__schedule_actions(data_set, input, NULL); pe_reset_working_set(data_set); } printf(" %.2f secs\n", (clock() - start) / (float) CLOCKS_PER_SEC); } #ifndef FILENAME_MAX # define FILENAME_MAX 512 #endif static void -profile_all(const char *dir, long long repeat, pe_working_set_t *data_set) +profile_all(const char *dir, long long repeat, pe_working_set_t *data_set, char *use_date) { struct dirent **namelist; int file_num = scandir(dir, &namelist, 0, alphasort); if (file_num > 0) { struct stat prop; char buffer[FILENAME_MAX]; 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_one(buffer, repeat, data_set); + profile_one(buffer, repeat, data_set, use_date); } free(namelist[file_num]); } free(namelist); } } +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { + GOptionContext *context = NULL; + + GOptionEntry extra_prog_entries[] = { + { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), + "Display only essential output", + NULL }, + + { NULL } + }; + + const char *description = "Operation Specification:\n\n" + "The OPSPEC in any command line option is of the form ${resource}_${task}_${interval_in_ms}@${node}=${rc} " + "memcached_monitor_20000@bart.example.com=7, for example). ${rc} is an OCF return code. For more " + "information on these return codes, refer to " + "https://clusterlabs.org/pacemaker/doc/en-US/Pacemaker/2.0/html/Pacemaker_Administration/s-ocf-return-codes.html\n\n" + "Examples:\n\n" + "Pretend a recurring monitor action found memcached stopped on node " + "fred.example.com and, during recovery, that the memcached stop " + "action failed:\n\n" + "\tcrm_simulate -LS --op-inject memcached:0_monitor_20000@bart.example.com=7 " + "--op-fail memcached:0_stop_0@fred.example.com=1 --save-output /tmp/memcached-test.xml\n\n" + "Now see what the reaction to the stop failed would be:\n\n" + "\tcrm_simulate -S --xml-file /tmp/memcached-test.xml\n\n"; + + context = pcmk__build_arg_context(args, NULL, group); + pcmk__add_main_args(context, extra_prog_entries); + g_option_context_set_description(context, description); + + pcmk__add_arg_group(context, "operations", "Operations:", + "Show operations options", operation_entries); + pcmk__add_arg_group(context, "synthetic", "Synthetic Cluster Events:", + "Show synthetic cluster event options", synthetic_entries); + pcmk__add_arg_group(context, "output", "Output Options:", + "Show output options", output_entries); + pcmk__add_arg_group(context, "source", "Data Source:", + "Show data source options", source_entries); + + return context; +} + int main(int argc, char **argv) { - int rc = pcmk_ok; - guint modified = 0; - - gboolean store = FALSE; - gboolean process = FALSE; - gboolean simulate = FALSE; - gboolean all_actions = FALSE; - gboolean have_stdout = FALSE; + GError *error = NULL; + GOptionGroup *output_group = NULL; + gchar **processed_args = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + GOptionContext *context = build_arg_context(args, &output_group); + int rc = pcmk_rc_ok; pe_working_set_t *data_set = NULL; - const char *xml_file = "-"; - const char *quorum = NULL; - const char *watchdog = NULL; - const char *test_dir = NULL; - const char *dot_file = NULL; - const char *graph_file = NULL; - const char *input_file = NULL; - const char *output_file = NULL; - const char *repeat_s = NULL; - - int flag = 0; - int index = 0; - int argerr = 0; - long long repeat = 1; - - GListPtr node_up = NULL; - GListPtr node_down = NULL; - GListPtr node_fail = NULL; - GListPtr op_inject = NULL; - GListPtr ticket_grant = NULL; - GListPtr ticket_revoke = NULL; - GListPtr ticket_standby = NULL; - GListPtr ticket_activate = NULL; - xmlNode *input = NULL; + options.xml_file = strdup("-"); + crm_log_cli_init("crm_simulate"); - pcmk__set_cli_options(NULL, " [options]", - long_options, - "simulate a Pacemaker cluster's response to events"); - if (argc < 2) { - pcmk__cli_help('?', CRM_EX_USAGE); + processed_args = pcmk__cmdline_preproc(argv, "bdefgiqrtuwxDFGINO"); + + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + goto done; } - while (1) { - flag = pcmk__next_cli_option(argc, argv, &index, NULL); - if (flag == -1) - break; - - switch (flag) { - case 'V': - if (have_stdout == FALSE) { - /* Redirect stderr to stdout so we can grep the output */ - have_stdout = TRUE; - close(STDERR_FILENO); - dup2(STDOUT_FILENO, STDERR_FILENO); - } - - crm_bump_log_level(argc, argv); - action_numbers = TRUE; - break; - case '?': - case '$': - pcmk__cli_help(flag, CRM_EX_OK); - break; - case 'p': - xml_file = "-"; - break; - case 'Q': - quiet = TRUE; - break; - case 'L': - xml_file = NULL; - break; - case 'x': - xml_file = optarg; - break; - case 'u': - modified++; - bringing_nodes_online = TRUE; - node_up = g_list_append(node_up, optarg); - break; - case 'd': - modified++; - node_down = g_list_append(node_down, optarg); - break; - case 'f': - modified++; - node_fail = g_list_append(node_fail, optarg); - break; - case 't': - use_date = strdup(optarg); - break; - case 'i': - modified++; - op_inject = g_list_append(op_inject, optarg); - break; - case 'F': - process = TRUE; - simulate = TRUE; - op_fail = g_list_append(op_fail, optarg); - break; - case 'w': - modified++; - watchdog = optarg; - break; - case 'q': - modified++; - quorum = optarg; - break; - case 'g': - modified++; - ticket_grant = g_list_append(ticket_grant, optarg); - break; - case 'r': - modified++; - ticket_revoke = g_list_append(ticket_revoke, optarg); - break; - case 'b': - modified++; - ticket_standby = g_list_append(ticket_standby, optarg); - break; - case 'e': - modified++; - ticket_activate = g_list_append(ticket_activate, optarg); - break; - case 'a': - all_actions = TRUE; - break; - case 's': - process = TRUE; - show_scores = TRUE; - break; - case 'U': - process = TRUE; - show_utilization = TRUE; - break; - case 'j': - print_pending = TRUE; - break; - case 'S': - process = TRUE; - simulate = TRUE; - break; - case 'X': - store = TRUE; - process = TRUE; - simulate = TRUE; - break; - case 'R': - process = TRUE; - break; - case 'D': - process = TRUE; - dot_file = optarg; - break; - case 'G': - process = TRUE; - graph_file = optarg; - break; - case 'I': - input_file = optarg; - break; - case 'O': - output_file = optarg; - break; - case 'P': - test_dir = optarg; - break; - case 'N': - repeat_s = optarg; - break; - default: - ++argerr; - break; - } + for (int i = 0; i < args->verbosity; i++) { + crm_bump_log_level(argc, argv); + } + + if (args->version) { + /* FIXME: When crm_simulate is converted to use formatted output, this can go. */ + pcmk__cli_help('v', CRM_EX_USAGE); + goto done; } if (optind > argc) { - ++argerr; + gchar *help = g_option_context_get_help(context, TRUE, NULL); + fprintf(stderr, "%s", help); + g_free(help); + goto done; + } + + if (args->verbosity > 0) { + /* Redirect stderr to stdout so we can grep the output */ + close(STDERR_FILENO); + dup2(STDOUT_FILENO, STDERR_FILENO); + + crm_bump_log_level(argc, argv); + action_numbers = TRUE; } - if (argerr) { - pcmk__cli_help('?', CRM_EX_USAGE); + if (args->quiet) { + quiet = TRUE; } data_set = pe_new_working_set(); if (data_set == NULL) { - crm_perror(LOG_ERR, "Could not allocate working set"); - rc = -ENOMEM; + rc = ENOMEM; + g_set_error(&error, G_OPTION_ERROR, pcmk_rc2exitc(rc), + "Could not allocate working set"); goto done; } set_bit(data_set->flags, pe_flag_no_compat); - if (test_dir != NULL) { - if (repeat_s != NULL) { - repeat = crm_parse_ll(repeat_s, NULL); - if (errno || (repeat < 1)) { - fprintf(stderr, "--repeat must be positive integer, not '%s' -- using 1", - repeat_s); - repeat = 1; - } - } - profile_all(test_dir, repeat, data_set); - return CRM_EX_OK; + if (options.test_dir != NULL) { + profile_all(options.test_dir, options.repeat, data_set, options.use_date); + rc = pcmk_rc_ok; + goto done; } - setup_input(xml_file, store ? xml_file : output_file); + rc = setup_input(options.xml_file, options.store ? options.xml_file : options.output_file, &error); + if (rc != pcmk_rc_ok) { + goto done; + } global_cib = cib_new(); rc = global_cib->cmds->signon(global_cib, crm_system_name, cib_command); - if (rc != pcmk_ok) { - fprintf(stderr, "Could not connect to the CIB: %s\n", - pcmk_strerror(rc)); + if (rc != pcmk_rc_ok) { + rc = pcmk_legacy2rc(rc); + g_set_error(&error, G_OPTION_ERROR, pcmk_rc2exitc(rc), + "Could not connect to the CIB: %s", pcmk_rc_str(rc)); goto done; } rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local); - if (rc != pcmk_ok) { - fprintf(stderr, "Could not get local CIB: %s\n", pcmk_strerror(rc)); + if (rc != pcmk_rc_ok) { + rc = pcmk_legacy2rc(rc); + g_set_error(&error, G_OPTION_ERROR, pcmk_rc2exitc(rc), + "Could not get local CIB: %s", pcmk_rc_str(rc)); goto done; } data_set->input = input; - get_date(data_set, true); - if(xml_file) { + get_date(data_set, true, options.use_date); + if(options.xml_file) { set_bit(data_set->flags, pe_flag_sanitized); } set_bit(data_set->flags, pe_flag_stdout); cluster_status(data_set); if (quiet == FALSE) { - int options = print_pending ? pe_print_pending : 0; + int opts = options.print_pending ? pe_print_pending : 0; if (is_set(data_set->flags, pe_flag_maintenance_mode)) { quiet_log("\n *** Resource management is DISABLED ***"); quiet_log("\n The cluster will not attempt to start, stop or recover services"); quiet_log("\n"); } if (data_set->disabled_resources || data_set->blocked_resources) { quiet_log("%d of %d resource instances DISABLED and %d BLOCKED " "from further action due to failure\n", data_set->disabled_resources, data_set->ninstances, data_set->blocked_resources); } quiet_log("\nCurrent cluster status:\n"); - print_cluster_status(data_set, options); + print_cluster_status(data_set, opts); } - if (modified) { + if (options.modified) { quiet_log("Performing requested modifications\n"); - modify_configuration(data_set, global_cib, quorum, watchdog, node_up, node_down, node_fail, op_inject, - ticket_grant, ticket_revoke, ticket_standby, ticket_activate); + modify_configuration(data_set, global_cib, options.quorum, options.watchdog, options.node_up, + options.node_down, options.node_fail, options.op_inject, + options.ticket_grant, options.ticket_revoke, options.ticket_standby, + options.ticket_activate); rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call); - if (rc != pcmk_ok) { - fprintf(stderr, "Could not get modified CIB: %s\n", pcmk_strerror(rc)); + if (rc != pcmk_rc_ok) { + rc = pcmk_legacy2rc(rc); + g_set_error(&error, G_OPTION_ERROR, pcmk_rc2exitc(rc), + "Could not get modified CIB: %s", pcmk_rc_str(rc)); goto done; } cleanup_calculations(data_set); data_set->input = input; - get_date(data_set, true); + get_date(data_set, true, options.use_date); - if(xml_file) { + if(options.xml_file) { set_bit(data_set->flags, pe_flag_sanitized); } set_bit(data_set->flags, pe_flag_stdout); cluster_status(data_set); } - if (input_file != NULL) { - rc = write_xml_file(input, input_file, FALSE); + if (options.input_file != NULL) { + rc = write_xml_file(input, options.input_file, FALSE); if (rc < 0) { - fprintf(stderr, "Could not create '%s': %s\n", - input_file, pcmk_strerror(rc)); + rc = pcmk_legacy2rc(rc); + g_set_error(&error, G_OPTION_ERROR, pcmk_rc2exitc(rc), + "Could not create '%s': %s", options.input_file, pcmk_rc_str(rc)); goto done; } } - if (process || simulate) { + if (options.process || options.simulate) { crm_time_t *local_date = NULL; if (show_scores && show_utilization) { printf("Allocation scores and utilization information:\n"); } else if (show_scores) { fprintf(stdout, "Allocation scores:\n"); } else if (show_utilization) { printf("Utilization information:\n"); } pcmk__schedule_actions(data_set, input, local_date); input = NULL; /* Don't try and free it twice */ - if (graph_file != NULL) { - write_xml_file(data_set->graph, graph_file, FALSE); + if (options.graph_file != NULL) { + write_xml_file(data_set->graph, options.graph_file, FALSE); } - if (dot_file != NULL) { - create_dotfile(data_set, dot_file, all_actions); + if (options.dot_file != NULL) { + if (!create_dotfile(data_set, options.dot_file, options.all_actions, &error)) { + goto done; + } } if (quiet == FALSE) { GListPtr gIter = NULL; quiet_log("%sTransition Summary:\n", show_scores || show_utilization - || modified ? "\n" : ""); + || options.modified ? "\n" : ""); fflush(stdout); LogNodeActions(data_set, TRUE); for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; LogActions(rsc, data_set, TRUE); } } } - rc = pcmk_ok; + rc = pcmk_rc_ok; - if (simulate) { - if (run_simulation(data_set, global_cib, op_fail, quiet) != pcmk_ok) { - rc = pcmk_err_generic; + if (options.simulate) { + if (run_simulation(data_set, global_cib, options.op_fail, quiet) != pcmk_rc_ok) { + rc = pcmk_rc_error; } if(quiet == FALSE) { - get_date(data_set, true); + get_date(data_set, true, options.use_date); quiet_log("\nRevised cluster status:\n"); set_bit(data_set->flags, pe_flag_stdout); cluster_status(data_set); print_cluster_status(data_set, 0); } } done: - pe_free_working_set(data_set); - global_cib->cmds->signoff(global_cib); - cib_delete(global_cib); - free(use_date); + if (error != NULL) { + fprintf(stderr, "%s\n", error->message); + g_clear_error(&error); + } + + /* There sure is a lot to free in options. */ + free(options.dot_file); + free(options.graph_file); + g_free(options.input_file); + g_list_free_full(options.node_up, g_free); + g_list_free_full(options.node_down, g_free); + g_list_free_full(options.node_fail, g_free); + g_list_free_full(options.op_fail, g_free); + g_list_free_full(options.op_inject, g_free); + g_free(options.output_file); + free(options.quorum); + g_free(options.test_dir); + g_list_free_full(options.ticket_grant, g_free); + g_list_free_full(options.ticket_revoke, g_free); + g_list_free_full(options.ticket_standby, g_free); + g_list_free_full(options.ticket_activate, g_free); + free(options.use_date); + free(options.watchdog); + free(options.xml_file); + + pcmk__free_arg_context(context); + g_strfreev(processed_args); + + if (data_set) { + pe_free_working_set(data_set); + } + + if (global_cib) { + global_cib->cmds->signoff(global_cib); + cib_delete(global_cib); + } + fflush(stderr); if (temp_shadow) { unlink(temp_shadow); free(temp_shadow); } - crm_exit(crm_errno2exit(rc)); + crm_exit(pcmk_rc2exitc(rc)); } diff --git a/tools/fix-manpages b/tools/fix-manpages index e93fae9001..714ecce106 100644 --- a/tools/fix-manpages +++ b/tools/fix-manpages @@ -1,33 +1,33 @@ # Because tools/*.8.inc include a synopsis, the following line removes # a redundant Usage: header from the man page and the couple lines after # it. /.SS "Usage:"/,+3d # The tools/*.8.inc files also include some additional section headers # on a per-tool basis. These section headers will get printed out as # .SH lines, but then the header from the --help-all output will also # get turned into groff. For instance, the following will be in the # man page for NOTES: # # .SH NOTES # .PP # Notes: # .PP # # The following block looks for any of those additional headers. The # 'n' command puts the next line in the pattern space, the two 'N' # commands append the next two lines, and then the 'd' command deletes # them. So basically, this just deletes # # .PP # Notes: # .PP # # This leaves the --help-all output looking good and removes redundant # stuff from the man page. Feel free to add additional headers here. # Not all tools will have all headers. -/.SH NOTES\|.SH OUTPUT CONTROL\|.SH TIME SPECIFICATION/{ n +/.SH NOTES\|.SH OPERATION SPECIFICATION\|.SH OUTPUT CONTROL\|.SH TIME SPECIFICATION/{ n N N d }