Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: libpe_rules: New timezone attribute in date_expression of rules #3068

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions daemons/attrd/attrd_alerts.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void
return;
}

pe_free_alert_list(attrd_alert_list);
attrd_alert_list = pe_unpack_alerts(crmalerts);
pe__free_alert_list(attrd_alert_list);
attrd_alert_list = pe__unpack_alerts(crmalerts);
}

gboolean
Expand Down
4 changes: 2 additions & 2 deletions daemons/controld/controld_alerts.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ static GList *crmd_alert_list = NULL;
void
crmd_unpack_alerts(xmlNode *alerts)
{
pe_free_alert_list(crmd_alert_list);
crmd_alert_list = pe_unpack_alerts(alerts);
pe__free_alert_list(crmd_alert_list);
crmd_alert_list = pe__unpack_alerts(alerts);
}

void
Expand Down
27 changes: 25 additions & 2 deletions doc/sphinx/Pacemaker_Explained/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,21 +254,27 @@ A ``date_expression`` element may optionally contain a ``date_spec`` or
| | pair: start; date_expression |
| | |
| | A date/time conforming to the |
| | `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ |
| | `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ |
| | specification. May be used when ``operation`` is |
| | ``in_range`` (in which case at least one of ``start`` or |
| | ``end`` must be specified) or ``gt`` (in which case |
| | ``start`` is required). |
| | |
| | Parsing of ISO 8601 timezones in date/time strings is |
| | currently incomplete and should not be relied upon. |
+---------------+-----------------------------------------------------------+
| end | .. index:: |
| | pair: end; date_expression |
| | |
| | A date/time conforming to the |
| | `ISO8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ |
| | `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_ |
| | specification. May be used when ``operation`` is |
| | ``in_range`` (in which case at least one of ``start`` or |
| | ``end`` must be specified) or ``lt`` (in which case |
| | ``end`` is required). |
| | |
| | Parsing of ISO 8601 timezones in date/time strings is |
| | currently incomplete and should not be relied upon. |
nrwahl2 marked this conversation as resolved.
Show resolved Hide resolved
+---------------+-----------------------------------------------------------+
| operation | .. index:: |
| | pair: operation; date_expression |
Expand All @@ -288,10 +294,27 @@ A ``date_expression`` element may optionally contain a ``date_spec`` or
| | the specification given in the contained ``date_spec`` |
| | element (described below) |
+---------------+-----------------------------------------------------------+
| timezone | .. index:: |
| | pair: timezone; date_expression |
| | |
| | A timezone conforming to the format specified in the |
| | ``tzset(3)`` man page. The ``date_expression`` is |
| | evaluated based on the current time in this timezone. |
| | Defaults to the existing value of the ``TZ`` environment |
| | variable if set, or to the system timezone otherwise. As |
| | with ``TZ``, if the value is set but empty or cannot be |
| | interpreted, UTC is used. |
+---------------+-----------------------------------------------------------+


.. note:: There is no ``eq``, ``neq``, ``gte``, or ``lte`` operation, since
they would be valid only for a single second.
.. note:: Pacemaker will try to adjust ``start`` and ``end`` to ``timezone`` if
specified, or to the local timezone otherwise, for correct comparison
to the current time. This adjustment takes place after parsing and
applying the timezone (if present) from the ``start`` or ``end``
string. As noted above, this parsing is incomplete and should not be
relied upon.


.. index::
Expand Down
1 change: 1 addition & 0 deletions include/crm/common/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ header_HEADERS = acl.h \
ipc_pacemakerd.h \
ipc_schedulerd.h \
iso8601.h \
iso8601_compat.h \
logging.h \
logging_compat.h \
mainloop.h \
Expand Down
7 changes: 5 additions & 2 deletions include/crm/common/iso8601.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2005-2020 the Pacemaker project contributors
* Copyright 2005-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
Expand Down Expand Up @@ -85,7 +85,6 @@ int crm_time_compare(const crm_time_t *a, const crm_time_t *b);

int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
uint32_t *s);
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m);
int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
uint32_t *d);
int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d);
Expand Down Expand Up @@ -123,6 +122,10 @@ int crm_time_days_in_month(int month, int year);
bool crm_time_leapyear(int year);
bool crm_time_check(const crm_time_t *dt);

#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include <crm/common/iso8601_compat.h>
#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)

