diff --git a/crates/xlineapi/src/classifier.rs b/crates/xlineapi/src/classifier.rs new file mode 100644 index 000000000..8edcaef77 --- /dev/null +++ b/crates/xlineapi/src/classifier.rs @@ -0,0 +1,137 @@ +use super::RequestWrapper; + +/// Backend store of request +#[allow(missing_docs)] +#[derive(Debug, PartialEq, Eq)] +pub enum RequestBackend { + Kv, + Auth, + Lease, + Alarm, +} + +impl From<&RequestWrapper> for RequestBackend { + fn from(value: &RequestWrapper) -> Self { + match *value { + RequestWrapper::PutRequest(_) + | RequestWrapper::RangeRequest(_) + | RequestWrapper::DeleteRangeRequest(_) + | RequestWrapper::TxnRequest(_) + | RequestWrapper::CompactionRequest(_) => RequestBackend::Kv, + RequestWrapper::AuthEnableRequest(_) + | RequestWrapper::AuthDisableRequest(_) + | RequestWrapper::AuthStatusRequest(_) + | RequestWrapper::AuthRoleAddRequest(_) + | RequestWrapper::AuthRoleDeleteRequest(_) + | RequestWrapper::AuthRoleGetRequest(_) + | RequestWrapper::AuthRoleGrantPermissionRequest(_) + | RequestWrapper::AuthRoleListRequest(_) + | RequestWrapper::AuthRoleRevokePermissionRequest(_) + | RequestWrapper::AuthUserAddRequest(_) + | RequestWrapper::AuthUserChangePasswordRequest(_) + | RequestWrapper::AuthUserDeleteRequest(_) + | RequestWrapper::AuthUserGetRequest(_) + | RequestWrapper::AuthUserGrantRoleRequest(_) + | RequestWrapper::AuthUserListRequest(_) + | RequestWrapper::AuthUserRevokeRoleRequest(_) + | RequestWrapper::AuthenticateRequest(_) => RequestBackend::Auth, + RequestWrapper::LeaseGrantRequest(_) + | RequestWrapper::LeaseRevokeRequest(_) + | RequestWrapper::LeaseLeasesRequest(_) => RequestBackend::Lease, + RequestWrapper::AlarmRequest(_) => RequestBackend::Alarm, + } + } +} + +/// Type of request. This is the extending of [`RequestBackend`]. +#[allow(missing_docs)] +#[derive(Debug, PartialEq, Eq)] +pub enum RequestType { + Put, + Compaction, + Txn, + Range, + Auth, + Lease, + Alarm, +} + +impl From<&RequestWrapper> for RequestType { + fn from(value: &RequestWrapper) -> Self { + match *value { + RequestWrapper::PutRequest(_) => RequestType::Put, + RequestWrapper::RangeRequest(_) | RequestWrapper::DeleteRangeRequest(_) => { + RequestType::Range + } + RequestWrapper::TxnRequest(_) => RequestType::Txn, + RequestWrapper::CompactionRequest(_) => RequestType::Compaction, + RequestWrapper::AuthEnableRequest(_) + | RequestWrapper::AuthDisableRequest(_) + | RequestWrapper::AuthStatusRequest(_) + | RequestWrapper::AuthRoleAddRequest(_) + | RequestWrapper::AuthRoleDeleteRequest(_) + | RequestWrapper::AuthRoleGetRequest(_) + | RequestWrapper::AuthRoleGrantPermissionRequest(_) + | RequestWrapper::AuthRoleListRequest(_) + | RequestWrapper::AuthRoleRevokePermissionRequest(_) + | RequestWrapper::AuthUserAddRequest(_) + | RequestWrapper::AuthUserChangePasswordRequest(_) + | RequestWrapper::AuthUserDeleteRequest(_) + | RequestWrapper::AuthUserGetRequest(_) + | RequestWrapper::AuthUserGrantRoleRequest(_) + | RequestWrapper::AuthUserListRequest(_) + | RequestWrapper::AuthUserRevokeRoleRequest(_) + | RequestWrapper::AuthenticateRequest(_) => RequestType::Auth, + RequestWrapper::LeaseGrantRequest(_) + | RequestWrapper::LeaseRevokeRequest(_) + | RequestWrapper::LeaseLeasesRequest(_) => RequestType::Lease, + RequestWrapper::AlarmRequest(_) => RequestType::Alarm, + } + } +} + +/// indicates if the request is readonly or write +#[derive(Debug, PartialEq, Eq)] +pub enum RequestRw { + /// Read only request + Read, + /// Write request. + /// + /// NOTE: A `TxnRequest` or a `DeleteRangeRequest` might be read-only, but we + /// assume they will mutate the state machine to simplify the implementation. + Write, +} + +impl From<&RequestWrapper> for RequestRw { + fn from(value: &RequestWrapper) -> Self { + match *value { + RequestWrapper::RangeRequest(_) + | RequestWrapper::AuthStatusRequest(_) + | RequestWrapper::AuthRoleGetRequest(_) + | RequestWrapper::AuthRoleListRequest(_) + | RequestWrapper::AuthUserGetRequest(_) + | RequestWrapper::AuthUserListRequest(_) + | RequestWrapper::LeaseLeasesRequest(_) => Self::Read, + + RequestWrapper::PutRequest(_) + | RequestWrapper::DeleteRangeRequest(_) + | RequestWrapper::TxnRequest(_) + | RequestWrapper::CompactionRequest(_) + | RequestWrapper::AuthEnableRequest(_) + | RequestWrapper::AuthDisableRequest(_) + | RequestWrapper::AuthRoleAddRequest(_) + | RequestWrapper::AuthRoleDeleteRequest(_) + | RequestWrapper::AuthRoleGrantPermissionRequest(_) + | RequestWrapper::AuthRoleRevokePermissionRequest(_) + | RequestWrapper::AuthUserAddRequest(_) + | RequestWrapper::AuthUserChangePasswordRequest(_) + | RequestWrapper::AuthUserDeleteRequest(_) + | RequestWrapper::AuthUserGrantRoleRequest(_) + | RequestWrapper::AuthUserRevokeRoleRequest(_) + | RequestWrapper::AuthenticateRequest(_) + | RequestWrapper::LeaseGrantRequest(_) + | RequestWrapper::LeaseRevokeRequest(_) + | RequestWrapper::AlarmRequest(_) => Self::Write, + } + } +} diff --git a/crates/xlineapi/src/command.rs b/crates/xlineapi/src/command.rs index 28aa44f63..4ad1a3bdd 100644 --- a/crates/xlineapi/src/command.rs +++ b/crates/xlineapi/src/command.rs @@ -10,8 +10,10 @@ use prost::Message; use serde::{Deserialize, Serialize}; use crate::{ - execute_error::ExecuteError, AuthInfo, PbCommand, PbCommandResponse, PbKeyRange, - PbSyncResponse, RequestWrapper, ResponseWrapper, + classifier::{RequestBackend, RequestRw, RequestType}, + execute_error::ExecuteError, + AuthInfo, PbCommand, PbCommandResponse, PbKeyRange, PbSyncResponse, RequestWrapper, + ResponseWrapper, }; /// The curp client trait object on the command of xline @@ -221,67 +223,119 @@ pub struct Command { auth_info: Option, } -impl ConflictCheck for Command { - #[inline] - fn is_conflict(&self, other: &Self) -> bool { - let this_req = &self.request; - let other_req = &other.request; - // auth read request will not conflict with any request except the auth write request - if (this_req.is_auth_read_request() && other_req.is_auth_read_request()) - || (this_req.is_kv_request() && other_req.is_auth_read_request()) - || (this_req.is_auth_read_request() && other_req.is_kv_request()) - { - return false; - } - // any two requests that don't meet the above conditions will conflict with each other - // because the auth write request will make all previous token invalid - if this_req.is_auth_request() - || other_req.is_auth_request() - || this_req.is_alarm_request() - || other_req.is_alarm_request() - { - return true; - } +/// Match all Classifiers seperated by `&` +/// +/// # Returns +/// +/// `Fn(x) -> bool` indicates the match result. +/// +/// # Example +/// +/// ```ignore +/// match_all!(Class1::Tag1 & Class2::Tag2)(x) +/// ``` +macro_rules! match_all { + ($($cls:ident :: $tag:ident)&*) => { + |_x| $(matches!($cls::from(_x), $cls::$tag))&&* + }; +} +pub(crate) use match_all; // used by lib.rs - // Lease leases request is conflict with Lease grant and revoke requests - if (this_req.is_lease_read_request() && other_req.is_lease_write_request()) - || (this_req.is_lease_write_request() && other_req.is_lease_read_request()) - { - return true; +/// swapable match, returns a `Fn(x, y) -> bool` indicates the match result. +/// +/// # Returns +/// +/// Returns `Fn(x, y) -> true` if *x match Class1 and y match Class2*, or *x match Class2 and y match Class1*. +macro_rules! swap_match { + + ($($cls1:ident::$tag1:ident)&*, _) => {{ + |_x, _| match_all!($($cls1::$tag1)&*)(_x) + }}; + ($($cls1:ident :: $tag1:ident)&*, $($cls2:ident :: $tag2:ident)&*) => { + |_x, _y| { + (match_all!($($cls1::$tag1)&*)(_x) && match_all!($($cls2::$tag2)&*)(_y)) || ( + match_all!($($cls2::$tag2)&*)(_x) && match_all!($($cls1::$tag1)&*)(_y) + ) } + }; +} - if this_req.is_compaction_request() && other_req.is_compaction_request() { - return true; - } +/// swapable map, to swappable match two `RequestWrapper`, extract the inner request +/// and calculate the inners into an `Option`. +/// This can be only used in the same enum and different variant. +/// +/// # Returns +/// +/// `Fn(x, y) -> Option` indicates the swap_map result. +macro_rules! swap_map { + ($cls1:ident :: $tag1:ident, $cls2:ident :: $tag2:ident, |$x:ident, $y:ident| $body:expr) => { + (|_self, _other| match (_self, _other) { + (&$cls1::$tag1(ref $x), &$cls2::$tag2(ref $y)) + | (&$cls2::$tag2(ref $y), &$cls1::$tag1(ref $x)) => Some($body), + _ => None, + }) + }; +} - if (this_req.is_txn_request() && other_req.is_compaction_request()) - || (this_req.is_compaction_request() && other_req.is_txn_request()) - { - match (this_req, other_req) { - ( - &RequestWrapper::CompactionRequest(ref com_req), - &RequestWrapper::TxnRequest(ref txn_req), - ) - | ( - &RequestWrapper::TxnRequest(ref txn_req), - &RequestWrapper::CompactionRequest(ref com_req), - ) => { - let target_revision = com_req.revision; - return txn_req.is_conflict_with_rev(target_revision) - } - _ => unreachable!("The request must be either a transaction or a compaction request! \nthis_req = {this_req:?} \nother_req = {other_req:?}") - } +/// Conflict check, contains the whole `match` statement +/// +/// # Returns +/// +/// `Fn(x, y) -> Option` indicates the conflict result. +macro_rules! is_conflict { + ($(($body:expr, $($cls1:ident :: $tag1:ident)&*, $($pat:tt)*)),*) => { + |_self, _other| match (_self, _other) { + $((x, y) if swap_match!($($cls1::$tag1)&*, $($pat)*)(x, y) => Some($body),)* + _ => None, } + }; +} - let this_lease_ids = this_req.leases().into_iter().collect::>(); - let other_lease_ids = other_req.leases().into_iter().collect::>(); - let lease_conflict = !this_lease_ids.is_disjoint(&other_lease_ids); - let key_conflict = self - .keys() - .iter() - .cartesian_product(other.keys().iter()) - .any(|(k1, k2)| k1.is_conflicted(k2)); - lease_conflict || key_conflict +impl ConflictCheck for Command { + #[inline] + fn is_conflict(&self, other: &Self) -> bool { + let t_req = &self.request; + let o_req = &other.request; + is_conflict!( + // auth read request will not conflict with any request except the auth write request + ( + true, + RequestBackend::Auth & RequestRw::Read, + RequestBackend::Auth & RequestRw::Write + ), + (false, RequestBackend::Auth & RequestRw::Read, _), + // any two requests that don't meet the above conditions will conflict with each other + // because the auth write request will make all previous token invalid + (true, RequestBackend::Auth & RequestRw::Write, _), + (true, RequestBackend::Alarm, _), + // Lease leases request is conflict with Lease grant and revoke requests + ( + true, + RequestBackend::Lease & RequestRw::Read, + RequestBackend::Lease & RequestRw::Write + ), + (true, RequestType::Compaction, RequestType::Compaction) + )(t_req, o_req) + .or_else(|| { + swap_map!( + RequestWrapper::TxnRequest, + RequestWrapper::CompactionRequest, + |x, y| x.is_conflict_with_rev(y.revision) + )(t_req, o_req) + }) + // the fallback map + .or_else(|| { + let this_lease_ids = t_req.leases().into_iter().collect::>(); + let other_lease_ids = o_req.leases().into_iter().collect::>(); + let lease_conflict = !this_lease_ids.is_disjoint(&other_lease_ids); + let key_conflict = self + .keys() + .iter() + .cartesian_product(other.keys().iter()) + .any(|(k1, k2)| k1.is_conflict(k2)); + Some(lease_conflict || key_conflict) + }) + .unwrap_or_default() } } @@ -476,7 +530,7 @@ impl CurpCommand for Command { #[inline] fn is_read_only(&self) -> bool { - self.request().is_read_only() + match_all!(RequestRw::Read)(self.request()) } } diff --git a/crates/xlineapi/src/lib.rs b/crates/xlineapi/src/lib.rs index c152912b8..02e6befbb 100644 --- a/crates/xlineapi/src/lib.rs +++ b/crates/xlineapi/src/lib.rs @@ -172,6 +172,7 @@ ) )] +pub mod classifier; pub mod command; pub mod execute_error; pub mod interval; @@ -212,6 +213,7 @@ use utils::write_vec; pub use self::{ authpb::{permission::Type, Permission, Role, User, UserAddOptions}, + classifier::{RequestBackend, RequestRw}, commandpb::{ command::{AuthInfo, RequestWrapper}, command_response::ResponseWrapper, @@ -273,6 +275,8 @@ pub use self::{ }, }; +use crate::command::match_all; + impl User { /// Check if user has the given role pub fn has_role(&self, role: &str) -> bool { @@ -317,19 +321,6 @@ impl ResponseWrapper { } } -/// Backend store of request -#[derive(Debug, PartialEq, Eq)] -pub enum RequestBackend { - /// Kv backend - Kv, - /// Auth backend - Auth, - /// Lease backend - Lease, - /// Alarm backend - Alarm, -} - /// Command attributes pub trait CommandAttr { /// Key ranges @@ -456,87 +447,12 @@ impl RequestWrapper { /// Get the backend of the request pub fn backend(&self) -> RequestBackend { - match *self { - RequestWrapper::PutRequest(_) - | RequestWrapper::RangeRequest(_) - | RequestWrapper::DeleteRangeRequest(_) - | RequestWrapper::TxnRequest(_) - | RequestWrapper::CompactionRequest(_) => RequestBackend::Kv, - RequestWrapper::AuthEnableRequest(_) - | RequestWrapper::AuthDisableRequest(_) - | RequestWrapper::AuthStatusRequest(_) - | RequestWrapper::AuthRoleAddRequest(_) - | RequestWrapper::AuthRoleDeleteRequest(_) - | RequestWrapper::AuthRoleGetRequest(_) - | RequestWrapper::AuthRoleGrantPermissionRequest(_) - | RequestWrapper::AuthRoleListRequest(_) - | RequestWrapper::AuthRoleRevokePermissionRequest(_) - | RequestWrapper::AuthUserAddRequest(_) - | RequestWrapper::AuthUserChangePasswordRequest(_) - | RequestWrapper::AuthUserDeleteRequest(_) - | RequestWrapper::AuthUserGetRequest(_) - | RequestWrapper::AuthUserGrantRoleRequest(_) - | RequestWrapper::AuthUserListRequest(_) - | RequestWrapper::AuthUserRevokeRoleRequest(_) - | RequestWrapper::AuthenticateRequest(_) => RequestBackend::Auth, - RequestWrapper::LeaseGrantRequest(_) - | RequestWrapper::LeaseRevokeRequest(_) - | RequestWrapper::LeaseLeasesRequest(_) => RequestBackend::Lease, - RequestWrapper::AlarmRequest(_) => RequestBackend::Alarm, - } - } - - /// Checks if this request is read only - /// - /// NOTE: A `TxnRequest` or a `DeleteRangeRequest` might be read-only, but we - /// assume they will mutate the state machine to simplify the implementation. - pub fn is_read_only(&self) -> bool { - match *self { - RequestWrapper::RangeRequest(_) - | RequestWrapper::AuthStatusRequest(_) - | RequestWrapper::AuthRoleGetRequest(_) - | RequestWrapper::AuthRoleListRequest(_) - | RequestWrapper::AuthUserGetRequest(_) - | RequestWrapper::AuthUserListRequest(_) - | RequestWrapper::LeaseLeasesRequest(_) => true, - - RequestWrapper::PutRequest(_) - | RequestWrapper::DeleteRangeRequest(_) - | RequestWrapper::TxnRequest(_) - | RequestWrapper::CompactionRequest(_) - | RequestWrapper::AuthEnableRequest(_) - | RequestWrapper::AuthDisableRequest(_) - | RequestWrapper::AuthRoleAddRequest(_) - | RequestWrapper::AuthRoleDeleteRequest(_) - | RequestWrapper::AuthRoleGrantPermissionRequest(_) - | RequestWrapper::AuthRoleRevokePermissionRequest(_) - | RequestWrapper::AuthUserAddRequest(_) - | RequestWrapper::AuthUserChangePasswordRequest(_) - | RequestWrapper::AuthUserDeleteRequest(_) - | RequestWrapper::AuthUserGrantRoleRequest(_) - | RequestWrapper::AuthUserRevokeRoleRequest(_) - | RequestWrapper::AuthenticateRequest(_) - | RequestWrapper::LeaseGrantRequest(_) - | RequestWrapper::LeaseRevokeRequest(_) - | RequestWrapper::AlarmRequest(_) => false, - } - } - - /// Check if this request is a auth read request - pub fn is_auth_read_request(&self) -> bool { - matches!( - *self, - RequestWrapper::AuthStatusRequest(_) - | RequestWrapper::AuthRoleGetRequest(_) - | RequestWrapper::AuthRoleListRequest(_) - | RequestWrapper::AuthUserGetRequest(_) - | RequestWrapper::AuthUserListRequest(_) - ) + RequestBackend::from(self) } /// Check whether this auth request should skip the revision or not pub fn skip_auth_revision(&self) -> bool { - self.is_auth_read_request() + match_all!(RequestRw::Read)(self) || matches!( *self, RequestWrapper::AuthEnableRequest(_) | RequestWrapper::AuthenticateRequest(_) @@ -553,39 +469,6 @@ impl RequestWrapper { _ => false, } } - - /// Check if this request is a auth request - pub fn is_auth_request(&self) -> bool { - self.backend() == RequestBackend::Auth - } - - /// Check if this request is a kv request - pub fn is_kv_request(&self) -> bool { - self.backend() == RequestBackend::Kv - } - - pub fn is_compaction_request(&self) -> bool { - matches!(*self, RequestWrapper::CompactionRequest(_)) - } - - pub fn is_txn_request(&self) -> bool { - matches!(*self, RequestWrapper::TxnRequest(_)) - } - - pub fn is_lease_read_request(&self) -> bool { - matches!(*self, RequestWrapper::LeaseLeasesRequest(_)) - } - - pub fn is_lease_write_request(&self) -> bool { - matches!( - *self, - RequestWrapper::LeaseGrantRequest(_) | RequestWrapper::LeaseRevokeRequest(_) - ) - } - - pub fn is_alarm_request(&self) -> bool { - matches!(*self, RequestWrapper::AlarmRequest(_)) - } } /// impl `From` trait for all request types