metalos/lib/service/helper/src/volumes.rs (119 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ use btrfs::{DeleteFlags, SnapshotFlags, Subvolume}; use std::path::Path; use thiserror::Error; use service::ServiceInstance; #[derive(Error, Debug)] pub enum Error { /// There was a problem creating a subvol. The inner [btrfs::Error] provides /// enough context to debug this problem. #[error(transparent)] Create(btrfs::Error), /// There was a problem retrieving info about the subvol. The inner /// [btrfs::Error] provides enough context to debug this problem. #[error(transparent)] Get(btrfs::Error), /// There was a problem setting up the root subvol. The inner [btrfs::Error] /// provides enough context to debug this problem. #[error(transparent)] RootSetup(btrfs::Error), #[error("failed to delete one or more subvols: {0:?}")] Delete(Vec<btrfs::Error>), } pub type Result<T> = std::result::Result<T, Error>; /// See [service::Paths] for the details on all of the MetalOS Native Service /// subvolumes. #[derive(Debug)] pub(crate) struct ServiceVolumes { root: Subvolume, runtime: Subvolume, } impl ServiceVolumes { fn ensure_subvol_exists(path: &Path) -> Result<Subvolume> { Subvolume::get(path) .or_else(|_| Subvolume::create(path)) .map_err(Error::Create) } /// Create (or ensure that they have already been created) the subvolumes /// required for a specific run of a native service. pub(crate) fn create(svc: &ServiceInstance) -> Result<Self> { let paths = svc.paths(); // ensure that the persistent subvolumes exist, creating them if not Self::ensure_subvol_exists(paths.state())?; Self::ensure_subvol_exists(paths.cache())?; Self::ensure_subvol_exists(paths.logs())?; // root and runtime are ephemeral for each run of the native service, so // create them fresh let root_src = Subvolume::get(paths.root_source()).map_err(Error::Get)?; let mut root = root_src .snapshot(paths.root(), SnapshotFlags::RECURSIVE) .map_err(Error::RootSetup)?; root.set_readonly(false).map_err(Error::RootSetup)?; let runtime = Subvolume::create(paths.runtime()).map_err(Error::Create)?; Ok(Self { root, runtime }) } /// Get the existing set of subvolumes for a native service instance. pub(crate) fn get(svc: &ServiceInstance) -> Result<Self> { let paths = svc.paths(); Ok(Self { root: Subvolume::get(paths.root()).map_err(Error::Get)?, runtime: Subvolume::get(paths.runtime()).map_err(Error::Get)?, }) } pub(crate) fn delete(self) -> Result<()> { let mut errors = vec![]; if let Err(e) = self.root.delete(DeleteFlags::RECURSIVE) { errors.push(e); } if let Err(e) = self.runtime.delete(DeleteFlags::RECURSIVE) { errors.push(e); } if errors.is_empty() { Ok(()) } else { Err(Error::Delete(errors)) } } } #[cfg(test)] mod tests { use super::*; use anyhow::Result; use metalos_macros::containertest; use std::path::Path; fn do_create() -> Result<(ServiceVolumes, ServiceInstance)> { let svc = ServiceInstance::new( "metalos.demo".into(), "00000000-0000-4000-8000-000000000001".parse().unwrap(), ); let svc_vols = ServiceVolumes::create(&svc)?; Ok((svc_vols, svc)) } fn assert_paths(svc_vols: ServiceVolumes, svc: ServiceInstance) { assert_eq!( svc_vols.root.path(), Path::new(&format!( "/run/fs/control/run/service_roots/metalos.demo-{}-{}", svc.version().to_simple(), svc.run_uuid().to_simple(), )), ); assert_eq!( svc_vols.runtime.path(), Path::new(&format!( "/run/fs/control/run/runtime/metalos.demo-{}-{}", svc.version().to_simple(), svc.run_uuid().to_simple(), )), ); // ensure that the other subvols exist assert!(Path::new("/run/fs/control/run/state/metalos.demo").exists()); assert!(Path::new("/run/fs/control/run/cache/metalos.demo").exists()); assert!(Path::new("/run/fs/control/run/logs/metalos.demo").exists()); } #[containertest] fn create() -> Result<()> { crate::tests::wait_for_systemd()?; let (svc_vols, svc) = do_create()?; assert_paths(svc_vols, svc); Ok(()) } #[containertest] fn get() -> Result<()> { crate::tests::wait_for_systemd()?; let (_, svc) = do_create()?; let svc_vols = ServiceVolumes::get(&svc)?; assert_paths(svc_vols, svc); Ok(()) } }