#ifdef __cplusplus
}
#endif
Expand Down
37 changes: 37 additions & 0 deletions include/crm/common/iso8601_compat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2005-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__CRM_COMMON_ISO8601_COMPAT__H
#define PCMK__CRM_COMMON_ISO8601_COMPAT__H

#include <stdint.h> // uint32_t

#include <crm/common/iso8601.h> // crm_time_t

#ifdef __cplusplus
extern "C" {
#endif

/**
* \file
* \brief Deprecated Pacemaker ISO 8601 API
* \ingroup core
* \deprecated Do not include this header directly. The ISO 8601 APIs in this
* header, and the header itself, will be removed in a future
* release.
*/

//! \deprecated Do not use Pacemaker for general-purpose date and time
int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m);

#ifdef __cplusplus
}
#endif

#endif // PCMK__CRM_COMMON_ISO8601_COMPAT__H
4 changes: 3 additions & 1 deletion include/crm/common/iso8601_internal.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2022 the Pacemaker project contributors
* Copyright 2015-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
Expand All @@ -17,6 +17,7 @@

typedef struct pcmk__time_us pcmk__time_hr_t;

void pcmk__time_get_timezone(const crm_time_t *dt, int *hours, int *minutes);
pcmk__time_hr_t *pcmk__time_hr_convert(pcmk__time_hr_t *target,
const crm_time_t *dt);
void pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt);
Expand All @@ -28,6 +29,7 @@ char *pcmk__epoch2str(const time_t *source, uint32_t flags);
char *pcmk__timespec2str(const struct timespec *ts, uint32_t flags);
const char *pcmk__readable_interval(guint interval_ms);
crm_time_t *pcmk__copy_timet(time_t source);
void pcmk__time_set_timezone(crm_time_t *dt, int hours, int minutes);

struct pcmk__time_us {
int years;
Expand Down
2 changes: 1 addition & 1 deletion include/crm/crm.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ extern "C" {
* >=3.2.0: DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED
* >=3.19.0: DC supports PCMK__CIB_REQUEST_COMMIT_TRANSACT
*/
# define CRM_FEATURE_SET "3.19.0"
#define CRM_FEATURE_SET "3.19.1"

/* Pacemaker's CPG protocols use fixed-width binary fields for the sender and
* recipient of a CPG message. This imposes an arbitrary limit on cluster node
Expand Down
1 change: 1 addition & 0 deletions include/crm/msg_xml.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ extern "C" {
#define PCMK_XA_PROMOTED_MAX_LEGACY "master-max"
#define PCMK_XA_PROMOTED_NODE_MAX_LEGACY "master-node-max"

#define PCMK_XA_TIME_ZONE "timezone"

/*
* Meta attributes
Expand Down
8 changes: 4 additions & 4 deletions include/crm/pengine/rules_internal.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2022 the Pacemaker project contributors
* Copyright 2015-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
Expand All @@ -16,8 +16,8 @@
#include <crm/pengine/common.h>
#include <crm/pengine/rules.h>

GList *pe_unpack_alerts(const xmlNode *alerts);
void pe_free_alert_list(GList *alert_list);
GList *pe__unpack_alerts(const xmlNode *alerts);
void pe__free_alert_list(GList *alert_list);

gboolean pe__eval_attr_expr(const xmlNode *expr,
const pe_rule_eval_data_t *rule_data);
Expand All @@ -31,6 +31,6 @@ gboolean pe__eval_role_expr(const xmlNode *expr,
gboolean pe__eval_rsc_expr(const xmlNode *expr,
const pe_rule_eval_data_t *rule_data);

int pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec);
int pe__cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec);

#endif
87 changes: 81 additions & 6 deletions lib/common/iso8601.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2005-2022 the Pacemaker project contributors
* Copyright 2005-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
Expand Down Expand Up @@ -303,13 +303,71 @@ crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
return TRUE;
}

int
crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
/*!
* \internal
* \brief Get a date/time object's timezone as offset relative to UTC
*
* \param[in] dt Date/time object whose timezone to get
* \param[out] hours Where to store hours offset from UTC
* \param[out] minutes Where to store remaining minutes offset from UTC
*
* \note If the offset is negative, both \p hours and \p minutes will be set to
* nonpositive values. Make note of this if formatting text. Typically, a
* negative sign should precede only the hours field.
*/
void
pcmk__time_get_timezone(const crm_time_t *dt, int *hours, int *minutes)
{
uint32_t s;
// Be paranoid and don't cast (int *) to (uint32_t *)
uint32_t hours_u = 0;
uint32_t minutes_u = 0;
uint32_t seconds_u = 0;

crm_time_get_sec(dt->seconds, h, m, &s);
return TRUE;
CRM_ASSERT((dt != NULL) && (hours != NULL) && (minutes != NULL));

crm_time_get_sec(dt->offset, &hours_u, &minutes_u, &seconds_u);

if (dt->offset < 0) {
*hours = -hours_u;
*minutes = -minutes_u;
} else {
*hours = hours_u;
*minutes = minutes_u;
}
}

/*!
* \internal
* \brief Set a date/time object's timezone relative to UTC
*
* \param[in,out] dt Date/time object whose timezone to set
* \param[in] hours Hours offset from UTC
* \param[in] minutes Minutes offset from UTC
*
* \note \p hours and \p minutes are summed. If \p hours is negative, then
* \p minutes typically should also be nonpositive, and vice-versa.
*/
void
pcmk__time_set_timezone(crm_time_t *dt, int hours, int minutes)
{
// Desired offset from UTC in seconds
int seconds = (hours * HOUR_SECONDS) + (minutes * 60);

// Current offsets from UTC
int hours_current = 0;
int minutes_current = 0;
int seconds_current = 0;

CRM_ASSERT(dt != NULL);

pcmk__time_get_timezone(dt, &hours_current, &minutes_current);
seconds_current = (hours_current * HOUR_SECONDS) + (minutes_current * 60);

// Adjust the object's time
crm_time_add_seconds(dt, seconds - seconds_current);

// Set the object's timezone
dt->offset = seconds;
}

long long
Expand Down Expand Up @@ -1968,3 +2026,20 @@ pcmk__readable_interval(guint interval_ms)
}
return str;
}

// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START

#include <crm/common/iso8601_compat.h>

int
crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m)
{
uint32_t s;

crm_time_get_sec(dt->offset, h, m, &s);
return TRUE;
}

// LCOV_EXCL_STOP
// End deprecated API
3 changes: 3 additions & 0 deletions lib/pengine/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ static pcmk__cluster_option_t pe_opts[] = {
"passed since the shutdown was initiated, even if the node has not "
"rejoined.")
},
{
PCMK_XA_TIME_ZONE, NULL, "string", NULL,
},

// Fencing-related options
{
Expand Down
4 changes: 2 additions & 2 deletions lib/pengine/rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ check_passes(int rc) {
} while (0)

int
pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec)
pe__cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec)
{
uint32_t h, m, s, y, d, w;

Expand Down Expand Up @@ -1083,7 +1083,7 @@ pe__eval_date_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data,
}

} else if (pcmk__str_eq(op, "date_spec", pcmk__str_casei)) {
rc = pe_cron_range_satisfied(rule_data->now, date_spec);
rc = pe__cron_range_satisfied(rule_data->now, date_spec);
// @TODO set next_change appropriately

} else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) {
Expand Down
6 changes: 3 additions & 3 deletions lib/pengine/rules_alerts.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
* but is supplied for use by daemons that need to send alerts.
*/
GList *
pe_unpack_alerts(const xmlNode *alerts)
pe__unpack_alerts(const xmlNode *alerts)
{
xmlNode *alert;
pcmk__alert_t *entry;
Expand Down Expand Up @@ -281,12 +281,12 @@ pe_unpack_alerts(const xmlNode *alerts)

/*!
* \internal
* \brief Free an alert list generated by pe_unpack_alerts()
* \brief Free an alert list generated by pe__unpack_alerts()
*
* \param[in,out] alert_list Alert list to free
*/
void
pe_free_alert_list(GList *alert_list)
pe__free_alert_list(GList *alert_list)
{
if (alert_list) {
g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
Expand Down
2 changes: 1 addition & 1 deletion lib/pengine/tests/rules/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ include $(top_srcdir)/mk/unittest.mk
LDADD += $(top_builddir)/lib/pengine/libpe_rules_test.la

# Add "_test" to the end of all test program names to simplify .gitignore.
check_PROGRAMS = pe_cron_range_satisfied_test
check_PROGRAMS = pe__cron_range_satisfied_test

TESTS = $(check_PROGRAMS)
Loading