diff --git a/src/failure_domain.rs b/src/failure_domain.rs index e777099bd7864354e0898abd27ac409c795e1cd4..6cc980993577b37fb79f982bc486b1310e963a55 100644 --- a/src/failure_domain.rs +++ b/src/failure_domain.rs @@ -34,7 +34,7 @@ use std::collections::{HashMap, HashSet}; #[derive(Default, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)] pub struct FailureDomain { #[serde(flatten)] - data: HashMap<Uppercase, Uppercase>, + pub data: HashMap<Uppercase, Uppercase>, } impl FailureDomain { diff --git a/src/instance.rs b/src/instance.rs index b81899e1ade64d80c5eede04647543cf7fc8186c..a19d41808530ff9c9018429bac21ebd88a819635 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -54,6 +54,36 @@ pub struct Instance { impl Encode for Instance {} impl Instance { + /// Index of field "instance_id" in the space _pico_instance format. + /// + /// Index of first field is 0. + pub const FIELD_INSTANCE_ID: u32 = 0; + + /// Index of field "raft_id" in the space _pico_instance format. + /// + /// Index of first field is 0. + pub const FIELD_RAFT_ID: u32 = 2; + + /// Index of field "failure_domain" in the space _pico_instance format. + /// + /// Index of first field is 0. + pub const FIELD_FAILURE_DOMAIN: u32 = 7; + + pub fn format() -> Vec<tarantool::space::Field> { + use tarantool::space::{Field, FieldType}; + vec![ + Field::from(("instance_id", FieldType::String)), + Field::from(("instance_uuid", FieldType::String)), + Field::from(("raft_id", FieldType::Unsigned)), + Field::from(("replicaset_id", FieldType::String)), + Field::from(("replicaset_uuid", FieldType::String)), + Field::from(("current_grade", FieldType::Array)), + Field::from(("target_grade", FieldType::Array)), + Field::from(("failure_domain", FieldType::Map)), + Field::from(("tier", FieldType::String)), + ] + } + /// Construct an instance. pub fn new( raft_id: Option<RaftId>, @@ -611,14 +641,15 @@ mod test { use tarantool::tuple::ToTupleBuffer; #[test] + #[rustfmt::skip] fn matches_format() { let i = Instance::default(); let tuple_data = i.to_tuple_buffer().unwrap(); - let format = crate::storage::instance_format(); - crate::util::check_tuple_matches_format( - tuple_data.as_ref(), - &format, - "define_instance_fields", - ); + let format = Instance::format(); + crate::util::check_tuple_matches_format(tuple_data.as_ref(), &format, "Instance::format"); + + assert_eq!(format[Instance::FIELD_INSTANCE_ID as usize].name, "instance_id"); + assert_eq!(format[Instance::FIELD_RAFT_ID as usize].name, "raft_id"); + assert_eq!(format[Instance::FIELD_FAILURE_DOMAIN as usize].name, "failure_domain"); } } diff --git a/src/storage.rs b/src/storage.rs index a374682df0b47eeda396551922c481ffe7837178..6025f8db3b0abd5f7156e15882f373b160ce534a 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -14,10 +14,9 @@ use tarantool::tuple::KeyDef; use tarantool::tuple::{Decode, DecodeOwned, Encode}; use tarantool::tuple::{RawBytes, ToTupleBuffer, Tuple, TupleBuffer}; -use crate::failure_domain as fd; -use crate::instance::Grade; +use crate::failure_domain::FailureDomain; use crate::instance::{self, Instance}; -use crate::replicaset::{Replicaset, ReplicasetId}; +use crate::replicaset::Replicaset; use crate::schema::Distribution; use crate::schema::{IndexDef, SpaceDef}; use crate::schema::{PrivilegeDef, RoleDef, UserDef}; @@ -1366,28 +1365,28 @@ impl Instances { let space_instances = Space::builder(Self::SPACE_NAME) .id(Self::SPACE_ID) .space_type(SpaceType::DataLocal) - .format(instance_format()) + .format(Instance::format()) .if_not_exists(true) .create()?; let index_instance_id = space_instances .index_builder("instance_id") .unique(true) - .part(instance_field::InstanceId) + .part("instance_id") .if_not_exists(true) .create()?; let index_raft_id = space_instances .index_builder("raft_id") .unique(true) - .part(instance_field::RaftId) + .part("raft_id") .if_not_exists(true) .create()?; let index_replicaset_id = space_instances .index_builder("replicaset_id") .unique(false) - .part(instance_field::ReplicasetId) + .part("replicaset_id") .if_not_exists(true) .create()?; @@ -1422,6 +1421,14 @@ impl Instances { Ok(res) } + /// Finds an instance by `id` (see trait [`InstanceId`]). + /// Returns the tuple without deserializing. + #[inline(always)] + pub fn get_raw(&self, id: &impl InstanceId) -> Result<Tuple> { + let res = id.find_in(self)?; + Ok(res) + } + /// Checks if an instance with `id` (see trait [`InstanceId`]) is present. #[inline] pub fn contains(&self, id: &impl InstanceId) -> Result<bool> { @@ -1432,29 +1439,6 @@ impl Instances { } } - /// Finds an instance by `id` (see `InstanceId`) and return a single field - /// specified by `F` (see `InstanceFieldDef` & `instance_field` module). - #[inline(always)] - pub fn field<F>(&self, id: &impl InstanceId) -> Result<F::Type> - where - F: InstanceFieldDef, - { - let tuple = id.find_in(self)?; - let res = F::get_in(&tuple)?; - Ok(res) - } - - /// Returns an iterator over all instances. Items of the iterator are - /// specified by `F` (see `InstanceFieldDef` & `instance_field` module). - #[inline(always)] - pub fn instances_fields<F>(&self) -> Result<InstancesFields<F>> - where - F: InstanceFieldDef, - { - let iter = self.space.select(IteratorType::All, &())?; - Ok(InstancesFields::new(iter)) - } - #[inline] pub fn all_instances(&self) -> tarantool::Result<Vec<Instance>> { self.space @@ -1473,31 +1457,34 @@ impl Instances { Ok(EntryIter::new(iter)) } - pub fn replicaset_fields<T>( - &self, - replicaset_id: &ReplicasetId, - ) -> tarantool::Result<Vec<T::Type>> - where - T: InstanceFieldDef, - { - self.index_replicaset_id - .select(IteratorType::Eq, &[replicaset_id])? - .map(|tuple| T::get_in(&tuple)) - .collect() - } - - pub fn max_raft_id(&self) -> tarantool::Result<RaftId> { - match self.index_raft_id.max(&())? { - None => Ok(0), - Some(tuple) => instance_field::RaftId::get_in(&tuple), - } + pub fn max_raft_id(&self) -> Result<RaftId> { + let Some(tuple) = self.index_raft_id.max(&())? else { + return Ok(0); + }; + let Some(raft_id) = tuple.field(Instance::FIELD_RAFT_ID)? else { + return Err(Error::StorageCorrupted { + field: "raft_id".into(), + table: "_pico_instance".into(), + }); + }; + Ok(raft_id) } pub fn failure_domain_names(&self) -> Result<HashSet<Uppercase>> { - Ok(self - .instances_fields::<instance_field::FailureDomain>()? - .flat_map(|fd| fd.names().cloned().collect::<Vec<_>>()) - .collect()) + let mut res = HashSet::with_capacity(16); + for tuple in self.space.select(IteratorType::All, &())? { + let Some(failure_domain) = tuple.field(Instance::FIELD_FAILURE_DOMAIN)? else { + return Err(Error::StorageCorrupted { + field: "failure_domain".into(), + table: "_pico_instance".into(), + }); + }; + let failure_domain: FailureDomain = failure_domain; + for name in failure_domain.data.into_keys() { + res.insert(name); + } + } + Ok(res) } } @@ -1510,138 +1497,6 @@ impl ToEntryIter for Instances { } } -//////////////////////////////////////////////////////////////////////////////// -// InstanceField -//////////////////////////////////////////////////////////////////////////////// - -macro_rules! define_instance_fields { - ($($field:ident: $ty:ty = ($name:literal, $tt_ty:path))+) => { - ::tarantool::define_str_enum! { - /// An enumeration of raft_space field names - pub enum InstanceField { - $($field = $name,)+ - } - } - - pub mod instance_field { - use super::*; - $( - /// Helper struct that represents - #[doc = stringify!($name)] - /// field of [`Instance`]. - /// - /// It's rust type is - #[doc = concat!("`", stringify!($ty), "`")] - /// and it's tarantool type is - #[doc = concat!("`", stringify!($tt_ty), "`")] - /// - /// [`Instance`]: crate::instance::Instance - pub struct $field; - - impl InstanceFieldDef for $field { - type Type = $ty; - - fn get_in(tuple: &Tuple) -> tarantool::Result<Self::Type> { - Ok(tuple.try_get($name)?.expect("instance fields aren't nullable")) - } - } - - impl From<$field> for ::tarantool::index::Part { - #[inline(always)] - fn from(_: $field) -> ::tarantool::index::Part { - $name.into() - } - } - - impl From<$field> for ::tarantool::space::Field { - #[inline(always)] - fn from(_: $field) -> ::tarantool::space::Field { - ($name, $tt_ty).into() - } - } - - impl ::tarantool::tuple::TupleIndex for $field { - #[inline(always)] - fn get_field<'a, T>(self, tuple: &'a Tuple) -> ::tarantool::Result<Option<T>> - where - T: ::tarantool::tuple::Decode<'a>, - { - $name.get_field(tuple) - } - } - )+ - } - - pub fn instance_format() -> Vec<::tarantool::space::Field> { - vec![ - $( ::tarantool::space::Field::from(($name, $tt_ty)), )+ - ] - } - }; -} - -define_instance_fields! { - InstanceId : instance::InstanceId = ("instance_id", FieldType::String) - InstanceUuid : String = ("instance_uuid", FieldType::String) - RaftId : traft::RaftId = ("raft_id", FieldType::Unsigned) - ReplicasetId : String = ("replicaset_id", FieldType::String) - ReplicasetUuid : String = ("replicaset_uuid", FieldType::String) - CurrentGrade : Grade = ("current_grade", FieldType::Array) - TargetGrade : Grade = ("target_grade", FieldType::Array) - FailureDomain : fd::FailureDomain = ("failure_domain", FieldType::Map) - Tier : String = ("tier", FieldType::String) -} - -impl tarantool::tuple::TupleIndex for InstanceField { - fn get_field<'a, T>(self, tuple: &'a Tuple) -> tarantool::Result<Option<T>> - where - T: tarantool::tuple::Decode<'a>, - { - self.as_str().get_field(tuple) - } -} - -/// A helper trait for type-safe and efficient access to a Instance's fields -/// without deserializing the whole tuple. -/// -/// This trait contains information needed to define and use a given tuple field. -pub trait InstanceFieldDef { - /// Rust type of the field. - /// - /// Used when decoding the field. - type Type: tarantool::tuple::DecodeOwned; - - /// Get the field in `tuple`. - fn get_in(tuple: &Tuple) -> tarantool::Result<Self::Type>; -} - -macro_rules! define_instance_field_def_for_tuples { - () => {}; - ($h:ident $($t:ident)*) => { - impl<$h, $($t),*> InstanceFieldDef for ($h, $($t),*) - where - $h: InstanceFieldDef, - $h::Type: serde::de::DeserializeOwned, - $( - $t: InstanceFieldDef, - $t::Type: serde::de::DeserializeOwned, - )* - { - type Type = ($h::Type, $($t::Type),*); - - fn get_in(tuple: &Tuple) -> tarantool::Result<Self::Type> { - Ok(($h::get_in(&tuple)?, $($t::get_in(&tuple)?,)*)) - } - } - - define_instance_field_def_for_tuples!{ $($t)* } - }; -} - -define_instance_field_def_for_tuples! { - T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 -} - //////////////////////////////////////////////////////////////////////////////// // InstanceId //////////////////////////////////////////////////////////////////////////////// @@ -1672,36 +1527,6 @@ impl InstanceId for instance::InstanceId { } } -//////////////////////////////////////////////////////////////////////////////// -// InstancesFields -//////////////////////////////////////////////////////////////////////////////// - -pub struct InstancesFields<F> { - iter: IndexIterator, - marker: PhantomData<F>, -} - -impl<F> InstancesFields<F> { - fn new(iter: IndexIterator) -> Self { - Self { - iter, - marker: PhantomData, - } - } -} - -impl<F> Iterator for InstancesFields<F> -where - F: InstanceFieldDef, -{ - type Item = F::Type; - - fn next(&mut self) -> Option<Self::Item> { - let res = self.iter.next().as_ref().map(F::get_in); - res.map(|res| res.expect("instance should decode correctly")) - } -} - //////////////////////////////////////////////////////////////////////////////// // EntryIter //////////////////////////////////////////////////////////////////////////////// diff --git a/src/traft/error.rs b/src/traft/error.rs index 810d0bfb64b7ec1c4b70912726da0b9bf14168e6..d212bbbdfee375063b29eb72998a3b9de511a12e 100644 --- a/src/traft/error.rs +++ b/src/traft/error.rs @@ -76,6 +76,9 @@ pub enum Error { #[error("transaction: {0}")] Transaction(String), + #[error("storage corrupted: failed to decode field '{field}' from table '{table}'")] + StorageCorrupted { table: String, field: String }, + #[error("{0}")] Other(Box<dyn std::error::Error>), } diff --git a/src/traft/network.rs b/src/traft/network.rs index cadc9908ddf71f6f81206a0a9eb5cd1d1f4d858c..2595bf8f238fb35dec96e99996de819dc11137ac 100644 --- a/src/traft/network.rs +++ b/src/traft/network.rs @@ -1,8 +1,9 @@ +use crate::instance::Instance; use crate::instance::InstanceId; use crate::mailbox::Mailbox; use crate::reachability::InstanceReachabilityManagerRef; use crate::rpc; -use crate::storage::{instance_field, Clusterwide, Instances, PeerAddresses}; +use crate::storage::{Clusterwide, Instances, PeerAddresses}; use crate::tlog; use crate::traft; use crate::traft::error::Error; @@ -430,11 +431,10 @@ impl ConnectionPool { if let Some(worker) = workers.get(&raft_id) { Ok(worker) } else { - let instance_id = self - .instances - .field::<instance_field::InstanceId>(&raft_id) - .map_err(|_| Error::NoInstanceWithRaftId(raft_id)) - .ok(); + let mut instance_id: Option<InstanceId> = None; + if let Ok(tuple) = self.instances.get_raw(&raft_id) { + instance_id = tuple.field(Instance::FIELD_INSTANCE_ID)?; + } // Check if address of this peer is known. // No need to store the result, // because it will be updated in the loop @@ -468,10 +468,10 @@ impl ConnectionPool { Ok(worker) } else { let instance_id = InstanceId::from(instance_id); - let raft_id = self - .instances - .field::<instance_field::RaftId>(&instance_id) - .map_err(|_| Error::NoInstanceWithInstanceId(instance_id.clone()))?; + let tuple = self.instances.get_raw(&instance_id)?; + let Some(raft_id) = tuple.field(Instance::FIELD_RAFT_ID)? else { + return Err(Error::other("storage corrupted: couldn't decode instance's raft id")); + }; self.get_or_create_by_raft_id(raft_id) } }