Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4639805
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
42 KB
Referenced Files
None
Subscribers
None
View Options
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, ¤t_cib);
if (rc != pcmk_rc_ok) {
if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
// Invalid at this point means we didn't query the pcmkd state
out->message(out, "pacemakerd-health",
NULL, pcmkd_state, NULL, last_updated);
}
goto done;
}
rc = pcmk__output_cluster_status(out, stonith, cib, current_cib,
pcmkd_state, fence_history, show,
show_opts, only_node, only_rsc,
neg_location_prefix, simple_output);
if (rc != pcmk_rc_ok) {
out->err(out, "Error outputting status info from the fencer or CIB");
}
done:
stonith_api_delete(stonith);
free_xml(current_cib);
return pcmk_rc_ok;
}
/* 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
Details
Attached
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)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment