feature: pack s9pk (#2642)

* TODO: images

* wip

* pack s9pk images

* include path in packsource error

* debug info

* add cmd as context to invoke

* filehelper bugfix

* fix file helper

* fix exposeForDependents

* misc fixes

* force image removal

* fix filtering

* fix deadlock

* fix api

* chore: Up the version of the package.json

* always allow concurrency within same call stack

* Update core/startos/src/s9pk/merkle_archive/expected.rs

Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com>

---------

Co-authored-by: J H <dragondef@gmail.com>
Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2024-06-12 11:46:59 -06:00
committed by GitHub
parent 5aefb707fa
commit 3f380fa0da
84 changed files with 2552 additions and 2108 deletions

View File

@@ -4,6 +4,7 @@ use models::{ActionId, ProcedureName};
use crate::action::ActionResult;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::config::GetConfig;
use crate::service::dependencies::DependencyConfig;
use crate::service::{Service, ServiceActor};
@@ -23,13 +24,18 @@ impl Handler<Action> for ServiceActor {
}
async fn handle(
&mut self,
Action { id, input }: Action,
id: Guid,
Action {
id: action_id,
input,
}: Action,
_: &BackgroundJobQueue,
) -> Self::Response {
let container = &self.0.persistent_container;
container
.execute::<ActionResult>(
ProcedureName::RunAction(id),
id,
ProcedureName::RunAction(action_id),
input,
Some(Duration::from_secs(30)),
)
@@ -39,7 +45,20 @@ impl Handler<Action> for ServiceActor {
}
impl Service {
pub async fn action(&self, id: ActionId, input: Value) -> Result<ActionResult, Error> {
self.actor.send(Action { id, input }).await?
pub async fn action(
&self,
id: Guid,
action_id: ActionId,
input: Value,
) -> Result<ActionResult, Error> {
self.actor
.send(
id,
Action {
id: action_id,
input,
},
)
.await?
}
}

View File

@@ -5,6 +5,7 @@ use models::ProcedureName;
use crate::config::action::ConfigRes;
use crate::config::ConfigureContext;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::dependencies::DependencyConfig;
use crate::service::{Service, ServiceActor};
use crate::util::actor::background::BackgroundJobQueue;
@@ -19,6 +20,7 @@ impl Handler<Configure> for ServiceActor {
}
async fn handle(
&mut self,
id: Guid,
Configure(ConfigureContext { timeout, config }): Configure,
_: &BackgroundJobQueue,
) -> Self::Response {
@@ -26,7 +28,7 @@ impl Handler<Configure> for ServiceActor {
let package_id = &self.0.id;
container
.execute::<NoOutput>(ProcedureName::SetConfig, to_value(&config)?, timeout)
.execute::<NoOutput>(id, ProcedureName::SetConfig, to_value(&config)?, timeout)
.await
.with_kind(ErrorKind::ConfigRulesViolation)?;
self.0
@@ -52,10 +54,11 @@ impl Handler<GetConfig> for ServiceActor {
fn conflicts_with(_: &GetConfig) -> ConflictBuilder<Self> {
ConflictBuilder::nothing().except::<Configure>()
}
async fn handle(&mut self, _: GetConfig, _: &BackgroundJobQueue) -> Self::Response {
async fn handle(&mut self, id: Guid, _: GetConfig, _: &BackgroundJobQueue) -> Self::Response {
let container = &self.0.persistent_container;
container
.execute::<ConfigRes>(
id,
ProcedureName::GetConfig,
Value::Null,
Some(Duration::from_secs(30)), // TODO timeout
@@ -66,10 +69,10 @@ impl Handler<GetConfig> for ServiceActor {
}
impl Service {
pub async fn configure(&self, ctx: ConfigureContext) -> Result<(), Error> {
self.actor.send(Configure(ctx)).await?
pub async fn configure(&self, id: Guid, ctx: ConfigureContext) -> Result<(), Error> {
self.actor.send(id, Configure(ctx)).await?
}
pub async fn get_config(&self) -> Result<ConfigRes, Error> {
self.actor.send(GetConfig).await?
pub async fn get_config(&self, id: Guid) -> Result<ConfigRes, Error> {
self.actor.send(id, GetConfig).await?
}
}

View File

@@ -1,4 +1,5 @@
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::config::GetConfig;
use crate::service::dependencies::DependencyConfig;
use crate::service::start_stop::StartStop;
@@ -15,7 +16,7 @@ impl Handler<Start> for ServiceActor {
.except::<GetConfig>()
.except::<DependencyConfig>()
}
async fn handle(&mut self, _: Start, _: &BackgroundJobQueue) -> Self::Response {
async fn handle(&mut self, _: Guid, _: Start, _: &BackgroundJobQueue) -> Self::Response {
self.0.persistent_container.state.send_modify(|x| {
x.desired_state = StartStop::Start;
});
@@ -23,8 +24,8 @@ impl Handler<Start> for ServiceActor {
}
}
impl Service {
pub async fn start(&self) -> Result<(), Error> {
self.actor.send(Start).await
pub async fn start(&self, id: Guid) -> Result<(), Error> {
self.actor.send(id, Start).await
}
}
@@ -36,7 +37,7 @@ impl Handler<Stop> for ServiceActor {
.except::<GetConfig>()
.except::<DependencyConfig>()
}
async fn handle(&mut self, _: Stop, _: &BackgroundJobQueue) -> Self::Response {
async fn handle(&mut self, _: Guid, _: Stop, _: &BackgroundJobQueue) -> Self::Response {
let mut transition_state = None;
self.0.persistent_container.state.send_modify(|x| {
x.desired_state = StartStop::Stop;
@@ -51,7 +52,7 @@ impl Handler<Stop> for ServiceActor {
}
}
impl Service {
pub async fn stop(&self) -> Result<(), Error> {
self.actor.send(Stop).await
pub async fn stop(&self, id: Guid) -> Result<(), Error> {
self.actor.send(id, Stop).await
}
}

View File

@@ -4,35 +4,28 @@ use imbl_value::json;
use models::{PackageId, ProcedureName};
use crate::prelude::*;
use crate::service::{Service, ServiceActor};
use crate::rpc_continuations::Guid;
use crate::service::{Service, ServiceActor, ServiceActorSeed};
use crate::util::actor::background::BackgroundJobQueue;
use crate::util::actor::{ConflictBuilder, Handler};
use crate::Config;
pub(super) struct DependencyConfig {
dependency_id: PackageId,
remote_config: Option<Config>,
}
impl Handler<DependencyConfig> for ServiceActor {
type Response = Result<Option<Config>, Error>;
fn conflicts_with(_: &DependencyConfig) -> ConflictBuilder<Self> {
ConflictBuilder::nothing()
}
async fn handle(
&mut self,
DependencyConfig {
dependency_id,
remote_config,
}: DependencyConfig,
_: &BackgroundJobQueue,
) -> Self::Response {
let container = &self.0.persistent_container;
impl ServiceActorSeed {
async fn dependency_config(
&self,
id: Guid,
dependency_id: PackageId,
remote_config: Option<Config>,
) -> Result<Option<Config>, Error> {
let container = &self.persistent_container;
container
.sanboxed::<Option<Config>>(
id.clone(),
ProcedureName::UpdateDependency(dependency_id.clone()),
json!({
"queryResults": container
.execute::<Value>(
id,
ProcedureName::QueryDependency(dependency_id),
Value::Null,
Some(Duration::from_secs(30)),
@@ -49,17 +42,45 @@ impl Handler<DependencyConfig> for ServiceActor {
}
}
pub(super) struct DependencyConfig {
dependency_id: PackageId,
remote_config: Option<Config>,
}
impl Handler<DependencyConfig> for ServiceActor {
type Response = Result<Option<Config>, Error>;
fn conflicts_with(_: &DependencyConfig) -> ConflictBuilder<Self> {
ConflictBuilder::nothing()
}
async fn handle(
&mut self,
id: Guid,
DependencyConfig {
dependency_id,
remote_config,
}: DependencyConfig,
_: &BackgroundJobQueue,
) -> Self::Response {
self.0
.dependency_config(id, dependency_id, remote_config)
.await
}
}
impl Service {
pub async fn dependency_config(
&self,
id: Guid,
dependency_id: PackageId,
remote_config: Option<Config>,
) -> Result<Option<Config>, Error> {
self.actor
.send(DependencyConfig {
dependency_id,
remote_config,
})
.send(
id,
DependencyConfig {
dependency_id,
remote_config,
},
)
.await?
}
}

View File

@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::ops::Deref;
use std::sync::{Arc, Weak};
use std::time::Duration;
use chrono::{DateTime, Utc};
@@ -68,13 +69,87 @@ pub enum LoadDisposition {
Undo,
}
pub struct ServiceRef(Arc<Service>);
impl ServiceRef {
pub fn weak(&self) -> Weak<Service> {
Arc::downgrade(&self.0)
}
pub async fn uninstall(
self,
target_version: Option<models::VersionString>,
) -> Result<(), Error> {
self.seed
.persistent_container
.execute(
Guid::new(),
ProcedureName::Uninit,
to_value(&target_version)?,
None,
) // TODO timeout
.await?;
let id = self.seed.persistent_container.s9pk.as_manifest().id.clone();
let ctx = self.seed.ctx.clone();
self.shutdown().await?;
if target_version.is_none() {
ctx.db
.mutate(|d| d.as_public_mut().as_package_data_mut().remove(&id))
.await?;
}
Ok(())
}
pub async fn shutdown(self) -> Result<(), Error> {
if let Some((hdl, shutdown)) = self.seed.persistent_container.rpc_server.send_replace(None)
{
self.seed
.persistent_container
.rpc_client
.request(rpc::Exit, Empty {})
.await?;
shutdown.shutdown();
hdl.await.with_kind(ErrorKind::Cancelled)?;
}
let service = Arc::try_unwrap(self.0).map_err(|_| {
Error::new(
eyre!("ServiceActor held somewhere after actor shutdown"),
ErrorKind::Unknown,
)
})?;
service
.actor
.shutdown(crate::util::actor::PendingMessageStrategy::FinishAll { timeout: None }) // TODO timeout
.await;
Arc::try_unwrap(service.seed)
.map_err(|_| {
Error::new(
eyre!("ServiceActorSeed held somewhere after actor shutdown"),
ErrorKind::Unknown,
)
})?
.persistent_container
.exit()
.await?;
Ok(())
}
}
impl Deref for ServiceRef {
type Target = Service;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl From<Service> for ServiceRef {
fn from(value: Service) -> Self {
Self(Arc::new(value))
}
}
pub struct Service {
actor: ConcurrentActor<ServiceActor>,
seed: Arc<ServiceActorSeed>,
}
impl Service {
#[instrument(skip_all)]
async fn new(ctx: RpcContext, s9pk: S9pk, start: StartStop) -> Result<Self, Error> {
async fn new(ctx: RpcContext, s9pk: S9pk, start: StartStop) -> Result<ServiceRef, Error> {
let id = s9pk.as_manifest().id.clone();
let persistent_container = PersistentContainer::new(
&ctx, s9pk,
@@ -89,13 +164,17 @@ impl Service {
ctx,
synchronized: Arc::new(Notify::new()),
});
seed.persistent_container
.init(Arc::downgrade(&seed))
.await?;
Ok(Self {
let service: ServiceRef = Self {
actor: ConcurrentActor::new(ServiceActor(seed.clone())),
seed,
})
}
.into();
service
.seed
.persistent_container
.init(service.weak())
.await?;
Ok(service)
}
#[instrument(skip_all)]
@@ -103,7 +182,7 @@ impl Service {
ctx: &RpcContext,
id: &PackageId,
disposition: LoadDisposition,
) -> Result<Option<Self>, Error> {
) -> Result<Option<ServiceRef>, Error> {
let handle_installed = {
let ctx = ctx.clone();
move |s9pk: S9pk, i: Model<PackageDataEntry>| async move {
@@ -137,7 +216,7 @@ impl Service {
match entry.as_state_info().as_match() {
PackageStateMatchModelRef::Installing(_) => {
if disposition == LoadDisposition::Retry {
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id), true).await.map_err(|e| {
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id)).await.map_err(|e| {
tracing::error!("Error opening s9pk for install: {e}");
tracing::debug!("{e:?}")
}) {
@@ -170,7 +249,7 @@ impl Service {
&& progress == &Progress::Complete(true)
})
{
if let Ok(s9pk) = S9pk::open(&s9pk_path, Some(id), true).await.map_err(|e| {
if let Ok(s9pk) = S9pk::open(&s9pk_path, Some(id)).await.map_err(|e| {
tracing::error!("Error opening s9pk for update: {e}");
tracing::debug!("{e:?}")
}) {
@@ -189,7 +268,7 @@ impl Service {
}
}
}
let s9pk = S9pk::open(s9pk_path, Some(id), true).await?;
let s9pk = S9pk::open(s9pk_path, Some(id)).await?;
ctx.db
.mutate({
|db| {
@@ -214,7 +293,7 @@ impl Service {
handle_installed(s9pk, entry).await
}
PackageStateMatchModelRef::Removing(_) | PackageStateMatchModelRef::Restoring(_) => {
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id), true).await.map_err(|e| {
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id)).await.map_err(|e| {
tracing::error!("Error opening s9pk for removal: {e}");
tracing::debug!("{e:?}")
}) {
@@ -225,7 +304,7 @@ impl Service {
tracing::debug!("{e:?}")
})
{
match service.uninstall(None).await {
match ServiceRef::from(service).uninstall(None).await {
Err(e) => {
tracing::error!("Error uninstalling service: {e}");
tracing::debug!("{e:?}")
@@ -242,7 +321,7 @@ impl Service {
Ok(None)
}
PackageStateMatchModelRef::Installed(_) => {
handle_installed(S9pk::open(s9pk_path, Some(id), true).await?, entry).await
handle_installed(S9pk::open(s9pk_path, Some(id)).await?, entry).await
}
PackageStateMatchModelRef::Error(e) => Err(Error::new(
eyre!("Failed to parse PackageDataEntry, found {e:?}"),
@@ -257,7 +336,7 @@ impl Service {
s9pk: S9pk,
src_version: Option<models::VersionString>,
progress: Option<InstallProgressHandles>,
) -> Result<Self, Error> {
) -> Result<ServiceRef, Error> {
let manifest = s9pk.as_manifest().clone();
let developer_key = s9pk.as_archive().signer();
let icon = s9pk.icon_data_url().await?;
@@ -265,7 +344,12 @@ impl Service {
service
.seed
.persistent_container
.execute(ProcedureName::Init, to_value(&src_version)?, None) // TODO timeout
.execute(
Guid::new(),
ProcedureName::Init,
to_value(&src_version)?,
None,
) // TODO timeout
.await
.with_kind(ErrorKind::MigrationFailed)?; // TODO: handle cancellation
if let Some(mut progress) = progress {
@@ -301,61 +385,21 @@ impl Service {
s9pk: S9pk,
backup_source: impl GenericMountGuard,
progress: Option<InstallProgressHandles>,
) -> Result<Self, Error> {
) -> Result<ServiceRef, Error> {
let service = Service::install(ctx.clone(), s9pk, None, progress).await?;
service
.actor
.send(transition::restore::Restore {
path: backup_source.path().to_path_buf(),
})
.send(
Guid::new(),
transition::restore::Restore {
path: backup_source.path().to_path_buf(),
},
)
.await??;
Ok(service)
}
pub async fn shutdown(self) -> Result<(), Error> {
self.actor
.shutdown(crate::util::actor::PendingMessageStrategy::FinishAll { timeout: None }) // TODO timeout
.await;
if let Some((hdl, shutdown)) = self.seed.persistent_container.rpc_server.send_replace(None)
{
self.seed
.persistent_container
.rpc_client
.request(rpc::Exit, Empty {})
.await?;
shutdown.shutdown();
hdl.await.with_kind(ErrorKind::Cancelled)?;
}
Arc::try_unwrap(self.seed)
.map_err(|_| {
Error::new(
eyre!("ServiceActorSeed held somewhere after actor shutdown"),
ErrorKind::Unknown,
)
})?
.persistent_container
.exit()
.await?;
Ok(())
}
pub async fn uninstall(self, target_version: Option<models::VersionString>) -> Result<(), Error> {
self.seed
.persistent_container
.execute(ProcedureName::Uninit, to_value(&target_version)?, None) // TODO timeout
.await?;
let id = self.seed.persistent_container.s9pk.as_manifest().id.clone();
let ctx = self.seed.ctx.clone();
self.shutdown().await?;
if target_version.is_none() {
ctx.db
.mutate(|d| d.as_public_mut().as_package_data_mut().remove(&id))
.await?;
}
Ok(())
}
#[instrument(skip_all)]
pub async fn backup(&self, guard: impl GenericMountGuard) -> Result<(), Error> {
let id = &self.seed.id;
@@ -368,9 +412,12 @@ impl Service {
.await?;
drop(file);
self.actor
.send(transition::backup::Backup {
path: guard.path().to_path_buf(),
})
.send(
Guid::new(),
transition::backup::Backup {
path: guard.path().to_path_buf(),
},
)
.await??;
Ok(())
}

View File

@@ -6,8 +6,7 @@ use std::time::Duration;
use futures::future::ready;
use futures::{Future, FutureExt};
use helpers::NonDetachingJoinHandle;
use imbl_value::InternedString;
use models::{ProcedureName, VolumeId};
use models::{ImageId, ProcedureName, VolumeId};
use rpc_toolkit::{Empty, Server, ShutdownHandle};
use serde::de::DeserializeOwned;
use tokio::fs::File;
@@ -24,14 +23,15 @@ use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::disk::mount::filesystem::{MountType, ReadOnly};
use crate::disk::mount::guard::MountGuard;
use crate::disk::mount::guard::{GenericMountGuard, MountGuard};
use crate::lxc::{LxcConfig, LxcContainer, HOST_RPC_SERVER_SOCKET};
use crate::net::net_controller::NetService;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::s9pk::merkle_archive::source::FileSource;
use crate::s9pk::S9pk;
use crate::service::start_stop::StartStop;
use crate::service::{rpc, RunningStatus};
use crate::service::{rpc, RunningStatus, Service};
use crate::util::rpc_client::UnixRpcClient;
use crate::util::Invoke;
use crate::volume::{asset_dir, data_dir};
@@ -89,7 +89,8 @@ pub struct PersistentContainer {
js_mount: MountGuard,
volumes: BTreeMap<VolumeId, MountGuard>,
assets: BTreeMap<VolumeId, MountGuard>,
pub(super) overlays: Arc<Mutex<BTreeMap<InternedString, OverlayGuard>>>,
pub(super) images: BTreeMap<ImageId, Arc<MountGuard>>,
pub(super) overlays: Arc<Mutex<BTreeMap<Guid, OverlayGuard<Arc<MountGuard>>>>>,
pub(super) state: Arc<watch::Sender<ServiceState>>,
pub(super) net_service: Mutex<NetService>,
destroyed: bool,
@@ -178,14 +179,62 @@ impl PersistentContainer {
.await?,
);
}
let mut images = BTreeMap::new();
let image_path = lxc_container.rootfs_dir().join("media/startos/images");
tokio::fs::create_dir_all(&image_path).await?;
for image in &s9pk.as_manifest().images {
for (image, config) in &s9pk.as_manifest().images {
let mut arch = ARCH;
let mut sqfs_path = Path::new("images")
.join(arch)
.join(image)
.with_extension("squashfs");
if !s9pk
.as_archive()
.contents()
.get_path(&sqfs_path)
.and_then(|e| e.as_file())
.is_some()
{
arch = if let Some(arch) = config.emulate_missing_as.as_deref() {
arch
} else {
continue;
};
sqfs_path = Path::new("images")
.join(arch)
.join(image)
.with_extension("squashfs");
}
let sqfs = s9pk
.as_archive()
.contents()
.get_path(&sqfs_path)
.and_then(|e| e.as_file())
.or_not_found(sqfs_path.display())?;
let mountpoint = image_path.join(image);
tokio::fs::create_dir_all(&mountpoint).await?;
Command::new("chown")
.arg("100000:100000")
.arg(&mountpoint)
.invoke(ErrorKind::Filesystem)
.await?;
images.insert(
image.clone(),
Arc::new(
MountGuard::mount(
&IdMapped::new(LoopDev::from(&**sqfs), 0, 100000, 65536),
&mountpoint,
ReadOnly,
)
.await?,
),
);
let env_filename = Path::new(image.as_ref()).with_extension("env");
if let Some(env) = s9pk
.as_archive()
.contents()
.get_path(Path::new("images").join(*ARCH).join(&env_filename))
.get_path(Path::new("images").join(arch).join(&env_filename))
.and_then(|e| e.as_file())
{
env.copy(&mut File::create(image_path.join(&env_filename)).await?)
@@ -195,7 +244,7 @@ impl PersistentContainer {
if let Some(json) = s9pk
.as_archive()
.contents()
.get_path(Path::new("images").join(*ARCH).join(&json_filename))
.get_path(Path::new("images").join(arch).join(&json_filename))
.and_then(|e| e.as_file())
{
json.copy(&mut File::create(image_path.join(&json_filename)).await?)
@@ -215,6 +264,7 @@ impl PersistentContainer {
js_mount,
volumes,
assets,
images,
overlays: Arc::new(Mutex::new(BTreeMap::new())),
state: Arc::new(watch::channel(ServiceState::new(start)).0),
net_service: Mutex::new(net_service),
@@ -257,7 +307,7 @@ impl PersistentContainer {
}
#[instrument(skip_all)]
pub async fn init(&self, seed: Weak<ServiceActorSeed>) -> Result<(), Error> {
pub async fn init(&self, seed: Weak<Service>) -> Result<(), Error> {
let socket_server_context = EffectContext::new(seed);
let server = Server::new(
move || ready(Ok(socket_server_context.clone())),
@@ -330,6 +380,7 @@ impl PersistentContainer {
let js_mount = self.js_mount.take();
let volumes = std::mem::take(&mut self.volumes);
let assets = std::mem::take(&mut self.assets);
let images = std::mem::take(&mut self.images);
let overlays = self.overlays.clone();
let lxc_container = self.lxc_container.take();
self.destroyed = true;
@@ -352,6 +403,9 @@ impl PersistentContainer {
for (_, overlay) in std::mem::take(&mut *overlays.lock().await) {
errs.handle(overlay.unmount(true).await);
}
for (_, images) in images {
errs.handle(images.unmount().await);
}
errs.handle(js_mount.unmount(true).await);
if let Some(lxc_container) = lxc_container {
errs.handle(lxc_container.exit().await);
@@ -378,6 +432,7 @@ impl PersistentContainer {
#[instrument(skip_all)]
pub async fn start(&self) -> Result<(), Error> {
self.execute(
Guid::new(),
ProcedureName::StartMain,
Value::Null,
Some(Duration::from_secs(5)), // TODO
@@ -389,7 +444,7 @@ impl PersistentContainer {
#[instrument(skip_all)]
pub async fn stop(&self) -> Result<Duration, Error> {
let timeout: Option<crate::util::serde::Duration> = self
.execute(ProcedureName::StopMain, Value::Null, None)
.execute(Guid::new(), ProcedureName::StopMain, Value::Null, None)
.await?;
Ok(timeout.map(|a| *a).unwrap_or(Duration::from_secs(30)))
}
@@ -397,6 +452,7 @@ impl PersistentContainer {
#[instrument(skip_all)]
pub async fn execute<O>(
&self,
id: Guid,
name: ProcedureName,
input: Value,
timeout: Option<Duration>,
@@ -404,7 +460,7 @@ impl PersistentContainer {
where
O: DeserializeOwned,
{
self._execute(name, input, timeout)
self._execute(id, name, input, timeout)
.await
.and_then(from_value)
}
@@ -412,6 +468,7 @@ impl PersistentContainer {
#[instrument(skip_all)]
pub async fn sanboxed<O>(
&self,
id: Guid,
name: ProcedureName,
input: Value,
timeout: Option<Duration>,
@@ -419,7 +476,7 @@ impl PersistentContainer {
where
O: DeserializeOwned,
{
self._sandboxed(name, input, timeout)
self._sandboxed(id, name, input, timeout)
.await
.and_then(from_value)
}
@@ -427,13 +484,15 @@ impl PersistentContainer {
#[instrument(skip_all)]
async fn _execute(
&self,
id: Guid,
name: ProcedureName,
input: Value,
timeout: Option<Duration>,
) -> Result<Value, Error> {
let fut = self
.rpc_client
.request(rpc::Execute, rpc::ExecuteParams::new(name, input, timeout));
let fut = self.rpc_client.request(
rpc::Execute,
rpc::ExecuteParams::new(id, name, input, timeout),
);
Ok(if let Some(timeout) = timeout {
tokio::time::timeout(timeout, fut)
@@ -447,13 +506,15 @@ impl PersistentContainer {
#[instrument(skip_all)]
async fn _sandboxed(
&self,
id: Guid,
name: ProcedureName,
input: Value,
timeout: Option<Duration>,
) -> Result<Value, Error> {
let fut = self
.rpc_client
.request(rpc::Sandbox, rpc::ExecuteParams::new(name, input, timeout));
let fut = self.rpc_client.request(
rpc::Sandbox,
rpc::ExecuteParams::new(id, name, input, timeout),
);
Ok(if let Some(timeout) = timeout {
tokio::time::timeout(timeout, fut)

View File

@@ -3,6 +3,7 @@ use std::time::Duration;
use models::ProcedureName;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::Service;
impl Service {
@@ -11,6 +12,7 @@ impl Service {
let container = &self.seed.persistent_container;
container
.execute::<Value>(
Guid::new(),
ProcedureName::Properties,
Value::Null,
Some(Duration::from_secs(30)),

View File

@@ -7,6 +7,7 @@ use rpc_toolkit::Empty;
use ts_rs::TS;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
#[derive(Clone)]
pub struct Init;
@@ -46,14 +47,21 @@ impl serde::Serialize for Exit {
#[derive(Clone, serde::Deserialize, serde::Serialize, TS)]
pub struct ExecuteParams {
id: Guid,
procedure: String,
#[ts(type = "any")]
input: Value,
timeout: Option<u128>,
}
impl ExecuteParams {
pub fn new(procedure: ProcedureName, input: Value, timeout: Option<Duration>) -> Self {
pub fn new(
id: Guid,
procedure: ProcedureName,
input: Value,
timeout: Option<Duration>,
) -> Self {
Self {
id,
procedure: procedure.js_function_name(),
input,
timeout: timeout.map(|d| d.as_millis()),

View File

@@ -7,9 +7,9 @@ use std::str::FromStr;
use std::sync::{Arc, Weak};
use clap::builder::ValueParserFactory;
use clap::Parser;
use clap::{CommandFactory, FromArgMatches, Parser};
use emver::VersionRange;
use imbl_value::{json, InternedString};
use imbl_value::json;
use itertools::Itertools;
use models::{
ActionId, DataUrl, HealthCheckId, HostId, ImageId, PackageId, ServiceInterfaceId, VolumeId,
@@ -25,35 +25,34 @@ use crate::db::model::package::{
ActionMetadata, CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind,
ManifestPreference,
};
use crate::disk::mount::filesystem::idmapped::IdMapped;
use crate::disk::mount::filesystem::loop_dev::LoopDev;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::echo;
use crate::net::host::address::HostAddress;
use crate::net::host::binding::{BindOptions, LanInfo};
use crate::net::host::{Host, HostKind};
use crate::net::service_interface::{AddressInfo, ServiceInterface, ServiceInterfaceType};
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::s9pk::merkle_archive::source::http::HttpSource;
use crate::s9pk::rpc::SKIP_ENV;
use crate::s9pk::S9pk;
use crate::service::cli::ContainerCliContext;
use crate::service::ServiceActorSeed;
use crate::service::Service;
use crate::status::health_check::HealthCheckResult;
use crate::status::MainStatus;
use crate::util::clap::FromStrParser;
use crate::util::{new_guid, Invoke};
use crate::{echo, ARCH};
use crate::util::Invoke;
#[derive(Clone)]
pub(super) struct EffectContext(Weak<ServiceActorSeed>);
pub(super) struct EffectContext(Weak<Service>);
impl EffectContext {
pub fn new(seed: Weak<ServiceActorSeed>) -> Self {
Self(seed)
pub fn new(service: Weak<Service>) -> Self {
Self(service)
}
}
impl Context for EffectContext {}
impl EffectContext {
fn deref(&self) -> Result<Arc<ServiceActorSeed>, Error> {
fn deref(&self) -> Result<Arc<Service>, Error> {
if let Some(seed) = Weak::upgrade(&self.0) {
Ok(seed)
} else {
@@ -66,11 +65,55 @@ impl EffectContext {
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct RpcData {
id: i64,
method: String,
params: Value,
#[serde(rename_all = "camelCase")]
pub struct WithProcedureId<T> {
#[serde(default)]
procedure_id: Guid,
#[serde(flatten)]
rest: T,
}
impl<T: FromArgMatches> FromArgMatches for WithProcedureId<T> {
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
let rest = T::from_arg_matches(matches)?;
Ok(Self {
procedure_id: matches.get_one("procedure-id").cloned().unwrap_or_default(),
rest,
})
}
fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result<Self, clap::Error> {
let rest = T::from_arg_matches_mut(matches)?;
Ok(Self {
procedure_id: matches.get_one("procedure-id").cloned().unwrap_or_default(),
rest,
})
}
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
self.rest.update_from_arg_matches(matches)?;
self.procedure_id = matches.get_one("procedure-id").cloned().unwrap_or_default();
Ok(())
}
fn update_from_arg_matches_mut(
&mut self,
matches: &mut clap::ArgMatches,
) -> Result<(), clap::Error> {
self.rest.update_from_arg_matches_mut(matches)?;
self.procedure_id = matches.get_one("procedure-id").cloned().unwrap_or_default();
Ok(())
}
}
impl<T: CommandFactory> CommandFactory for WithProcedureId<T> {
fn command() -> clap::Command {
T::command_for_update().arg(
clap::Arg::new("procedure-id")
.action(clap::ArgAction::Set)
.value_parser(clap::value_parser!(Guid)),
)
}
fn command_for_update() -> clap::Command {
Self::command()
}
}
pub fn service_effect_handler<C: Context>() -> ParentHandler<C> {
ParentHandler::new()
.subcommand("gitInfo", from_fn(|_: C| crate::version::git_info()))
@@ -290,6 +333,7 @@ struct MountParams {
async fn set_system_smtp(context: EffectContext, data: SetSystemSmtpParams) -> Result<(), Error> {
let context = context.deref()?;
context
.seed
.ctx
.db
.mutate(|db| {
@@ -304,6 +348,7 @@ async fn get_system_smtp(
) -> Result<String, Error> {
let context = context.deref()?;
let res = context
.seed
.ctx
.db
.peek()
@@ -323,7 +368,7 @@ async fn get_system_smtp(
}
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr, Error> {
let context = context.deref()?;
let net_service = context.persistent_container.net_service.lock().await;
let net_service = context.seed.persistent_container.net_service.lock().await;
Ok(net_service.get_ip())
}
async fn get_service_port_forward(
@@ -333,14 +378,15 @@ async fn get_service_port_forward(
let internal_port = data.internal_port as u16;
let context = context.deref()?;
let net_service = context.persistent_container.net_service.lock().await;
let net_service = context.seed.persistent_container.net_service.lock().await;
net_service.get_ext_port(data.host_id, internal_port)
}
async fn clear_network_interfaces(context: EffectContext, _: Empty) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let package_id = context.seed.id.clone();
context
.seed
.ctx
.db
.mutate(|db| {
@@ -369,7 +415,7 @@ async fn export_service_interface(
}: ExportServiceInterfaceParams,
) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let package_id = context.seed.id.clone();
let service_interface = ServiceInterface {
id: id.clone(),
@@ -384,6 +430,7 @@ async fn export_service_interface(
let svc_interface_with_host_info = service_interface;
context
.seed
.ctx
.db
.mutate(|db| {
@@ -407,7 +454,7 @@ async fn get_primary_url(
}: GetPrimaryUrlParams,
) -> Result<Option<HostAddress>, Error> {
let context = context.deref()?;
let package_id = package_id.unwrap_or_else(|| context.id.clone());
let package_id = package_id.unwrap_or_else(|| context.seed.id.clone());
Ok(None) // TODO
}
@@ -419,9 +466,10 @@ async fn list_service_interfaces(
}: ListServiceInterfacesParams,
) -> Result<BTreeMap<ServiceInterfaceId, ServiceInterface>, Error> {
let context = context.deref()?;
let package_id = package_id.unwrap_or_else(|| context.id.clone());
let package_id = package_id.unwrap_or_else(|| context.seed.id.clone());
context
.seed
.ctx
.db
.peek()
@@ -435,9 +483,10 @@ async fn list_service_interfaces(
}
async fn remove_address(context: EffectContext, data: RemoveAddressParams) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let package_id = context.seed.id.clone();
context
.seed
.ctx
.db
.mutate(|db| {
@@ -454,8 +503,9 @@ async fn remove_address(context: EffectContext, data: RemoveAddressParams) -> Re
}
async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let package_id = context.seed.id.clone();
context
.seed
.ctx
.db
.mutate(|db| {
@@ -477,8 +527,9 @@ async fn export_action(context: EffectContext, data: ExportActionParams) -> Resu
}
async fn remove_action(context: EffectContext, data: RemoveActionParams) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let package_id = context.seed.id.clone();
context
.seed
.ctx
.db
.mutate(|db| {
@@ -514,16 +565,16 @@ struct GetHostInfoParams {
callback: Callback,
}
async fn get_host_info(
ctx: EffectContext,
context: EffectContext,
GetHostInfoParams {
callback,
package_id,
host_id,
}: GetHostInfoParams,
) -> Result<Host, Error> {
let ctx = ctx.deref()?;
let db = ctx.ctx.db.peek().await;
let package_id = package_id.unwrap_or_else(|| ctx.id.clone());
let context = context.deref()?;
let db = context.seed.ctx.db.peek().await;
let package_id = package_id.unwrap_or_else(|| context.seed.id.clone());
db.as_public()
.as_package_data()
@@ -536,8 +587,8 @@ async fn get_host_info(
}
async fn clear_bindings(context: EffectContext, _: Empty) -> Result<(), Error> {
let ctx = context.deref()?;
let mut svc = ctx.persistent_container.net_service.lock().await;
let context = context.deref()?;
let mut svc = context.seed.persistent_container.net_service.lock().await;
svc.clear_bindings().await?;
Ok(())
}
@@ -559,8 +610,8 @@ async fn bind(context: EffectContext, bind_params: Value) -> Result<(), Error> {
internal_port,
options,
} = from_value(bind_params)?;
let ctx = context.deref()?;
let mut svc = ctx.persistent_container.net_service.lock().await;
let context = context.deref()?;
let mut svc = context.seed.persistent_container.net_service.lock().await;
svc.bind(kind, id, internal_port, options).await
}
@@ -575,16 +626,16 @@ struct GetServiceInterfaceParams {
}
async fn get_service_interface(
ctx: EffectContext,
context: EffectContext,
GetServiceInterfaceParams {
callback,
package_id,
service_interface_id,
}: GetServiceInterfaceParams,
) -> Result<ServiceInterface, Error> {
let ctx = ctx.deref()?;
let package_id = package_id.unwrap_or_else(|| ctx.id.clone());
let db = ctx.ctx.db.peek().await;
let context = context.deref()?;
let package_id = package_id.unwrap_or_else(|| context.seed.id.clone());
let db = context.seed.ctx.db.peek().await;
let interface = db
.as_public()
@@ -729,8 +780,8 @@ async fn get_store(
GetStoreParams { package_id, path }: GetStoreParams,
) -> Result<Value, Error> {
let context = context.deref()?;
let peeked = context.ctx.db.peek().await;
let package_id = package_id.unwrap_or(context.id.clone());
let peeked = context.seed.ctx.db.peek().await;
let package_id = package_id.unwrap_or(context.seed.id.clone());
let value = peeked
.as_private()
.as_package_stores()
@@ -758,8 +809,9 @@ async fn set_store(
SetStoreParams { value, path }: SetStoreParams,
) -> Result<(), Error> {
let context = context.deref()?;
let package_id = context.id.clone();
let package_id = context.seed.id.clone();
context
.seed
.ctx
.db
.mutate(|db| {
@@ -812,7 +864,7 @@ struct ParamsMaybePackageId {
async fn exists(context: EffectContext, params: ParamsPackageId) -> Result<Value, Error> {
let context = context.deref()?;
let peeked = context.ctx.db.peek().await;
let peeked = context.seed.ctx.db.peek().await;
let package = peeked
.as_public()
.as_package_data()
@@ -834,31 +886,30 @@ struct ExecuteAction {
}
async fn execute_action(
context: EffectContext,
ExecuteAction {
action_id,
input,
service_id,
}: ExecuteAction,
WithProcedureId {
procedure_id,
rest:
ExecuteAction {
service_id,
action_id,
input,
},
}: WithProcedureId<ExecuteAction>,
) -> Result<Value, Error> {
let context = context.deref()?;
let package_id = service_id.clone().unwrap_or_else(|| context.id.clone());
let service = context.ctx.services.get(&package_id).await;
let service = service.as_ref().ok_or_else(|| {
Error::new(
eyre!("Could not find package {package_id}"),
ErrorKind::Unknown,
)
})?;
let package_id = service_id
.clone()
.unwrap_or_else(|| context.seed.id.clone());
Ok(json!(service.action(action_id, input).await?))
Ok(json!(context.action(procedure_id, action_id, input).await?))
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct FromService {}
async fn get_configured(context: EffectContext, _: Empty) -> Result<Value, Error> {
let context = context.deref()?;
let peeked = context.ctx.db.peek().await;
let package_id = &context.id;
let peeked = context.seed.ctx.db.peek().await;
let package_id = &context.seed.id;
let package = peeked
.as_public()
.as_package_data()
@@ -872,8 +923,8 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result<Value, Error
async fn stopped(context: EffectContext, params: ParamsMaybePackageId) -> Result<Value, Error> {
let context = context.deref()?;
let peeked = context.ctx.db.peek().await;
let package_id = params.package_id.unwrap_or_else(|| context.id.clone());
let peeked = context.seed.ctx.db.peek().await;
let package_id = params.package_id.unwrap_or_else(|| context.seed.id.clone());
let package = peeked
.as_public()
.as_package_data()
@@ -887,7 +938,7 @@ async fn stopped(context: EffectContext, params: ParamsMaybePackageId) -> Result
async fn running(context: EffectContext, params: ParamsPackageId) -> Result<Value, Error> {
dbg!("Starting the running {params:?}");
let context = context.deref()?;
let peeked = context.ctx.db.peek().await;
let peeked = context.seed.ctx.db.peek().await;
let package_id = params.package_id;
let package = peeked
.as_public()
@@ -900,30 +951,24 @@ async fn running(context: EffectContext, params: ParamsPackageId) -> Result<Valu
Ok(json!(matches!(package, MainStatus::Running { .. })))
}
async fn restart(context: EffectContext, _: Empty) -> Result<Value, Error> {
async fn restart(
context: EffectContext,
WithProcedureId { procedure_id, .. }: WithProcedureId<Empty>,
) -> Result<(), Error> {
let context = context.deref()?;
let service = context.ctx.services.get(&context.id).await;
let service = service.as_ref().ok_or_else(|| {
Error::new(
eyre!("Could not find package {}", context.id),
ErrorKind::Unknown,
)
})?;
service.restart().await?;
Ok(json!(()))
dbg!("here");
context.restart(procedure_id).await?;
dbg!("here");
Ok(())
}
async fn shutdown(context: EffectContext, _: Empty) -> Result<Value, Error> {
async fn shutdown(
context: EffectContext,
WithProcedureId { procedure_id, .. }: WithProcedureId<Empty>,
) -> Result<(), Error> {
let context = context.deref()?;
let service = context.ctx.services.get(&context.id).await;
let service = service.as_ref().ok_or_else(|| {
Error::new(
eyre!("Could not find package {}", context.id),
ErrorKind::Unknown,
)
})?;
service.stop().await?;
Ok(json!(()))
context.stop(procedure_id).await?;
Ok(())
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
@@ -935,8 +980,9 @@ struct SetConfigured {
}
async fn set_configured(context: EffectContext, params: SetConfigured) -> Result<Value, Error> {
let context = context.deref()?;
let package_id = &context.id;
let package_id = &context.seed.id;
context
.seed
.ctx
.db
.mutate(|db| {
@@ -989,9 +1035,9 @@ async fn set_main_status(context: EffectContext, params: SetMainStatus) -> Resul
dbg!(format!("Status for main will be is {params:?}"));
let context = context.deref()?;
match params.status {
SetMainStatusStatus::Running => context.started(),
SetMainStatusStatus::Stopped => context.stopped(),
SetMainStatusStatus::Starting => context.stopped(),
SetMainStatusStatus::Running => context.seed.started(),
SetMainStatusStatus::Stopped => context.seed.stopped(),
SetMainStatusStatus::Starting => context.seed.stopped(),
}
Ok(Value::Null)
}
@@ -1011,8 +1057,9 @@ async fn set_health(
) -> Result<Value, Error> {
let context = context.deref()?;
let package_id = &context.id;
let package_id = &context.seed.id;
context
.seed
.ctx
.db
.mutate(move |db| {
@@ -1041,17 +1088,17 @@ async fn set_health(
#[command(rename_all = "camelCase")]
#[ts(export)]
pub struct DestroyOverlayedImageParams {
#[ts(type = "string")]
guid: InternedString,
guid: Guid,
}
#[instrument(skip_all)]
pub async fn destroy_overlayed_image(
ctx: EffectContext,
context: EffectContext,
DestroyOverlayedImageParams { guid }: DestroyOverlayedImageParams,
) -> Result<(), Error> {
let ctx = ctx.deref()?;
if ctx
let context = context.deref()?;
if context
.seed
.persistent_container
.overlays
.lock()
@@ -1068,30 +1115,25 @@ pub async fn destroy_overlayed_image(
#[command(rename_all = "camelCase")]
#[ts(export)]
pub struct CreateOverlayedImageParams {
#[ts(type = "string")]
image_id: ImageId,
}
#[instrument(skip_all)]
pub async fn create_overlayed_image(
ctx: EffectContext,
context: EffectContext,
CreateOverlayedImageParams { image_id }: CreateOverlayedImageParams,
) -> Result<(PathBuf, InternedString), Error> {
let ctx = ctx.deref()?;
let path = Path::new("images")
.join(*ARCH)
.join(&image_id)
.with_extension("squashfs");
if let Some(image) = ctx
) -> Result<(PathBuf, Guid), Error> {
let context = context.deref()?;
if let Some(image) = context
.seed
.persistent_container
.s9pk
.as_archive()
.contents()
.get_path(&path)
.and_then(|e| e.as_file())
.images
.get(&image_id)
.cloned()
{
let guid = new_guid();
let rootfs_dir = ctx
let guid = Guid::new();
let rootfs_dir = context
.seed
.persistent_container
.lxc_container
.get()
@@ -1102,7 +1144,9 @@ pub async fn create_overlayed_image(
)
})?
.rootfs_dir();
let mountpoint = rootfs_dir.join("media/startos/overlays").join(&*guid);
let mountpoint = rootfs_dir
.join("media/startos/overlays")
.join(guid.as_ref());
tokio::fs::create_dir_all(&mountpoint).await?;
let container_mountpoint = Path::new("/").join(
mountpoint
@@ -1110,18 +1154,16 @@ pub async fn create_overlayed_image(
.with_kind(ErrorKind::Incoherent)?,
);
tracing::info!("Mounting overlay {guid} for {image_id}");
let guard = OverlayGuard::mount(
&IdMapped::new(LoopDev::from(&**image), 0, 100000, 65536),
&mountpoint,
)
.await?;
let guard = OverlayGuard::mount(image, &mountpoint).await?;
Command::new("chown")
.arg("100000:100000")
.arg(&mountpoint)
.invoke(ErrorKind::Filesystem)
.await?;
tracing::info!("Mounted overlay {guid} for {image_id}");
ctx.persistent_container
context
.seed
.persistent_container
.overlays
.lock()
.await
@@ -1228,13 +1270,15 @@ struct SetDependenciesParams {
}
async fn set_dependencies(
ctx: EffectContext,
SetDependenciesParams { dependencies }: SetDependenciesParams,
context: EffectContext,
WithProcedureId {
procedure_id,
rest: SetDependenciesParams { dependencies },
}: WithProcedureId<SetDependenciesParams>,
) -> Result<(), Error> {
let ctx = ctx.deref()?;
let id = &ctx.id;
let service_guard = ctx.ctx.services.get(id).await;
let service = service_guard.as_ref().or_not_found(id)?;
let context = context.deref()?;
let id = &context.seed.id;
let mut deps = BTreeMap::new();
for dependency in dependencies {
let (dep_id, kind, registry_url, version_spec) = match dependency {
@@ -1264,14 +1308,13 @@ async fn set_dependencies(
let remote_s9pk = S9pk::deserialize(
&Arc::new(
HttpSource::new(
ctx.ctx.client.clone(),
context.seed.ctx.client.clone(),
registry_url
.join(&format!("package/v2/{}.s9pk?spec={}", dep_id, version_spec))?,
)
.await?,
),
None, // TODO
true,
)
.await?;
@@ -1291,14 +1334,19 @@ async fn set_dependencies(
)
}
};
let config_satisfied = if let Some(dep_service) = &*ctx.ctx.services.get(&dep_id).await {
service
.dependency_config(dep_id.clone(), dep_service.get_config().await?.config)
.await?
.is_none()
} else {
true
};
let config_satisfied =
if let Some(dep_service) = &*context.seed.ctx.services.get(&dep_id).await {
context
.dependency_config(
procedure_id.clone(),
dep_id.clone(),
dep_service.get_config(procedure_id.clone()).await?.config,
)
.await?
.is_none()
} else {
true
};
deps.insert(
dep_id,
CurrentDependencyInfo {
@@ -1311,7 +1359,9 @@ async fn set_dependencies(
},
);
}
ctx.ctx
context
.seed
.ctx
.db
.mutate(|db| {
db.as_public_mut()
@@ -1324,10 +1374,10 @@ async fn set_dependencies(
.await
}
async fn get_dependencies(ctx: EffectContext) -> Result<Vec<DependencyRequirement>, Error> {
let ctx = ctx.deref()?;
let id = &ctx.id;
let db = ctx.ctx.db.peek().await;
async fn get_dependencies(context: EffectContext) -> Result<Vec<DependencyRequirement>, Error> {
let context = context.deref()?;
let id = &context.seed.id;
let db = context.seed.ctx.db.peek().await;
let data = db
.as_public()
.as_package_data()
@@ -1384,16 +1434,16 @@ struct CheckDependenciesResult {
}
async fn check_dependencies(
ctx: EffectContext,
context: EffectContext,
CheckDependenciesParam { package_ids }: CheckDependenciesParam,
) -> Result<Vec<CheckDependenciesResult>, Error> {
let ctx = ctx.deref()?;
let db = ctx.ctx.db.peek().await;
let context = context.deref()?;
let db = context.seed.ctx.db.peek().await;
let current_dependencies = db
.as_public()
.as_package_data()
.as_idx(&ctx.id)
.or_not_found(&ctx.id)?
.as_idx(&context.seed.id)
.or_not_found(&context.seed.id)?
.as_current_dependencies()
.de()?;
let package_ids: Vec<_> = package_ids

View File

@@ -25,7 +25,7 @@ use crate::progress::{
use crate::s9pk::manifest::PackageId;
use crate::s9pk::merkle_archive::source::FileSource;
use crate::s9pk::S9pk;
use crate::service::{LoadDisposition, Service};
use crate::service::{LoadDisposition, Service, ServiceRef};
use crate::status::{MainStatus, Status};
use crate::util::serde::Pem;
@@ -39,23 +39,22 @@ pub struct InstallProgressHandles {
/// This is the structure to contain all the services
#[derive(Default)]
pub struct ServiceMap(Mutex<OrdMap<PackageId, Arc<RwLock<Option<Service>>>>>);
pub struct ServiceMap(Mutex<OrdMap<PackageId, Arc<RwLock<Option<ServiceRef>>>>>);
impl ServiceMap {
async fn entry(&self, id: &PackageId) -> Arc<RwLock<Option<Service>>> {
async fn entry(&self, id: &PackageId) -> Arc<RwLock<Option<ServiceRef>>> {
let mut lock = self.0.lock().await;
dbg!(lock.keys().collect::<Vec<_>>());
lock.entry(id.clone())
.or_insert_with(|| Arc::new(RwLock::new(None)))
.clone()
}
#[instrument(skip_all)]
pub async fn get(&self, id: &PackageId) -> OwnedRwLockReadGuard<Option<Service>> {
pub async fn get(&self, id: &PackageId) -> OwnedRwLockReadGuard<Option<ServiceRef>> {
self.entry(id).await.read_owned().await
}
#[instrument(skip_all)]
pub async fn get_mut(&self, id: &PackageId) -> OwnedRwLockWriteGuard<Option<Service>> {
pub async fn get_mut(&self, id: &PackageId) -> OwnedRwLockWriteGuard<Option<ServiceRef>> {
self.entry(id).await.write_owned().await
}
@@ -83,7 +82,7 @@ impl ServiceMap {
shutdown_err = service.shutdown().await;
}
// TODO: retry on error?
*service = Service::load(ctx, id, disposition).await?;
*service = Service::load(ctx, id, disposition).await?.map(From::from);
shutdown_err?;
Ok(())
}
@@ -95,6 +94,7 @@ impl ServiceMap {
mut s9pk: S9pk<S>,
recovery_source: Option<impl GenericMountGuard>,
) -> Result<DownloadInstallFuture, Error> {
s9pk.validate_and_filter(ctx.s9pk_arch)?;
let manifest = s9pk.as_manifest().clone();
let id = manifest.id.clone();
let icon = s9pk.icon_data_url().await?;
@@ -128,7 +128,7 @@ impl ServiceMap {
);
let restoring = recovery_source.is_some();
let mut reload_guard = ServiceReloadGuard::new(ctx.clone(), id.clone(), op_name);
let mut reload_guard = ServiceRefReloadGuard::new(ctx.clone(), id.clone(), op_name);
reload_guard
.handle(ctx.db.mutate({
@@ -231,7 +231,7 @@ impl ServiceMap {
Ok(reload_guard
.handle_last(async move {
finalization_progress.start();
let s9pk = S9pk::open(&installed_path, Some(&id), true).await?;
let s9pk = S9pk::open(&installed_path, Some(&id)).await?;
let prev = if let Some(service) = service.take() {
ensure_code!(
recovery_source.is_none(),
@@ -264,7 +264,8 @@ impl ServiceMap {
progress_handle,
}),
)
.await?,
.await?
.into(),
);
} else {
*service = Some(
@@ -277,7 +278,8 @@ impl ServiceMap {
progress_handle,
}),
)
.await?,
.await?
.into(),
);
}
sync_progress_task.await.map_err(|_| {
@@ -295,7 +297,7 @@ impl ServiceMap {
pub async fn uninstall(&self, ctx: &RpcContext, id: &PackageId) -> Result<(), Error> {
let mut guard = self.get_mut(id).await;
if let Some(service) = guard.take() {
ServiceReloadGuard::new(ctx.clone(), id.clone(), "Uninstall")
ServiceRefReloadGuard::new(ctx.clone(), id.clone(), "Uninstall")
.handle_last(async move {
let res = service.uninstall(None).await;
drop(guard);
@@ -326,17 +328,17 @@ impl ServiceMap {
}
}
pub struct ServiceReloadGuard(Option<ServiceReloadInfo>);
impl Drop for ServiceReloadGuard {
pub struct ServiceRefReloadGuard(Option<ServiceRefReloadInfo>);
impl Drop for ServiceRefReloadGuard {
fn drop(&mut self) {
if let Some(info) = self.0.take() {
tokio::spawn(info.reload(None));
}
}
}
impl ServiceReloadGuard {
impl ServiceRefReloadGuard {
pub fn new(ctx: RpcContext, id: PackageId, operation: &'static str) -> Self {
Self(Some(ServiceReloadInfo { ctx, id, operation }))
Self(Some(ServiceRefReloadInfo { ctx, id, operation }))
}
pub async fn handle<T>(
@@ -365,12 +367,12 @@ impl ServiceReloadGuard {
}
}
struct ServiceReloadInfo {
struct ServiceRefReloadInfo {
ctx: RpcContext,
id: PackageId,
operation: &'static str,
}
impl ServiceReloadInfo {
impl ServiceRefReloadInfo {
async fn reload(self, error: Option<Error>) -> Result<(), Error> {
self.ctx
.services

View File

@@ -6,6 +6,7 @@ use models::ProcedureName;
use super::TempDesiredRestore;
use crate::disk::mount::filesystem::ReadWrite;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::config::GetConfig;
use crate::service::dependencies::DependencyConfig;
use crate::service::transition::{TransitionKind, TransitionState};
@@ -24,7 +25,12 @@ impl Handler<Backup> for ServiceActor {
.except::<GetConfig>()
.except::<DependencyConfig>()
}
async fn handle(&mut self, backup: Backup, jobs: &BackgroundJobQueue) -> Self::Response {
async fn handle(
&mut self,
id: Guid,
backup: Backup,
jobs: &BackgroundJobQueue,
) -> Self::Response {
// So Need a handle to just a single field in the state
let temp: TempDesiredRestore = TempDesiredRestore::new(&self.0.persistent_container.state);
let mut current = self.0.persistent_container.state.subscribe();
@@ -45,7 +51,7 @@ impl Handler<Backup> for ServiceActor {
.mount_backup(path, ReadWrite)
.await?;
seed.persistent_container
.execute(ProcedureName::CreateBackup, Value::Null, None)
.execute(id, ProcedureName::CreateBackup, Value::Null, None)
.await?;
backup_guard.unmount(true).await?;

View File

@@ -2,6 +2,7 @@ use futures::FutureExt;
use super::TempDesiredRestore;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::config::GetConfig;
use crate::service::dependencies::DependencyConfig;
use crate::service::transition::{TransitionKind, TransitionState};
@@ -18,7 +19,8 @@ impl Handler<Restart> for ServiceActor {
.except::<GetConfig>()
.except::<DependencyConfig>()
}
async fn handle(&mut self, _: Restart, jobs: &BackgroundJobQueue) -> Self::Response {
async fn handle(&mut self, _: Guid, _: Restart, jobs: &BackgroundJobQueue) -> Self::Response {
dbg!("here");
// So Need a handle to just a single field in the state
let temp = TempDesiredRestore::new(&self.0.persistent_container.state);
let mut current = self.0.persistent_container.state.subscribe();
@@ -74,7 +76,8 @@ impl Handler<Restart> for ServiceActor {
}
impl Service {
#[instrument(skip_all)]
pub async fn restart(&self) -> Result<(), Error> {
self.actor.send(Restart).await
pub async fn restart(&self, id: Guid) -> Result<(), Error> {
dbg!("here");
self.actor.send(id, Restart).await
}
}

View File

@@ -5,6 +5,7 @@ use models::ProcedureName;
use crate::disk::mount::filesystem::ReadOnly;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::transition::{TransitionKind, TransitionState};
use crate::service::ServiceActor;
use crate::util::actor::background::BackgroundJobQueue;
@@ -19,7 +20,12 @@ impl Handler<Restore> for ServiceActor {
fn conflicts_with(_: &Restore) -> ConflictBuilder<Self> {
ConflictBuilder::everything()
}
async fn handle(&mut self, restore: Restore, jobs: &BackgroundJobQueue) -> Self::Response {
async fn handle(
&mut self,
id: Guid,
restore: Restore,
jobs: &BackgroundJobQueue,
) -> Self::Response {
// So Need a handle to just a single field in the state
let path = restore.path.clone();
let seed = self.0.clone();
@@ -32,7 +38,7 @@ impl Handler<Restore> for ServiceActor {
.mount_backup(path, ReadOnly)
.await?;
seed.persistent_container
.execute(ProcedureName::RestoreBackup, Value::Null, None)
.execute(id, ProcedureName::RestoreBackup, Value::Null, None)
.await?;
backup_guard.unmount(true).await?;