Skip to content

Adding Scheduler to Create Bulk Jobs#6

Open
shekhar316 wants to merge 7 commits intokruize:mvp_demofrom
shekhar316:scheduler
Open

Adding Scheduler to Create Bulk Jobs#6
shekhar316 wants to merge 7 commits intokruize:mvp_demofrom
shekhar316:scheduler

Conversation

@shekhar316
Copy link
Copy Markdown
Contributor

@shekhar316 shekhar316 commented Mar 13, 2026

This PR -

  • adds the scheduler for creating the bulk Jobs
  • runs scheduler at a fixed configurable interval
  • also adds webhook receiver endpoint

Note: This PR is based on profile manager module, that needs to be merged first.

Summary by Sourcery

Introduce a Quarkus-based Kruize Optimizer microservice with REST endpoints for managing Kruize datasources, profiles, and system status, backed by a Maven build and runtime configuration.

New Features:

  • Expose REST APIs to list and install metadata profiles, metric profiles, and layers in Kruize.
  • Provide a system status endpoint aggregating datasource, profile, and layer information with human-readable alerts.
  • Add REST APIs to list configured datasources retrieved from the Kruize backend.

Enhancements:

  • Introduce core service layer, REST client, and domain models to integrate with the external Kruize Autotune service.
  • Add centralized API response wrapper and global exception mapping for consistent error handling.
  • Define application configuration, startup logging, and constants for API paths, messages, and profile types.
  • Set up Maven-based Quarkus project structure with Dockerfile templates and Maven wrapper scripts.

Build:

  • Add Maven wrapper and a Quarkus-focused pom.xml configuring dependencies, plugins, and native build profile.

Documentation:

  • Replace the minimal README with detailed instructions for building and running the Quarkus application, including native image notes and related Quarkus guides.

Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
@shekhar316 shekhar316 added this to the Kruize 0.10.0 Release milestone Mar 13, 2026
@shekhar316 shekhar316 self-assigned this Mar 13, 2026
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 13, 2026

Reviewer's Guide

Introduces a new Quarkus-based "kruize-optimizer" microservice with REST endpoints and support classes to interact with the Kruize API (datasources, profiles, layers, status), adds Maven wrapper and build config, centralizes constants and error handling, and wires application startup; it lays the plumbing for scheduled/bulk operations and webhook handling though those specific schedulers/endpoints are likely in a dependent module.

Sequence diagram for listing metadata profiles via REST endpoint

sequenceDiagram
    actor ApiClient
    participant MetadataProfileResource
    participant ProfileService
    participant KruizeClient
    participant KruizeAPI

    ApiClient->>MetadataProfileResource: HTTP GET /kruize/metadataProfiles/list?verbose={v}
    MetadataProfileResource->>ProfileService: getMetadataProfiles(verbose)
    ProfileService->>KruizeClient: getMetadataProfiles(verbose)
    KruizeClient->>KruizeAPI: GET /listMetadataProfiles?verbose={v}
    KruizeAPI-->>KruizeClient: 200 OK, List<KruizeProfile>
    KruizeClient-->>ProfileService: List<KruizeProfile>
    ProfileService-->>MetadataProfileResource: List<KruizeProfile>
    MetadataProfileResource-->>ApiClient: 200 OK, ApiResponse{status="success", data=List<KruizeProfile>}
Loading

Class diagram for new Kruize Optimizer microservice domain, services, resources, and client

