Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion libpromises/attributes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,18 @@ ContextConstraint GetContextConstraints(const EvalContext *ctx, const Promise *p
a.expression = NULL;
a.persistent = PromiseGetConstraintAsInt(ctx, "persistence", pp);

{
const char *tp = PromiseGetConstraintAsRval(pp, "timer_policy", RVAL_TYPE_SCALAR);
if (tp != NULL && strncmp(tp, "abs", 3) == 0)
{
a.timer = CONTEXT_STATE_POLICY_PRESERVE;
}
else
{
a.timer = CONTEXT_STATE_POLICY_RESET;
}
}

{
const char *context_scope = PromiseGetConstraintAsRval(pp, "scope", RVAL_TYPE_SCALAR);
a.scope = ContextScopeFromString(context_scope);
Expand All @@ -1143,7 +1155,9 @@ ContextConstraint GetContextConstraints(const EvalContext *ctx, const Promise *p

for (int k = 0; CF_CLASSBODY[k].lval != NULL; k++)
{
if (strcmp(cp->lval, "persistence") == 0 || strcmp(cp->lval, "scope") == 0)
if (strcmp(cp->lval, "persistence") == 0 ||
strcmp(cp->lval, "scope") == 0 ||
strcmp(cp->lval, "timer_policy") == 0)
{
continue;
}
Expand Down
1 change: 1 addition & 0 deletions libpromises/cf3.defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,7 @@ typedef struct
ContextScope scope;
int nconstraints;
int persistent;
PersistentClassPolicy timer;
} ContextConstraint;

/*************************************************************************/
Expand Down
1 change: 1 addition & 0 deletions libpromises/mod_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ const ConstraintSyntax CF_CLASSBODY[] =
ConstraintSyntaxNewContext("not", "Evaluate the negation of string expression in normal form", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewContextList("select_class", "Select one of the named list of classes to define based on host identity. Default value: random_selection", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewContextList("xor", "Combine class sources with XOR", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewOption("timer_policy", "absolute,reset", "Whether a persistent class restarts its counter when rediscovered. Default value: reset", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewNull()
};

Expand Down
2 changes: 1 addition & 1 deletion libpromises/verify_classes.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ PromiseResult VerifyClassPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED
pp->promiser, a.context.persistent);
Buffer *buf = StringSetToBuffer(tags, ',');
EvalContextHeapPersistentSave(ctx, pp->promiser, a.context.persistent,
CONTEXT_STATE_POLICY_RESET, BufferData(buf));
a.context.timer, BufferData(buf));
BufferDestroy(buf);
}
if (inserted && (comment != NULL))
Expand Down
77 changes: 77 additions & 0 deletions tests/acceptance/02_classes/01_basic/persistent_timer_policy.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#######################################################
#
# CFE-4681: classes: promises should support timer_policy
#
# Test that timer_policy => "absolute" on a classes: promise
# causes the persistent class to be preserved (not reset)
# on subsequent agent runs.
#
#######################################################

body common control
{
inputs => { "../../default.sub.cf" };
bundlesequence => { default("$(this.promise_filename)") };
version => "1.0";
}

bundle agent init
{
vars:
"dflags" string => ifelse("EXTRA", "-DDEBUG,EXTRA", "-DDEBUG");

# Clean up any leftover persistent class from a previous test run
commands:
"$(G.echo)" classes => init_cancel_always;
}

body classes init_cancel_always
{
cancel_repaired => { "timer_policy_test_class" };
cancel_notkept => { "timer_policy_test_class" };
cancel_kept => { "timer_policy_test_class" };
}

bundle agent test
{
vars:
"dflags" string => ifelse("EXTRA", "-DDEBUG,EXTRA", "-DDEBUG");

# First run: define the persistent class with timer_policy => "absolute"
commands:
"$(sys.cf_agent) -K $(dflags) -f $(this.promise_filename).sub"
classes => test_always("done_persisting");
}

body classes test_always(x)
{
promise_repaired => { "$(x)" };
promise_kept => { "$(x)" };
repair_failed => { "$(x)" };
repair_denied => { "$(x)" };
repair_timeout => { "$(x)" };
}

bundle agent check
{
vars:
"dflags" string => ifelse("EXTRA", "-DDEBUG,EXTRA", "-DDEBUG");

done_persisting::
# Second run: the class should be in a preserved state
"subout" string => execresult("$(sys.cf_agent) -Kv $(dflags) -f $(this.promise_filename).sub 2>&1", "useshell");

classes:
done_persisting::
"preserved" expression => regcmp(".*already in a preserved state.*", "$(subout)");

methods:
done_persisting::
"" usebundle => dcs_passif("preserved", $(this.promise_filename));

reports:
DEBUG.done_persisting::
"Agent output: $(subout)";
!done_persisting::
"$(this.promise_filename) FAIL (first run did not complete)";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
body common control
{
bundlesequence => { run };
}

bundle common run
{
classes:
"timer_policy_test_class"
expression => "any",
persistence => "120",
timer_policy => "absolute";
}
46 changes: 46 additions & 0 deletions tests/unit/eval_context_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,51 @@ void test_eval_with_token_from_list(void)
StringSetDestroy(time_classes);
}

static void test_persistent_class_timer_policy(void)
{
EvalContext *ctx = EvalContextNew();

/* Save a persistent class with PRESERVE policy, 60 minute TTL */
EvalContextHeapPersistentSave(ctx, "timer_test", 60,
CONTEXT_STATE_POLICY_PRESERVE, "tag1");

/* Verify the class loads correctly after PRESERVE save */
EvalContextHeapPersistentLoadAll(ctx);

{
const Class *cls = EvalContextClassGet(ctx, "default", "timer_test");
assert_true(cls != NULL);
assert_string_equal("timer_test", cls->name);
}

/* Save again with PRESERVE -- the function should early-return
* (class is preserved, not expired, same tags), leaving the DB
* record unchanged. We verify by loading persistent classes and
* checking the class is still defined. */
EvalContextHeapPersistentSave(ctx, "timer_test", 60,
CONTEXT_STATE_POLICY_PRESERVE, "tag1");

/* Class should still be defined after the second PRESERVE save */
{
const Class *cls = EvalContextClassGet(ctx, "default", "timer_test");
assert_true(cls != NULL);
assert_string_equal("timer_test", cls->name);
}

/* Save with RESET policy -- the record SHOULD be overwritten.
* The class should still be loadable afterward. */
EvalContextHeapPersistentSave(ctx, "timer_test", 60,
CONTEXT_STATE_POLICY_RESET, "tag1");

{
const Class *cls = EvalContextClassGet(ctx, "default", "timer_test");
assert_true(cls != NULL);
assert_string_equal("timer_test", cls->name);
}

EvalContextDestroy(ctx);
}

int main()
{
PRINT_TEST_BANNER();
Expand All @@ -160,6 +205,7 @@ int main()
const UnitTest tests[] =
{
unit_test(test_class_persistence),
unit_test(test_persistent_class_timer_policy),
unit_test(test_changes_chroot),
unit_test(test_eval_with_token_from_list),
};
Expand Down
Loading