Skip to content

PR#8 Add support for experiment_types in Bulk API - #2002

Open
khansaad wants to merge 5 commits into
kruize:runtimes-iirjfrom
khansaad:bulk-exp-type-support
Open

PR#8 Add support for experiment_types in Bulk API#2002
khansaad wants to merge 5 commits into
kruize:runtimes-iirjfrom
khansaad:bulk-exp-type-support

Conversation

@khansaad

@khansaad khansaad commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds changes to support multiple experiment types in bulk.

Fixes # (issue)

Type of change

  • Bug fix
  • New feature
  • Docs update
  • Breaking change (What changes might users need to make in their application due to this PR?)
  • Requires DB changes

How has this been tested?

Please describe the tests that were run to verify your changes and steps to reproduce. Please specify any test configuration required.

  • New Test X
  • Functional testsuite

Test Configuration

  • Kubernetes clusters tested on:

Checklist 🎯

  • Followed coding guidelines
  • Comments added
  • Dependent changes merged
  • Documentation updated
  • Tests added or updated

Additional information

Include any additional information such as links, test results, screenshots here

Summary by Sourcery

Enable Bulk API jobs to create either container or namespace experiments through a validated experiment type selection.

New Features:

  • Add bulk job support for creating namespace-level experiments alongside the existing container-level experiments.
  • Allow bulk requests to select a single supported experiment type, defaulting to container experiments when omitted.

Enhancements:

  • Add validation and case-insensitive parsing for bulk experiment type values, including clear errors for unsupported or multiple types.
  • Define namespace-specific experiment naming and payload metadata.

Build:

  • Add the Apache Commons Collections dependency.

Documentation:

  • Update the Bulk API documentation to describe the currently supported single experiment type and container default.

@khansaad khansaad self-assigned this Jun 26, 2026
@khansaad khansaad added enhancement New feature or request iri-kruize labels Jun 26, 2026
@sourcery-ai

sourcery-ai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Extends Bulk API processing with validated, single-type experiment selection, including namespace-level experiment creation while preserving container defaults. Bulk-level cluster and recommendation settings are propagated to generated experiments, with supporting dependency and documentation updates.

Sequence diagram for Bulk API experiment type processing

sequenceDiagram
    participant Client
    participant BulkServiceValidation
    participant BulkJobManager
    participant Metadata
    participant CreateExperimentAPI

    Client->>BulkServiceValidation: validate(payload, jobID)
    BulkServiceValidation->>BulkServiceValidation: validateExperimentTypes(experiment_types)
    BulkServiceValidation-->>BulkJobManager: valid request
    BulkJobManager->>BulkJobManager: resolveExperimentType(experiment_types)
    BulkJobManager->>Metadata: getDatasources()
    alt experiment type is namespace
        BulkJobManager->>BulkJobManager: frameNamespaceExperimentName(labelString, dataSourceCluster, namespace)
        BulkJobManager->>BulkJobManager: prepareNamespaceExperimentJSONInput(dsc, namespace, experiment_name, objects)
    else absent or container type
        BulkJobManager->>BulkJobManager: frameExperimentName(labelString, clusterName, namespace, workload, container)
        BulkJobManager->>BulkJobManager: prepareCreateExperimentJSONInput(container, clusterName, workload, namespace, experiment_name, objects)
    end
    BulkJobManager->>CreateExperimentAPI: create experiments with recommendation settings
Loading

Flow diagram for Bulk API experiment type selection

flowchart TD
    A[Bulk API payload] --> B{experiment_types provided?}
    B -->|No or empty| C[Resolve CONTAINER]
    B -->|One container value| C
    B -->|One namespace value| D[Resolve NAMESPACE]
    B -->|More than one or invalid value| E[Return validation error]
    C --> F[Create container experiments]
    D --> G[Create one namespace experiment per namespace]
    F --> H[Propagate cluster_name, model_settings, term_settings]
    G --> H
Loading

File-Level Changes

Change Details Files
Add configurable experiment type handling to Bulk API requests, with namespace experiment generation and container as the default.
  • Parse case-insensitive experiment type values from JSON.
  • Validate that only one supported type, container or namespace, is supplied.
  • Generate one namespace experiment per namespace with namespace-specific names and Kubernetes payloads.
  • Retain container experiment generation for the default or container type.
