feat(container): ContainerOrchestrator trait, RpcHandler uses it in prod
Step 4 of the rust-orchestrator migration. Unifies the container lifecycle
surface behind a single trait so the RPC layer stops caring whether it is
talking to the dev or prod orchestrator.
* New trait core/archipelago/src/container/traits.rs: ContainerOrchestrator
with install / start / stop / restart / remove / upgrade / status / list /
logs / health, all keyed by app_id. Every method is async_trait-based.
* ProdContainerOrchestrator: the lifecycle methods are moved from inherent
impl into the trait impl (avoids name-shadowing recursion). Adoption and
reconcile remain inherent since only main.rs / BootReconciler call them.
* DevContainerOrchestrator: new trait impl that forwards to the existing
Dev-named methods, applying the dev container-name + port-offset rules
internally. New load_manifest_for() helper resolves app_id to
<data_dir>/apps/<app_id>/manifest.yml so trait-level install(app_id)
works in dev too. install_container(manifest, path) stays inherent for
the manifest-path RPC shape.
* RpcHandler now holds Option<Arc<dyn ContainerOrchestrator>> and, when in
dev mode, a separate Option<Arc<DevContainerOrchestrator>> for the
manifest_path install RPC. In prod mode RpcHandler::new() constructs a
ProdContainerOrchestrator and calls load_manifests() at startup.
* All seven container-* RPC guards no longer say dev mode required.
container-install still requires dev mode because its manifest_path
argument has no prod meaning; every other container RPC now works in both
modes via the trait.
BOOT STILL DOES NOT USE THIS. main.rs wire-up (Step 6) and BootReconciler
(Step 5) come next. Until then the prod orchestrator is constructed but nothing
populates /opt/archipelago/apps so it has zero manifests to manage, matching
the pre-Step-4 behaviour.
Verification: cargo build -p archipelago clean (11 expected unused method
warnings for methods not yet wired from main.rs). cargo test -p archipelago:
all 21 container::* tests pass (16 prod_orchestrator + 5 others). 24 other
test failures are pre-existing and unrelated (identity_manager / session /
wallet / mesh / credentials — all independently flaky on file-backed state).
This commit is contained in:
@@ -39,7 +39,7 @@ mod webhooks;
|
||||
|
||||
use crate::auth::AuthManager;
|
||||
use crate::config::Config;
|
||||
use crate::container::DevContainerOrchestrator;
|
||||
use crate::container::{ContainerOrchestrator, DevContainerOrchestrator, ProdContainerOrchestrator};
|
||||
use crate::monitoring::MetricsStore;
|
||||
use crate::port_allocator::PortAllocator;
|
||||
use crate::rate_limit::{EndpointRateLimiter, LoginRateLimiter};
|
||||
@@ -62,7 +62,14 @@ pub(crate) const DEV_DEFAULT_PASSWORD: &str = "password123";
|
||||
pub struct RpcHandler {
|
||||
config: Config,
|
||||
auth_manager: AuthManager,
|
||||
orchestrator: Option<Arc<DevContainerOrchestrator>>,
|
||||
/// Shared lifecycle orchestrator (Dev or Prod). Always `Some` in a normal
|
||||
/// build — the only reason it is `Option` is so tests that don't exercise
|
||||
/// container RPCs can skip constructing one.
|
||||
orchestrator: Option<Arc<dyn ContainerOrchestrator>>,
|
||||
/// Concrete handle to the dev orchestrator, when we're in dev mode. Used by
|
||||
/// `container-install { manifest_path }` which takes an ad-hoc manifest
|
||||
/// path and is not part of the shared trait.
|
||||
dev_orchestrator: Option<Arc<DevContainerOrchestrator>>,
|
||||
state_manager: Arc<StateManager>,
|
||||
pub(crate) metrics_store: Arc<MetricsStore>,
|
||||
port_allocator: Arc<tokio::sync::Mutex<PortAllocator>>,
|
||||
@@ -89,12 +96,22 @@ impl RpcHandler {
|
||||
session_store: SessionStore,
|
||||
) -> Result<Self> {
|
||||
let auth_manager = AuthManager::new(config.data_dir.clone());
|
||||
let orchestrator = if config.dev_mode {
|
||||
Some(Arc::new(
|
||||
DevContainerOrchestrator::new(config.clone()).await?,
|
||||
))
|
||||
let (orchestrator, dev_orchestrator): (
|
||||
Option<Arc<dyn ContainerOrchestrator>>,
|
||||
Option<Arc<DevContainerOrchestrator>>,
|
||||
) = if config.dev_mode {
|
||||
let dev = Arc::new(DevContainerOrchestrator::new(config.clone()).await?);
|
||||
let trait_obj: Arc<dyn ContainerOrchestrator> = dev.clone();
|
||||
(Some(trait_obj), Some(dev))
|
||||
} else {
|
||||
None
|
||||
let prod = Arc::new(ProdContainerOrchestrator::new(config.clone()).await?);
|
||||
// Best-effort manifest load; a missing /opt/archipelago/apps is
|
||||
// logged by load_manifests and not fatal.
|
||||
if let Err(e) = prod.load_manifests().await {
|
||||
tracing::error!(error = %e, "prod orchestrator: load_manifests failed at startup");
|
||||
}
|
||||
let trait_obj: Arc<dyn ContainerOrchestrator> = prod;
|
||||
(Some(trait_obj), None)
|
||||
};
|
||||
let port_allocator = Arc::new(tokio::sync::Mutex::new(
|
||||
PortAllocator::new(&config.data_dir).await?,
|
||||
@@ -129,6 +146,7 @@ impl RpcHandler {
|
||||
config,
|
||||
auth_manager,
|
||||
orchestrator,
|
||||
dev_orchestrator,
|
||||
state_manager,
|
||||
metrics_store,
|
||||
port_allocator,
|
||||
|
||||
Reference in New Issue
Block a user