Skip to content

Commit 3bb355a

Browse files
committed
add client version check
1 parent 58eeeed commit 3bb355a

7 files changed

Lines changed: 107 additions & 41 deletions

File tree

curvine-client/src/file/curvine_filesystem.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ impl CurvineFileSystem {
4747
pub fn with_rt(conf: ClusterConf, rt: Arc<Runtime>) -> FsResult<Self> {
4848
let fs_context = Arc::new(FsContext::with_rt(conf, rt.clone())?);
4949
let fs_client = FsClient::new(fs_context.clone());
50+
51+
// Perform version handshake with master
52+
rt.block_on(fs_client.handshake())?;
53+
5054
let fs = Self {
5155
fs_context,
5256
fs_client: Arc::new(fs_client),

curvine-client/src/file/fs_client.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ impl FsClient {
4545
self.context.clone()
4646
}
4747

48+
/// Perform version handshake with master
49+
pub async fn handshake(&self) -> FsResult<()> {
50+
let client_version = env!("CARGO_PKG_VERSION");
51+
52+
let req = GetMasterInfoRequest {
53+
client_version: Some(client_version.to_string()),
54+
};
55+
56+
// This will trigger version check on master side
57+
let _: GetMasterInfoResponse = self.rpc(RpcCode::GetMasterInfo, req).await?;
58+
59+
log::info!("Client version {} handshake successful", client_version);
60+
Ok(())
61+
}
62+
4863
pub async fn mkdir(&self, path: &Path, opts: MkdirOpts) -> FsResult<FileStatus> {
4964
let header = MkdirRequest {
5065
path: path.encode(),

curvine-common/proto/master.proto

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ message GetBlockLocationsResponse {
175175

176176
// Request master profile information.
177177
message GetMasterInfoRequest {
178-
178+
// Client version (format: "major.minor.patch")
179+
optional string client_version = 1 [default = ""];
179180
}
180181

181182
message GetMasterInfoResponse {

curvine-common/src/conf/master_conf.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub struct MasterConf {
9090

9191
pub log: LogConf,
9292
pub min_worker_version: String,
93+
pub min_client_version: String,
9394

9495
pub ttl_checker_retry_attempts: u32,
9596

@@ -267,6 +268,7 @@ impl Default for MasterConf {
267268
block_replication_concurrency_limit: 1000,
268269
log: Default::default(),
269270
min_worker_version: "0.1.0".to_string(),
271+
min_client_version: "0.1.0".to_string(),
270272

271273
ttl_checker_retry_attempts: 3,
272274

curvine-server/src/master/fs/worker_manager.rs

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,56 +20,35 @@ use curvine_common::state::{
2020
BlockLocation, ExtendedBlock, HeartbeatStatus, LocatedBlock, StorageInfo, WorkerAddress,
2121
WorkerCommand, WorkerInfo, WorkerStatus,
2222
};
23-
use curvine_common::version::{CompatibilityPolicy, CompatibilityResult, Version, VersionChecker};
23+
use curvine_common::version::Version;
2424
use curvine_common::FsResult;
2525
use log::{info, warn};
2626
use orpc::common::ByteUnit;
2727
use orpc::{err_box, CommonResult};
2828
use std::collections::HashSet;
2929
use std::fmt::{Display, Formatter};
30-
use std::str::FromStr;
3130

3231
pub struct WorkerManager {
3332
pub(crate) worker_map: WorkerMap,
3433
pub(crate) block_map: BlockMap,
3534
pub(crate) worker_policy: WorkerPolicyAdapter,
3635
pub(crate) cluster_id: String,
3736
pub(crate) conf: ClusterConf,
38-
pub(crate) version_checker: VersionChecker,
3937
}
4038

4139
impl WorkerManager {
4240
pub fn new(conf: &ClusterConf) -> Self {
4341
let worker_policy = WorkerPolicyAdapter::from_conf(conf).unwrap();
4442

45-
// Parse master version and compatibility policy
46-
let master_version = Version::current();
47-
let policy = Self::parse_compatibility_policy(conf);
48-
let version_checker = VersionChecker::new(master_version, policy);
49-
5043
Self {
5144
worker_map: WorkerMap::new(),
5245
block_map: BlockMap::new(),
5346
worker_policy,
5447
cluster_id: conf.cluster_id.to_string(),
5548
conf: conf.clone(),
56-
version_checker,
5749
}
5850
}
5951

60-
fn parse_compatibility_policy(conf: &ClusterConf) -> CompatibilityPolicy {
61-
// Parse min_worker_version from configuration
62-
let min_version = Version::from_str(&conf.master.min_worker_version).unwrap_or_else(|e| {
63-
warn!(
64-
"Failed to parse min_worker_version '{}': {}, using default 0.1.0",
65-
conf.master.min_worker_version, e
66-
);
67-
Version::new(0, 1, 0)
68-
});
69-
70-
CompatibilityPolicy::new(min_version)
71-
}
72-
7352
pub fn heartbeat(
7453
&mut self,
7554
cluster_id: &str,
@@ -87,20 +66,8 @@ impl WorkerManager {
8766
);
8867
}
8968

90-
// Check version compatibility
91-
let compat_result = self.version_checker.check_compatibility(&version);
92-
if let CompatibilityResult::Incompatible(reason) = compat_result {
93-
warn!(
94-
"Worker {} registration rejected due to version incompatibility: {}",
95-
addr, reason
96-
);
97-
return Err(curvine_common::error::FsError::version_incompatible(reason));
98-
}
99-
100-
info!(
101-
"Worker {} registered successfully with version {}",
102-
addr, version
103-
);
69+
// Version check is performed in MasterHandler layer
70+
info!("Worker {} registered with version {}", addr, version);
10471

10572
let cmds = match status {
10673
HeartbeatStatus::Start => {

curvine-server/src/master/master_handler.rs

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use curvine_common::state::{
2828
CreateFileOpts, FileBlocks, FileStatus, HeartbeatStatus, OpenFlags, RenameFlags,
2929
};
3030
use curvine_common::utils::ProtoUtils;
31-
use curvine_common::version::Version;
31+
use curvine_common::version::{CompatibilityPolicy, CompatibilityResult, Version, VersionChecker};
3232
use curvine_common::FsResult;
3333
use orpc::err_box;
3434
use orpc::handler::MessageHandler;
@@ -46,6 +46,8 @@ pub struct MasterHandler {
4646
pub(crate) job_handler: JobHandler,
4747
pub(crate) mount_manager: Arc<MountManager>,
4848
pub(crate) replication_handler: Option<MasterReplicationHandler>,
49+
pub(crate) worker_version_checker: VersionChecker,
50+
pub(crate) client_version_checker: VersionChecker,
4951
}
5052

5153
impl MasterHandler {
@@ -59,6 +61,34 @@ impl MasterHandler {
5961
job_handler: JobHandler,
6062
replication_manager: Arc<MasterReplicationManager>,
6163
) -> Self {
64+
let master_version = Version::current();
65+
66+
// Initialize Worker version checker
67+
let worker_min_version =
68+
Version::from_str(&conf.master.min_worker_version).unwrap_or_else(|e| {
69+
warn!(
70+
"Failed to parse min_worker_version '{}': {}, using default 0.1.0",
71+
conf.master.min_worker_version, e
72+
);
73+
Version::new(0, 1, 0)
74+
});
75+
let worker_policy = CompatibilityPolicy::new(worker_min_version);
76+
let worker_version_checker = VersionChecker::new(master_version, worker_policy);
77+
78+
// Initialize Client version checker (looser policy - no upper bound)
79+
let client_min_version =
80+
Version::from_str(&conf.master.min_client_version).unwrap_or_else(|e| {
81+
warn!(
82+
"Failed to parse min_client_version '{}': {}, using default 0.1.0",
83+
conf.master.min_client_version, e
84+
);
85+
Version::new(0, 1, 0)
86+
});
87+
let client_policy = CompatibilityPolicy::new(client_min_version);
88+
// Client uses a very large upper bound for compatibility
89+
let client_version_checker =
90+
VersionChecker::new(Version::new(u32::MAX, u32::MAX, u32::MAX), client_policy);
91+
6292
Self {
6393
fs,
6494
retry_cache,
@@ -68,6 +98,8 @@ impl MasterHandler {
6898
mount_manager,
6999
job_handler,
70100
replication_handler: Some(MasterReplicationHandler::new(replication_manager)),
101+
worker_version_checker,
102+
client_version_checker,
71103
}
72104
}
73105

@@ -384,7 +416,36 @@ impl MasterHandler {
384416
}
385417

386418
pub fn get_master_info(&self, ctx: &mut RpcContext<'_>) -> FsResult<Message> {
387-
let _: GetMasterInfoRequest = ctx.parse_header()?;
419+
let header: GetMasterInfoRequest = ctx.parse_header()?;
420+
421+
// Check client version if provided
422+
if let Some(client_version_str) = &header.client_version {
423+
if !client_version_str.is_empty() {
424+
let client_version = Version::from_str(client_version_str).map_err(|e| {
425+
FsError::common(format!(
426+
"Failed to parse client version '{}': {}",
427+
client_version_str, e
428+
))
429+
})?;
430+
431+
let compat_result = self
432+
.client_version_checker
433+
.check_compatibility(&client_version);
434+
if let CompatibilityResult::Incompatible(reason) = compat_result {
435+
warn!("Client version {} rejected: {}", client_version, reason);
436+
return Err(FsError::version_incompatible(reason));
437+
}
438+
439+
// Version check passed
440+
log::info!("Client version {} accepted", client_version);
441+
} else {
442+
// Empty version string - backward compatibility
443+
warn!("Client connected without version information (backward compatibility mode)");
444+
}
445+
} else {
446+
// No version field - old client (backward compatibility)
447+
warn!("Client connected without version information (backward compatibility mode)");
448+
}
388449

389450
let info = self.fs.master_info()?;
390451
let rep_header = ProtoUtils::master_info_to_pb(info);
@@ -393,7 +454,6 @@ impl MasterHandler {
393454

394455
pub fn worker_heartbeat(&self, ctx: &mut RpcContext<'_>) -> FsResult<Message> {
395456
let header: WorkerHeartbeatRequest = ctx.parse_header()?;
396-
let mut wm = self.fs.worker_manager.write();
397457

398458
// Parse worker version from request
399459
let worker_version_str = &header.version;
@@ -410,10 +470,25 @@ impl MasterHandler {
410470
Version::new(0, 1, 0)
411471
};
412472

473+
// Check version compatibility at Handler layer
474+
let worker_addr = ProtoUtils::worker_address_from_pb(&header.address);
475+
let compat_result = self
476+
.worker_version_checker
477+
.check_compatibility(&worker_version);
478+
if let CompatibilityResult::Incompatible(reason) = compat_result {
479+
warn!(
480+
"Worker {} registration rejected due to version incompatibility: {}",
481+
worker_addr, reason
482+
);
483+
return Err(FsError::version_incompatible(reason));
484+
}
485+
486+
// Version check passed, proceed with heartbeat
487+
let mut wm = self.fs.worker_manager.write();
413488
let cmds = wm.heartbeat(
414489
&header.cluster_id,
415490
HeartbeatStatus::from(header.status),
416-
ProtoUtils::worker_address_from_pb(&header.address),
491+
worker_addr,
417492
ProtoUtils::storage_info_list_from_pb(header.storages),
418493
worker_version,
419494
)?;

etc/curvine-cluster.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ format_worker = false
1010
[master]
1111
# Minimum supported worker version (backward compatibility)
1212
min_worker_version = "0.1.0"
13+
# Minimum supported client version (backward compatibility)
14+
min_client_version = "0.1.0"
1315
meta_dir = "testing/meta"
1416
log = { level = "info", log_dir = "stdout", file_name = "master.log" }
1517

0 commit comments

Comments
 (0)