src/main/java/com/autotune/analyzer/serviceObjects/BulkInput.java
src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java
src/main/java/com/autotune/analyzer/utils/AnalyzerErrorConstants.java
src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java
src/main/java/com/autotune/common/bulk/BulkServiceValidation.java
src/main/java/com/autotune/operator/KruizeDeploymentInfo.java
Propagate bulk-level cluster and recommendation configuration into created experiments.
  • Prefer a trimmed, non-empty bulk cluster_name over datasource metadata.
  • Pass model_settings and term_settings into container and namespace recommendation settings.
  • Add Apache Commons Collections for collection validation utilities.
src/main/java/com/autotune/analyzer/serviceObjects/BulkInput.java
src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java
src/main/java/com/autotune/common/bulk/BulkServiceValidation.java
pom.xml
Update Bulk API documentation to define the currently supported experiment_types contract.
  • Document container as the default and namespace as the alternative.
  • Clarify that a request accepts only one experiment type and does not yet support multiple types in one request.
design/BulkAPI.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 6 issues, and left some high level feedback:

  • The new experiment_types handling in BulkServiceValidation allows a list, but BulkJobManager.resolveExperimentType only uses the first entry and silently ignores the rest; either enforce a single element at validation time or update the job manager to actually support multiple experiment types as implied by the API and PR title.
  • In DataSourceInfoAdapter, the authentication field is serialized as authenticationConfig, whereas the existing datasources design and JSON examples use the authentication key – align the serialized field name with the rest of the API to avoid breaking clients consuming /listDatasources.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `experiment_types` handling in `BulkServiceValidation` allows a list, but `BulkJobManager.resolveExperimentType` only uses the first entry and silently ignores the rest; either enforce a single element at validation time or update the job manager to actually support multiple experiment types as implied by the API and PR title.
- In `DataSourceInfoAdapter`, the authentication field is serialized as `authenticationConfig`, whereas the existing datasources design and JSON examples use the `authentication` key – align the serialized field name with the rest of the API to avoid breaking clients consuming `/listDatasources`.

## Individual Comments

### Comment 1
<location path="src/main/java/com/autotune/common/bulk/BulkServiceValidation.java" line_range="35-36" />
<code_context>
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Custom Gson serializer for DataSourceInfo to conditionally exclude empty clusters field
+ */
</code_context>
<issue_to_address>
**issue:** JavaDoc for validateExperimentTypes mentions duplicate checks that are not implemented in the method body.

The method currently only checks for non-empty values and membership in VALID_EXPERIMENT_TYPES, but not duplicates as documented. Please either add duplicate detection (e.g., track lowercased values in a Set and fail on repeats) or update the JavaDoc so it accurately describes the existing behavior.
</issue_to_address>