classDiagram
    %% Domain models
    class ApiResponse~T~ {
        - String status
        - String message
        - T data
        - List~String~ errors
        + ApiResponse()
        + ApiResponse(status String, message String)
        + ApiResponse(status String, message String, data T)
        + static success(message String, data T) ApiResponse~T~
        + static success(message String) ApiResponse~T~
        + static error(message String, errors List~String~) ApiResponse~T~
        + static error(message String) ApiResponse~T~
        + getStatus() String
        + setStatus(status String) void
        + getMessage() String
        + setMessage(message String) void
        + getData() T
        + setData(data T) void
        + getErrors() List~String~
        + setErrors(errors List~String~) void
    }

    class Datasource {
        - String name
        - String provider
        - String url
        - String serviceName
        - String namespace
        + Datasource()
        + Datasource(name String, provider String, url String)
        + getName() String
        + setName(name String) void
        + getProvider() String
        + setProvider(provider String) void
        + getUrl() String
        + setUrl(url String) void
        + getServiceName() String
        + setServiceName(serviceName String) void
        + getNamespace() String
        + setNamespace(namespace String) void
    }

    class KruizeProfile {
        - String name
        - Metadata metadata
        - String profileVersion
        + KruizeProfile()
        + KruizeProfile(name String, profileVersion String)
        + getName() String
        + setName(name String) void
        + getMetadata() Metadata
        + setMetadata(metadata Metadata) void
        + getProfileVersion() String
        + setProfileVersion(profileVersion String) void
    }

    class Metadata {
        - String name
        + Metadata()
        + Metadata(name String)
        + getName() String
        + setName(name String) void
    }

    KruizeProfile o-- Metadata

    class KruizeStatus {
        - DatasourceStatus datasources
        - ProfileStatus metadataProfiles
        - ProfileStatus metricProfiles
        - ProfileStatus layers
        - List~String~ alerts
        + KruizeStatus()
        + getDatasources() DatasourceStatus
        + setDatasources(datasources DatasourceStatus) void
        + getMetadataProfiles() ProfileStatus
        + setMetadataProfiles(metadataProfiles ProfileStatus) void
        + getMetricProfiles() ProfileStatus
        + setMetricProfiles(metricProfiles ProfileStatus) void
        + getLayers() ProfileStatus
        + setLayers(layers ProfileStatus) void
        + getAlerts() List~String~
        + setAlerts(alerts List~String~) void
        + addAlert(alert String) void
    }

    class DatasourceStatus {
        - int count
        - List~Datasource~ list
        + DatasourceStatus()
        + DatasourceStatus(count int, list List~Datasource~)
        + getCount() int
        + setCount(count int) void
        + getList() List~Datasource~
        + setList(list List~Datasource~) void
    }

    class ProfileStatus {
        - int count
        - List~ProfileInfo~ installed
        + ProfileStatus()
        + ProfileStatus(count int, installed List~ProfileInfo~)
        + getCount() int
        + setCount(count int) void
        + getInstalled() List~ProfileInfo~
        + setInstalled(installed List~ProfileInfo~) void
    }

    class ProfileInfo {
        - String name
        - String profileVersion
        + ProfileInfo()
        + ProfileInfo(name String, profileVersion String)
        + getName() String
        + setName(name String) void
        + getProfileVersion() String
        + setProfileVersion(profileVersion String) void
    }

    KruizeStatus o-- DatasourceStatus
    KruizeStatus o-- ProfileStatus
    DatasourceStatus o-- Datasource
    ProfileStatus o-- ProfileInfo

    class DatasourceListResponse {
        - List~Datasource~ datasources
        + DatasourceListResponse()
        + DatasourceListResponse(datasources List~Datasource~)
        + getDatasources() List~Datasource~
        + setDatasources(datasources List~Datasource~) void
    }

    %% Client
    class KruizeClient {
        <<interface>>
        + getDatasources() DatasourceListResponse
        + getMetadataProfiles(verbose boolean) List~KruizeProfile~
        + getMetricProfiles(verbose boolean) List~KruizeProfile~
        + getLayers() List~KruizeProfile~
        + createMetadataProfile(profileDefinition Object) String
        + createMetricProfile(profileDefinition Object) String
        + createLayer(layerDefinition Object) String
    }

    %% Services
    class DatasourceService {
        - KruizeClient kruizeClient
        + getDatasources() List~Datasource~
        + getDatasourceCount() int
        + isKruizeAvailable() boolean
    }

    class ProfileService {
        - KruizeClient kruizeClient
        - String profileBaseUrl
        - ObjectMapper objectMapper
        + getMetadataProfiles(verbose boolean) List~KruizeProfile~
        + getMetricProfiles(verbose boolean) List~KruizeProfile~
        + getLayers() List~KruizeProfile~
        + installMissingProfiles(profileType String) List~String~
        - installProfile(profileType String, profileName String) void
        - loadProfileFromLocal(profileType String, profileName String) Object
        - getResourcePath(profileType String, profileName String) String
        - getAvailableProfilesFromLocal(profileType String) List~String~
        - getInstalledProfiles(profileType String) List~KruizeProfile~
    }

    class StatusService {
        - DatasourceService datasourceService
        - ProfileService profileService
        + getSystemStatus() KruizeStatus
        - convertToProfileInfo(profiles List~KruizeProfile~) List~ProfileInfo~
        + isKruizeHealthy() boolean
    }

    DatasourceService ..> KruizeClient
    ProfileService ..> KruizeClient
    StatusService ..> DatasourceService
    StatusService ..> ProfileService

    %% REST resources
    class DatasourceResource {
        - DatasourceService datasourceService
        + listDatasources() Response
    }

    class MetadataProfileResource {
        - ProfileService profileService
        + listMetadataProfiles(verbose boolean) Response
        + installMetadataProfiles() Response
    }

    class MetricProfileResource {
        - ProfileService profileService
        + listMetricProfiles(verbose boolean) Response
        + installMetricProfiles() Response
    }

    class LayerResource {
        - ProfileService profileService
        + listLayers() Response
        + installLayers() Response
    }

    class StatusResource {
        - StatusService statusService
        + getStatus() Response
    }

    DatasourceResource ..> DatasourceService
    MetadataProfileResource ..> ProfileService
    MetricProfileResource ..> ProfileService
    LayerResource ..> ProfileService
    StatusResource ..> StatusService

    %% Exceptions and startup
    class KruizeServiceException {
        - int statusCode
        + KruizeServiceException(message String)
        + KruizeServiceException(message String, statusCode int)
        + KruizeServiceException(message String, cause Throwable)
        + KruizeServiceException(message String, cause Throwable, statusCode int)
        + getStatusCode() int
    }

    class GlobalExceptionMapper {
        + toResponse(exception Exception) Response
    }

    GlobalExceptionMapper ..> ApiResponse
    KruizeServiceException --|> RuntimeException

    class Startup {
        + onStart(ev StartupEvent) void
    }
