diff --git a/cmd/zfs_object_agent/zettacache/src/features.rs b/cmd/zfs_object_agent/zettacache/src/features.rs new file mode 100644 index 000000000000..7a023ce88ed1 --- /dev/null +++ b/cmd/zfs_object_agent/zettacache/src/features.rs @@ -0,0 +1,92 @@ +use lazy_static::lazy_static; +use log::info; +use more_asserts::assert_lt; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt::Display}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum FeatureType { + Upgradeable, + NonUpgradeable, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct FeatureName(pub String); + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Feature { + name: FeatureName, + info: FeatureType, +} + +lazy_static! { + pub static ref SUPPORTED_FEATURES: HashMap = [ORIGIN.clone()] + .map(|feature| (feature.name, feature.info)) + .into_iter() + .collect(); + pub static ref ORIGIN: Feature = Feature { + name: FeatureName("com.delphix:origin".to_string()), + info: FeatureType::Upgradeable + }; +} + +#[derive(Debug)] +pub struct FeatureError { + non_upgradeable_features: Vec, + unknown_features: Vec, +} + +impl Display for FeatureError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + assert_lt!( + 0, + self.non_upgradeable_features.len() + self.unknown_features.len(), + ); + f.write_fmt(format_args!( + "Missing Zettacache Features: {:?} - Unknown Features Encountered: {:?}", + self.non_upgradeable_features, self.unknown_features, + )) + } +} + +impl std::error::Error for FeatureError {} + +pub fn check_features<'a, I>(feature_list: I) -> Result<(), FeatureError> +where + I: Iterator, +{ + let mut upgradeable_features = vec![]; + let mut non_upgradeable_features = vec![]; + let mut unknown_features = vec![]; + let mut supported_features = SUPPORTED_FEATURES.clone(); + + for feature in feature_list { + match supported_features.contains_key(feature) { + true => { + supported_features.remove(feature); + } + false => { + unknown_features.push(feature.clone()); + } + } + } + for (feature_name, feature_type) in supported_features { + match feature_type { + FeatureType::Upgradeable => upgradeable_features.push(feature_name), + FeatureType::NonUpgradeable => non_upgradeable_features.push(feature_name), + } + } + + if !non_upgradeable_features.is_empty() || !unknown_features.is_empty() { + Err(FeatureError { + non_upgradeable_features, + unknown_features, + }) + } else { + info!( + "enabling the following upgradeable features: {:?}", + upgradeable_features + ); + Ok(()) + } +} diff --git a/cmd/zfs_object_agent/zettacache/src/lib.rs b/cmd/zfs_object_agent/zettacache/src/lib.rs index 907af981320b..a5318a979bff 100644 --- a/cmd/zfs_object_agent/zettacache/src/lib.rs +++ b/cmd/zfs_object_agent/zettacache/src/lib.rs @@ -9,6 +9,7 @@ mod block_access; mod block_allocator; mod block_based_log; mod extent_allocator; +mod features; mod index; mod size_histogram; mod space_map; diff --git a/cmd/zfs_object_agent/zettacache/src/superblock.rs b/cmd/zfs_object_agent/zettacache/src/superblock.rs index 140aa14081e5..f40f30b70c9d 100644 --- a/cmd/zfs_object_agent/zettacache/src/superblock.rs +++ b/cmd/zfs_object_agent/zettacache/src/superblock.rs @@ -1,5 +1,6 @@ use crate::base_types::*; use crate::block_access::*; +use crate::features::FeatureName; use anyhow::anyhow; use anyhow::Result; use futures::stream::*; @@ -26,6 +27,7 @@ pub struct PrimaryPhys { pub checkpoint_capacity: Extent, // space available for checkpoints pub checkpoint: Extent, // space used by latest checkpoint pub num_disks: usize, + pub feature_flags: Vec, } impl PrimaryPhys { diff --git a/cmd/zfs_object_agent/zettacache/src/zettacache.rs b/cmd/zfs_object_agent/zettacache/src/zettacache.rs index c13c2a5eea3e..aad87ca7e9bd 100644 --- a/cmd/zfs_object_agent/zettacache/src/zettacache.rs +++ b/cmd/zfs_object_agent/zettacache/src/zettacache.rs @@ -10,6 +10,8 @@ use crate::block_based_log::*; use crate::extent_allocator::ExtentAllocator; use crate::extent_allocator::ExtentAllocatorBuilder; use crate::extent_allocator::ExtentAllocatorPhys; +use crate::features::check_features; +use crate::features::SUPPORTED_FEATURES; use crate::index::*; use crate::size_histogram::SizeHistogramPhys; use crate::superblock::PrimaryPhys; @@ -545,6 +547,7 @@ impl ZettaCache { checkpoint_capacity, checkpoint: checkpoint_extent, num_disks, + feature_flags: SUPPORTED_FEATURES.keys().cloned().collect(), } .write_all(DiskId(0), guid, block_access) .await; @@ -564,6 +567,9 @@ impl ZettaCache { PrimaryPhys::read(&block_access).await.unwrap() } }; + if let Err(feature_error) = check_features(primary.feature_flags.iter()) { + panic!("{}", feature_error) + }; let checkpoint = ZettaCheckpointPhys::read(&block_access, primary.checkpoint).await; @@ -1731,6 +1737,7 @@ impl ZettaCacheState { self.primary.checkpoint = checkpoint_extent; self.primary.checkpoint_id = self.primary.checkpoint_id.next(); + self.primary.feature_flags = SUPPORTED_FEATURES.keys().cloned().collect(); self.primary .write_all(self.primary_disk, self.guid, &self.block_access) .await;