### Comment 2
<location path="src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java" line_range="699-701" />
<code_context>
+     * @param experimentTypes the raw list from BulkInput.experiment_types
+     * @return the resolved ExperimentType
+     */
+    private AnalyzerConstants.ExperimentType resolveExperimentType(List<String> experimentTypes) {
+        if (experimentTypes == null || experimentTypes.isEmpty()) {
+            return AnalyzerConstants.ExperimentType.CONTAINER;
+        }
+        try {
</code_context>
<issue_to_address>
**issue (bug_risk):** resolveExperimentType ignores all but the first experiment_types entry, which may conflict with expectations from the BulkInput contract.

BulkInput exposes experiment_types as a List<String> and BulkServiceValidation permits multiple entries, but this method always uses experimentTypes.get(0). If a caller passes both "container" and "namespace", only the first is used with no warning. Either enforce and document a single allowed experiment type at validation time, or update orchestration to support multiple experiment types instead of silently discarding the rest.
</issue_to_address>

### Comment 3
<location path="src/main/java/com/autotune/common/datasource/DataSourceInfo.java" line_range="165-166" />
<code_context>
+     *
+     * @return list of cluster names; empty list if no clusters were provided
+     */
+    public List<String> getClusters() {
+        return clusters;
+    }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** getClusters exposes the internal mutable list, which can be modified by callers and break the constructor invariant.

Because this returns the internal mutable list, callers can change DataSourceInfo’s state after construction, violating the “set once at construction time” contract. To preserve the invariant and prevent accidental mutation, return an unmodifiable view (e.g., Collections.unmodifiableList(clusters)) or a defensive copy instead.

Suggested implementation:

```java
    /**
     * Returns the list of cluster names associated with this datasource.
     * The {@code clusters} field is final and set once at construction time.
     * To preserve the immutability contract and prevent callers from mutating
     * the internal state, this method returns an unmodifiable view of the list.
     *
     * @return unmodifiable list of cluster names; empty list if no clusters were provided
     */
    public List<String> getClusters() {
        return Collections.unmodifiableList(clusters);
    }

```

```java
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

```
</issue_to_address>

### Comment 4
<location path="src/test/java/com/autotune/analyzer/workerimpl/BulkJobManagerPassthroughTest.java" line_range="96-105" />
<code_context>
+    @DisplayName("Backward Compatibility Tests")
+    class BackwardCompatibilityTests {
+
+        @Test
+        @DisplayName("Should maintain existing behavior when cluster name not provided")
+        void shouldMaintainExistingBehaviorWhenClusterNameNotProvided() {
+            // Given - Old-style bulk input without cluster name
+            when(bulkInput.getCluster_name()).thenReturn(null);
+
+            // When
+            String experimentName = bulkJobManager.frameExperimentName(
+                    null, cluster, namespace, workload, container
+            );
+
+            // Then
+            assertTrue(experimentName.contains("metadata-cluster"),
+                    "Should use metadata cluster when bulk payload cluster is not provided");
+            assertEquals("prometheus-metadata-cluster-default-test-app-deployment-app-container",
+                    experimentName,
+                    "Experiment name should follow existing format");
+        }
+
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen BulkJobManagerPassthroughTest by asserting the actual experiment payload uses the overridden cluster_name and experiment_type.

Current tests only read `bulkInput.getCluster_name()` or `frameExperimentName` and never exercise `getExperimentMap`/`prepareCreateExperimentJSONInput`. As a result, they don’t verify that `CreateExperimentAPIObject` actually uses the trimmed `cluster_name` override or the resolved `experiment_type` (`NAMESPACE` vs `CONTAINER`). To validate the new behavior, drive `BulkJobManager` end‑to‑end with a minimal `metadataInfo` (cluster/namespace/workload/container), call `getExperimentMap`, and assert the resulting experiment’s `clusterName`, `experimentType`, and `kubernetesObjects` fields.

Suggested implementation:

```java
import com.autotune.analyzer.serviceObjects.BulkInput;
import com.autotune.analyzer.experiment.CreateExperimentAPIObject;

```

```java
import com.autotune.operator.KruizeDeploymentInfo;
import org.junit.jupiter.api.AfterEach;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

```

```java
    @Nested
    @DisplayName("Cluster Name Passthrough Tests")
    class ClusterNamePassthroughTests {

        @Test
        @DisplayName("Should propagate trimmed cluster name and resolved experiment type into experiment payload")
        void shouldPropagateClusterNameAndExperimentTypeIntoExperimentPayload() {
            // Given - bulk payload overrides cluster_name with surrounding whitespace
            when(bulkInput.getCluster_name()).thenReturn("  my-metadata-cluster  ");
            // Let BulkJobManager resolve the experiment type from metadata (e.g. NAMESPACE/CONTAINER)
            when(bulkInput.getExperiment_type()).thenReturn(null);

            Map<String, Object> metadataInfo = new HashMap<>();
            metadataInfo.put("cluster_name", "my-metadata-cluster");
            metadataInfo.put("namespace", "default");
            metadataInfo.put("workload_name", "test-app-deployment");
            metadataInfo.put("workload_type", "deployment");
            metadataInfo.put("container_name", "app-container");

            // When - drive BulkJobManager end-to-end through getExperimentMap
            Map<String, CreateExperimentAPIObject> experimentMap =
                    bulkJobManager.getExperimentMap(Collections.singletonList(metadataInfo), bulkInput);

            // Then
            assertEquals(1, experimentMap.size(), "Exactly one experiment should be generated from the metadata");

            CreateExperimentAPIObject experiment = experimentMap.values().iterator().next();

            // clusterName should use the trimmed bulkInput cluster_name override
            assertEquals(
                    "my-metadata-cluster",
                    experiment.getClusterName(),
                    "Experiment clusterName should use trimmed bulk cluster_name override"
            );

            // experimentType in payload should match the resolved experiment type used by BulkJobManager
            String resolvedExperimentType = bulkJobManager.getExperimentType(metadataInfo, bulkInput);
            assertEquals(
                    resolvedExperimentType,
                    experiment.getExperimentType(),
                    "Experiment experimentType should match resolved experiment type"
            );

            // kubernetesObjects should reflect metadataInfo fields
            assertFalse(
                    experiment.getKubernetesObjects().isEmpty(),
                    "Experiment payload should contain kubernetesObjects"
            );
            CreateExperimentAPIObject.KubernetesObject ko = experiment.getKubernetesObjects().get(0);
            assertEquals("default", ko.getNamespace(), "Namespace should match metadataInfo");
            assertEquals("test-app-deployment", ko.getWorkloadName(), "Workload name should match metadataInfo");
            assertEquals("deployment", ko.getWorkloadType(), "Workload type should match metadataInfo");
            assertEquals("app-container", ko.getContainerName(), "Container name should match metadataInfo");
        }

```

Depending on the existing test fixture in `BulkJobManagerPassthroughTest`, you may need to:

1. Ensure `bulkJobManager` and `bulkInput` are already initialized/mocked in a `@BeforeEach` method and accessible from the nested class (e.g. as fields in the outer test class). If they are defined with narrower scope, move them to fields or adjust visibility.
2. If the actual experiment payload type is not `com.autotune.analyzer.experiment.CreateExperimentAPIObject` or uses different accessor names (`getClusterName`, `getExperimentType`, `getKubernetesObjects`, `getNamespace`, `getWorkloadName`, `getWorkloadType`, `getContainerName`), update the import and method calls accordingly.
3. If `BulkJobManager` exposes a different API for deriving experiment type than `getExperimentType(metadataInfo, bulkInput)`, replace that call with the appropriate method or inline the expected value (e.g. `"NAMESPACE"` or `"CONTAINER"`) based on how your production code resolves it.
4. If `getExperimentMap` returns a different generic type (e.g. `Map<String, KruizeObject>` or a wrapper object before reaching `CreateExperimentAPIObject`), adapt the test to extract the `CreateExperimentAPIObject` from that structure before asserting on `clusterName`, `experimentType`, and `kubernetesObjects`.
</issue_to_address>

### Comment 5
<location path="design/BulkAPI.md" line_range="107-108" />
<code_context>
 - **metadata_profile:** Name of the metadata profile to import the cluster metadata. This is a mandatory field `metadata_profile` 
   should be installed / created before invoking bulk API.

-- **measurement_duration:** The historic data duration to fetch the cluster metadata. This is an optional field, if not 
+- **measurement_duration:** The historic data duration to fetch the cluster metadata. This is an optional field, if not
   specified `15min` as default measurement_duration value is considered.
</code_context>
<issue_to_address>
**suggestion (typo):** Consider rephrasing the measurement_duration description for clearer grammar and wording.

"Historic data duration" reads a bit awkward; "historical data duration" is more standard here. Also, the sentence currently uses a comma splice—consider something like: "This is an optional field; if not specified, `15min` is used as the default measurement_duration value."

```suggestion
- **measurement_duration:** The historical data duration used to fetch the cluster metadata. This is an optional field; if not
  specified, `15min` is used as the default measurement_duration value.
```
</issue_to_address>

### Comment 6
<location path="design/NotificationCodes.md" line_range="92" />
<code_context>
+| 324006 |  NOTICE  |   MEMORY_REQUESTS_OVER_PROVISIONED    |       Specifies that the workload is over-provisioned for Memory requests       | Workload is over-provisioned for Memory. Kruize recommends reducing Memory allocation to optimize costs. | DATA USER |
</code_context>
<issue_to_address>
**nitpick (typo):** Spelling of "optimize" is inconsistent with existing "optimised" in the table.

This entry uses “optimize” while others in the table use “optimised” (e.g., “Workload is optimised wrt CPU REQUESTS”). Please align the spelling within the table, either by changing this to “optimised” or updating the earlier entries to “optimized.”

```suggestion
| 324006 |  NOTICE  |   MEMORY_REQUESTS_OVER_PROVISIONED    |       Specifies that the workload is over-provisioned for Memory requests       | Workload is over-provisioned for Memory. Kruize recommends reducing Memory allocation to optimise costs. | DATA USER |
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/main/java/com/autotune/common/bulk/BulkServiceValidation.java
Comment thread src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java Outdated
Comment thread src/main/java/com/autotune/common/datasource/DataSourceInfo.java
Comment thread src/test/java/com/autotune/analyzer/workerimpl/BulkJobManagerPassthroughTest.java Outdated
Comment thread design/BulkAPI.md Outdated
Comment thread design/NotificationCodes.md Outdated
@khansaad khansaad changed the title Add support for experiment_types in Bulk API PR#8 Add support for experiment_types in Bulk API Jul 9, 2026
@khansaad
khansaad force-pushed the bulk-exp-type-support branch from 340e665 to e3d290b Compare July 9, 2026 09:48
@rbadagandi1 rbadagandi1 added this to the Release 0.0.1 milestone Jul 24, 2026
@rbadagandi1 rbadagandi1 moved this to In Progress in Monitoring Jul 24, 2026
@kusumachalasani kusumachalasani moved this from In Progress to Under Review in Monitoring Jul 27, 2026
@khansaad
khansaad force-pushed the bulk-exp-type-support branch 8 times, most recently from 3270b6e to 0bfff4f Compare August 3, 2026 03:27
Comment thread src/main/java/com/autotune/analyzer/serviceObjects/BulkInput.java Outdated
Comment thread src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java Outdated
@kusumachalasani

Copy link
Copy Markdown
Contributor

@khansaad Please update bulk documentation that we are supporting only one experiment_type for now. As it is a list, it might be confusing to users. We can mention multiple types are supported in future.

@khansaad

Copy link
Copy Markdown
Contributor Author

@khansaad Please update bulk documentation that we are supporting only one experiment_type for now. As it is a list, it might be confusing to users. We can mention multiple types are supported in future.

Done

@khansaad
khansaad requested a review from bharathappali August 10, 2026 11:08

@kusumachalasani kusumachalasani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Comment thread src/main/java/com/autotune/analyzer/serviceObjects/BulkInput.java Outdated

@kusumachalasani kusumachalasani left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

}

public void setExperiment_types(List<String> experiment_types) {
if (experiment_types != null && !experiment_types.isEmpty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added now

}
// Validation guarantees exactly one entry; get(0) is intentional.
try {
return AnalyzerConstants.ExperimentType.valueOf(experimentTypes.get(0).trim().toUpperCase());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this is for now. In future, we support multiple types. Correct me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

* If provided, only experiments of the specified type(s) will be created.
* If not provided or empty, defaults to container experiments.
*/
private List<String> experiment_types;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can store experiment_types as list of enum instead of String. This helps in upfront validation and avoid converting it to enum at later point in time via resolveExperimentType.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made sense.
Updated now

* @return an error message if validation fails; otherwise an empty string
*/
public static String validateExperimentTypes(List<String> experimentTypes) {
if (experimentTypes == null || experimentTypes.isEmpty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here also, replace with CollectionUtils method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@khansaad
khansaad force-pushed the bulk-exp-type-support branch from 336e9cc to 2007fbb Compare August 27, 2026 11:58
- Add experiment_types field to BulkInput (container/namespace)
- Add resolveExperimentType and namespace experiment creation path
  (prepareNamespaceExperimentJSONInput, frameNamespaceExperimentName)
- Add validateExperimentTypes to BulkServiceValidation
- Add BULK_INVALID_EXPERIMENT_TYPES error constant
Signed-off-by: Saad Khan <saakhan@ibm.com>
Signed-off-by: Saad Khan <saakhan@ibm.com>
Signed-off-by: Saad Khan <saakhan@ibm.com>
@khansaad
khansaad force-pushed the bulk-exp-type-support branch from 2007fbb to f57ba94 Compare August 27, 2026 12:04
* (null, empty, or exactly one recognized value)
* @return the resolved ExperimentType
*/
private AnalyzerConstants.ExperimentType resolveExperimentType(List<AnalyzerConstants.ExperimentType> experimentTypes) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have started storing experiment_types as ENUM, I don't think the keyword resolve in method name is apt. There is nothing to resolve. Do we need this method?

Comment thread pom.xml Outdated
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections4-version}</version>
</dependency>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it requires to add this additional library, lets not do that only for couple of instances. This will create additional overhead in patching this time to time.

Lets go back to check for null and empty explicitly and remove this additional dependency.

@mbvreddy mbvreddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted few more comments. Please check.

@khansaad
khansaad deleted the branch kruize:runtimes-iirj August 28, 2026 08:41
@khansaad khansaad closed this Aug 28, 2026
@github-project-automation github-project-automation Bot moved this from Under Review to Done in Monitoring Aug 28, 2026
@khansaad khansaad moved this from Done to In Progress in Monitoring Aug 28, 2026
@khansaad khansaad reopened this Aug 28, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java" line_range="332-336" />
<code_context>
-        WORKLOAD // For application-specific experiments
+        WORKLOAD; // For application-specific experiments
+
+        @JsonCreator
+        public static ExperimentType fromString(String value) {
+            if (value == null) return null;
+            return ExperimentType.valueOf(value.trim().toUpperCase());
+        }
     }

</code_context>
<issue_to_address>
**issue (bug_risk):** `ExperimentType.fromString` throws `IllegalArgumentException` for an invalid or empty `experiment_types` value, so `ObjectMapper.readValue` fails before `BulkServiceValidation.validateExperimentTypes` runs and the API cannot return the intended `BULK_INVALID_EXPERIMENT_TYPES` validation response.

**Triggers:** When a request contains an unsupported, empty, or otherwise malformed experiment type.

**Suggested fix:** Return a nullable/invalid marker that validation can inspect, or catch the enum conversion exception in the request layer and map it to the bulk validation error.
</issue_to_address>

### Comment 2
<location path="src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java" line_range="869-873" />
<code_context>
+        String clusterName = dataSourceCluster.getDataSourceClusterName();
+        String namespaceName = namespace.getNamespace();
+
+        // Namespace experiment name: datasource|clustername|namespace
+        String experimentName = KruizeDeploymentInfo.namespace_experiment_name_format
+                .replace("%datasource%", datasource)
+                .replace("%clustername%", clusterName)
+                .replace("%namespace%", namespaceName);
+
+        if (null != labelString) {
</code_context>
<issue_to_address>
**issue (broader_impact):** Namespace experiments use the datasource metadata cluster in their experiment name, while `prepareNamespaceExperimentJSONInput` uses the trimmed `bulkInput.cluster_name` in the experiment payload. With a cluster override, the created experiment name identifies one cluster but its `cluster_name` field identifies another, breaking the name/cluster identity invariant and causing inconsistent lookups or duplicate experiments.

**Triggers:** When a namespace bulk request supplies `cluster_name` that differs from the cluster name in datasource metadata.

**Suggested fix:** Resolve and trim the cluster name once, pass it to `frameNamespaceExperimentName`, and use that same value for both the experiment name and payload.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and if the type resolution or namespace payload is wrong, the bulk job can create the wrong set of persisted experiments and associated recommendation data; reverting the code will not remove those records, though the impact is bounded and can be repaired by cleanup and rerunning the job.

Blocking findings: src/main/java/com/autotune/analyzer/utils/AnalyzerConstants.java:336, src/main/java/com/autotune/analyzer/workerimpl/BulkJobManager.java:873


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +332 to +336
@JsonCreator
public static ExperimentType fromString(String value) {
if (value == null) return null;
return ExperimentType.valueOf(value.trim().toUpperCase());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): ExperimentType.fromString throws IllegalArgumentException for an invalid or empty experiment_types value, so ObjectMapper.readValue fails before BulkServiceValidation.validateExperimentTypes runs and the API cannot return the intended BULK_INVALID_EXPERIMENT_TYPES validation response.

Triggers: When a request contains an unsupported, empty, or otherwise malformed experiment type.

Suggested fix: Return a nullable/invalid marker that validation can inspect, or catch the enum conversion exception in the request layer and map it to the bulk validation error.

Comment on lines +869 to +873
// Namespace experiment name: datasource|clustername|namespace
String experimentName = KruizeDeploymentInfo.namespace_experiment_name_format
.replace("%datasource%", datasource)
.replace("%clustername%", clusterName)
.replace("%namespace%", namespaceName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): Namespace experiments use the datasource metadata cluster in their experiment name, while prepareNamespaceExperimentJSONInput uses the trimmed bulkInput.cluster_name in the experiment payload. With a cluster override, the created experiment name identifies one cluster but its cluster_name field identifies another, breaking the name/cluster identity invariant and causing inconsistent lookups or duplicate experiments.

Triggers: When a namespace bulk request supplies cluster_name that differs from the cluster name in datasource metadata.

Suggested fix: Resolve and trim the cluster name once, pass it to frameNamespaceExperimentName, and use that same value for both the experiment name and payload.

Signed-off-by: Saad Khan <saakhan@ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request iri-kruize

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants