Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/pcmki/pcmki_acl.h b/include/pcmki/pcmki_acl.h
index ced6ca2a35..d7dd59e749 100644
--- a/include/pcmki/pcmki_acl.h
+++ b/include/pcmki/pcmki_acl.h
@@ -1,78 +1,79 @@
/*
- * Copyright 2004-2022 the Pacemaker project contributors
+ * Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__PCMKI_PCMKI_ACL__H
#define PCMK__PCMKI_PCMKI_ACL__H
#include <crm/common/xml.h>
enum pcmk__acl_cred_type {
pcmk__acl_cred_unset = 0,
pcmk__acl_cred_user,
/* XXX no proper support for groups yet */
};
enum pcmk__acl_render_how {
pcmk__acl_render_none = 0,
pcmk__acl_render_namespace,
pcmk__acl_render_text,
pcmk__acl_render_color,
//! Context-dependent default render mode
pcmk__acl_render_default,
};
/*
* Version compatibility tracking incl. open-ended intervals for occasional
* bumps (to avoid hard to follow open-coding throughout). Grouped by context.
*/
/* Schema version vs. evaluate-as-namespace-annotations-per-credentials */
#define PCMK__COMPAT_ACL_2_MIN_INCL "pacemaker-2.0"
/*!
* \brief Mark CIB with namespace-encoded result of ACLs eval'd per credential
*
- * \param[in] cred_type credential type that \p cred represents
- * \param[in] cred credential whose ACL perspective to switch to
- * \param[in] cib_doc XML document representing CIB
- * \param[out] acl_evaled_doc XML document representing CIB, with said
- * namespace-based annotations throughout
+ * \param[in] cred Credential whose ACL perspective to switch to
+ * \param[in] cib_doc CIB XML to annotate
+ * \param[out] acl_evaled_doc Where to store annotated CIB XML
*
* \return A standard Pacemaker return code
* Namely:
* - pcmk_rc_ok upon success,
* - pcmk_rc_already if ACLs were not applicable,
* - pcmk_rc_schema_validation if the validation schema version
* is unsupported (see note), or
* - EINVAL or ENOMEM as appropriate;
*
* \note Only supported schemas are those following acls-2.0.rng, that is,
* those validated with pacemaker-2.0.rng and newer.
*/
-int pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc,
- xmlDoc **acl_evaled_doc);
+int pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
+ xmlDoc **acl_evaled_doc);
/*!
* \internal
* \brief Serialize-render already pcmk__acl_annotate_permissions annotated XML
*
- * \param[in] annotated_doc pcmk__acl_annotate_permissions annotated XML
- * \param[in] how render kind, see #pcmk__acl_render_how enumeration
- * \param[out] doc_txt_ptr where to put the final outcome string
+ * \param[in,out] annotated_doc XML annotated by pcmk__acl_annotate_permissions
+ * \param[in] how Desired rendering (#pcmk__acl_render_how value)
+ * \param[out] doc_txt_ptr Where to put the final outcome string
+ *
* \return A standard Pacemaker return code
*
* \note Currently, the function did not receive enough of testing regarding
* leak of resources, hence it is not recommended for anything other
* than short-lived processes at this time.
+ * \note This function will free \p annotated_doc, which should not be used
+ * after calling this function.
*/
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how,
xmlChar **doc_txt_ptr);
#endif
diff --git a/include/pcmki/pcmki_status.h b/include/pcmki/pcmki_status.h
index 49e4e238ea..6b48069e14 100644
--- a/include/pcmki/pcmki_status.h
+++ b/include/pcmki/pcmki_status.h
@@ -1,61 +1,63 @@
/*
- * Copyright 2022 the Pacemaker project contributors
+ * Copyright 2022-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__PCMKI_PCMKI_STATUS__H
#define PCMK__PCMKI_PCMKI_STATUS__H
#include <stdbool.h>
#include <stdint.h>
#include <crm/cib/cib_types.h>
#include <crm/pengine/pe_types.h>
#include <crm/common/ipc_pacemakerd.h>
#include <crm/common/output_internal.h>
#include <pcmki/pcmki_fence.h>
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \internal
* \brief Print one-line status suitable for use with monitoring software
*
- * \param[in] data_set Working set of CIB state
+ * \param[in,out] out Output object
+ * \param[in] data_set Cluster working set
*
* \return Standard Pacemaker return code
*
- * \note This function's output (and the return code when the program exits)
- * should conform to https://www.monitoring-plugins.org/doc/guidelines.html
+ * \note This function's output should conform to
+ * https://www.monitoring-plugins.org/doc/guidelines.html
*
* \note This function is planned to be deprecated and then removed in the
* future. It should only be called from crm_mon, and no additional
* callers should be added.
*/
-int pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set);
+int pcmk__output_simple_status(pcmk__output_t *out,
+ const pe_working_set_t *data_set);
int pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith,
cib_t *cib, xmlNode *current_cib,
enum pcmk_pacemakerd_state pcmkd_state,
enum pcmk__fence_history fence_history,
uint32_t show, uint32_t show_opts,
const char *only_node, const char *only_rsc,
const char *neg_location_prefix,
bool simple_output);
int pcmk__status(pcmk__output_t *out, cib_t *cib,
enum pcmk__fence_history fence_history, uint32_t show,
uint32_t show_opts, const char *only_node,
const char *only_rsc, const char *neg_location_prefix,
bool simple_output, unsigned int timeout_ms);
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/pacemaker/pcmk_acl.c b/lib/pacemaker/pcmk_acl.c
index 935269a26e..c2072dce61 100644
--- a/lib/pacemaker/pcmk_acl.c
+++ b/lib/pacemaker/pcmk_acl.c
@@ -1,377 +1,379 @@
/*
- * Copyright 2004-2022 the Pacemaker project contributors
+ * Copyright 2004-2023 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 <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxslt/transform.h>
#include <libxslt/variables.h>
#include <libxslt/xsltutils.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/common/internal.h>
#include <pacemaker-internal.h>
#define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
#define ACL_NS_Q_PREFIX "pcmk-access-"
#define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX "writable"
#define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX "readable"
#define ACL_NS_Q_DENIED (const xmlChar *) ACL_NS_Q_PREFIX "denied"
static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied";
/*!
* \brief This function takes a node and marks it with the namespace
* given in the ns parameter.
*
* \param[in,out] i_node
* \param[in] ns
* \param[in,out] ret
* \param[in,out] ns_recycle_writable
* \param[in,out] ns_recycle_readable
* \param[in,out] ns_recycle_denied
*/
static void
pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret, xmlNs **ns_recycle_writable, xmlNs **ns_recycle_readable, xmlNs **ns_recycle_denied)
{
if (ns == NS_WRITABLE)
{
if (*ns_recycle_writable == NULL)
{
*ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_WRITABLE, ACL_NS_Q_WRITABLE);
}
xmlSetNs(i_node, *ns_recycle_writable);
*ret = pcmk_rc_ok;
}
else if (ns == NS_READABLE)
{
if (*ns_recycle_readable == NULL)
{
*ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_READABLE, ACL_NS_Q_READABLE);
}
xmlSetNs(i_node, *ns_recycle_readable);
*ret = pcmk_rc_ok;
}
else if (ns == NS_DENIED)
{
if (*ns_recycle_denied == NULL)
{
*ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
NS_DENIED, ACL_NS_Q_DENIED);
};
xmlSetNs(i_node, *ns_recycle_denied);
*ret = pcmk_rc_ok;
}
}
/*!
* \brief This function takes some XML, and annotates it with XML
* namespaces to indicate the ACL permissions.
*
* \param[in,out] xml_modify
*
* \return A standard Pacemaker return code
* Namely:
* - pcmk_rc_ok upon success,
* - pcmk_rc_already if ACLs were not applicable,
* - pcmk_rc_schema_validation if the validation schema version
* is unsupported (see note), or
* - EINVAL or ENOMEM as appropriate;
*
* \note This function is recursive
*/
static int
pcmk__acl_annotate_permissions_recursive(xmlNode *xml_modify)
{
static xmlNs *ns_recycle_writable = NULL,
*ns_recycle_readable = NULL,
*ns_recycle_denied = NULL;
static const xmlDoc *prev_doc = NULL;
xmlNode *i_node = NULL;
const xmlChar *ns;
int ret = EINVAL; // nodes have not been processed yet
if (prev_doc == NULL || prev_doc != xml_modify->doc) {
prev_doc = xml_modify->doc;
ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
}
for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
switch (i_node->type) {
case XML_ELEMENT_NODE:
pcmk__set_xml_doc_flag(i_node, pcmk__xf_tracking);
if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
ns = NS_DENIED;
} else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
ns = NS_READABLE;
} else {
ns = NS_WRITABLE;
}
pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied);
/* XXX recursion can be turned into plain iteration to save stack */
if (i_node->properties != NULL) {
/* this is not entirely clear, but relies on the very same
class-hierarchy emulation that libxml2 has firmly baked in
its API/ABI */
ret |= pcmk__acl_annotate_permissions_recursive((xmlNodePtr) i_node->properties);
}
if (i_node->children != NULL) {
ret |= pcmk__acl_annotate_permissions_recursive(i_node->children);
}
break;
case XML_ATTRIBUTE_NODE:
/* we can utilize that parent has already been assigned the ns */
if (!pcmk__check_acl(i_node->parent,
(const char *) i_node->name,
pcmk__xf_acl_read)) {
ns = NS_DENIED;
} else if (!pcmk__check_acl(i_node,
(const char *) i_node->name,
pcmk__xf_acl_write)) {
ns = NS_READABLE;
} else {
ns = NS_WRITABLE;
}
pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied);
break;
case XML_COMMENT_NODE:
/* we can utilize that parent has already been assigned the ns */
if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_read))
{
ns = NS_DENIED;
}
else if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_write))
{
ns = NS_READABLE;
}
else
{
ns = NS_WRITABLE;
}
pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied);
break;
default:
break;
}
}
return ret;
}
int
-pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc,
- xmlDoc **acl_evaled_doc)
+pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
+ xmlDoc **acl_evaled_doc)
{
int ret, version;
xmlNode *target, *comment;
const char *validation;
CRM_CHECK(cred != NULL, return EINVAL);
CRM_CHECK(cib_doc != NULL, return EINVAL);
CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
/* avoid trivial accidental XML injection */
if (strpbrk(cred, "<>&") != NULL) {
return EINVAL;
}
if (!pcmk_acl_required(cred)) {
/* nothing to evaluate */
return pcmk_rc_already;
}
- validation = crm_element_value(xmlDocGetRootElement(cib_doc),
+ // @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2
+
+ validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc),
XML_ATTR_VALIDATION);
version = get_schema_version(validation);
if (get_schema_version(PCMK__COMPAT_ACL_2_MIN_INCL) > version) {
return pcmk_rc_schema_validation;
}
- target = copy_xml(xmlDocGetRootElement(cib_doc));
+ target = copy_xml(xmlDocGetRootElement((xmlDoc *) cib_doc));
if (target == NULL) {
return EINVAL;
}
pcmk__enable_acl(target, target, cred);
ret = pcmk__acl_annotate_permissions_recursive(target);
if (ret == pcmk_rc_ok) {
char* credentials = crm_strdup_printf("ACLs as evaluated for user %s", cred);
comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
free(credentials);
if (comment == NULL) {
xmlFreeNode(target);
return EINVAL;
}
xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
*acl_evaled_doc = target->doc;
return pcmk_rc_ok;
} else {
xmlFreeNode(target);
return ret; //for now, it should be some kind of error
}
}
int
pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
xmlChar **doc_txt_ptr)
{
xmlDoc *xslt_doc;
xsltStylesheet *xslt;
xsltTransformContext *xslt_ctxt;
xmlDoc *res;
char *sfile;
static const char *params_namespace[] = {
"accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
"accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
"accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
"accessrendercfg:c-reset", "",
"accessrender:extra-spacing", "no",
"accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
NULL
}, *params_useansi[] = {
/* start with hard-coded defaults, then adapt per the template ones */
"accessrendercfg:c-writable", "\x1b[32m",
"accessrendercfg:c-readable", "\x1b[34m",
"accessrendercfg:c-denied", "\x1b[31m",
"accessrendercfg:c-reset", "\x1b[0m",
"accessrender:extra-spacing", "no",
"accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
NULL
}, *params_noansi[] = {
"accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
"accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
"accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
"accessrendercfg:c-reset", "",
"accessrender:extra-spacing", "yes",
"accessrender:self-reproducing-prefix", "",
NULL
};
const char **params;
int ret;
xmlParserCtxtPtr parser_ctxt;
/* unfortunately, the input (coming from CIB originally) was parsed with
blanks ignored, and since the output is a conversion of XML to text
format (we would be covered otherwise thanks to implicit
pretty-printing), we need to dump the tree to string output first,
only to subsequently reparse it -- this time with blanks honoured */
xmlChar *annotated_dump;
int dump_size;
CRM_ASSERT(how != pcmk__acl_render_none);
// Color is the default render mode for terminals; text is default otherwise
if (how == pcmk__acl_render_default) {
if (isatty(STDOUT_FILENO)) {
how = pcmk__acl_render_color;
} else {
how = pcmk__acl_render_text;
}
}
xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
XML_PARSE_NONET);
CRM_ASSERT(res != NULL);
xmlFree(annotated_dump);
xmlFreeDoc(annotated_doc);
annotated_doc = res;
sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
"access-render-2");
parser_ctxt = xmlNewParserCtxt();
CRM_ASSERT(sfile != NULL);
CRM_ASSERT(parser_ctxt != NULL);
xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
if (xslt == NULL) {
crm_crit("Problem in parsing %s", sfile);
return EINVAL;
}
free(sfile);
sfile = NULL;
xmlFreeParserCtxt(parser_ctxt);
xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
CRM_ASSERT(xslt_ctxt != NULL);
switch (how) {
case pcmk__acl_render_namespace:
params = params_namespace;
break;
case pcmk__acl_render_text:
params = params_noansi;
break;
default:
/* pcmk__acl_render_color is the only remaining option.
* The compiler complains about params possibly uninitialized if we
* don't use default here.
*/
params = params_useansi;
break;
}
xsltQuoteUserParams(xslt_ctxt, params);
res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
NULL, NULL, xslt_ctxt);
xmlFreeDoc(annotated_doc);
annotated_doc = NULL;
xsltFreeTransformContext(xslt_ctxt);
xslt_ctxt = NULL;
if (how == pcmk__acl_render_color && params != params_useansi) {
char **param_i = (char **) params;
do {
free(*param_i);
} while (*param_i++ != NULL);
free(params);
}
if (res == NULL) {
ret = EINVAL;
} else {
int doc_txt_len;
int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
xmlFreeDoc(res);
if (temp == 0) {
ret = pcmk_rc_ok;
} else {
ret = EINVAL;
}
}
xsltFreeStylesheet(xslt);
return ret;
}
diff --git a/lib/pacemaker/pcmk_rule.c b/lib/pacemaker/pcmk_rule.c
index ab4a01189b..b8ca453a52 100644
--- a/lib/pacemaker/pcmk_rule.c
+++ b/lib/pacemaker/pcmk_rule.c
@@ -1,295 +1,295 @@
/*
- * Copyright 2022 the Pacemaker project contributors
+ * Copyright 2022-2023 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 <crm_internal.h>
#include <crm/cib/internal.h>
#include <crm/common/cib.h>
#include <crm/common/iso8601.h>
#include <crm/msg_xml.h>
#include <crm/pengine/rules_internal.h>
#include <pacemaker-internal.h>
/*!
* \internal
* \brief Evaluate a date expression for a specific time
*
* \param[in] expr date_expression XML
* \param[in] now Time for which to evaluate expression
*
* \return Standard Pacemaker return code
*/
static int
-eval_date_expression(xmlNodePtr expr, crm_time_t *now)
+eval_date_expression(const xmlNode *expr, crm_time_t *now)
{
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.role = RSC_ROLE_UNKNOWN,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
return pe__eval_date_expr(expr, &rule_data, NULL);
}
/*!
* \internal
* \brief Initialize the cluster working set for checking rules
*
* Make our own copies of the CIB XML and date/time object, if they're not
* \c NULL. This way we don't have to take ownership of the objects passed via
* the API.
*
* \param[in,out] out Output object
* \param[in] input The CIB XML to check (if \c NULL, use current CIB)
* \param[in] date Check whether the rule is in effect at this date
* and time (if \c NULL, use current date and time)
* \param[out] data_set Where to store the cluster working set
*
* \return Standard Pacemaker return code
*/
static int
init_rule_check(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
pe_working_set_t **data_set)
{
// Allows for cleaner syntax than dereferencing the data_set argument
pe_working_set_t *new_data_set = NULL;
new_data_set = pe_new_working_set();
if (new_data_set == NULL) {
return ENOMEM;
}
pe__set_working_set_flags(new_data_set,
pe_flag_no_counts|pe_flag_no_compat);
// Populate the working set instance
// Make our own copy of the given input or fetch the CIB and use that
if (input != NULL) {
new_data_set->input = copy_xml(input);
if (new_data_set->input == NULL) {
out->err(out, "Failed to copy input XML");
pe_free_working_set(new_data_set);
return ENOMEM;
}
} else {
int rc = cib__signon_query(out, NULL, &(new_data_set->input));
if (rc != pcmk_rc_ok) {
pe_free_working_set(new_data_set);
return rc;
}
}
// Make our own copy of the given crm_time_t object; otherwise
// cluster_status() populates with the current time
if (date != NULL) {
// pcmk_copy_time() guarantees non-NULL
new_data_set->now = pcmk_copy_time(date);
}
// Unpack everything
cluster_status(new_data_set);
*data_set = new_data_set;
return pcmk_rc_ok;
}
#define XPATH_NODE_RULE "//" XML_TAG_RULE "[@" XML_ATTR_ID "='%s']"
/*!
* \internal
* \brief Check whether a given rule is in effect
*
* \param[in] data_set Cluster working set
* \param[in] rule_id The ID of the rule to check
* \param[out] error Where to store a rule evaluation error message
*
* \return Standard Pacemaker return code
*/
static int
eval_rule(pe_working_set_t *data_set, const char *rule_id, const char **error)
{
xmlNodePtr cib_constraints = NULL;
xmlNodePtr match = NULL;
xmlXPathObjectPtr xpath_obj = NULL;
char *xpath = NULL;
int rc = pcmk_rc_ok;
int num_results = 0;
*error = NULL;
/* Rules are under the constraints node in the XML, so first find that. */
cib_constraints = pcmk_find_cib_element(data_set->input,
XML_CIB_TAG_CONSTRAINTS);
/* Get all rules matching the given ID that are also simple enough for us
* to check. For the moment, these rules must only have a single
* date_expression child and:
* - Do not have a date_spec operation, or
* - Have a date_spec operation that contains years= but does not contain
* moon=.
*
* We do this in steps to provide better error messages. First, check that
* there's any rule with the given ID.
*/
xpath = crm_strdup_printf(XPATH_NODE_RULE, rule_id);
xpath_obj = xpath_search(cib_constraints, xpath);
num_results = numXpathResults(xpath_obj);
free(xpath);
freeXpathObject(xpath_obj);
if (num_results == 0) {
*error = "Rule not found";
return ENXIO;
}
if (num_results > 1) {
// Should not be possible; schema prevents this
*error = "Found more than one rule with matching ID";
return pcmk_rc_duplicate_id;
}
/* Next, make sure it has exactly one date_expression. */
xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression", rule_id);
xpath_obj = xpath_search(cib_constraints, xpath);
num_results = numXpathResults(xpath_obj);
free(xpath);
freeXpathObject(xpath_obj);
if (num_results != 1) {
if (num_results == 0) {
*error = "Rule does not have a date expression";
} else {
*error = "Rule has more than one date expression";
}
return EOPNOTSUPP;
}
/* Then, check that it's something we actually support. */
xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression["
"@" XML_EXPR_ATTR_OPERATION "!='date_spec']",
rule_id);
xpath_obj = xpath_search(cib_constraints, xpath);
num_results = numXpathResults(xpath_obj);
free(xpath);
if (num_results == 0) {
freeXpathObject(xpath_obj);
xpath = crm_strdup_printf(XPATH_NODE_RULE "//date_expression["
"@" XML_EXPR_ATTR_OPERATION "='date_spec' "
"and date_spec/@years "
"and not(date_spec/@moon)]", rule_id);
xpath_obj = xpath_search(cib_constraints, xpath);
num_results = numXpathResults(xpath_obj);
free(xpath);
if (num_results == 0) {
freeXpathObject(xpath_obj);
*error = "Rule must either not use date_spec, or use date_spec "
"with years= but not moon=";
return EOPNOTSUPP;
}
}
match = getXpathResult(xpath_obj, 0);
/* We should have ensured this with the xpath query above, but double-
* checking can't hurt.
*/
CRM_ASSERT(match != NULL);
CRM_ASSERT(find_expression_type(match) == time_expr);
rc = eval_date_expression(match, data_set->now);
if (rc == pcmk_rc_undetermined) {
/* pe__eval_date_expr() should return this only if something is
* malformed or missing
*/
*error = "Error parsing rule";
}
freeXpathObject(xpath_obj);
return rc;
}
/*!
* \internal
* \brief Check whether each rule in a list is in effect
*
* \param[in,out] out Output object
* \param[in] input The CIB XML to check (if \c NULL, use current CIB)
* \param[in] date Check whether the rule is in effect at this date and
* time (if \c NULL, use current date and time)
* \param[in] rule_ids The IDs of the rules to check, as a <tt>NULL</tt>-
* terminated list.
*
* \return Standard Pacemaker return code
*/
int
pcmk__check_rules(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
const char **rule_ids)
{
pe_working_set_t *data_set = NULL;
int rc = pcmk_rc_ok;
CRM_ASSERT(out != NULL);
if (rule_ids == NULL) {
// Trivial case; every rule specified is in effect
return pcmk_rc_ok;
}
rc = init_rule_check(out, input, date, &data_set);
if (rc != pcmk_rc_ok) {
return rc;
}
for (const char **rule_id = rule_ids; *rule_id != NULL; rule_id++) {
const char *error = NULL;
int last_rc = eval_rule(data_set, *rule_id, &error);
out->message(out, "rule-check", *rule_id, last_rc, error);
if (last_rc != pcmk_rc_ok) {
rc = last_rc;
}
}
pe_free_working_set(data_set);
return rc;
}
// Documented in pacemaker.h
int
pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date,
const char **rule_ids)
{
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
pcmk__register_lib_messages(out);
rc = pcmk__check_rules(out, input, date, rule_ids);
pcmk__xml_output_finish(out, xml);
return rc;
}
diff --git a/lib/pacemaker/pcmk_status.c b/lib/pacemaker/pcmk_status.c
index bfce94219f..0e82633fb9 100644
--- a/lib/pacemaker/pcmk_status.c
+++ b/lib/pacemaker/pcmk_status.c
@@ -1,367 +1,368 @@
/*
- * Copyright 2004-2022 the Pacemaker project contributors
+ * Copyright 2004-2023 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 <crm_internal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <crm/cib/internal.h>
#include <crm/common/output.h>
#include <crm/common/results.h>
#include <crm/fencing/internal.h>
#include <crm/stonith-ng.h>
#include <pacemaker.h>
#include <pacemaker-internal.h>
static stonith_t *
fencing_connect(void)
{
stonith_t *st = stonith_api_new();
int rc = pcmk_rc_ok;
if (st == NULL) {
return NULL;
}
rc = st->cmds->connect(st, crm_system_name, NULL);
if (rc == pcmk_rc_ok) {
return st;
} else {
stonith_api_delete(st);
return NULL;
}
}
/*!
* \internal
* \brief Output the cluster status given a fencer and CIB connection
*
* \param[in,out] out Output object
* \param[in,out] stonith Fencer connection
* \param[in,out] cib CIB connection
* \param[in] current_cib Current CIB XML
* \param[in] pcmkd_state \p pacemakerd state
* \param[in] fence_history How much of the fencing history to output
* \param[in] show Group of \p pcmk_section_e flags
* \param[in] show_opts Group of \p pcmk_show_opt_e flags
* \param[in] only_node If a node name or tag, include only the
* matching node(s) (if any) in the output.
* If \p "*" or \p NULL, include all nodes
* in the output.
* \param[in] only_rsc If a resource ID or tag, include only the
* matching resource(s) (if any) in the
* output. If \p "*" or \p NULL, include all
* resources in the output.
* \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID
* \param[in] simple_output Whether to use a simple output format.
* Note: This is for use by \p crm_mon only
* and is planned to be deprecated.
*
* \return Standard Pacemaker return code
*/
int
pcmk__output_cluster_status(pcmk__output_t *out, stonith_t *stonith, cib_t *cib,
xmlNode *current_cib,
enum pcmk_pacemakerd_state pcmkd_state,
enum pcmk__fence_history fence_history,
uint32_t show, uint32_t show_opts,
const char *only_node, const char *only_rsc,
const char *neg_location_prefix, bool simple_output)
{
xmlNode *cib_copy = copy_xml(current_cib);
stonith_history_t *stonith_history = NULL;
int history_rc = 0;
pe_working_set_t *data_set = NULL;
GList *unames = NULL;
GList *resources = NULL;
int rc = pcmk_rc_ok;
if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
cib__clean_up_connection(&cib);
free_xml(cib_copy);
rc = pcmk_rc_schema_validation;
out->err(out, "Upgrade failed: %s", pcmk_rc_str(rc));
return rc;
}
/* get the stonith-history if there is evidence we need it */
if (fence_history != pcmk__fence_history_none) {
history_rc = pcmk__get_fencing_history(stonith, &stonith_history,
fence_history);
}
data_set = pe_new_working_set();
CRM_ASSERT(data_set != NULL);
pe__set_working_set_flags(data_set, pe_flag_no_compat);
data_set->input = cib_copy;
data_set->priv = out;
cluster_status(data_set);
if ((cib->variant == cib_native) && pcmk_is_set(show, pcmk_section_times)) {
if (pcmk__our_nodename == NULL) {
// Currently used only in the times section
pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0);
}
data_set->localhost = pcmk__our_nodename;
}
/* Unpack constraints if any section will need them
* (tickets may be referenced in constraints but not granted yet,
* and bans need negative location constraints) */
if (pcmk_is_set(show, pcmk_section_bans) || pcmk_is_set(show, pcmk_section_tickets)) {
pcmk__unpack_constraints(data_set);
}
unames = pe__build_node_name_list(data_set, only_node);
resources = pe__build_rsc_list(data_set, only_rsc);
/* Always print DC if NULL. */
if (data_set->dc_node == NULL) {
show |= pcmk_section_dc;
}
if (simple_output) {
rc = pcmk__output_simple_status(out, data_set);
} else {
out->message(out, "cluster-status",
data_set, pcmkd_state, pcmk_rc2exitc(history_rc),
stonith_history, fence_history, show, show_opts,
neg_location_prefix, unames, resources);
}
g_list_free_full(unames, free);
g_list_free_full(resources, free);
stonith_history_free(stonith_history);
stonith_history = NULL;
pe_free_working_set(data_set);
return rc;
}
int
pcmk_status(xmlNodePtr *xml)
{
cib_t *cib = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
uint32_t show_opts = pcmk_show_pending | pcmk_show_inactive_rscs | pcmk_show_timing;
cib = cib_new();
if (cib == NULL) {
return pcmk_rc_cib_corrupt;
}
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
cib_delete(cib);
return rc;
}
pcmk__register_lib_messages(out);
pe__register_messages(out);
stonith__register_messages(out);
rc = pcmk__status(out, cib, pcmk__fence_history_full, pcmk_section_all,
show_opts, NULL, NULL, NULL, false, 0);
pcmk__xml_output_finish(out, xml);
cib_delete(cib);
return rc;
}
/*!
* \internal
* \brief Query and output the cluster status
*
* The operation is considered a success if we're able to get the \p pacemakerd
* state. If possible, we'll also try to connect to the fencer and CIB and
* output their respective status information.
*
* \param[in,out] out Output object
* \param[in,out] cib CIB connection
* \param[in] fence_history How much of the fencing history to output
* \param[in] show Group of \p pcmk_section_e flags
* \param[in] show_opts Group of \p pcmk_show_opt_e flags
* \param[in] only_node If a node name or tag, include only the
* matching node(s) (if any) in the output.
* If \p "*" or \p NULL, include all nodes
* in the output.
* \param[in] only_rsc If a resource ID or tag, include only the
* matching resource(s) (if any) in the
* output. If \p "*" or \p NULL, include all
* resources in the output.
* \param[in] neg_location_prefix Prefix denoting a ban in a constraint ID
* \param[in] simple_output Whether to use a simple output format.
* Note: This is for use by \p crm_mon only
* and is planned to be deprecated.
* \param[in] timeout_ms How long to wait for a reply from the
* \p pacemakerd API. If 0,
* \p pcmk_ipc_dispatch_sync will be used.
* If positive, \p pcmk_ipc_dispatch_main
* will be used, and a new mainloop will be
* created for this purpose (freed before
* return).
*
* \return Standard Pacemaker return code
*/
int
pcmk__status(pcmk__output_t *out, cib_t *cib,
enum pcmk__fence_history fence_history, uint32_t show,
uint32_t show_opts, const char *only_node, const char *only_rsc,
const char *neg_location_prefix, bool simple_output,
unsigned int timeout_ms)
{
xmlNode *current_cib = NULL;
int rc = pcmk_rc_ok;
stonith_t *stonith = NULL;
enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid;
time_t last_updated = 0;
if (cib == NULL) {
return ENOTCONN;
}
if (cib->variant == cib_native) {
rc = pcmk__pacemakerd_status(out, crm_system_name, timeout_ms, false,
&pcmkd_state);
if (rc != pcmk_rc_ok) {
return rc;
}
last_updated = time(NULL);
switch (pcmkd_state) {
case pcmk_pacemakerd_state_running:
case pcmk_pacemakerd_state_shutting_down:
case pcmk_pacemakerd_state_remote:
/* Fencer and CIB may still be available while shutting down or
* running on a Pacemaker Remote node
*/
break;
default:
// Fencer and CIB are definitely unavailable
out->message(out, "pacemakerd-health",
NULL, pcmkd_state, NULL, last_updated);
return rc;
}
if (fence_history != pcmk__fence_history_none) {
stonith = fencing_connect();
}
}
rc = cib__signon_query(out, &cib, &current_cib);
if (rc != pcmk_rc_ok) {
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
// Invalid at this point means we didn't query the pcmkd state
out->message(out, "pacemakerd-health",
NULL, pcmkd_state, NULL, last_updated);
}
goto done;
}
rc = pcmk__output_cluster_status(out, stonith, cib, current_cib,
pcmkd_state, fence_history, show,
show_opts, only_node, only_rsc,
neg_location_prefix, simple_output);
if (rc != pcmk_rc_ok) {
out->err(out, "Error outputting status info from the fencer or CIB");
}
done:
stonith_api_delete(stonith);
free_xml(current_cib);
return pcmk_rc_ok;
}
/* This is an internal-only function that is planned to be deprecated and removed.
* It should only ever be called from crm_mon.
*/
int
-pcmk__output_simple_status(pcmk__output_t *out, pe_working_set_t *data_set)
+pcmk__output_simple_status(pcmk__output_t *out,
+ const pe_working_set_t *data_set)
{
int nodes_online = 0;
int nodes_standby = 0;
int nodes_maintenance = 0;
GString *offline_nodes = NULL;
bool no_dc = false;
bool offline = false;
bool has_warnings = false;
if (data_set->dc_node == NULL) {
has_warnings = true;
no_dc = true;
}
for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
if (node->details->standby && node->details->online) {
nodes_standby++;
} else if (node->details->maintenance && node->details->online) {
nodes_maintenance++;
} else if (node->details->online) {
nodes_online++;
} else {
pcmk__add_word(&offline_nodes, 1024, "offline node:");
pcmk__add_word(&offline_nodes, 0, pe__node_name(node));
has_warnings = true;
offline = true;
}
}
if (has_warnings) {
out->info(out, "CLUSTER WARN: %s%s%s",
no_dc ? "No DC" : "",
no_dc && offline ? ", " : "",
(offline? (const char *) offline_nodes->str : ""));
if (offline_nodes != NULL) {
g_string_free(offline_nodes, TRUE);
}
} else {
char *nodes_standby_s = NULL;
char *nodes_maint_s = NULL;
if (nodes_standby > 0) {
nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby,
pcmk__plural_s(nodes_standby));
}
if (nodes_maintenance > 0) {
nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
nodes_maintenance,
pcmk__plural_s(nodes_maintenance));
}
out->info(out, "CLUSTER OK: %d node%s online%s%s, "
"%d resource instance%s configured",
nodes_online, pcmk__plural_s(nodes_online),
nodes_standby_s != NULL ? nodes_standby_s : "",
nodes_maint_s != NULL ? nodes_maint_s : "",
data_set->ninstances, pcmk__plural_s(data_set->ninstances));
free(nodes_standby_s);
free(nodes_maint_s);
}
if (has_warnings) {
return pcmk_rc_error;
} else {
return pcmk_rc_ok;
}
/* coverity[leaked_storage] False positive */
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 10, 3:26 AM (11 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2003226
Default Alt Text
(42 KB)

Event Timeline