Loading

File-Level Changes

Change Details Files
Add Maven/Quarkus build configuration and wrapper scripts so the optimizer can be built and run independently.
  • Introduce Maven POM with Quarkus dependencies (REST, scheduler, REST client, OpenAPI, Kubernetes client, JSON logging, YAML config, Prometheus, testing) and plugins for build/test/native image
  • Add Maven wrapper scripts for Unix and Windows plus wrapper properties pointing to Maven 3.9.12
  • Stub Docker-related and ignore files for container builds and local development
pom.xml
mvnw
mvnw.cmd
.mvn/wrapper/maven-wrapper.properties
.dockerignore
.gitignore
src/main/docker/Dockerfile.jvm
src/main/docker/Dockerfile.native
src/main/docker/Dockerfile.native-micro
src/main/docker/Dockerfile.legacy-jar
Introduce core domain models and API response types for interacting with Kruize datasources, profiles, and system status.
  • Add generic API response wrapper with success/error helpers for consistent REST responses
  • Define models for datasources, profiles (including metadata and version), and overall system status with nested status representations
  • Add a typed response for datasource list calls from the Kruize backend
src/main/java/com/kruize/optimizer/model/api/ApiResponse.java
src/main/java/com/kruize/optimizer/model/api/DatasourceListResponse.java
src/main/java/com/kruize/optimizer/model/kruize/Datasource.java
src/main/java/com/kruize/optimizer/model/kruize/KruizeProfile.java
src/main/java/com/kruize/optimizer/model/kruize/KruizeStatus.java
Implement a REST client and services that wrap the remote Kruize API for datasources and profiles, including local profile installation logic.
  • Create a MicroProfile REST client interface that maps to Kruize list/create endpoints for datasources, profiles, and layers
  • Implement a datasource service that calls the REST client, exposes a simple health check, and normalizes list responses
  • Add a profile service that retrieves installed profiles/layers, discovers a fixed set of local profile names, loads their JSON definitions from resources, and installs any missing ones via the REST client
src/main/java/com/kruize/optimizer/client/KruizeClient.java
src/main/java/com/kruize/optimizer/service/DatasourceService.java
src/main/java/com/kruize/optimizer/service/ProfileService.java
Expose REST endpoints for listing and installing profiles, listing datasources, and retrieving aggregated system status.
  • Add datasource resource exposing a list endpoint that wraps service output in ApiResponse and handles empty/error cases
  • Add separate resources for metadata profiles, metric profiles, and layers, each providing list and install endpoints that delegate to ProfileService
  • Add a status service and resource that aggregate datasources, profiles, and layers into a single status object with alerts and a basic health indicator
