Compare commits

10 Commits

Author SHA1 Message Date
Eduardo Rios
ae8066cb26 SIENTIAPDE-2072: bump sientia_do pin to 1.12.2
Picks up the notification timestamp -> native datetime fix; also
aligns requirements.txt's floor with requirements-local.txt's pin
(was >=1.11.0/@1.12.0, out of sync with the other repos).
2026-08-17 16:42:24 -03:00
Bruno Domingues
85642bed25 Merge pull request #27 from Aignosi/release/SIENTIAPDE-1645
Comprehensive Updates: Training Workflow, Configuration, and Dependency Management
2026-08-05 13:17:43 -03:00
Bruno Domingues
e3e1c712a8 SIENTIAPDE-1645: Refactor temporary directory cleanup activity by extracting processing and deletion logic into dedicated helper methods. Update train_test_split to use a local numpy.random generator for improved reproducibility. Remove an unnecessary else: pass block. 2026-08-05 12:10:54 -03:00
Bruno Domingues
4133e200f7 SIENTIAPDE-1645: Remove validate.sh script, add jsonschema dependency, and improve test code readability 2026-08-05 11:23:22 -03:00
Bruno Domingues
ebc7e5ce6f SIENTIAPDE-1645: Overhaul deployment, add Claude AI guidance, and enhance CI/CD.
Migrated to a shared sientia-module image and umbrella Helm chart, removing the standalone Dockerfile, .dockerignore, and values.yaml. Introduced CLAUDE.md for AI-assisted development. Updated quality-gate.yml with push triggers, concurrency, and refined permissions. Bumped project version to 2.0.0 to reflect these architectural changes.
2026-08-05 10:38:38 -03:00
github-actions[bot]
b6e59cf18e chore: bump version to 1.3.0 [skip ci] 2026-07-10 14:48:08 +00:00
Bruno Domingues
3457496c0a Merge pull request #28 from Aignosi/feature/SIENTIAPDE-1945
SIENTIAPDE-1945: Adopt Shared Docker/Kubernetes Deployment Strategy
2026-07-10 11:47:50 -03:00
Bruno Domingues
159607fc72 SIENTIAPDE-1945: Adopt shared Docker image and Kubernetes deployment strategy.
Removed the local Dockerfile, .dockerignore, and values.yaml, as image building and Kubernetes deployment are now handled by external sientia-module components. Updated README accordingly.
2026-07-08 20:22:32 -03:00
Bruno Domingues
1c06a8e584 ci(sonar): Update project key 2026-07-01 21:07:47 -03:00
vitor-aignosi
2c63414770 SIENTIAPDE-1645: Expose detailed model training information via a new Prometheus gauge. This gauge, sientia_training_info, records metadata such as dataset sizes, feature count, and evaluation metrics (MSE, MAE, R2) along with the training run's timestamp. 2026-06-18 09:56:30 -03:00
18 changed files with 304 additions and 891 deletions

View File

