gnodet-bot commented on code in PR #29480:
URL: https://github.com/apache/camel/pull/29480#discussion_r4513164757


##########
components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java:
##########
@@ -709,8 +711,109 @@ public Map<String, Object> finishExploration() {
                 throw new IllegalStateException("No exploration is running");
             }
             finish(now, ChaosExploration.Finish.FINISHED);
-            return exploration.toJson(now);
+            return exploration.toJson(now, this::findRule, new 
TreeMap<>(routes));
+        }
+    }
+
+    /**
+     * Begins an experiment of the running exploration (CAMEL-26774): a 
hypothesis and the rules that test it, added
+     * together (all or none), with at least one expectation ({@code expect=}) 
to decide it. The counts per route are
+     * read now, so its end says what changed while it ran. One experiment is 
open at a time.
+     *
+     * @param  hypothesis               what the experiment tests, in a few 
words, such as "a failed refund is read
+     *                                  again"
+     * @param  rules                    the rules in their one-line text form, 
separated by {@code ;}
+     * @return                          the experiment, as in the report
+     * @throws IllegalArgumentException when a rule is not valid, or no rule 
has an expectation
+     * @throws IllegalStateException    when no exploration is running, or an 
experiment is open
+     */
+    public Map<String, Object> beginExperiment(String hypothesis, String 
rules) {
+        List<String> texts = ChaosRuleParser.splitRules(rules);
+        if (texts.isEmpty()) {
+            throw new IllegalArgumentException("The chaos rule is empty");
+        }
+        List<ChaosRule.Builder> definitions = new ArrayList<>();
+        for (String text : texts) {
+            definitions.add(parse(text));
+        }
+        if (definitions.stream().allMatch(d -> d.expectRoute == null)) {
+            throw new IllegalArgumentException(
+                    "An experiment needs an expectation (expect=) on one of 
its rules to decide its hypothesis, such as"
+                                               + " expect=orders:failed<=0 
within=1m");
+        }
+        synchronized (lock) {
+            long now = TimeSource.currentTimeMillis();
+            checkDeadline(now);
+            if (exploration == null || !exploration.isRunning()) {
+                throw new IllegalStateException("No exploration is running: an 
experiment is part of one");
+            }
+            ChaosExperiment open = exploration.openExperiment();
+            if (open != null) {
+                throw new IllegalStateException("Experiment " + open.number + 
" is open: end it first");
+            }
+            Map<String, RouteCounts> counts = new TreeMap<>(routes);
+            List<String> ids = add(definitions);
+            ChaosExperiment experiment = 
exploration.beginExperiment(hypothesis, ids, counts, now);
+            LOG.info("Chaos experiment {} began with rules {}{}", 
experiment.number, ids,
+                    experiment.hypothesis != null ? ": " + 
experiment.hypothesis : "");
+            return experiment.toJson(this::findRule, counts, now);
+        }
+    }
+
+    /**
+     * Ends the open experiment: what came of it is read (the difference of 
the counts per route, the faults of each
+     * rule, the states of the expectations and the verdict), its rules are 
removed, and it stays in the journal.
+     *
+     * @param  note                  what the agent makes of it (optional)
+     * @return                       the experiment, as in the report
+     * @throws IllegalStateException when no experiment is open
+     */
+    public Map<String, Object> endExperiment(String note) {
+        synchronized (lock) {
+            long now = TimeSource.currentTimeMillis();
+            // an experiment open at the deadline was ended with the 
exploration
+            checkDeadline(now);
+            ChaosExperiment open = exploration != null ? 
exploration.openExperiment() : null;
+            if (open == null) {
+                throw new IllegalStateException("No experiment is open");
+            }
+            endExperiment(open, now, ChaosExperiment.End.ENDED, note);
+            return open.toJson(this::findRule, routes, now);

Review Comment:
   ⚠️ **Inconsistent defensive copy** — this call passes the live `routes` map, 
while every other `toJson`/`end` call in this class wraps it in `new 
TreeMap<>(routes)` (see `beginExperiment`, the private `endExperiment`, and 
`report`). This works today only because `experiment.end()` already cached the 
result, so `toJson()` returns early without touching `routes` — but that is 
non-obvious coupling.
   
   ```suggestion
               return open.toJson(this::findRule, new TreeMap<>(routes), now);
   ```



##########
components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java:
##########
@@ -709,8 +711,109 @@ public Map<String, Object> finishExploration() {
                 throw new IllegalStateException("No exploration is running");
             }
             finish(now, ChaosExploration.Finish.FINISHED);
-            return exploration.toJson(now);
+            return exploration.toJson(now, this::findRule, new 
TreeMap<>(routes));
+        }
+    }
+
+    /**
+     * Begins an experiment of the running exploration (CAMEL-26774): a 
hypothesis and the rules that test it, added
+     * together (all or none), with at least one expectation ({@code expect=}) 
to decide it. The counts per route are
+     * read now, so its end says what changed while it ran. One experiment is 
open at a time.
+     *
+     * @param  hypothesis               what the experiment tests, in a few 
words, such as "a failed refund is read
+     *                                  again"
+     * @param  rules                    the rules in their one-line text form, 
separated by {@code ;}
+     * @return                          the experiment, as in the report
+     * @throws IllegalArgumentException when a rule is not valid, or no rule 
has an expectation
+     * @throws IllegalStateException    when no exploration is running, or an 
experiment is open
+     */
+    public Map<String, Object> beginExperiment(String hypothesis, String 
rules) {
+        List<String> texts = ChaosRuleParser.splitRules(rules);
+        if (texts.isEmpty()) {
+            throw new IllegalArgumentException("The chaos rule is empty");
+        }
+        List<ChaosRule.Builder> definitions = new ArrayList<>();
+        for (String text : texts) {
+            definitions.add(parse(text));
+        }
+        if (definitions.stream().allMatch(d -> d.expectRoute == null)) {
+            throw new IllegalArgumentException(
+                    "An experiment needs an expectation (expect=) on one of 
its rules to decide its hypothesis, such as"
+                                               + " expect=orders:failed<=0 
within=1m");
+        }
+        synchronized (lock) {
+            long now = TimeSource.currentTimeMillis();
+            checkDeadline(now);
+            if (exploration == null || !exploration.isRunning()) {
+                throw new IllegalStateException("No exploration is running: an 
experiment is part of one");
+            }
+            ChaosExperiment open = exploration.openExperiment();
+            if (open != null) {
+                throw new IllegalStateException("Experiment " + open.number + 
" is open: end it first");
+            }
+            Map<String, RouteCounts> counts = new TreeMap<>(routes);
+            List<String> ids = add(definitions);
+            ChaosExperiment experiment = 
exploration.beginExperiment(hypothesis, ids, counts, now);
+            LOG.info("Chaos experiment {} began with rules {}{}", 
experiment.number, ids,
+                    experiment.hypothesis != null ? ": " + 
experiment.hypothesis : "");
+            return experiment.toJson(this::findRule, counts, now);
+        }
+    }
+
+    /**
+     * Ends the open experiment: what came of it is read (the difference of 
the counts per route, the faults of each
+     * rule, the states of the expectations and the verdict), its rules are 
removed, and it stays in the journal.
+     *
+     * @param  note                  what the agent makes of it (optional)
+     * @return                       the experiment, as in the report
+     * @throws IllegalStateException when no experiment is open
+     */
+    public Map<String, Object> endExperiment(String note) {
+        synchronized (lock) {
+            long now = TimeSource.currentTimeMillis();
+            // an experiment open at the deadline was ended with the 
exploration
+            checkDeadline(now);
+            ChaosExperiment open = exploration != null ? 
exploration.openExperiment() : null;
+            if (open == null) {
+                throw new IllegalStateException("No experiment is open");
+            }
+            endExperiment(open, now, ChaosExperiment.End.ENDED, note);
+            return open.toJson(this::findRule, routes, now);
+        }
+    }
+
+    // reads what came of the experiment, and then removes its rules; with the 
lock
+    private int endExperiment(ChaosExperiment experiment, long now, 
ChaosExperiment.End by, String note) {
+        experiment.end(this::findRule, new TreeMap<>(routes), now, by, note);
+        int count = 0;
+        for (String id : experiment.rules) {
+            int index = indexOf(id);
+            if (index >= 0) {
+                ChaosRule removed = rules.remove(index);
+                removed.removed(State.REMOVED, now);
+                remember(removed);
+                count++;
+            }
+        }
+        update();
+        LOG.info("Chaos experiment {} ended: {}", experiment.number,
+                experiment.toJson(this::findRule, routes, 
now).getString("verdict"));

Review Comment:
   ⚠️ **Same inconsistency** — the `LOG.info` call just below the 
`experiment.end(…, new TreeMap<>(routes), …)` call passes raw `routes` to 
`toJson()`. Again safe today due to caching, but inconsistent with the pattern 
used everywhere else.
   
   ```suggestion
                   experiment.toJson(this::findRule, new TreeMap<>(routes), 
now).getString("verdict"));
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to