lablup / lablup/backend.ai

Deprecate and remove BaseAction; partition the action layer by action type (single-entity / bulk / scope)

Open
#11,951 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
670
Forks
183
Avg merge
15h 13m
Merged PRs (30d)
368

Description

Summary

=======

Remove the catch-all BaseAction base class. Make the three action-type bases - BaseSingleEntityAction, BaseBulkAction, BaseScopeAction - fully independent (each carrying its own common contract), and partition the action execution framework (Processor / Runner / Validator / Monitor) per action type rather than having one generic framework keyed on BaseAction.

Background & Motivation

=======================

The action layer currently funnels every action through a single ABC, BaseAction (src/ai/backend/manager/actions/action/base.py), which exposes a lowest-common-denominator contract:

- entity_id() -> str | None (optional, defaults to None)

- entity_type() -> EntityType (abstract classmethod)

- operation_type() -> ActionOperationType (abstract classmethod)

- spec() -> ActionSpec

Type-specific bases already exist but still inherit BaseAction, so the richer per-type contracts are bolted on top of the generic one:

- BaseSingleEntityAction adds target_entity_id(), target_element(), field_data()

- BaseBulkAction[TTarget] adds targets() (individual processing, partial failures allowed)

- BaseScopeAction adds scope_type(), scope_id(), target_element()

Problems with the current shape:

1. Weak typing at the framework boundary. ActionProcessor[TAction: BaseAction, ...], ActionValidator.validate(action: BaseAction, ...), and the ActionMonitor hooks only see BaseAction. RBAC validation differs fundamentally by type (single entity -> target_element(); scope -> scope_type()/scope_id(); bulk -> element_refs()), but the generic framework cannot express this - validators must downcast.

2. BaseAction is mid-migration debris. ~46 entity bases (UserAction(BaseAction), AgentAction(BaseAction), ImageAction(BaseAction), ...) still inherit BaseAction directly, with concrete actions defining operation_type() ad hoc and no type semantics. The already-migrated entities (keypair, group, project, session, user-RBAC, vfolder, model_deployment) demonstrate the target pattern.

Goal

====

- BaseAction is completely removed from the codebase.

- Each action type has its own self-contained base action AND its own framework path (processor/runner/validator/monitor) typed to that base.

- No single shared supertype remains across the three types; search actions are expressed via the bulk or scope base (no dedicated BaseSearchAction).

Proposed Design

===============

1. Self-contained type bases

BaseSingleEntityAction, BaseBulkAction, BaseScopeAction each define the previously-shared contract themselves (entity_id, entity_type, operation_type, spec) and no longer inherit BaseAction. Minor duplication of spec()/entity_id() across the three files is accepted as the cost of independence. Mirror this on the result side (BaseSingleEntityActionResult, BaseBulkActionResult, BaseScopeActionResult).

2. Per-type framework

Replace the single generic framework with type-specific implementations, each bound to its own base:

- SingleEntityActionProcessor / Runner / Validator / Monitor - bound to BaseSingleEntityAction(Result)

- BulkActionProcessor / ... - bound to BaseBulkAction(Result)

- ScopeActionProcessor / ... - bound to BaseScopeAction(Result)

Each path can then use its type's full contract directly (e.g. the single-entity validator reads target_element(); the scope validator reads scope_type()/scope_id(); the bulk monitor iterates element_refs()).

3. Remove BaseAction

After all entities migrate, delete BaseAction, the TAction/TActionResult typevars bound to it, and all imports.

Migration Plan (phased)

=======================

- Phase 1 - Foundation (review target): Make the three type bases self-contained; stand up the per-type framework (processor/runner/validator/monitor). Keep BaseAction temporarily as a deprecated shim so the ~46 unmigrated entities keep compiling.

- Phase 2 - Per-entity migration: Batch-migrate each XAction(BaseAction) entity. For each concrete action: classify as single/bulk/scope, move it onto the corresponding X{SingleEntity,Bulk,Scope}Action base, and implement the required methods (target_entity_id/target_element, targets, scope_\*). Route it through the matching per-type processor.

- Phase 3 - Removal: Once no references remain, delete BaseAction and its typevars.

Scope & Impact

==============

- Framework files: actions/processor/base.py, actions/validator/base.py, actions/monitors/monitor.py, actions/monitors/audit_log.py, actions/monitors/reporter.py, actions/action/{base,single_entity,bulk,scope,__init__}.py.

- Entity bases: ~46 XAction(BaseAction) classes across services/\*/actions/base.py and their concrete actions (several hundred classes total) - Phase 2.

- BaseRBACAction (actions/action/rbac.py) is independent of BaseAction and is unaffected.

Open Questions

==============

1. Monitor duplication: audit_log and reporter monitors are currently generic and registered once. Under a per-type framework, do we duplicate them per type, or keep a thin shared monitor that consumes only the common fields? (Affects how much of the contract truly must be per-type vs. shared.)

2. Service wiring: Each service constructs ActionProcessor(...) per action. Per-type processors change every service's processor wiring - confirm this is in Phase 2 scope.

Out of Scope

============

- Concrete action migration (Phase 2/3) is not part of this foundation work.

- No BaseSearchAction; search remains expressed via bulk/scope.

JIRA Issue: BA-6285

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.