@@ -1,93 +0,0 @@
# ============================================================================
# WHITELIST APPROACH: Block everything by default, then allow only what's needed
# ============================================================================
# Block everything first
*
# ============================================================================
# ALLOW: Application source code (model_manager package)
# ============================================================================
# Allow the main package directory and all Python files
!model_manager/
!model_manager/**/*.py
!model_manager/**/__init__.py
# Allow subdirectories structure
!model_manager/activities/
!model_manager/activities/**
!model_manager/schedules/
!model_manager/schedules/**
!model_manager/sientia/
!model_manager/sientia/**
!model_manager/utils/
!model_manager/utils/**
!model_manager/utils/models/
!model_manager/utils/models/**
!model_manager/utils/repository/
!model_manager/utils/repository/**
!model_manager/worker/
!model_manager/worker/**
!model_manager/workflows/
!model_manager/workflows/**
# Allow reports directory with header.html
!model_manager/reports/
!model_manager/reports/header.html
# Allow temp directory structure (but not its contents)
!model_manager/reports/temp/
# ============================================================================
# ALLOW: Dependencies file (needed for pip install in Dockerfile)
# ============================================================================
!requirements.txt
# ============================================================================
# BLOCK: Explicitly block unwanted files even if they match above patterns
# ============================================================================
# Python cache and compiled files
**/__pycache__/
**/*.pyc
**/*.pyo
**/*.pyd
**/.Python
**/*.so
**/*.egg
**/*.egg-info/
# Tests (not needed in production)
model_manager/**/test_*.py
model_manager/**/*_test.py
# IDE and editor files
**/.vscode/
**/.idea/
**/*.swp
**/*.swo
**/*~
# OS files
**/.DS_Store
**/Thumbs.db
# Logs and temporary files
**/*.log
**/*.tmp
**/*.temp
# Local configuration
**/.env
**/.env.local
**/*.local
# Documentation inside code
**/*.md
**/README*
# Backup files
**/*.bak
**/*.backup
**/*.old

View File

@@ -1,6 +1,11 @@
name: Quality gate name: Quality gate
on: on:
push:
branches:
- main
- 'release/**'
- 'feature/**'
pull_request: pull_request:
branches: branches:
- main - main
@@ -8,10 +13,17 @@ on:
- 'feature/**' - 'feature/**'
types: [ opened, synchronize, reopened ] types: [ opened, synchronize, reopened ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
quality-gate: quality-gate:
uses: Aignosi/github_workflow_templates/.github/workflows/python-quality-gate.yml@main uses: Aignosi/github_workflow_templates/.github/workflows/python-quality-gate.yml@main
permissions: write-all permissions:
contents: read
pull-requests: write
issues: write
with: with:
project_name: 'model_manager' project_name: 'model_manager'
repositories: 'sientia-dataops-library,sientia-model-library' repositories: 'sientia-dataops-library,sientia-model-library'

78
CLAUDE.md Normal file
View File

@@ -0,0 +1,78 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
Enterprise ML model training orchestration platform built on **Temporal**. Orchestrates training workflows (validation → train → save to MLFlow → cleanup), backed by PostgreSQL (experiment tracking), MinIO (data/artifacts), MongoDB (notifications), and a Git-based "Plugin Store" that supplies model runtimes/wrappers dynamically. Python 3.11+.
## Commands
```bash
# Local run (loads .env, starts worker)
./run_local.sh
# or manually:
python -m model_manager.worker.worker
# Full validation (format check, lint, mypy, bandit, tests w/ coverage) — run before committing
./validate.sh
./validate.sh --fix # auto-fix format/lint, no tests
./validate.sh --no-tests # skip tests
./validate.sh --only-tests # tests only
# Individual tools
ruff format --check model_manager/ tests/
ruff format model_manager/ tests/ # auto-fix
ruff check model_manager/ tests/
ruff check --fix model_manager/ tests/
mypy model_manager/
bandit -r model_manager/ -ll
# Tests
pytest # runs tests/ and e2e/ (see pyproject testpaths)
pytest tests/ # unit tests only
pytest tests/activities/test_training.py -v # single file
pytest tests/activities/test_training.py::test_name -v # single test
pytest tests/ --cov=model_manager --cov-report=term-missing --cov-report=html
# Manual integration/E2E scripts (require live Temporal/Postgres/MinIO stack; run via IDE "Run Cell" or directly)
python scripts/run_training_test.py # supports --all for batch scenario execution
python scripts/run_cleanup_test.py
```
Coverage gate in CI is 80% minimum; this repo targets 100% in practice — don't add uncovered code paths without tests.
## Architecture
### Temporal layering
- **Worker** (`model_manager/worker/worker.py`): entrypoint. Builds config from env, connects Postgres/MLFlow/MinIO/Plugin Store, installs the `RUNTIME` model runtime via `PluginStore.install_runtime`, starts two Temporal workers (one per task queue) plus Prometheus metrics server and the cleanup schedule. All heavy imports are wrapped in `workflow.unsafe.imports_passed_through()` — required because Temporal workflow sandboxing forbids non-deterministic imports at module scope outside that block.
- **Workflows** (`model_manager/workflows/`): pure orchestration, no I/O beyond `execute_activity_method` calls.
- `train_model.py` (`TrainModel`, workflow name `train_model`): validate `experiment_run_id``load_model_metadata``validate_train_params` → update DB status `ORCHESTRATOR_WAITING_PROC``train_model` activity → update DB status `TRAINING_SUCCESS`/`TRAINING_ERROR``cleanup_resources` (always runs in `finally` if a `run_dir` exists, even on failure). Uses distinct `RetryPolicy`s per operation class: `network_retry_policy` (transient I/O), `no_retry_policy` (training/validation — data errors are permanent, and retrying would duplicate MLflow writes), `database_retry_policy` (Postgres). Timeouts are env-driven (`TIMEOUT_*`), extend `TIMEOUT_TRAIN_MODEL` instead of adding retries for slow training.
- `cleanup_files.py` (`CleanupFiles`): scheduled (`cleanup_schedule.py`, default daily at midnight UTC via `CLEANUP_CRON`) deletion of stale local temp dirs matching `name_YYYYMMDD_HHMMSS_microseconds`, older than `CLEANUP_RETENTION_HOURS`.
- Task queues are derived from `RUNTIME` env var (default `single`) via `prepare_worker.build_queue_name`: `train_model-<runtime>-queue`, `cleanup_files-<runtime>-queue`. A Temporal client starting a workflow must target the matching queue.
- **Activities** (`model_manager/activities/`): `Activities` (`activities.py`) combines `ExperimentTracking` + `Training` + `Cleanup` via multiple inheritance into one class registered with the worker — mind MRO and `__init__`/`__del__` ordering when touching this class (see the defensive `hasattr(self, 'engine')` check in `__del__`).
- `experiment_tracking.py`: single `update_experiment_run()` entrypoint handling three update kinds via `UpdateType`: `STATUS`, `STATUS_WITH_ERROR`, `MODEL_SAVED`. Error messages truncated to 1024 chars before persisting.
- `training.py`: single `train_model()` entrypoint. Receives pre-downloaded data as `BytesIO` (not re-downloaded per-activity) to avoid memory leaks; never raises on training failure — returns a result/error so the workflow can update DB status without an unhandled exception mid-activity.
- `cleanup.py`: local-filesystem-only cleanup (`cleanup_temp_directories`), no MinIO involved — MinIO artifacts are managed externally.
### Data/config layer
- `model_manager/utils/connectors_config.py`: single source of truth for env-var → config-dict builders (`build_postgres_config`, `build_mlflow_config`, `build_minio_config`, `build_mongodb_config`, `build_plugin_store_config`). Read this file (and `worker.py`) before trusting any env var default — the README table mirrors it but this file is authoritative.
- `model_manager/utils/models/train_model_params.py`: `TrainModelParams` — the workflow's single input contract. `from_dict` does type coercion; `validate_business_rules()` runs after, enforcing 7 rules (train_size range, non-empty variable_columns, required model_metadata, JSON-Schema validation of `data_model_kwargs`/`model_kwargs`/`opt_params` against schemas in `model_metadata` via `Draft202012Validator`, required non-blank strings, allowed `date_format` values, numeric `experiment_run_id`). Extending training params means updating this file's validation, not just the workflow.
- `model_manager/utils/repository/data_manager_repository.py`: data loading, feature prep, metrics, report generation — the actual ML/data logic activities delegate into.
- `model_manager/sientia/`: RCE drift metrics (`metrics.py`), Evidently-based HTML report generation (`reports.py`), custom exceptions.
- `model_manager/metrics.py`: Prometheus metric definitions (`app_up`, `workflow_execution_total`, `activity_execution_total`, training/cleanup/RCE-drift gauges). New metrics go here, not inline in activities.
### External dependencies
Two private Aignosi packages carry non-trivial logic: `sientia_do` (observability/logger, notification handler, metrics controller, Postgres/MinIO repositories) and `sientia_model` (Plugin Store, MLflow repository, model wrappers). Both are pinned in `requirements.txt`/`requirements-local.txt`; when behavior looks like it lives outside this repo, it's likely in one of these.
### Test bootstrap quirk
`tests/conftest.py` stubs `evidently` and parts of `sientia_do` at `pytest_configure` time so unit tests can import `model_manager.sientia.reports` without the full (heavy/optional) Evidently install. If you add new imports from these packages in production code paths that unit tests exercise, you likely need to extend the stubs here too.
### Test layout
- `tests/` — unit tests, mirrors `model_manager/` package structure (`tests/activities/`, `tests/schedules/`, `tests/sientia/`, etc.).
- `e2e/` — integration tests against a real Temporal/Postgres/MinIO/MongoDB stack (via `testcontainers`), driven by JSON scenario files in `docs/test-scenarios/` (see `e2e/scenarios.md` for the scenario→test mapping). Scenario payloads are snake_case, shaped like `TrainModelParams`/`input-sample.json`.
- New scenario: copy an existing `docs/test-scenarios/*.json`, add/extend a test in `e2e/test_train_model_workflow.py`, update `e2e/scenarios.md`.
## Conventions
- Ruff: line-length 100, single quotes, `select = [E,W,F,I,B,C4,UP,N,YTT,S,BLE,A,C90]`, max complexity 15. `N802`/`N806` ignored (Temporal decorators use non-lowercase names). `S101`/`S105`/`S106` ignored in tests.
- mypy: `disallow_untyped_defs = false` but `check_untyped_defs = true` — existing untyped defs are tolerated, but code inside them is still checked. Several external packages (`temporalio`, `mlflow`, `sientia_model`, etc.) have `ignore_missing_imports`; `model_repository.py`/`data_manager_repository.py` have `ignore_errors = true` (legacy — don't extend that list without reason).

View File

@@ -1,78 +0,0 @@
# Multi-stage build for optimized Python application
FROM python:3.11-slim AS builder
# Set build-time environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Install build dependencies only
RUN apt-get update && apt-get install -y \
build-essential \
curl \
git \
&& rm -rf /var/lib/apt/lists/* && \
apt-get clean
# Configure SSH to trust GitHub host key
RUN mkdir -p ~/.ssh && \
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts && \
chmod 600 ~/.ssh/known_hosts
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Upgrade pip and wheel for better caching
RUN pip install --upgrade pip setuptools wheel
# Copy requirements files for better Docker layer caching
COPY requirements.txt ./
# Install only production dependencies with no cache
RUN --mount=type=ssh echo "=== Installing dependencies ===" && \
pip install --no-cache-dir -r requirements.txt && \
echo "=== Dependencies installed successfully ===" && \
pip list | wc -l && \
echo "=== Cleaning cache files ===" && \
find /opt/venv -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && \
find /opt/venv -name "*.pyc" -delete 2>/dev/null || true && \
rm -rf /root/.cache/pip/* && \
echo "=== Cleaning venv site-packages ===" && \
find /opt/venv/lib/python3.11/site-packages/ -type f -name "*.md" -delete 2>/dev/null || true && \
echo "=== Stripping .so files ===" && \
find /opt/venv -name "*.so" -exec strip {} + 2>/dev/null || true
# Production stage using python-slim for better functionality
FROM python:3.11-slim AS production
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \
POD_ID=unknown \
HOME="/app"
# Copy virtual environment from builder stage
COPY --from=builder /opt/venv /opt/venv
# Set working directory
WORKDIR /app
# Copy application code
COPY . .
# Create necessary directories for runtime file creation
RUN mkdir -p /app/model_manager/reports /app/logs /app/temp /app/models /app/data && \
chmod 755 /app/model_manager/reports /app/logs /app/temp /app/models /app/data
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser && \
chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Set entrypoint for proper signal handling and PID 1
ENTRYPOINT ["/opt/venv/bin/python", "-m", "model_manager.worker.worker"]

View File

@@ -74,7 +74,7 @@ An enterprise-grade ML model training orchestration platform built on Temporal.
- [Usage](#usage) - [Usage](#usage)
- [Command Reference](#command-reference) - [Command Reference](#command-reference)
- [Docker](#docker) - [Docker](#docker)
- [Helm Chart](#helm-chart) - [Kubernetes Deployment](#kubernetes-deployment)
## Features ## Features
@@ -1186,8 +1186,6 @@ sientia-dataops-model-manager/
├── .github/workflows/ # CI/CD workflows ├── .github/workflows/ # CI/CD workflows
│ ├── quality-gate.yml # PR quality checks │ ├── quality-gate.yml # PR quality checks
│ └── deploy.yml # Deployment workflow │ └── deploy.yml # Deployment workflow
├── Dockerfile # Container image definition
├── values.yaml # Helm chart values
├── pyproject.toml # Project configuration ├── pyproject.toml # Project configuration
├── requirements.txt # Production dependencies ├── requirements.txt # Production dependencies
├── requirements-dev.txt # Development dependencies ├── requirements-dev.txt # Development dependencies
@@ -1475,79 +1473,17 @@ act -n
## Docker ## Docker
### Create image Model Manager runs on the shared **`sientia-module`** image — the application code is cloned from the internal Gitea at runtime, not baked into an image. The image is defined in the [Aignosi/sientia-container-images](https://github.com/Aignosi/sientia-container-images) repository (`templates/sientia-module/`) and published to GCP Artifact Registry (`southamerica-east1-docker.pkg.dev/sientia-dev/sientia/sientia-module`).
```bash The legacy repo-specific Dockerfile was removed — no image is built from this repo.
$ docker build --ssh default --no-cache --progress=plain -t aignosi.azurecr.io/sientia-dataops-model-manager:0.0.0 .
```
### Create container To run locally, use `./run_local.sh` (see [Local Execution](#local-execution)).
```bash ## Kubernetes Deployment
$ docker run --env-file .env --network="host" --name sientia-dataops-model-manager -d aignosi.azurecr.io/sientia-dataops-model-manager:0.0.0
$ docker logs -f sientia-dataops-model-manager Kubernetes deployment is managed by the [Aignosi/sientia-helm-chart](https://github.com/Aignosi/sientia-helm-chart) umbrella chart (`src/charts-internal/model-manager/`, which wraps the shared `sientia-module` chart) — the local `values.yaml` and the vendored `sientia-module/` chart copy were removed. Worker configuration, Gitea repo/branch, task queues, and sizing are all defined in the umbrella values.
```
### Login using access token The standalone `sientia-module` chart still lives in [sientia-dataops-helm-repo](https://github.com/Aignosi/sientia-dataops-helm-repo) for the legacy deploy flow (temporarily broken until the CI/CD redesign).
```bash
$ docker login -u <username> -p <access-token> aignosi.azurecr.io
```
### Push image to repository
```bash
$ docker push aignosi.azurecr.io/sientia-dataops-model-manager:0.0.0
```
## Helm Chart
### Reference
https://aignosi-wiki.atlassian.net/wiki/spaces/IT1/pages/274563074/Como+utilizar+o+Helm+Repo+Privado
### Add Helm Chart repository
```bash
$ helm repo add sientia \
https://raw.githubusercontent.com/Aignosi/sientia-dataops-helm-repo/refs/heads/main/ \
--username $GITHUB_USER \
--password $GITHUB_PASS
# Update repository
$ helm repo update
# List repositories
$ helm repo list
# List versions of a specific chart
$ helm search repo sientia --versions
# List all charts available
$ helm search repo sientia
# List chart details
$ helm show all sientia/sientia-module
# Download chart to current directory
$ helm pull sientia/sientia-module --version 0.6.0 --untar
# Remove chart directory
$ rm -rf sientia-module
```
### Helm Install
```shell
$ helm upgrade --install sientia-dataops-model-manager sientia/sientia-module -n sientia --create-namespace -f ./values.yaml --version 0.6.0
```
### Uninstall Helm Chart
```shell
$ helm uninstall sientia-dataops-model-manager -n sientia
```
--- ---

View File

@@ -61,6 +61,87 @@ class Cleanup(SientiaMonitoring):
r'^(.+)_(\d{8}_\d{6}_\d{6})$' r'^(.+)_(\d{8}_\d{6}_\d{6})$'
) # name_YYYYMMDD_HHMMSS_microseconds ) # name_YYYYMMDD_HHMMSS_microseconds
def _process_directory_item(
self,
item_path: str,
item_name: str,
cutoff_time: datetime,
metadata: dict[str, Any],
) -> tuple[bool, str | None]:
"""
Handle a single temp-directory entry: skip if the name doesn't match the
timestamp pattern, otherwise delete (or dry-run log) it when stale.
Args:
item_path: Full path to the directory being evaluated
item_name: Directory name (used to extract the embedded timestamp)
cutoff_time: Directories older than this are considered stale
metadata: Workflow execution metadata for logging
Return:
tuple[bool, str | None]: (deleted, error_message). `deleted` is True
if the directory was removed (or would be, in dry-run mode).
"""
match = self.dir_timestamp_pattern.match(item_name)
if not match:
self.debug(f'Skipping directory without timestamp pattern: {item_name}', metadata)
return False, None
timestamp_str = match.group(2)
try:
# Parse YYYYMMDD_HHMMSS_microseconds
dir_time = datetime.strptime(timestamp_str, '%Y%m%d_%H%M%S_%f')
except ValueError as e:
error_msg = f'Invalid timestamp format in directory {item_name}: {str(e)}'
self.error(error_msg, metadata)
return False, error_msg
return self._delete_if_stale(item_path, item_name, dir_time, cutoff_time, metadata)
def _delete_if_stale(
self,
item_path: str,
item_name: str,
dir_time: datetime,
cutoff_time: datetime,
metadata: dict[str, Any],
) -> tuple[bool, str | None]:
"""
Delete (or dry-run log) a directory whose embedded timestamp is older than
cutoff_time; otherwise leave it alone.
Args:
item_path: Full path to the directory
item_name: Directory name (for logging)
dir_time: Timestamp parsed from the directory name
cutoff_time: Directories older than this are considered stale
metadata: Workflow execution metadata for logging
Return:
tuple[bool, str | None]: (deleted, error_message)
"""
if dir_time >= cutoff_time:
age_hours = (datetime.now() - dir_time).total_seconds() / 3600
self.debug(f'Keeping recent directory: {item_name} (age: {age_hours:.1f}h)', metadata)
return False, None
age_hours = (datetime.now() - dir_time).total_seconds() / 3600
if self.dry_run:
self.info(
f'[DRY RUN] Would delete directory: {item_name} (age: {age_hours:.1f}h)', metadata
)
return True, None
try:
shutil.rmtree(item_path)
self.info(f'Deleted stale directory: {item_name} (age: {age_hours:.1f}h)', metadata)
return True, None
except OSError as e:
error_msg = f'Failed to delete directory {item_name}: {str(e)}'
self.error(error_msg, metadata)
return False, error_msg
@activity.defn(name='cleanup_temp_directories') @activity.defn(name='cleanup_temp_directories')
def cleanup_temp_directories(self, input_data: dict[str, Any]) -> None: def cleanup_temp_directories(self, input_data: dict[str, Any]) -> None:
""" """
@@ -109,51 +190,13 @@ class Cleanup(SientiaMonitoring):
directories_scanned += 1 directories_scanned += 1
# Extract timestamp from directory name deleted, error = self._process_directory_item(
match = self.dir_timestamp_pattern.match(item_name) item_path, item_name, cutoff_time, metadata
if not match: )
self.debug( if error:
f'Skipping directory without timestamp pattern: {item_name}', metadata errors.append(error)
) if deleted:
continue directories_deleted += 1
timestamp_str = match.group(2)
try:
# Parse YYYYMMDD_HHMMSS_microseconds
dir_time = datetime.strptime(timestamp_str, '%Y%m%d_%H%M%S_%f')
if dir_time < cutoff_time:
age_hours = (datetime.now() - dir_time).total_seconds() / 3600
if self.dry_run:
self.info(
f'[DRY RUN] Would delete directory: {item_name} (age: {age_hours:.1f}h)',
metadata,
)
directories_deleted += 1
else:
try:
shutil.rmtree(item_path)
self.info(
f'Deleted stale directory: {item_name} (age: {age_hours:.1f}h)',
metadata,
)
directories_deleted += 1
except OSError as e:
error_msg = f'Failed to delete directory {item_name}: {str(e)}'
errors.append(error_msg)
self.error(error_msg, metadata)
else:
age_hours = (datetime.now() - dir_time).total_seconds() / 3600
self.debug(
f'Keeping recent directory: {item_name} (age: {age_hours:.1f}h)',
metadata,
)
except ValueError as e:
error_msg = f'Invalid timestamp format in directory {item_name}: {str(e)}'
errors.append(error_msg)
self.error(error_msg, metadata)
self.info( self.info(
f'Directory cleanup completed - Scanned: {directories_scanned}, ' f'Directory cleanup completed - Scanned: {directories_scanned}, '

View File

@@ -262,6 +262,18 @@ class Training(SientiaMonitoring):
train_result.r2_val train_result.r2_val
) )
mm_metrics.SIENTIA_TRAINING_INFO.labels(
pod_id=labels['pod_id'],
model_name=train_params.model_name,
model_type=train_params.model_type,
dataset_train_rows=str(len(train_result.train_data)),
dataset_val_rows=str(len(train_result.val_data)),
feature_count=str(len(train_params.variable_columns)),
mse=str(train_result.mse_val) if train_result.mse_val is not None else '',
mae=str(train_result.mae_val) if train_result.mae_val is not None else '',
r2=str(train_result.r2_val) if train_result.r2_val is not None else '',
).set(time.time() * 1000)
self.info(f'Starting MLflow run for {train_params.model_type}', metadata) self.info(f'Starting MLflow run for {train_params.model_type}', metadata)
with self.mlflow_repository.start_run( with self.mlflow_repository.start_run(
model_name=train_params.model_name, model_name=train_params.model_name,

View File

@@ -27,6 +27,22 @@ APP_UP = Gauge(
_TRAINING_LABELS = ['pod_id', 'model_name', 'model_type'] _TRAINING_LABELS = ['pod_id', 'model_name', 'model_type']
SIENTIA_TRAINING_INFO = Gauge(
'sientia_training_info',
'Metadata and execution timestamp (ms) of the last successful model training run',
[
'pod_id',
'model_name',
'model_type',
'dataset_train_rows',
'dataset_val_rows',
'feature_count',
'mse',
'mae',
'r2',
],
)
SIENTIA_TRAINING_MODEL_TRAINED_TOTAL = Counter( SIENTIA_TRAINING_MODEL_TRAINED_TOTAL = Counter(
'sientia_training_model_trained_total', 'sientia_training_model_trained_total',
'Number of successfully completed model training runs', 'Number of successfully completed model training runs',

View File

@@ -42,15 +42,13 @@ def train_test_split(
random_state: int | None = None, random_state: int | None = None,
shuffle: bool = True, shuffle: bool = True,
) -> tuple[pd.DataFrame, pd.DataFrame]: ) -> tuple[pd.DataFrame, pd.DataFrame]:
# 1. Definir a semente (seed) para reprodutibilidade
if random_state is not None:
np.random.seed(random_state)
# 2. Gerar índices e embaralhar se necessário # 2. Gerar índices e embaralhar se necessário
indices = np.arange(len(data)) indices = np.arange(len(data))
if shuffle: if shuffle:
np.random.shuffle(indices) # 1. Generator local (em vez do estado global np.random) para reprodutibilidade
rng = np.random.default_rng(random_state)
rng.shuffle(indices)
# 3. Calcular o ponto de corte (split point) # 3. Calcular o ponto de corte (split point)
# Cálculo: N_treino = tamanho_total * proporcao_treino # Cálculo: N_treino = tamanho_total * proporcao_treino

View File

@@ -136,8 +136,6 @@ class TrainModel:
run_dir=train_result.get('run_dir'), run_dir=train_result.get('run_dir'),
metadata=metadata, metadata=metadata,
) )
else:
pass
except Exception: # noqa: BLE001 except Exception: # noqa: BLE001
# If cleanup fails after training failed, there is nothing extra to log (DB not committed). # If cleanup fails after training failed, there is nothing extra to log (DB not committed).
if training_succeeded: # pragma: no branch if training_succeeded: # pragma: no branch

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "model-manager" name = "model-manager"
version = "1.2.0" version = "2.0.0"
description = "Sientia DataOps Model Manager - ML Model Orchestration System" description = "Sientia DataOps Model Manager - ML Model Orchestration System"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"

View File

@@ -3,8 +3,9 @@ psycopg2-binary==2.9.11
sqlalchemy==2.0.49 sqlalchemy==2.0.49
boto3==1.42.70 boto3==1.42.70
botocore==1.42.70 botocore==1.42.70
git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.0 git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.12.2
git+ssh://git@github.com/Aignosi/sientia-model-library.git@0.8.3 git+ssh://git@github.com/Aignosi/sientia-model-library.git@0.8.3
prometheus-client==0.23.1 prometheus-client==0.23.1
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
evidently==0.6.7 evidently==0.6.7
jsonschema==4.26.0

View File

@@ -3,7 +3,7 @@ psycopg2-binary==2.9.11
sqlalchemy==2.0.50 sqlalchemy==2.0.50
boto3==1.42.70 boto3==1.42.70
botocore==1.42.70 botocore==1.42.70
sientia_do>=1.11.0 sientia_do>=1.12.2
sientia_model>=0.8.1 sientia_model>=0.8.1
prometheus-client==0.23.1 prometheus-client==0.23.1
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3

View File

@@ -1,4 +1,4 @@
sonar.projectKey=Aignosi_sientia-dataops-model-manager_dc6e051c-995a-4b57-9cde-f511018184c7 sonar.projectKey=Aignosi_sientia-dataops-model-manager_ac6a7d4d-508d-4cbf-bd3c-1bb0d79db881
sonar.projectName=sientia-dataops-model-manager sonar.projectName=sientia-dataops-model-manager
sonar.sources=model_manager sonar.sources=model_manager
sonar.tests=tests sonar.tests=tests

View File

@@ -369,7 +369,10 @@ def test_prepare_data_increments_error_counter_and_still_observes_lag_on_failure
training.observe_lag_sync.assert_called_once() training.observe_lag_sync.assert_called_once()
training.emit_metric_sync.assert_called_once() training.emit_metric_sync.assert_called_once()
call_args = training.emit_metric_sync.call_args call_args = training.emit_metric_sync.call_args
assert call_args.kwargs['metric_object'] is mm_metrics.SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL assert (
call_args.kwargs['metric_object']
is mm_metrics.SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL
)
def test_fit_model_observes_lag_on_success(training): def test_fit_model_observes_lag_on_success(training):
@@ -419,12 +422,16 @@ def test_fit_model_increments_error_counter_on_failure(training):
training.observe_lag_sync.assert_called_once() training.observe_lag_sync.assert_called_once()
training.emit_metric_sync.assert_called_once() training.emit_metric_sync.assert_called_once()
call_args = training.emit_metric_sync.call_args call_args = training.emit_metric_sync.call_args
assert call_args.kwargs['metric_object'] is mm_metrics.SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL assert (
call_args.kwargs['metric_object'] is mm_metrics.SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL
)
@patch('model_manager.activities.training.mm_metrics') @patch('model_manager.activities.training.mm_metrics')
@patch('model_manager.activities.training.mlflow') @patch('model_manager.activities.training.mlflow')
def test_train_model_sets_quality_gauges_after_compute_metrics(mock_mlflow, mock_mm_metrics, training): def test_train_model_sets_quality_gauges_after_compute_metrics(
mock_mlflow, mock_mm_metrics, training
):
tp = TrainModelParams.from_dict( tp = TrainModelParams.from_dict(
{**_minimal_params_dict(), 'model_metadata': {'schemas': {'components': {'schemas': {}}}}} {**_minimal_params_dict(), 'model_metadata': {'schemas': {'components': {'schemas': {}}}}}
) )
@@ -473,9 +480,15 @@ def test_train_model_sets_quality_gauges_after_compute_metrics(mock_mlflow, mock
training.train_model({'metadata': {}, 'train_params': tp.to_dict()}) training.train_model({'metadata': {}, 'train_params': tp.to_dict()})
mock_mm_metrics.SIENTIA_TRAINING_MODEL_QUALITY_MSE.labels.return_value.set.assert_called_once_with(0.5) mock_mm_metrics.SIENTIA_TRAINING_MODEL_QUALITY_MSE.labels.return_value.set.assert_called_once_with(
mock_mm_metrics.SIENTIA_TRAINING_MODEL_QUALITY_MAE.labels.return_value.set.assert_called_once_with(0.3) 0.5
mock_mm_metrics.SIENTIA_TRAINING_MODEL_QUALITY_R2.labels.return_value.set.assert_called_once_with(-0.1) )
mock_mm_metrics.SIENTIA_TRAINING_MODEL_QUALITY_MAE.labels.return_value.set.assert_called_once_with(
0.3
)
mock_mm_metrics.SIENTIA_TRAINING_MODEL_QUALITY_R2.labels.return_value.set.assert_called_once_with(
-0.1
)
@patch('model_manager.activities.training.mm_metrics') @patch('model_manager.activities.training.mm_metrics')

View File

@@ -237,7 +237,10 @@ def test_sientia_training_data_preparation_error_count_total_is_counter():
from model_manager.metrics import SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL from model_manager.metrics import SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL
assert isinstance(SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL, Counter) assert isinstance(SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL, Counter)
assert 'sientia_training_data_preparation_error_count' in SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL._name assert (
'sientia_training_data_preparation_error_count'
in SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL._name
)
_assert_training_labels(SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL) _assert_training_labels(SIENTIA_TRAINING_DATA_PREPARATION_ERROR_COUNT_TOTAL)
@@ -257,7 +260,10 @@ def test_sientia_training_model_fit_error_count_total_is_counter():
from model_manager.metrics import SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL from model_manager.metrics import SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL
assert isinstance(SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL, Counter) assert isinstance(SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL, Counter)
assert 'sientia_training_model_fit_error_count' in SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL._name assert (
'sientia_training_model_fit_error_count'
in SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL._name
)
_assert_training_labels(SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL) _assert_training_labels(SIENTIA_TRAINING_MODEL_FIT_ERROR_COUNT_TOTAL)
@@ -329,3 +335,57 @@ def test_sientia_training_feature_count_is_gauge():
assert isinstance(SIENTIA_TRAINING_FEATURE_COUNT, Gauge) assert isinstance(SIENTIA_TRAINING_FEATURE_COUNT, Gauge)
assert SIENTIA_TRAINING_FEATURE_COUNT._name == 'sientia_training_feature_count' assert SIENTIA_TRAINING_FEATURE_COUNT._name == 'sientia_training_feature_count'
_assert_training_labels(SIENTIA_TRAINING_FEATURE_COUNT) _assert_training_labels(SIENTIA_TRAINING_FEATURE_COUNT)
def test_sientia_training_info_is_gauge():
from prometheus_client import Gauge
from model_manager.metrics import SIENTIA_TRAINING_INFO
assert isinstance(SIENTIA_TRAINING_INFO, Gauge)
assert SIENTIA_TRAINING_INFO._name == 'sientia_training_info'
expected_labels = {
'pod_id',
'model_name',
'model_type',
'dataset_train_rows',
'dataset_val_rows',
'feature_count',
'mse',
'mae',
'r2',
}
assert expected_labels == set(SIENTIA_TRAINING_INFO._labelnames)
def test_sientia_training_info_set_value():
import time
from model_manager.metrics import SIENTIA_TRAINING_INFO
ts = time.time() * 1000
SIENTIA_TRAINING_INFO.labels(
pod_id='test-pod',
model_name='my_model',
model_type='linear',
dataset_train_rows='1000',
dataset_val_rows='200',
feature_count='5',
mse='0.01',
mae='0.08',
r2='0.95',
).set(ts)
value = SIENTIA_TRAINING_INFO.labels(
pod_id='test-pod',
model_name='my_model',
model_type='linear',
dataset_train_rows='1000',
dataset_val_rows='200',
feature_count='5',
mse='0.01',
mae='0.08',
r2='0.95',
)._value._value
assert abs(value - ts) < 2000

View File

@@ -1,195 +0,0 @@
#!/bin/bash
# Model Manager Code Validation Script
# This script runs all code quality checks before committing or deploying
#
# Usage:
# ./validate.sh # Run all checks including tests (default)
# ./validate.sh --no-tests # Skip unit tests
# ./validate.sh --skip-tests # Skip unit tests (alias)
# ./validate.sh --only-tests # Run only unit tests
# ./validate.sh --fix # Auto-fix formatting and linting, then run validations (no tests)
set -e # Exit on any error
# Parse command line arguments
RUN_TESTS=true
ONLY_TESTS=false
FIX_MODE=false
for arg in "$@"; do
case $arg in
--no-tests|--skip-tests)
RUN_TESTS=false
shift
;;
--only-tests)
ONLY_TESTS=true
shift
;;
--fix)
FIX_MODE=true
RUN_TESTS=false
shift
;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --no-tests, --skip-tests Skip unit tests (default: run tests)"
echo " --only-tests Run only unit tests"
echo " --fix Auto-fix formatting and linting, then run validations (no tests)"
echo " --help, -h Show this help message"
echo ""
exit 0
;;
*)
echo "Unknown option: $arg"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}╔════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Model Manager - Code Validation Suite ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════╝${NC}"
echo ""
# Handle --fix mode
if [ "$FIX_MODE" = true ]; then
echo -e "${BLUE}🔧 Running auto-fix mode...${NC}"
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}▶ Auto-fixing code formatting (Ruff)${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
ruff format model_manager/ tests/
echo -e "${GREEN}✅ Code formatting applied${NC}"
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}▶ Auto-fixing linting issues (Ruff)${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
ruff check --fix model_manager/ tests/
echo -e "${GREEN}✅ Linting fixes applied${NC}"
echo ""
echo -e "${YELLOW} Now running validations (without tests)...${NC}"
echo ""
fi
# Handle --only-tests mode
if [ "$ONLY_TESTS" = true ]; then
echo -e "${BLUE}🧪 Running only unit tests...${NC}"
echo ""
fi
if [ "$RUN_TESTS" = false ] && [ "$ONLY_TESTS" = false ]; then
echo -e "${YELLOW} Unit tests will be skipped${NC}"
echo ""
fi
# Check if virtual environment is activated
if [[ -z "${VIRTUAL_ENV}" ]] && [[ -z "${CONDA_DEFAULT_ENV}" ]]; then
echo -e "${YELLOW}⚠️ Warning: No virtual environment detected${NC}"
echo -e "${YELLOW} Consider activating your venv/conda environment${NC}"
echo ""
fi
# Function to run a validation step
run_step() {
local step_name=$1
local step_command=$2
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}${step_name}${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if eval "$step_command"; then
echo -e "${GREEN}${step_name} - PASSED${NC}"
echo ""
return 0
else
echo -e "${RED}${step_name} - FAILED${NC}"
echo ""
return 1
fi
}
# Track failures
FAILED_STEPS=()
# Handle --only-tests mode
if [ "$ONLY_TESTS" = true ]; then
# Step 5: Unit Tests (pytest)
if ! run_step "Unit Tests (pytest)" "pytest tests/ --cov=model_manager --cov-report=term-missing --cov-report=xml --cov-report=html --cov-fail-under=80 -q"; then
FAILED_STEPS+=("Unit Tests")
fi
else
# Step 1: Code Formatting Check (Ruff)
if ! run_step "1. Code Formatting (Ruff)" "ruff format --check model_manager/ tests/"; then
FAILED_STEPS+=("Code Formatting")
fi
# Step 2: Linting (Ruff)
if ! run_step "2. Code Linting (Ruff)" "ruff check model_manager/ tests/"; then
FAILED_STEPS+=("Linting")
fi
# Step 3: Type Checking (mypy)
if ! run_step "3. Type Checking (mypy)" "mypy model_manager/"; then
FAILED_STEPS+=("Type Checking")
fi
# Step 4: Security Analysis (Bandit)
if ! run_step "4. Security Analysis (Bandit)" "bandit -r model_manager/ -ll -q"; then
FAILED_STEPS+=("Security Analysis")
fi
# Step 5: Unit Tests (pytest)
if [ "$RUN_TESTS" = true ]; then
if ! run_step "5. Unit Tests (pytest)" "pytest tests/ --cov=model_manager --cov-report=term-missing --cov-report=xml --cov-report=html --cov-fail-under=80 -q"; then
FAILED_STEPS+=("Unit Tests")
fi
else
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}▶ 5. Unit Tests (pytest)${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}⏭️ Unit Tests - SKIPPED${NC}"
echo ""
fi
fi
# Summary
echo -e "${BLUE}╔════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Validation Summary ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════╝${NC}"
echo ""
if [ ${#FAILED_STEPS[@]} -eq 0 ]; then
echo -e "${GREEN}✅ All validation checks passed!${NC}"
echo -e "${GREEN} Your code is ready for commit/deployment.${NC}"
echo ""
exit 0
else
echo -e "${RED}❌ Validation failed for the following steps:${NC}"
for step in "${FAILED_STEPS[@]}"; do
echo -e "${RED}${step}${NC}"
done
echo ""
echo -e "${YELLOW}💡 Tips:${NC}"
echo -e "${YELLOW} • Run 'ruff format model_manager/ tests/' to auto-fix formatting${NC}"
echo -e "${YELLOW} • Run 'ruff check --fix model_manager/ tests/' to auto-fix linting issues${NC}"
echo -e "${YELLOW} • Review mypy errors and add type hints where needed${NC}"
echo -e "${YELLOW} • Check bandit warnings for security issues${NC}"
echo -e "${YELLOW} • Fix failing tests or improve test coverage${NC}"
echo ""
exit 1
fi

View File

@@ -1,388 +0,0 @@
#
# Default values for sientia-model-manager using the sientia-module chart (0.6.x).
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
#
projectName: &projectName "sientia-model-manager"
# -----------------------------------------------------------------------------
# Global configuration shared by all runtimes
# -----------------------------------------------------------------------------
global:
# Namespace used by the chart.
namespace: sientia
# -----------------------------------------------------------------------------
# Image configuration (chart-level)
# -----------------------------------------------------------------------------
# The sientia-module chart allows overriding the image used by all runtimes.
# Per requirement, we deploy using the sientia-module image v1.0.0.
image:
repository: aignosi.azurecr.io/sientia-module
pullPolicy: Always
tag: "1.0.2"
# Common labels applied to pods (can be extended per project).
commonLabels: {}
# Resources inherited by all runtimes unless overridden.
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# Probes inherited by all runtimes unless overridden.
# More information:
# https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
exec:
command:
- python3
- -c
- "import requests; requests.get('http://localhost:9090/metrics')"
initialDelaySeconds: 20
periodSeconds: 30
readinessProbe:
exec:
command:
- python3
- -c
- "import requests; requests.get('http://localhost:9090/metrics')"
initialDelaySeconds: 10
periodSeconds: 15
# Autoscaling configuration inherited by all runtimes unless overridden.
# More information:
# https://kubernetes.io/docs/concepts/workloads/autoscaling/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Environment variables shared by all runtimes.
env:
# Entrypoint variables
- name: GITHUB_REPO_URL
value: "git@github.com:Aignosi/sientia-dataops-model-manager.git"
- name: GITHUB_BRANCH
value: "release/SIENTIAPDE-1645"
- name: PYTHON_APP
value: "model_manager.worker.worker"
- name: PYPI_SERVER
value: "http://library-distribution-server.library.svc.cluster.local:5000"
- name: POSTGRES_HOST
value: "paradedb-rw.paradedb.svc.cluster.local"
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "nFqc81y6kwmr2zuAIx43DhiOosFCVPpeEfTtTWZflkNjB2j1KtEeIANkhFR9mAX3"
- name: POSTGRES_DBNAME
value: "sientia-core-mlops-bff"
- name: POSTGRES_MIN_CONNECTIONS
value: "10"
- name: POSTGRES_MAX_CONNECTIONS
value: "30"
- name: MLFLOW_URL
value: "http://sientia-tracker-mlflow-tracking.sientia-tracker.svc.cluster.local:80"
- name: MLFLOW_USERNAME
value: "aignosi"
- name: MLFLOW_PASSWORD
value: "1L0FP50j3ncp123"
- name: LOG_LEVEL
value: "DEBUG"
- name: HTTP_METRICS_PORT
value: "9090"
- name: HTTP_SDK_METRICS_PORT
value: "9091"
- name: TEMPORAL_HOST
value: "temporal-frontend.temporal.svc.cluster.local:7233"
- name: TEMPORAL_NAMESPACE
value: "model-manager"
- name: TRAIN_TASK_QUEUE
value: "train_model-queue"
- name: CLEANUP_TASK_QUEUE
value: "cleanup-queue"
- name: TEMPORAL_USE_TLS
value: "false"
- name: STORE_BASE_URL
value: "http://gitea-http.gitea.svc.cluster.local:3000"
- name: STORE_OWNER
value: "aignosi"
- name: STORE_REPO
value: "suse-model-store"
- name: STORE_USERNAME
valueFrom:
secretKeyRef:
name: sientia-plugin-store-credentials
key: username
- name: STORE_PASSWORD
valueFrom:
secretKeyRef:
name: sientia-plugin-store-credentials
key: password
- name: STORE_CACHE_TTL_SECONDS
value: "3600"
- name: MONGODB_USERNAME
value: "root"
- name: MONGODB_PASSWORD
value: "wKZDbMNU1c"
- name: MONGODB_URL
value: "my-release-mongodb.mongodb.svc.cluster.local:27017"
- name: MONGODB_DATABASE
value: "sientia"
- name: MONGODB_TTL_INDEX_HOURS
value: "1"
- name: MINIO_ENDPOINT_URL
value: "http://minio.minio.svc.cluster.local:9000"
- name: MINIO_ACCESS_KEY
value: "model-training-user"
- name: MINIO_SECRET_KEY
value: "modelTrainingUser123"
- name: MINIO_REGION
value: "us-east-1"
- name: MINIO_SECURE
value: "false"
- name: MINIO_MAX_RETRY_ATTEMPTS
value: "3"
- name: MINIO_RETRY_MODE
value: "adaptive"
- name: MINIO_CONNECT_TIMEOUT
value: "10"
- name: MINIO_READ_TIMEOUT
value: "60"
- name: MINIO_DEFAULT_BUCKET
value: "model-training"
- name: TIMEOUT_VALIDATE_PARAMS
value: "30"
- name: TIMEOUT_TRAIN_MODEL
value: "2700"
- name: TIMEOUT_DELETE_FILE
value: "120"
- name: TIMEOUT_UPDATE_DATABASE
value: "30"
- name: CLEANUP_RETENTION_HOURS
value: "24"
- name: CLEANUP_DRY_RUN
value: "false"
- name: TIMEOUT_CLEANUP_MINIO
value: "300"
- name: TIMEOUT_CLEANUP_LOCAL
value: "120"
- name: MAX_KEYS_CLEANUP
value: "1000"
- name: DEFAULT_CLEANUP_BUCKET
value: "model-training"
# Cleanup Schedule Configuration
- name: CLEANUP_SCHEDULE_ID
value: "cleanup-files-daily"
- name: CLEANUP_CRON
value: "0 0 * * *" # Midnight UTC
- name: CLEANUP_TIMEZONE
value: "UTC"
- name: CLEANUP_EXECUTION_TIMEOUT_HOURS
value: "1"
# -----------------------------------------------------------------------------
# Runtimes configuration
# -----------------------------------------------------------------------------
# Each runtime inherits settings from `global` (resources, env, probes, autoscaling)
# unless overridden here.
runtimes:
basic:
# Replicas for this runtime. Replaces the old replicaCount.
replicas: 1
xgboost:
replicas: 1
# -----------------------------------------------------------------------------
# Chart-level configuration (applies to all runtimes)
# -----------------------------------------------------------------------------
# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets:
- name: docker-hub-secret
# This is to override the chart name.
nameOverride: *projectName
fullnameOverride: *projectName
# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: *projectName
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# Additional volumes on the output Deployment definition.
volumes:
- name: model-manager-runtime
emptyDir:
sizeLimit: 1Gi
# Additional volumeMounts on the output Deployment definition.
volumeMounts:
- name: model-manager-runtime
mountPath: "/var/lib/model-manager"
# Deployment strategy configuration
# More information: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
deploymentStrategy:
type: Recreate
# rollingUpdate:
# maxSurge: 0
# maxUnavailable: 1
# Number of old ReplicaSets to retain
revisionHistoryLimit: 2
nodeSelector: {}
tolerations: []
affinity: {}
services:
sdk-metrics:
enabled: true
type: ClusterIP
port: 9091
targetPort: 9091
name: sdk-metrics
metrics:
enabled: true
type: ClusterIP
port: 9090
targetPort: 9090
name: metrics
# Configuração do ServiceMonitor para o Prometheus Operator
# ref: https://github.com/prometheus-operator/prometheus-operator
serviceMonitor:
# Se true, um recurso ServiceMonitor será criado.
enabled: true
# O intervalo no qual as métricas devem ser coletadas (ex: 30s, 1m).
endpoints:
- port: metrics
path: /metrics
interval: 30s
relabelings: []
- port: sdk-metrics
path: /metrics
interval: 30s
relabelings: []
additionalLabels:
release: kube-prometheus-stack
ssh:
enabled: true
secretName: git-ssh-key-sientia-model-manager-worker
sshPath: /mnt/.ssh
knownHostsPath: /mnt/known_hosts
# Configuração para dashboards do Grafana
grafanaDashboard:
# Habilita a criação de ConfigMaps para dashboards
enabled: true
# Namespace onde o Grafana está instalado (ajuste conforme seu ambiente)
namespace: monitoring
# Labels para que o sidecar do Grafana encontre os dashboards
labels:
grafana_dashboard: "1"
# Lista de dashboards para importar
dashboards:
- name: sientia-dataops-model-manager
title: "Sientia DataOps Model Manager"
uid: "sientia-dataops-model-manager"
folder: "Sientia"
jsonFile: "dashboards/sientia-dataops-model-manager.json"
overwrite: true # Sobrescreve dashboard se já existir
version: "1.0.0" # Version inicial do dashboard
# Configuração para datasources do Grafana
grafanaDatasource:
# Habilita a criação de ConfigMap para datasources
enabled: false
# Namespace onde o Grafana está instalado
namespace: monitoring
# Labels para que o sidecar do Grafana encontre os datasources
labels:
grafana_datasource: "1"
# Lista de datasources para configurar
datasources: []
# Exemplo de datasource:
# - name: Prometheus
# type: prometheus
# url: http://prometheus-server.monitoring.svc.cluster.local
# isDefault: true
# jsonData:
# timeInterval: "5s"
# -----------------------------------------------------------------------------
# Helm usage examples
# -----------------------------------------------------------------------------
# kubectl create secret docker-registry docker-hub-secret --namespace sientia --docker-server=http://aignosi.azurecr.io --docker-username=aignosi --docker-password=<pwd>
#
# helm upgrade --install sientia-model-manager sientia/sientia-module -n sientia --create-namespace -f ./values.yaml --version 0.6.1
#
# Global/runtimes layout note:
# - Shared configuration lives under `global` (env, probes, autoscaling, namespace).
# - Individual runtimes are defined under `runtimes`, each with its own `name` and `replicas`.
# - Runtimes inherit `global` settings unless overridden at the runtime level.
#
# kubectl create secret generic git-ssh-key-sientia-model-manager-worker \
# --namespace sientia \
# --from-file=ssh-privatekey=git_key \
# --type=kubernetes.io/ssh-auth
# kubectl create secret generic sientia-plugin-store-credentials \
# --namespace sientia \
# --from-literal=username=<username> \
# --from-literal=password=<password>