src/main/java/com/kruize/optimizer/resource/DatasourceResource.java
src/main/java/com/kruize/optimizer/resource/MetadataProfileResource.java
src/main/java/com/kruize/optimizer/resource/MetricProfileResource.java
src/main/java/com/kruize/optimizer/resource/LayerResource.java
src/main/java/com/kruize/optimizer/service/StatusService.java
src/main/java/com/kruize/optimizer/resource/StatusResource.java
Centralize constants, configuration, startup logging, and error handling behaviors for the optimizer service.
  • Introduce optimizer-wide constants for Kruize client endpoints, API paths, messages, and profile types
  • Add Quarkus YAML configuration for application metadata, logging, Swagger/OpenAPI, the Kruize REST client base URL, scan interval, profile git settings, and target label defaults
  • Add a startup bean that logs a startup message and a global exception mapper that converts exceptions (including a custom KruizeServiceException) into uniform API error responses
src/main/java/com/kruize/optimizer/utils/OptimizerConstants.java
src/main/resources/application.yml
src/main/java/com/kruize/optimizer/Startup.java
src/main/java/com/kruize/optimizer/exception/KruizeServiceException.java
src/main/java/com/kruize/optimizer/exception/GlobalExceptionMapper.java
Update project documentation to describe the Quarkus application lifecycle and related extensions.
  • Replace the minimal README with Quarkus-focused instructions for dev mode, packaging, native builds, and links to relevant Quarkus guides used by the project
README.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

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

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

  • Several configuration fields/constants are currently unused (e.g., ProfileService.profileBaseUrl, GenericOptimizerConstants.STARTUP_MESSAGE); either wire them into the implementation (for profile retrieval/logging) or remove them to avoid confusion.
  • Service layers frequently wrap all exceptions in generic RuntimeException while you also introduced KruizeServiceException and a GlobalExceptionMapper; consider standardizing on the custom exception and letting the mapper handle translation to HTTP responses to reduce duplicated try/catch and make error handling more consistent.
  • The hardcoded profile names in ProfileService.getAvailableProfilesFromLocal will require code changes whenever you add or rename profile files; consider discovering available profiles from the classpath/resource tree instead so it stays in sync with the actual JSON definitions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Several configuration fields/constants are currently unused (e.g., `ProfileService.profileBaseUrl`, `GenericOptimizerConstants.STARTUP_MESSAGE`); either wire them into the implementation (for profile retrieval/logging) or remove them to avoid confusion.
- Service layers frequently wrap all exceptions in generic `RuntimeException` while you also introduced `KruizeServiceException` and a `GlobalExceptionMapper`; consider standardizing on the custom exception and letting the mapper handle translation to HTTP responses to reduce duplicated try/catch and make error handling more consistent.
- The hardcoded profile names in `ProfileService.getAvailableProfilesFromLocal` will require code changes whenever you add or rename profile files; consider discovering available profiles from the classpath/resource tree instead so it stays in sync with the actual JSON definitions.

## Individual Comments

