This is an automated email from the ASF dual-hosted git repository.
shuber pushed a commit to branch unomi-3-dev
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/unomi-3-dev by this push:
new eded9dfcc UNOMI-881 Remove unused `CONFIGURATION_UPDATE_ANALYSIS.md`,
adjust logging detail levels, and refine test utilities and progress display
logic.
eded9dfcc is described below
commit eded9dfcc48636dd5c816b6e90b2a8bbccb58d64
Author: Serge Huber <[email protected]>
AuthorDate: Thu Jan 1 15:15:16 2026 +0100
UNOMI-881 Remove unused `CONFIGURATION_UPDATE_ANALYSIS.md`, adjust logging
detail levels, and refine test utilities and progress display logic.
---
CONFIGURATION_UPDATE_ANALYSIS.md | 504 ---------------------
.../org/apache/unomi/itests/ProgressListener.java | 80 +++-
services-common/pom.xml | 10 +
.../impl/scheduler/SchedulerServiceImpl.java | 32 +-
.../impl/scheduler/TaskExecutionManager.java | 14 +-
.../impl/scheduler/SchedulerServiceImplTest.java | 22 +-
tracing/tracing-impl/pom.xml | 4 +-
7 files changed, 117 insertions(+), 549 deletions(-)
diff --git a/CONFIGURATION_UPDATE_ANALYSIS.md b/CONFIGURATION_UPDATE_ANALYSIS.md
deleted file mode 100644
index 71eaa74f7..000000000
--- a/CONFIGURATION_UPDATE_ANALYSIS.md
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-//
-
-# Configuration Update Analysis: ElasticSearch and OpenSearch Persistence
-
-## Problem Statement
-
-The current `updated()` method in both `ElasticSearchPersistenceServiceImpl`
and `OpenSearchPersistenceServiceImpl` only updates in-memory configuration
values. It does **not** update the corresponding infrastructure components
(index templates, rollover policies, lifecycle policies, etc.) that were
created during initialization based on these configuration values.
-
-### Example: rollover.indices Change
-
-When `rollover.indices` changes (e.g., from `"event,session"` to
`"event,session,profile"`), the system needs to:
-1. **Create Index Templates**: New rollover templates must be created for
newly added item types (e.g., `profile`)
-2. **Update OpenSearch ISM Policy**: The ISM policy's
`ism_template.index_patterns` must be updated to include patterns for new item
types
-3. **Handle Removed Item Types**: Templates for removed item types may need
cleanup (though existing indices will continue to work)
-
-However, the current `updated()` method only calls `setRolloverIndices()`,
which updates the in-memory value but does not:
-- Create rollover templates for newly added item types
-- Update the OpenSearch ISM policy template patterns
-- Remove templates for item types no longer in the list (optional)
-
-## Configuration Properties Analysis
-
-### Infrastructure-Affecting Properties
-
-These properties affect infrastructure components that need to be updated when
changed:
-
-#### 1. `indexPrefix`
-**Used in:**
-- Rollover alias: `indexPrefix + "-" + itemName`
-- Index names: `indexPrefix + "-" + itemType`
-- Rollover index patterns: `indexPrefix + "-" + itemType + "-*"`
-- Lifecycle policy name: `indexPrefix + "-" + ROLLOVER_LIFECYCLE_NAME` (ES) or
`indexPrefix + "-rollover-lifecycle-policy"` (OS)
-- Index template names: `rolloverAlias + "-rollover-template"`
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Template names and index patterns depend on
`indexPrefix`
-- ✅ **Rollover Lifecycle Policies**: Policy names depend on `indexPrefix`
-- ✅ **Existing Indices**: Index names depend on `indexPrefix` (but existing
indices cannot be renamed)
-- ⚠️ **Note**: Changing `indexPrefix` effectively creates a new namespace.
Existing indices won't be affected, but new operations will use the new prefix.
-
-**Update Required:**
-- Re-register rollover lifecycle policy (with new name)
-- Recreate index templates for rollover indices (with new names/patterns)
-- Update rollover alias references in template settings
-
-#### 2. `rolloverIndexNumberOfShards`
-**Used in:**
-- Index template settings for rollover indices
-- New rollover index creation
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `number_of_shards`
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects
new indices created from template)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-
-#### 3. `rolloverIndexNumberOfReplicas`
-**Used in:**
-- Index template settings for rollover indices
-- New rollover index creation
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `number_of_replicas`
-- ⚠️ **Existing Indices**: Can be updated via index settings API (affects
existing indices)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-- Optionally update existing rollover indices (if desired)
-
-#### 4. `rolloverIndexMappingTotalFieldsLimit`
-**Used in:**
-- Index template settings for rollover indices
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `mapping.total_fields.limit`
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects
new indices)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-
-#### 5. `rolloverIndexMaxDocValueFieldsSearch`
-**Used in:**
-- Index template settings for rollover indices
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `max_docvalue_fields_search`
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects
new indices)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-
-#### 6. `rolloverMaxSize`
-**Used in:**
-- Rollover lifecycle policy rollover action
-
-**Infrastructure Impact:**
-- ✅ **Rollover Lifecycle Policy**: Policy contains rollover action with
`maxSize`
-
-**Update Required:**
-- Re-register rollover lifecycle policy
-
-#### 7. `rolloverMaxAge`
-**Used in:**
-- Rollover lifecycle policy rollover action
-
-**Infrastructure Impact:**
-- ✅ **Rollover Lifecycle Policy**: Policy contains rollover action with
`maxAge`
-
-**Update Required:**
-- Re-register rollover lifecycle policy
-
-#### 8. `rolloverMaxDocs`
-**Used in:**
-- Rollover lifecycle policy rollover action
-
-**Infrastructure Impact:**
-- ✅ **Rollover Lifecycle Policy**: Policy contains rollover action with
`maxDocs`
-
-**Update Required:**
-- Re-register rollover lifecycle policy
-
-#### 9. `rolloverIndices` (Configuration: `rollover.indices`)
-**Used in:**
-- Determines which item types use rollover (checked via
`isItemTypeRollingOver(itemType)`)
-- Controls whether `internalCreateRolloverTemplate()` is called during index
creation
-- **OpenSearch only**: ISM policy's `ism_template.index_patterns` contains
patterns for each item type in the list
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Templates are only created for item types in
`rolloverIndices` list
- - When an item type is added: A rollover template must be created for that
item type
- - When an item type is removed: The template may remain but won't be used
for new indices
-- ✅ **OpenSearch ISM Policy**: The `ism_template.index_patterns` array must
match the current `rolloverIndices` list
- - Each item type in the list should have a corresponding pattern:
`indexPrefix + "-" + itemType + "-*"`
-- ⚠️ **Existing Indices**: Existing indices are not affected, but new indices
will follow the new configuration
-
-**Update Required:**
-- **For newly added item types:**
- - Create rollover index template (call
`internalCreateRolloverTemplate(itemType)`)
- - Ensure mapping exists for the item type
-- **For OpenSearch:**
- - Re-register rollover lifecycle policy to update
`ism_template.index_patterns`
-- **For removed item types (optional):**
- - Optionally remove rollover templates (though keeping them doesn't cause
issues)
-
-**Example Scenario:**
-- **Before**: `rollover.indices = "event,session"`
-- **After**: `rollover.indices = "event,session,profile"`
-- **Required Actions:**
- 1. Create rollover template for `profile` item type
- 2. Update OpenSearch ISM policy to include pattern for `profile` indices
- 3. When `profile` index is first created, it will use rollover instead of
regular index creation
-
-#### 10. `numberOfShards` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects
new indices)
-
-**Update Required:**
-- None (only affects new indices created after change)
-
-#### 11. `numberOfReplicas` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ✅ **Existing Indices**: Can be updated via index settings API
-
-**Update Required:**
-- Optionally update existing non-rollover indices (if desired)
-
-#### 12. `indexMappingTotalFieldsLimit` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects
new indices)
-
-**Update Required:**
-- None (only affects new indices created after change)
-
-#### 13. `indexMaxDocValueFieldsSearch` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects
new indices)
-
-**Update Required:**
-- None (only affects new indices created after change)
-
-### Non-Infrastructure Properties
-
-These properties only affect runtime behavior and don't require infrastructure
updates:
-- `throwExceptions`
-- `alwaysOverwrite`
-- `useBatchingForSave`
-- `useBatchingForUpdate`
-- `aggQueryThrowOnMissingDocs`
-- `logLevelRestClient`
-- `clientSocketTimeout`
-- `taskWaitingTimeout`
-- `taskWaitingPollingInterval`
-- `aggQueryMaxResponseSizeHttp`
-- `aggregateQueryBucketSize`
-- `itemTypeToRefreshPolicy`
-
-## Current Initialization Flow
-
-### ElasticSearch
-1. `start()` method calls:
- - `registerRolloverLifecyclePolicy()` - Creates ILM policy with name
`indexPrefix + "-" + ROLLOVER_LIFECYCLE_NAME`
- - `loadPredefinedMappings()` - For each mapping:
- - If item type is in `rolloverIndices`: calls
`internalCreateRolloverTemplate()` and `internalCreateRolloverIndex()`
- - Otherwise: calls `internalCreateIndex()`
-
-### OpenSearch
-1. `start()` method calls:
- - `registerRolloverLifecyclePolicy()` - Creates ISM policy with name
`indexPrefix + "-rollover-lifecycle-policy"`
- - `loadPredefinedMappings()` - For each mapping:
- - If item type is in `rolloverIndices`: calls
`internalCreateRolloverTemplate()` and `internalCreateRolloverIndex()`
- - Otherwise: calls `internalCreateIndex()`
-
-## Required Updates for Configuration Changes
-
-### Minimal Change Solution Approach
-
-The solution should:
-1. **Track previous configuration values** to detect what changed
-2. **Identify affected infrastructure** based on changed properties
-3. **Update infrastructure components** only when necessary
-4. **Minimize disruption** by only updating what's needed
-
-### Update Strategy by Property
-
-#### For `indexPrefix`:
-- **Action**: Re-register lifecycle policy, recreate all rollover templates
-- **Reason**: Policy and template names depend on prefix
-- **Note**: Existing indices keep old prefix (cannot rename indices)
-
-#### For `rolloverIndexNumberOfShards`, `rolloverIndexNumberOfReplicas`,
`rolloverIndexMappingTotalFieldsLimit`, `rolloverIndexMaxDocValueFieldsSearch`:
-- **Action**: Update index template settings for all rollover item types
-- **Method**: Use `PutIndexTemplateRequest` with updated settings
-- **Note**: Only affects new indices created from template
-
-#### For `rolloverMaxSize`, `rolloverMaxAge`, `rolloverMaxDocs`:
-- **Action**: Re-register rollover lifecycle policy
-- **Method**: Call `registerRolloverLifecyclePolicy()` again
-- **Note**: Policy is updated in-place (same name)
-
-#### For `rolloverIndices`:
-- **Action**:
- - **Detect changes**: Compare old and new lists to find added/removed item
types
- - **For newly added item types**:
- - Verify mapping exists for the item type
- - Create rollover index template (call
`internalCreateRolloverTemplate(itemType)`)
- - **For OpenSearch**: Re-register lifecycle policy to update
`ism_template.index_patterns`
- - **For removed item types** (optional):
- - Optionally remove rollover templates (though keeping them doesn't cause
issues)
-- **Method**:
- - Parse comma-separated list and compare with previous list
- - For each new item type: `internalCreateRolloverTemplate(itemType)` (if
mapping exists)
- - For OpenSearch: `registerRolloverLifecyclePolicy()` (updates ISM template
patterns)
- - Optionally: `removeIndexTemplate()` for removed types
-- **Note**: This is one of the most complex updates because it requires:
- - List comparison (added/removed items)
- - Access to mappings to verify item types are valid
- - Different handling for ElasticSearch vs OpenSearch (ISM policy update)
-
-#### For `numberOfReplicas` (non-rollover):
-- **Action**: Optionally update existing non-rollover indices
-- **Method**: Use index settings update API
-- **Note**: This is optional - only affects existing indices, not templates
-
-## Implementation Requirements
-
-### 1. Configuration Change Detection
-- Store previous configuration values (or use OSGi Configuration Admin to
detect changes)
-- Compare old vs new values to determine what changed
-
-### 2. Infrastructure Update Methods
-- **Update index template**: The existing `internalCreateRolloverTemplate()`
method can be reused - `putIndexTemplate`/`putTemplate` operations are
idempotent and will update existing templates
-- **Re-register lifecycle policy**: Already exists
(`registerRolloverLifecyclePolicy()`) - policy updates are also idempotent
-- **Update existing indices**: Method to update index settings (for
`numberOfReplicas`) - requires separate index settings update API call
-
-### 3. Rollover Item Type Discovery
-- Need to know which item types use rollover (from `rolloverIndices`)
-- Need access to mappings for those item types
-
-### 4. Error Handling
-- Handle cases where templates/policies don't exist
-- Handle partial failures gracefully
-- Log warnings for properties that can't be updated on existing indices
-
-## Minimal Change Implementation Strategy
-
-### Option 1: Extend `updated()` Method (Recommended)
-1. Add property change detection
-2. For each changed infrastructure-affecting property:
- - Determine affected components
- - Call appropriate update methods
-3. Keep existing simple property updates for non-infrastructure properties
-
-**Pros:**
-- Minimal code changes
-- Reuses existing methods
-- Clear separation of concerns
-
-**Cons:**
-- `updated()` method becomes more complex
-- Need to track previous values
-
-### Option 2: Separate Update Handler
-1. Create `InfrastructureUpdateHandler` class
-2. `updated()` method delegates infrastructure updates to handler
-3. Handler manages change detection and updates
-
-**Pros:**
-- Better separation of concerns
-- Easier to test
-- More maintainable
-
-**Cons:**
-- More code changes
-- Additional class to maintain
-
-### Recommended Approach: Option 1 with Helper Methods
-
-1. **Add configuration tracking**: Store previous values in instance variables
-2. **Create helper methods**:
- - `updateRolloverTemplatesForAllItemTypes()` - Updates all rollover
templates
- - `updateRolloverTemplate(String itemType)` - Updates single template
- - `updateExistingRolloverIndices(String itemType, String setting, Object
value)` - Updates existing indices (optional)
-3. **Extend `updated()` method**:
- - Detect changes by comparing old vs new values
- - For each changed property, call appropriate update methods
- - Update stored previous values after successful updates
-
-## Code Structure Changes Required
-
-### ElasticSearchPersistenceServiceImpl
-```java
-// Add fields to track previous values
-private String previousIndexPrefix;
-private String previousRolloverIndexNumberOfShards;
-private List<String> previousRolloverIndices;
-// ... etc for all infrastructure-affecting properties
-
-@Override
-public void updated(Dictionary<String, ?> properties) {
- // Existing simple property mappings
- Map<String, ConfigurationUpdateHelper.PropertyMapping> propertyMappings =
new HashMap<>();
- // ... existing mappings ...
-
- // Detect infrastructure-affecting changes
- String newIndexPrefix = (String) properties.get("indexPrefix");
- if (newIndexPrefix != null && !newIndexPrefix.equals(previousIndexPrefix))
{
- updateInfrastructureForIndexPrefixChange(newIndexPrefix);
- previousIndexPrefix = newIndexPrefix;
- }
-
- // Handle rolloverIndices change (most complex)
- String newRolloverIndicesStr = (String) properties.get("rolloverIndices");
- if (newRolloverIndicesStr != null) {
- List<String> newRolloverIndices =
StringUtils.isNotEmpty(newRolloverIndicesStr)
- ? Arrays.asList(newRolloverIndicesStr.split(","))
- : null;
- if (!Objects.equals(newRolloverIndices, previousRolloverIndices)) {
- updateInfrastructureForRolloverIndicesChange(newRolloverIndices);
- previousRolloverIndices = newRolloverIndices;
- }
- }
-
- // Similar for other infrastructure properties...
-
- // Process simple property updates
- ConfigurationUpdateHelper.processConfigurationUpdates(properties, LOGGER,
"ElasticSearch persistence", propertyMappings);
-}
-
-private void updateInfrastructureForIndexPrefixChange(String newIndexPrefix) {
- // Re-register lifecycle policy
- registerRolloverLifecyclePolicy();
-
- // Recreate all rollover templates
- if (rolloverIndices != null) {
- for (String itemType : rolloverIndices) {
- if (mappings.containsKey(itemType)) {
- internalCreateRolloverTemplate(itemType);
- }
- }
- }
-}
-
-private void updateRolloverTemplatesForSettingsChange() {
- if (rolloverIndices != null) {
- for (String itemType : rolloverIndices) {
- if (mappings.containsKey(itemType)) {
- updateRolloverTemplate(itemType);
- }
- }
- }
-}
-
-private void updateInfrastructureForRolloverIndicesChange(List<String>
newRolloverIndices) {
- // Find added and removed item types
- Set<String> previousSet = previousRolloverIndices != null
- ? new HashSet<>(previousRolloverIndices)
- : Collections.emptySet();
- Set<String> newSet = newRolloverIndices != null
- ? new HashSet<>(newRolloverIndices)
- : Collections.emptySet();
-
- // Find newly added item types
- Set<String> added = new HashSet<>(newSet);
- added.removeAll(previousSet);
-
- // Find removed item types
- Set<String> removed = new HashSet<>(previousSet);
- removed.removeAll(newSet);
-
- // Create templates for newly added item types
- for (String itemType : added) {
- if (mappings.containsKey(itemType)) {
- LOGGER.info("Creating rollover template for newly added item type:
{}", itemType);
- internalCreateRolloverTemplate(itemType);
- } else {
- LOGGER.warn("Cannot create rollover template for item type {}:
mapping not found", itemType);
- }
- }
-
- // Optionally remove templates for removed item types
- // (Keeping them doesn't cause issues, so this is optional)
- for (String itemType : removed) {
- String templateName = buildRolloverAlias(itemType) +
"-rollover-template";
- LOGGER.info("Removing rollover template for item type no longer in
rollover list: {}", itemType);
- // Note: This would require a removeIndexTemplate method or similar
- }
-
- // For OpenSearch, also need to update ISM policy template patterns
- // This is handled in OpenSearchPersistenceServiceImpl
-}
-```
-
-### OpenSearchPersistenceServiceImpl
-Similar structure, but with OpenSearch-specific API calls.
-
-## Testing Considerations
-
-1. **Unit Tests**: Test change detection logic
-2. **Integration Tests**: Test infrastructure updates with real ES/OS clusters
-3. **Edge Cases**:
- - Template doesn't exist
- - Policy doesn't exist
- - Multiple properties change simultaneously
- - Rollover indices list changes
-
-## Migration Notes
-
-- Existing deployments: First update will detect all properties as "changed"
(no previous values)
-- Consider adding a flag to skip infrastructure updates on first update (only
update in-memory values)
-- Or, always perform infrastructure updates (idempotent operations should be
safe)
-
-## Summary
-
-The current `updated()` method is incomplete for infrastructure-affecting
configuration properties. A minimal-change solution would:
-
-1. Track previous configuration values
-2. Detect changes in infrastructure-affecting properties
-3. Update corresponding infrastructure components:
- - Index templates (for rollover settings)
- - Lifecycle policies (for rollover parameters)
- - Existing indices (optionally, for `numberOfReplicas`)
-4. Maintain backward compatibility with existing simple property updates
-
-The most critical properties requiring infrastructure updates are:
-- `rolloverIndices` (affects which templates exist, requires list comparison
and template creation/removal)
-- `indexPrefix` (affects policy names, template names, aliases)
-- Rollover settings (`rolloverIndexNumberOfShards`,
`rolloverIndexNumberOfReplicas`, etc.)
-- Rollover policy parameters (`rolloverMaxSize`, `rolloverMaxAge`,
`rolloverMaxDocs`)
-
-**Note**: The `rolloverIndices` property is particularly important because:
-- It requires comparing old and new lists to detect added/removed item types
-- Newly added item types need rollover templates created immediately
-- OpenSearch requires ISM policy template patterns to be updated
-- This is a common configuration change scenario (e.g., adding `profile` to
rollover list)
-
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
index 49987a480..c0a826317 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
@@ -21,6 +21,10 @@ import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.PriorityQueue;
import java.util.concurrent.atomic.AtomicInteger;
@@ -125,6 +129,8 @@ public class ProgressListener extends RunListener {
private long startTime = System.currentTimeMillis();
/** Timestamp when the current individual test started */
private long startTestTime = System.currentTimeMillis();
+ /** Formatter for human-readable timestamps */
+ private static final DateTimeFormatter TIMESTAMP_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* Creates a new ProgressListener instance.
@@ -164,6 +170,28 @@ public class ProgressListener extends RunListener {
return text;
}
+ /**
+ * Generates a separator bar of the specified length using the separator
character.
+ *
+ * @param length the desired length of the separator bar
+ * @return a string of separator characters of the specified length
+ */
+ private String generateSeparator(int length) {
+ return "━".repeat(Math.max(1, length));
+ }
+
+ /**
+ * Calculates the visual length of a string, excluding ANSI escape codes.
+ *
+ * @param text the text to measure
+ * @return the visual length of the text without ANSI codes
+ */
+ private int getVisualLength(String text) {
+ // Remove ANSI escape sequences (pattern: ESC[ ... m)
+ String withoutAnsi = text.replaceAll("\u001B\\[[0-9;]*m", "");
+ return withoutAnsi.length();
+ }
+
/**
* Called when the test run starts. Displays an ASCII art logo and welcome
message.
*
@@ -228,10 +256,13 @@ public class ProgressListener extends RunListener {
startTestTime = System.currentTimeMillis();
// Print test start boundary with test name
String testName = extractTestName(description);
+ String timestamp = formatTimestamp(startTestTime);
+ String message = "▶ START: " + testName + " [" + timestamp + "]";
+ String separator = generateSeparator(message.length());
System.out.println(); // Blank line before test
-
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
CYAN));
- System.out.println(colorize("▶ START: " + testName, GREEN));
-
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
CYAN));
+ System.out.println(colorize(separator, CYAN));
+ System.out.println(colorize(message, GREEN));
+ System.out.println(colorize(separator, CYAN));
}
/**
@@ -241,7 +272,8 @@ public class ProgressListener extends RunListener {
*/
@Override
public void testFinished(Description description) {
- long testDuration = System.currentTimeMillis() - startTestTime;
+ long endTestTime = System.currentTimeMillis();
+ long testDuration = endTestTime - startTestTime;
completedTests.incrementAndGet();
successfulTests.incrementAndGet(); // Default to success unless a
failure is recorded separately.
slowTests.add(new TestTime(description.getDisplayName(),
testDuration));
@@ -252,9 +284,12 @@ public class ProgressListener extends RunListener {
// Print test end boundary
String testName = extractTestName(description);
String durationStr = formatTime(testDuration);
-
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
CYAN));
- System.out.println(colorize("✓ END: " + testName + " (Duration: " +
durationStr + ")", GREEN));
-
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
CYAN));
+ String timestamp = formatTimestamp(endTestTime);
+ String message = "✓ END: " + testName + " [" + timestamp + "]
(Duration: " + durationStr + ")";
+ String separator = generateSeparator(message.length());
+ System.out.println(colorize(separator, CYAN));
+ System.out.println(colorize(message, GREEN));
+ System.out.println(colorize(separator, CYAN));
System.out.println(); // Blank line before progress bar
displayProgress();
System.out.println(); // Blank line after progress bar
@@ -405,9 +440,21 @@ public class ProgressListener extends RunListener {
String progressBar = generateProgressBar(((double) completed /
totalTests) * 100);
String humanReadableTime = formatTime(estimatedRemainingTime);
- // Add visual separator and make progress bar more prominent
- String separator =
colorize("════════════════════════════════════════════════════════════════════════════════",
CYAN);
- System.out.println(separator);
+ // Build the plain message string (without ANSI codes) to calculate
its length
+ String progressBarPlain = progressBar.replaceAll("\u001B\\[[0-9;]*m",
"");
+ String plainMessage = String.format("[%s] Progress: %.2f%% (%d/%d
tests). Estimated time remaining: %s. " +
+ "Successful: %d, Failed: %d",
+ progressBarPlain,
+ ((double) completed / totalTests) * 100,
+ completed,
+ totalTests,
+ humanReadableTime,
+ successfulTests.get(),
+ failedTests.get());
+
+ // Generate separator to match message length
+ String separator = generateSeparator(plainMessage.length());
+ System.out.println(colorize(separator, CYAN));
System.out.printf("%s[%s]%s %sProgress: %s%.2f%%%s (%d/%d tests).
Estimated time remaining: %s%s%s. " +
"Successful: %s%d%s, Failed: %s%d%s%n",
ansiSupported ? CYAN : "",
@@ -428,7 +475,7 @@ public class ProgressListener extends RunListener {
ansiSupported ? RED : "",
failedTests.get(),
ansiSupported ? RESET : "");
- System.out.println(separator);
+ System.out.println(colorize(separator, CYAN));
if (completed % Math.max(1, totalTests / 10) == 0 && completed <
totalTests) {
String quote = QUOTES[completed % QUOTES.length];
@@ -436,6 +483,17 @@ public class ProgressListener extends RunListener {
}
}
+ /**
+ * Formats a timestamp in milliseconds into a human-readable date-time
string.
+ *
+ * @param timeInMillis the timestamp in milliseconds since epoch
+ * @return a formatted timestamp string (e.g., "2024-01-15 14:30:45")
+ */
+ private String formatTimestamp(long timeInMillis) {
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(timeInMillis),
ZoneId.systemDefault())
+ .format(TIMESTAMP_FORMATTER);
+ }
+
/**
* Formats a time duration in milliseconds into a human-readable string.
*
diff --git a/services-common/pom.xml b/services-common/pom.xml
index d4d77e89b..8344e9bee 100644
--- a/services-common/pom.xml
+++ b/services-common/pom.xml
@@ -139,6 +139,16 @@
</instructions>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <!-- Suppress stack traces in unit tests for expected
errors to reduce log noise -->
+
<org.apache.unomi.ipvalidation.suppress.stacktraces>true</org.apache.unomi.ipvalidation.suppress.stacktraces>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
</plugins>
</build>
diff --git
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
index d11167eb9..8bcddf22e 100644
---
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
+++
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
@@ -316,7 +316,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
return; // Services not ready yet
}
- LOGGER.info("Processing {} pending operations",
pendingOperations.size());
+ LOGGER.debug("Processing {} pending operations",
pendingOperations.size());
int processedCount = 0;
int errorCount = 0;
int skippedCount = 0;
@@ -374,7 +374,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
}
if (processedCount > 0 || errorCount > 0 || skippedCount > 0) {
- LOGGER.info("Processed {} pending operations ({} successful,
{} errors, {} skipped due to missing persistence)",
+ LOGGER.debug("Processed {} pending operations ({} successful,
{} errors, {} skipped due to missing persistence)",
processedCount + errorCount + skippedCount,
processedCount, errorCount, skippedCount);
}
} finally {
@@ -602,7 +602,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
// Process any pending operations that were waiting for the
persistence provider
if (servicesInitialized.get() && !pendingOperations.isEmpty()) {
- LOGGER.info("Processing {} pending operations that were waiting
for persistence provider", pendingOperations.size());
+ LOGGER.debug("Processing {} pending operations that were waiting
for persistence provider", pendingOperations.size());
processPendingOperations();
}
}
@@ -674,13 +674,13 @@ public class SchedulerServiceImpl implements
SchedulerService {
int clearedCount = originalSize - validOperations.size();
if (clearedCount > 0) {
- LOGGER.info("Cleared {} expired operations from pending queue",
clearedCount);
+ LOGGER.debug("Cleared {} expired operations from pending queue",
clearedCount);
}
}
public void unsetPersistenceProvider(SchedulerProvider
persistenceProvider) {
this.persistenceProvider = null;
- LOGGER.info("PersistenceSchedulerProvider unbound from
SchedulerService");
+ LOGGER.debug("PersistenceSchedulerProvider unbound from
SchedulerService");
}
/**
@@ -753,13 +753,13 @@ public class SchedulerServiceImpl implements
SchedulerService {
shutdownNow = true; // Set shutdown flag before other operations
running.set(false);
- LOGGER.info("SchedulerService preDestroy: beginning shutdown process");
+ LOGGER.debug("SchedulerService preDestroy: beginning shutdown
process");
// Clear pending operations queue
int pendingCount = pendingOperations.size();
if (pendingCount > 0) {
pendingOperations.clear();
- LOGGER.info("Cleared {} pending operations during shutdown",
pendingCount);
+ LOGGER.debug("Cleared {} pending operations during shutdown",
pendingCount);
}
// Notify all managers about shutdown
@@ -810,7 +810,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
LOGGER.debug("Error clearing task collections: {}",
e.getMessage());
}
- LOGGER.info("SchedulerService shutdown completed");
+ LOGGER.debug("SchedulerService shutdown completed");
}
/**
@@ -1450,7 +1450,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
*/
private void initializeTaskPurgeInternal() {
if (!purgeTaskEnabled) {
- LOGGER.info("Task purge is disabled, skipping initialization");
+ LOGGER.debug("Task purge is disabled, skipping initialization");
return;
}
@@ -1471,12 +1471,12 @@ public class SchedulerServiceImpl implements
SchedulerService {
@Override
public void execute(ScheduledTask task, TaskStatusCallback
callback) {
- LOGGER.info("Purge task executor called - starting purge of
old tasks");
+ LOGGER.debug("Purge task executor called - starting purge of
old tasks");
try {
if (persistenceProvider != null) {
- LOGGER.info("Calling
persistenceProvider.purgeOldTasks() with TTL: {} days", completedTaskTtlDays);
+ LOGGER.debug("Calling
persistenceProvider.purgeOldTasks() with TTL: {} days", completedTaskTtlDays);
persistenceProvider.purgeOldTasks();
- LOGGER.info("Purge task completed successfully");
+ LOGGER.debug("Purge task completed successfully");
} else {
LOGGER.warn("Persistence provider is null, cannot
purge tasks");
}
@@ -1489,7 +1489,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
};
registerTaskExecutor(taskPurgeExecutor);
- LOGGER.info("Registered purge task executor");
+ LOGGER.debug("Registered purge task executor");
// Check if a task purge task already exists
List<ScheduledTask> existingTasks = getTasksByType("task-purge", 0, 1,
null).getList();
@@ -1504,7 +1504,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
taskPurgeTask.setFixedRate(true);
taskPurgeTask.setEnabled(true);
saveTask(taskPurgeTask);
- LOGGER.info("Reusing existing system task purge task: {}",
taskPurgeTask.getItemId());
+ LOGGER.debug("Reusing existing system task purge task: {}",
taskPurgeTask.getItemId());
} else {
// Create a new task if none exists or existing one isn't a system
task
taskPurgeTask = newTask("task-purge")
@@ -1512,7 +1512,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
.withFixedRate()
.asSystemTask()
.schedule();
- LOGGER.info("Created new system task purge task: {}",
taskPurgeTask.getItemId());
+ LOGGER.debug("Created new system task purge task: {}",
taskPurgeTask.getItemId());
}
}
@@ -1995,7 +1995,7 @@ public class SchedulerServiceImpl implements
SchedulerService {
if (!existingTasks.isEmpty() &&
existingTasks.get(0).isSystemTask()) {
// Reuse the existing system task
ScheduledTask existingTask = existingTasks.get(0);
- LOGGER.info("Reusing existing system task: {}",
existingTask.getItemId());
+ LOGGER.debug("Reusing existing system task: {}",
existingTask.getItemId());
// Schedule the existing task
schedulerService.scheduleTask(existingTask);
diff --git
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
index a608d55ca..bff78e02e 100644
---
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
+++
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
@@ -18,12 +18,12 @@ package org.apache.unomi.services.impl.scheduler;
import org.apache.unomi.api.tasks.ScheduledTask;
import org.apache.unomi.api.tasks.TaskExecutor;
-import org.apache.unomi.api.services.SchedulerService;
-import org.apache.unomi.persistence.spi.PersistenceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -115,7 +115,7 @@ public class TaskExecutionManager {
TASK_CHECK_INTERVAL,
TimeUnit.MILLISECONDS
);
- LOGGER.info("Task checker started with interval {} ms",
TASK_CHECK_INTERVAL);
+ LOGGER.debug("Task checker started with interval {} ms",
TASK_CHECK_INTERVAL);
}
}
@@ -126,7 +126,7 @@ public class TaskExecutionManager {
if (running.compareAndSet(true, false) && taskCheckerFuture != null) {
taskCheckerFuture.cancel(false);
taskCheckerFuture = null;
- LOGGER.info("Task checker stopped");
+ LOGGER.debug("Task checker stopped");
}
}
@@ -263,7 +263,7 @@ public class TaskExecutionManager {
LOGGER.debug("Node {} : Skipping task {} execution as
scheduler is shutting down", nodeId, task != null ? task.getItemId() :
"unknown");
return;
}
-
+
if (task == null) {
LOGGER.error("Node {} : Cannot execute null task", nodeId);
return;
@@ -291,7 +291,7 @@ public class TaskExecutionManager {
if (!prepareForExecution(task)) {
return;
}
-
+
// Final shutdown check before executing
if (schedulerService != null && schedulerService.isShutdownNow()) {
LOGGER.debug("Node {} : Skipping task {} execution as
scheduler is shutting down", nodeId, taskId);
diff --git
a/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
b/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
index a1fbd0531..18afaf028 100644
---
a/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
+++
b/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
@@ -17,6 +17,7 @@
package org.apache.unomi.services.impl.scheduler;
import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.services.SchedulerService.TaskBuilder;
import org.apache.unomi.api.tasks.ScheduledTask;
import org.apache.unomi.api.tasks.TaskExecutor;
import org.apache.unomi.persistence.spi.CustomObjectMapper;
@@ -24,15 +25,14 @@ import org.apache.unomi.persistence.spi.PersistenceService;
import
org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluatorDispatcher;
import org.apache.unomi.services.TestHelper;
import org.apache.unomi.services.common.security.ExecutionContextManagerImpl;
-import org.apache.unomi.services.impl.InMemoryPersistenceServiceImpl;
import org.apache.unomi.services.common.security.KarafSecurityService;
+import org.apache.unomi.services.impl.InMemoryPersistenceServiceImpl;
import org.apache.unomi.services.impl.TestConditionEvaluators;
import org.apache.unomi.services.impl.cluster.ClusterServiceImpl;
-import org.apache.unomi.api.services.SchedulerService.TaskBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -40,10 +40,14 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
-import java.util.concurrent.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -76,7 +80,7 @@ import static org.mockito.Mockito.when;
public class SchedulerServiceImplTest {
private static final Logger LOGGER =
LoggerFactory.getLogger(SchedulerServiceImplTest.class);
-
+
// Test configuration constants
/** Maximum number of retries for storage operations */
@@ -206,7 +210,7 @@ public class SchedulerServiceImplTest {
assertTrue(executionLatch.await(TEST_TIMEOUT, TEST_TIME_UNIT), "Task
should execute");
assertTrue(executed.get(), "Task should have executed");
- ScheduledTask completedTask =
schedulerService.getTask(task.getItemId());
+ ScheduledTask completedTask = waitForTaskStatus(task.getItemId(),
ScheduledTask.TaskStatus.COMPLETED, TEST_WAIT_TIMEOUT, 50);
assertEquals(ScheduledTask.TaskStatus.COMPLETED,
completedTask.getStatus(), "Task should be completed");
assertNotNull(completedTask.getStatusDetails().get("executionHistory"), "Task
should have execution history");
}
@@ -1541,7 +1545,7 @@ public class SchedulerServiceImplTest {
long extendedTimeout = Math.max(TEST_TIMEOUT, 3000); // At
least 3 seconds
node2Executed = node2Latch.await(extendedTimeout,
TEST_TIME_UNIT);
}
-
+
assertTrue(
node2Executed,
"Task should execute on node2 within timeout after discovery
(allowing for task period and checker cycles)");
diff --git a/tracing/tracing-impl/pom.xml b/tracing/tracing-impl/pom.xml
index 2b4a487e2..696a1413c 100644
--- a/tracing/tracing-impl/pom.xml
+++ b/tracing/tracing-impl/pom.xml
@@ -68,8 +68,8 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>