2023-11-09 13:12:05 +00:00
use std ::sync ::{ Arc , RwLock , RwLockReadGuard } ;
2023-11-09 11:55:36 +00:00
use std ::time ::Duration ;
2023-11-09 12:34:14 +00:00
use serde ::{ Deserialize , Serialize } ;
2023-11-09 13:12:05 +00:00
use tokio ::sync ::Notify ;
2023-11-09 11:55:36 +00:00
use netapp ::endpoint ::Endpoint ;
use netapp ::peering ::fullmesh ::FullMeshPeeringStrategy ;
use netapp ::NodeID ;
use garage_util ::config ::Config ;
use garage_util ::data ::* ;
use garage_util ::error ::* ;
use garage_util ::persister ::Persister ;
use super ::* ;
use crate ::rpc_helper ::* ;
use crate ::system ::* ;
pub struct LayoutManager {
2023-11-09 13:53:34 +00:00
node_id : Uuid ,
2023-11-09 11:55:36 +00:00
replication_factor : usize ,
persist_cluster_layout : Persister < LayoutHistory > ,
2023-11-09 13:12:05 +00:00
layout : Arc < RwLock < LayoutHistory > > ,
pub ( crate ) change_notify : Arc < Notify > ,
2023-11-09 11:55:36 +00:00
pub ( crate ) rpc_helper : RpcHelper ,
system_endpoint : Arc < Endpoint < SystemRpc , System > > ,
}
2023-11-09 12:34:14 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize, Default) ]
pub struct LayoutStatus {
/// Cluster layout version
pub cluster_layout_version : u64 ,
/// Hash of cluster layout update trackers
2023-11-09 13:53:34 +00:00
pub cluster_layout_trackers_hash : Hash ,
2023-11-09 12:34:14 +00:00
/// Hash of cluster layout staging data
pub cluster_layout_staging_hash : Hash ,
}
2023-11-09 11:55:36 +00:00
impl LayoutManager {
pub fn new (
config : & Config ,
node_id : NodeID ,
system_endpoint : Arc < Endpoint < SystemRpc , System > > ,
fullmesh : Arc < FullMeshPeeringStrategy > ,
replication_factor : usize ,
2023-11-09 12:34:14 +00:00
) -> Result < Arc < Self > , Error > {
2023-11-09 11:55:36 +00:00
let persist_cluster_layout : Persister < LayoutHistory > =
Persister ::new ( & config . metadata_dir , " cluster_layout " ) ;
let cluster_layout = match persist_cluster_layout . load ( ) {
Ok ( x ) = > {
if x . current ( ) . replication_factor ! = replication_factor {
return Err ( Error ::Message ( format! (
" Prevous cluster layout has replication factor {}, which is different than the one specified in the config file ({}). The previous cluster layout can be purged, if you know what you are doing, simply by deleting the `cluster_layout` file in your metadata directory. " ,
x . current ( ) . replication_factor ,
replication_factor
) ) ) ;
}
x
}
Err ( e ) = > {
info! (
" No valid previous cluster layout stored ({}), starting fresh. " ,
e
) ;
LayoutHistory ::new ( replication_factor )
}
} ;
2023-11-09 13:12:05 +00:00
let layout = Arc ::new ( RwLock ::new ( cluster_layout ) ) ;
let change_notify = Arc ::new ( Notify ::new ( ) ) ;
2023-11-09 11:55:36 +00:00
let rpc_helper = RpcHelper ::new (
node_id . into ( ) ,
fullmesh ,
2023-11-09 13:12:05 +00:00
layout . clone ( ) ,
2023-11-09 11:55:36 +00:00
config . rpc_timeout_msec . map ( Duration ::from_millis ) ,
) ;
2023-11-09 12:34:14 +00:00
Ok ( Arc ::new ( Self {
2023-11-09 13:53:34 +00:00
node_id : node_id . into ( ) ,
2023-11-09 11:55:36 +00:00
replication_factor ,
persist_cluster_layout ,
2023-11-09 13:12:05 +00:00
layout ,
change_notify ,
2023-11-09 11:55:36 +00:00
system_endpoint ,
rpc_helper ,
2023-11-09 12:34:14 +00:00
} ) )
2023-11-09 11:55:36 +00:00
}
// ---- PUBLIC INTERFACE ----
2023-11-09 13:53:34 +00:00
pub fn layout ( & self ) -> RwLockReadGuard < '_ , LayoutHistory > {
self . layout . read ( ) . unwrap ( )
}
2023-11-09 12:34:14 +00:00
pub fn status ( & self ) -> LayoutStatus {
let layout = self . layout ( ) ;
LayoutStatus {
cluster_layout_version : layout . current ( ) . version ,
2023-11-09 13:53:34 +00:00
cluster_layout_trackers_hash : layout . trackers_hash ,
2023-11-09 12:34:14 +00:00
cluster_layout_staging_hash : layout . staging_hash ,
}
}
pub async fn update_cluster_layout (
self : & Arc < Self > ,
layout : & LayoutHistory ,
) -> Result < ( ) , Error > {
2023-11-09 11:55:36 +00:00
self . handle_advertise_cluster_layout ( layout ) . await ? ;
Ok ( ( ) )
}
2023-11-09 13:53:34 +00:00
// ---- INTERNALS ---
fn merge_layout ( & self , adv : & LayoutHistory ) -> Option < LayoutHistory > {
let mut layout = self . layout . write ( ) . unwrap ( ) ;
let prev_layout_check = layout . check ( ) . is_ok ( ) ;
if ! prev_layout_check | | adv . check ( ) . is_ok ( ) {
if layout . merge ( adv ) {
if prev_layout_check & & layout . check ( ) . is_err ( ) {
panic! ( " Merged two correct layouts and got an incorrect layout. " ) ;
}
return Some ( layout . clone ( ) ) ;
}
}
None
2023-11-09 11:55:36 +00:00
}
2023-11-09 13:53:34 +00:00
fn merge_layout_trackers ( & self , adv : & UpdateTrackers ) -> Option < UpdateTrackers > {
let mut layout = self . layout . write ( ) . unwrap ( ) ;
if layout . update_trackers ! = * adv {
if layout . update_trackers . merge ( adv ) {
return Some ( layout . update_trackers . clone ( ) ) ;
}
}
None
}
async fn pull_cluster_layout ( self : & Arc < Self > , peer : Uuid ) {
2023-11-09 11:55:36 +00:00
let resp = self
. rpc_helper
. call (
& self . system_endpoint ,
peer ,
SystemRpc ::PullClusterLayout ,
RequestStrategy ::with_priority ( PRIO_HIGH ) ,
)
. await ;
if let Ok ( SystemRpc ::AdvertiseClusterLayout ( layout ) ) = resp {
2023-11-09 13:53:34 +00:00
if let Err ( e ) = self . handle_advertise_cluster_layout ( & layout ) . await {
warn! ( " In pull_cluster_layout: {} " , e ) ;
}
2023-11-09 11:55:36 +00:00
}
}
2023-11-09 13:53:34 +00:00
async fn pull_cluster_layout_trackers ( self : & Arc < Self > , peer : Uuid ) {
let resp = self
. rpc_helper
. call (
& self . system_endpoint ,
peer ,
SystemRpc ::PullClusterLayoutTrackers ,
RequestStrategy ::with_priority ( PRIO_HIGH ) ,
)
. await ;
if let Ok ( SystemRpc ::AdvertiseClusterLayoutTrackers ( trackers ) ) = resp {
if let Err ( e ) = self
. handle_advertise_cluster_layout_trackers ( & trackers )
. await
{
warn! ( " In pull_cluster_layout_trackers: {} " , e ) ;
}
}
}
2023-11-09 11:55:36 +00:00
2023-11-09 13:53:34 +00:00
/// Save cluster layout data to disk
2023-11-09 11:55:36 +00:00
async fn save_cluster_layout ( & self ) -> Result < ( ) , Error > {
2023-11-09 13:53:34 +00:00
let layout = self . layout . read ( ) . unwrap ( ) . clone ( ) ;
2023-11-09 11:55:36 +00:00
self . persist_cluster_layout
. save_async ( & layout )
. await
. expect ( " Cannot save current cluster layout " ) ;
Ok ( ( ) )
}
2023-11-09 13:53:34 +00:00
fn broadcast_update ( self : & Arc < Self > , rpc : SystemRpc ) {
tokio ::spawn ( {
let this = self . clone ( ) ;
async move {
if let Err ( e ) = this
. rpc_helper
. broadcast (
& this . system_endpoint ,
rpc ,
RequestStrategy ::with_priority ( PRIO_HIGH ) ,
)
. await
{
warn! ( " Error while broadcasting new cluster layout: {} " , e ) ;
2023-11-09 13:12:05 +00:00
}
}
2023-11-09 13:53:34 +00:00
} ) ;
2023-11-09 13:12:05 +00:00
}
2023-11-09 11:55:36 +00:00
// ---- RPC HANDLERS ----
2023-11-09 13:53:34 +00:00
pub ( crate ) fn handle_advertise_status ( self : & Arc < Self > , from : Uuid , remote : & LayoutStatus ) {
let local = self . status ( ) ;
if remote . cluster_layout_version > local . cluster_layout_version
| | remote . cluster_layout_staging_hash ! = local . cluster_layout_staging_hash
2023-11-09 12:34:14 +00:00
{
tokio ::spawn ( {
let this = self . clone ( ) ;
async move { this . pull_cluster_layout ( from ) . await }
} ) ;
2023-11-09 13:53:34 +00:00
} else if remote . cluster_layout_trackers_hash ! = local . cluster_layout_trackers_hash {
tokio ::spawn ( {
let this = self . clone ( ) ;
async move { this . pull_cluster_layout_trackers ( from ) . await }
} ) ;
2023-11-09 12:34:14 +00:00
}
}
2023-11-09 11:55:36 +00:00
pub ( crate ) fn handle_pull_cluster_layout ( & self ) -> SystemRpc {
2023-11-09 13:12:05 +00:00
let layout = self . layout . read ( ) . unwrap ( ) . clone ( ) ; // TODO: avoid cloning
2023-11-09 11:55:36 +00:00
SystemRpc ::AdvertiseClusterLayout ( layout )
}
2023-11-09 13:53:34 +00:00
pub ( crate ) fn handle_pull_cluster_layout_trackers ( & self ) -> SystemRpc {
let layout = self . layout . read ( ) . unwrap ( ) ;
SystemRpc ::AdvertiseClusterLayoutTrackers ( layout . update_trackers . clone ( ) )
}
2023-11-09 11:55:36 +00:00
pub ( crate ) async fn handle_advertise_cluster_layout (
2023-11-09 12:34:14 +00:00
self : & Arc < Self > ,
2023-11-09 11:55:36 +00:00
adv : & LayoutHistory ,
) -> Result < SystemRpc , Error > {
if adv . current ( ) . replication_factor ! = self . replication_factor {
let msg = format! (
" Received a cluster layout from another node with replication factor {}, which is different from what we have in our configuration ({}). Discarding the cluster layout we received. " ,
adv . current ( ) . replication_factor ,
self . replication_factor
) ;
error! ( " {} " , msg ) ;
return Err ( Error ::Message ( msg ) ) ;
}
2023-11-09 13:12:05 +00:00
if let Some ( new_layout ) = self . merge_layout ( adv ) {
self . change_notify . notify_waiters ( ) ;
2023-11-09 13:53:34 +00:00
self . broadcast_update ( SystemRpc ::AdvertiseClusterLayout ( new_layout ) ) ;
self . save_cluster_layout ( ) . await ? ;
}
2023-11-09 12:34:14 +00:00
2023-11-09 13:53:34 +00:00
Ok ( SystemRpc ::Ok )
}
2023-11-09 12:34:14 +00:00
2023-11-09 13:53:34 +00:00
pub ( crate ) async fn handle_advertise_cluster_layout_trackers (
self : & Arc < Self > ,
trackers : & UpdateTrackers ,
) -> Result < SystemRpc , Error > {
if let Some ( new_trackers ) = self . merge_layout_trackers ( trackers ) {
self . change_notify . notify_waiters ( ) ;
self . broadcast_update ( SystemRpc ::AdvertiseClusterLayoutTrackers ( new_trackers ) ) ;
2023-11-09 13:12:05 +00:00
self . save_cluster_layout ( ) . await ? ;
2023-11-09 11:55:36 +00:00
}
Ok ( SystemRpc ::Ok )
}
}