### Comment 1
<location path="src/main/java/com/kruize/optimizer/service/ProfileService.java" line_range="106" />
<code_context>
+     * @param profileType type of profile (metadata, metric, layer)
+     * @return List of installation results
+     */
+    public List<String> installMissingProfiles(String profileType) {
+        List<String> results = new ArrayList<>();
+        
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Use a stronger type than raw String for `profileType` or validate early to avoid silent misconfiguration.

Because `profileType` is a free-form `String`, unexpected values just produce empty lists from `getInstalledProfiles()` and `getAvailableProfilesFromLocal()`, so `installMissingProfiles()` returns an empty `results` without any error. `installProfile()` only throws for bad types when there is an available profile, which won’t happen if the list is empty. Consider using an enum (or the existing `ProfileType` constants) or adding an explicit validation branch that fails fast on unknown types to surface misconfigurations instead of silently ignoring them.

Suggested implementation:

```java
    /**
     * Install missing profiles from local repository
     *
     * @param profileType type of profile (see {@link ProfileType})
     * @return List of installation results
     */

```

```java
    public List<String> installMissingProfiles(ProfileType profileType) {

```

1. Update all call sites of `installMissingProfiles(String profileType)` to pass a `ProfileType` value instead of a `String`, e.g. `installMissingProfiles(ProfileType.METADATA)`.
2. If the method body currently relies on `String` operations (e.g. `equalsIgnoreCase("metadata")`), replace them with `switch`/`if` on `ProfileType` enum values or direct comparisons (e.g. `profileType == ProfileType.METADATA`).
3. If any external interfaces (REST endpoints, config, etc.) provide a string profile type, convert/validate those strings at the boundary (e.g. `ProfileType.valueOf(...)` with appropriate error handling) before calling this method, so misconfigurations surface clearly.
</issue_to_address>

### Comment 2
<location path="src/main/java/com/kruize/optimizer/service/ProfileService.java" line_range="45-46" />
<code_context>
+    @RestClient
+    KruizeClient kruizeClient;
+
+    @ConfigProperty(name = "kruize.profile.git.base-url", defaultValue = "local")
+    String profileBaseUrl;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
</code_context>
<issue_to_address>
**suggestion:** The `profileBaseUrl` config is defined but never used, which can be confusing for operators.

Right now profiles are always loaded via `loadProfileFromLocal()` (with hardcoded `configs/v1.0/...` paths) and `getAvailableProfilesFromLocal()` (with fixed names), so `kruize.profile.git.base-url` / `profileBaseUrl` never affect behavior. Please either integrate `profileBaseUrl` into profile path/discovery, or remove this config until it’s actually used to avoid a no-op setting.

Suggested implementation:

```java
    private final ObjectMapper objectMapper = new ObjectMapper();

```

1. In `ProfileService.java`, remove the now-unused `org.eclipse.microprofile.config.inject.ConfigProperty` import if present.
2. Search the codebase for `"kruize.profile.git.base-url"` and remove any corresponding entries from configuration files (e.g., `application.properties`, `application.yml`) and documentation, or update them later when you actually wire this setting into profile discovery.
</issue_to_address>

### Comment 3
<location path="src/main/java/com/kruize/optimizer/service/DatasourceService.java" line_range="57-59" />
<code_context>
+                    datasources
+            )).build();
+            
+        } catch (Exception e) {
+            LOG.error("Error fetching datasources", e);
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
</code_context>
<issue_to_address>
**suggestion:** Consider throwing `KruizeServiceException` instead of a raw `RuntimeException` for consistency with the global exception mapper.

These services (and `ProfileService`) currently throw generic `RuntimeException`s, which end up as default 500s with a generic message. Using `KruizeServiceException` (or a shared hierarchy) from service methods would let you set both status code and client-facing message explicitly, making error handling more predictable and consistent across the API.
</issue_to_address>

### Comment 4
<location path="README.md" line_range="46" />
<code_context>
+./mvnw package -Dnative
+```
+
+Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
+
+```shell script
</code_context>
<issue_to_address>
**suggestion (typo):** Fix the grammatical issue in "native executable build".

Consider rephrasing to something like “run the native executable built in a container” or “run the build of the native executable in a container” to fix the grammar around “build/built.”

Suggested implementation:

```
Or, if you don't have GraalVM installed, you can run the native executable built in a container using:

```

From the provided snippet, it looks like some markdown code fences around the `./mvnw quarkus:dev` command might be misaligned or missing. You may want to review that section in the full README to ensure all ```shell script fences are correctly opened and closed.
</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.

./mvnw package -Dnative
```

Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (typo): Fix the grammatical issue in "native executable build".

Consider rephrasing to something like “run the native executable built in a container” or “run the build of the native executable in a container” to fix the grammar around “build/built.”

Suggested implementation:

Or, if you don't have GraalVM installed, you can run the native executable built in a container using:

From the provided snippet, it looks like some markdown code fences around the ./mvnw quarkus:dev command might be misaligned or missing. You may want to review that section in the full README to ensure all ```shell script fences are correctly opened and closed.

Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
Signed-off-by: Shekhar Saxena <shekhar.saxena@ibm.com>
@shekhar316 shekhar316 requested a review from dinogun March 16, 2026 05:21
@shekhar316 shekhar316 changed the base branch from main to mvp_demo March 16, 2026 05:24
@rbadagandi1 rbadagandi1 moved this to In Progress in Monitoring Mar 16, 2026
@shekhar316 shekhar316 moved this from In Progress to Under Review in Monitoring Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Under Review

Development

Successfully merging this pull request may close these issues.

3 participants