Add concurrent structures, and initial attempt at benchmarking (#173)

This commit is contained in:
Firstyear 2020-01-26 19:55:53 +10:00 committed by GitHub
parent 4470311f84
commit 1e7ba58fe8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 480 additions and 353 deletions

View file

@ -20,6 +20,10 @@ path = "src/lib/lib.rs"
name = "kanidmd"
path = "src/server/main.rs"
# [[bench]]
# name = "kanidm_benchmark"
# harness = false
[dependencies]
kanidm_proto = { path = "../kanidm_proto", version = "0.1" }
@ -64,3 +68,6 @@ num_cpus = "1.10"
idlset = "0.1"
zxcvbn = "2.0"
[dev-dependencies]
criterion = "0.3"

View file

@ -0,0 +1,28 @@
use criterion::{criterion_group, criterion_main, Criterion};
#[macro_use]
use kanidm;
use kanidm::constants::UUID_ADMIN;
pub fn criterion_benchmark_search_1(c: &mut Criterion) {
// Setup
//
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
let filt = filter!(f_eq("name", PartialValue::new_iutf8s("testperson")));
let admin = server_txn
.internal_search_uuid(audit, &UUID_ADMIN)
.expect("failed");
let se1 = unsafe { SearchEvent::new_impersonate_entry(admin.clone(), filt.clone()) };
c.bench_function("search 2000", |b| {
b.iter(|| {
let r1 = server_txn.search(audit, &se1).expect("search failure");
assert!(r1.is_empty());
})
});
})
}
criterion_group!(benches, criterion_benchmark_search_1);
criterion_main!(benches);

View file

@ -15,10 +15,10 @@
// requirements (also search).
//
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
use concread::collections::bptree::*;
use kanidm_proto::v1::Filter as ProtoFilter;
use kanidm_proto::v1::OperationError;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeSet;
use uuid::Uuid;
use crate::audit::AuditScope;
@ -338,32 +338,19 @@ impl AccessControlProfile {
// ACP transactions and management for server bits.
// =========================================================================
#[derive(Debug, Clone)]
pub struct AccessControlsInner {
// What is the correct key here?
acps_search: BTreeMap<Uuid, AccessControlSearch>,
acps_create: BTreeMap<Uuid, AccessControlCreate>,
acps_modify: BTreeMap<Uuid, AccessControlModify>,
acps_delete: BTreeMap<Uuid, AccessControlDelete>,
}
impl AccessControlsInner {
fn new() -> Self {
AccessControlsInner {
acps_search: BTreeMap::new(),
acps_create: BTreeMap::new(),
acps_modify: BTreeMap::new(),
acps_delete: BTreeMap::new(),
}
}
}
pub struct AccessControls {
inner: CowCell<AccessControlsInner>,
// inner: CowCell<AccessControlsInner>,
acps_search: BptreeMap<Uuid, AccessControlSearch>,
acps_create: BptreeMap<Uuid, AccessControlCreate>,
acps_modify: BptreeMap<Uuid, AccessControlModify>,
acps_delete: BptreeMap<Uuid, AccessControlDelete>,
}
pub trait AccessControlsTransaction {
fn get_inner(&self) -> &AccessControlsInner;
fn get_search(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlSearch>;
fn get_create(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlCreate>;
fn get_modify(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlModify>;
fn get_delete(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlDelete>;
// Contains all the way to eval acps to entries
fn search_filter_entries(
@ -385,11 +372,10 @@ pub trait AccessControlsTransaction {
};
// Some useful references we'll use for the remainder of the operation
let state = self.get_inner();
let search_state = self.get_search();
// First get the set of acps that apply to this receiver
let related_acp: Vec<&AccessControlSearch> = state
.acps_search
let related_acp: Vec<&AccessControlSearch> = search_state
.iter()
.filter_map(|(_, acs)| {
// Now resolve the receiver filter
@ -537,11 +523,10 @@ pub trait AccessControlsTransaction {
};
// Some useful references we'll use for the remainder of the operation
let state = self.get_inner();
let search_state = self.get_search();
// Get the relevant acps for this receiver.
let related_acp: Vec<&AccessControlSearch> = state
.acps_search
let related_acp: Vec<&AccessControlSearch> = search_state
.iter()
.filter_map(|(_, acs)| {
let f_val = acs.acp.receiver.clone();
@ -674,7 +659,7 @@ pub trait AccessControlsTransaction {
};
// Some useful references we'll use for the remainder of the operation
let state = self.get_inner();
let modify_state = self.get_modify();
// Pre-check if the no-no purge class is present
let disallow = me.modlist.iter().fold(false, |acc, m| {
@ -693,8 +678,7 @@ pub trait AccessControlsTransaction {
}
// Find the acps that relate to the caller.
let related_acp: Vec<&AccessControlModify> = state
.acps_modify
let related_acp: Vec<&AccessControlModify> = modify_state
.iter()
.filter_map(|(_, acs)| {
let f_val = acs.acp.receiver.clone();
@ -868,11 +852,10 @@ pub trait AccessControlsTransaction {
};
// Some useful references we'll use for the remainder of the operation
let state = self.get_inner();
let create_state = self.get_create();
// Find the acps that relate to the caller.
let related_acp: Vec<&AccessControlCreate> = state
.acps_create
let related_acp: Vec<&AccessControlCreate> = create_state
.iter()
.filter_map(|(_, acs)| {
let f_val = acs.acp.receiver.clone();
@ -1028,11 +1011,10 @@ pub trait AccessControlsTransaction {
};
// Some useful references we'll use for the remainder of the operation
let state = self.get_inner();
let delete_state = self.get_delete();
// Find the acps that relate to the caller.
let related_acp: Vec<&AccessControlDelete> = state
.acps_delete
let related_acp: Vec<&AccessControlDelete> = delete_state
.iter()
.filter_map(|(_, acs)| {
let f_val = acs.acp.receiver.clone();
@ -1113,14 +1095,14 @@ pub trait AccessControlsTransaction {
}
pub struct AccessControlsWriteTransaction<'a> {
inner: CowCellWriteTxn<'a, AccessControlsInner>,
// inner: CowCellWriteTxn<'a, AccessControlsInner>,
acps_search: BptreeMapWriteTxn<'a, Uuid, AccessControlSearch>,
acps_create: BptreeMapWriteTxn<'a, Uuid, AccessControlCreate>,
acps_modify: BptreeMapWriteTxn<'a, Uuid, AccessControlModify>,
acps_delete: BptreeMapWriteTxn<'a, Uuid, AccessControlDelete>,
}
impl<'a> AccessControlsWriteTransaction<'a> {
fn get_inner_mut(&mut self) -> &mut AccessControlsInner {
&mut self.inner
}
// We have a method to update each set, so that if an error
// occurs we KNOW it's an error, rather than using errors as
// part of the logic (IE try-parse-fail method).
@ -1128,54 +1110,78 @@ impl<'a> AccessControlsWriteTransaction<'a> {
// Clear the existing tree. We don't care that we are wiping it
// because we have the transactions to protect us from errors
// to allow rollbacks.
let inner = self.get_inner_mut();
inner.acps_search.clear();
self.acps_search.clear();
for acp in acps {
let uuid = acp.acp.uuid;
inner.acps_search.insert(uuid, acp);
self.acps_search.insert(uuid, acp);
}
self.acps_search.compact();
Ok(())
}
pub fn update_create(&mut self, acps: Vec<AccessControlCreate>) -> Result<(), OperationError> {
let inner = self.get_inner_mut();
inner.acps_create.clear();
self.acps_create.clear();
for acp in acps {
let uuid = acp.acp.uuid;
inner.acps_create.insert(uuid, acp);
self.acps_create.insert(uuid, acp);
}
self.acps_create.compact();
Ok(())
}
pub fn update_modify(&mut self, acps: Vec<AccessControlModify>) -> Result<(), OperationError> {
let inner = self.get_inner_mut();
inner.acps_modify.clear();
self.acps_modify.clear();
for acp in acps {
let uuid = acp.acp.uuid;
inner.acps_modify.insert(uuid, acp);
self.acps_modify.insert(uuid, acp);
}
self.acps_modify.compact();
Ok(())
}
pub fn update_delete(&mut self, acps: Vec<AccessControlDelete>) -> Result<(), OperationError> {
let inner = self.get_inner_mut();
inner.acps_delete.clear();
self.acps_delete.clear();
for acp in acps {
let uuid = acp.acp.uuid;
inner.acps_delete.insert(uuid, acp);
self.acps_delete.insert(uuid, acp);
}
// We could consider compact here ...
self.acps_delete.compact();
Ok(())
}
pub fn commit(self) -> Result<(), OperationError> {
self.inner.commit();
let AccessControlsWriteTransaction {
acps_search,
acps_create,
acps_modify,
acps_delete,
} = self;
acps_search.commit();
acps_create.commit();
acps_modify.commit();
acps_delete.commit();
Ok(())
}
}
impl<'a> AccessControlsTransaction for AccessControlsWriteTransaction<'a> {
fn get_inner(&self) -> &AccessControlsInner {
&self.inner
fn get_search(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlSearch> {
self.acps_search.to_snapshot()
}
fn get_create(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlCreate> {
self.acps_create.to_snapshot()
}
fn get_modify(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlModify> {
self.acps_modify.to_snapshot()
}
fn get_delete(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlDelete> {
self.acps_delete.to_snapshot()
}
}
@ -1184,12 +1190,27 @@ impl<'a> AccessControlsTransaction for AccessControlsWriteTransaction<'a> {
// =========================================================================
pub struct AccessControlsReadTransaction {
inner: CowCellReadTxn<AccessControlsInner>,
acps_search: BptreeMapReadTxn<Uuid, AccessControlSearch>,
acps_create: BptreeMapReadTxn<Uuid, AccessControlCreate>,
acps_modify: BptreeMapReadTxn<Uuid, AccessControlModify>,
acps_delete: BptreeMapReadTxn<Uuid, AccessControlDelete>,
}
impl AccessControlsTransaction for AccessControlsReadTransaction {
fn get_inner(&self) -> &AccessControlsInner {
&self.inner
fn get_search(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlSearch> {
self.acps_search.to_snapshot()
}
fn get_create(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlCreate> {
self.acps_create.to_snapshot()
}
fn get_modify(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlModify> {
self.acps_modify.to_snapshot()
}
fn get_delete(&self) -> BptreeMapReadSnapshot<Uuid, AccessControlDelete> {
self.acps_delete.to_snapshot()
}
}
@ -1200,19 +1221,31 @@ impl AccessControlsTransaction for AccessControlsReadTransaction {
impl AccessControls {
pub fn new() -> Self {
AccessControls {
inner: CowCell::new(AccessControlsInner::new()),
// inner: CowCell::new(AccessControlsInner::new()),
acps_search: BptreeMap::new(),
acps_create: BptreeMap::new(),
acps_modify: BptreeMap::new(),
acps_delete: BptreeMap::new(),
}
}
pub fn read(&self) -> AccessControlsReadTransaction {
AccessControlsReadTransaction {
inner: self.inner.read(),
// inner: self.inner.read(),
acps_search: self.acps_search.read(),
acps_create: self.acps_create.read(),
acps_modify: self.acps_modify.read(),
acps_delete: self.acps_delete.read(),
}
}
pub fn write(&self) -> AccessControlsWriteTransaction {
AccessControlsWriteTransaction {
inner: self.inner.write(),
// inner: self.inner.write(),
acps_search: self.acps_search.write(),
acps_create: self.acps_create.write(),
acps_modify: self.acps_modify.write(),
acps_delete: self.acps_delete.write(),
}
}
}

View file

@ -1202,7 +1202,7 @@ pub fn restore_server_core(config: Configuration, dst_path: &str) {
};
// Limit the scope of the schema txn.
let idxmeta = { schema.write().get_idxmeta() };
let idxmeta = { schema.write().get_idxmeta_set() };
let mut be_wr_txn = be.write(idxmeta);
let r = be_wr_txn
@ -1266,7 +1266,7 @@ pub fn reindex_server_core(config: Configuration) {
info!("Start Index Phase 1 ...");
// Limit the scope of the schema txn.
let idxmeta = { schema.write().get_idxmeta() };
let idxmeta = { schema.write().get_idxmeta_set() };
// Reindex only the core schema attributes to bootstrap the process.
let be_wr_txn = be.write(idxmeta);

View file

@ -17,8 +17,7 @@ use kanidm_proto::v1::AuthState;
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::RadiusAuthToken;
use concread::cowcell::{CowCell, CowCellWriteTxn};
use std::collections::BTreeMap;
use concread::collections::bptree::*;
use std::time::Duration;
use uuid::Uuid;
use zxcvbn;
@ -28,7 +27,7 @@ pub struct IdmServer {
// means that limits to sessions can be easily applied and checked to
// variaous accounts, and we have a good idea of how to structure the
// in memory caches related to locking.
sessions: CowCell<BTreeMap<Uuid, AuthSession>>,
sessions: BptreeMap<Uuid, AuthSession>,
// Need a reference to the query server.
qs: QueryServer,
// thread/server id
@ -39,7 +38,7 @@ pub struct IdmServerWriteTransaction<'a> {
// Contains methods that require writes, but in the context of writing to
// the idm in memory structures (maybe the query server too). This is
// things like authentication
sessions: CowCellWriteTxn<'a, BTreeMap<Uuid, AuthSession>>,
sessions: BptreeMapWriteTxn<'a, Uuid, AuthSession>,
qs: &'a QueryServer,
sid: &'a SID,
}
@ -60,7 +59,7 @@ impl IdmServer {
// TODO #59: Make number of authsessions configurable!!!
pub fn new(qs: QueryServer, sid: SID) -> IdmServer {
IdmServer {
sessions: CowCell::new(BTreeMap::new()),
sessions: BptreeMap::new(),
qs,
sid,
}
@ -97,9 +96,8 @@ impl<'a> IdmServerWriteTransaction<'a> {
// ct is current time - sub the timeout. and then split.
let expire = ct - Duration::from_secs(AUTH_SESSION_TIMEOUT);
let split_at = uuid_from_duration(expire, *self.sid);
let valid = self.sessions.split_off(&split_at);
// swap them?
*self.sessions = valid;
// Removes older sessions in place.
self.sessions.split_off_lt(&split_at);
// expired will now be dropped, and can't be used by future sessions.
}
@ -189,7 +187,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
// Do we have a session?
let auth_session = try_audit!(
au,
(*self.sessions)
self.sessions
// Why is the session missing?
.get_mut(&creds.sessionid)
.ok_or(OperationError::InvalidSessionState)

View file

@ -300,9 +300,13 @@ impl Plugins {
cand: &[Entry<EntryValid, EntryNew>],
ce: &CreateEvent,
) -> Result<(), OperationError> {
audit_segment!(au, || {
run_pre_create_plugin!(au, qs, cand, ce, protected::Protected)
})
audit_segment!(au, || run_pre_create_plugin!(
au,
qs,
cand,
ce,
protected::Protected
))
}
pub fn run_post_create(
@ -311,10 +315,20 @@ impl Plugins {
cand: &[Entry<EntryValid, EntryCommitted>],
ce: &CreateEvent,
) -> Result<(), OperationError> {
audit_segment!(au, || {
run_post_create_plugin!(au, qs, cand, ce, refint::ReferentialIntegrity)
.and_then(|_| run_post_create_plugin!(au, qs, cand, ce, memberof::MemberOf))
})
audit_segment!(au, || run_post_create_plugin!(
au,
qs,
cand,
ce,
refint::ReferentialIntegrity
)
.and_then(|_| run_post_create_plugin!(
au,
qs,
cand,
ce,
memberof::MemberOf
)))
}
pub fn run_pre_modify(
@ -340,13 +354,23 @@ impl Plugins {
cand: &[Entry<EntryValid, EntryCommitted>],
me: &ModifyEvent,
) -> Result<(), OperationError> {
audit_segment!(au, || {
run_post_modify_plugin!(au, qs, pre_cand, cand, me, refint::ReferentialIntegrity)
.and_then(|_| {
run_post_modify_plugin!(au, qs, pre_cand, cand, me, memberof::MemberOf)
})
.and_then(|_| run_post_modify_plugin!(au, qs, pre_cand, cand, me, spn::Spn))
})
audit_segment!(au, || run_post_modify_plugin!(
au,
qs,
pre_cand,
cand,
me,
refint::ReferentialIntegrity
)
.and_then(|_| run_post_modify_plugin!(au, qs, pre_cand, cand, me, memberof::MemberOf))
.and_then(|_| run_post_modify_plugin!(
au,
qs,
pre_cand,
cand,
me,
spn::Spn
)))
}
pub fn run_pre_delete(
@ -355,9 +379,13 @@ impl Plugins {
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
de: &DeleteEvent,
) -> Result<(), OperationError> {
audit_segment!(au, || {
run_pre_delete_plugin!(au, qs, cand, de, protected::Protected)
})
audit_segment!(au, || run_pre_delete_plugin!(
au,
qs,
cand,
de,
protected::Protected
))
}
pub fn run_post_delete(
@ -366,10 +394,20 @@ impl Plugins {
cand: &[Entry<EntryValid, EntryCommitted>],
de: &DeleteEvent,
) -> Result<(), OperationError> {
audit_segment!(au, || {
run_post_delete_plugin!(au, qs, cand, de, refint::ReferentialIntegrity)
.and_then(|_| run_post_delete_plugin!(au, qs, cand, de, memberof::MemberOf))
})
audit_segment!(au, || run_post_delete_plugin!(
au,
qs,
cand,
de,
refint::ReferentialIntegrity
)
.and_then(|_| run_post_delete_plugin!(
au,
qs,
cand,
de,
memberof::MemberOf
)))
}
pub fn run_verify(

View file

@ -5,11 +5,11 @@ use crate::value::{IndexType, PartialValue, SyntaxType, Value};
use kanidm_proto::v1::{ConsistencyError, OperationError, SchemaError};
use std::borrow::Borrow;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::{BTreeMap, BTreeSet};
use uuid::Uuid;
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
use concread::collections::bptree::*;
// representations of schema that confines object types, classes
// and attributes. This ties in deeply with "Entry".
@ -23,6 +23,24 @@ lazy_static! {
static ref PVCLASS_CLASSTYPE: PartialValue = PartialValue::new_class("classtype");
}
pub struct Schema {
classes: BptreeMap<String, SchemaClass>,
attributes: BptreeMap<String, SchemaAttribute>,
idxmeta: BptreeMap<String, IndexType>,
}
pub struct SchemaWriteTransaction<'a> {
classes: BptreeMapWriteTxn<'a, String, SchemaClass>,
attributes: BptreeMapWriteTxn<'a, String, SchemaAttribute>,
idxmeta: BptreeMapWriteTxn<'a, String, IndexType>,
}
pub struct SchemaReadTransaction {
classes: BptreeMapReadTxn<String, SchemaClass>,
attributes: BptreeMapReadTxn<String, SchemaAttribute>,
idxmeta: BptreeMapReadTxn<String, IndexType>,
}
#[derive(Debug, Clone)]
pub struct SchemaAttribute {
// Is this ... used?
@ -466,14 +484,91 @@ pub struct SchemaInner {
}
pub trait SchemaTransaction {
fn get_inner(&self) -> &SchemaInner;
fn get_classes(&self) -> BptreeMapReadSnapshot<String, SchemaClass>;
fn get_attributes(&self) -> BptreeMapReadSnapshot<String, SchemaAttribute>;
fn get_idxmeta(&self) -> BptreeMapReadSnapshot<String, IndexType>;
fn validate(&self, audit: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
self.get_inner().validate(audit)
fn validate(&self, _audit: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
let mut res = Vec::new();
let class_snapshot = self.get_classes();
let attribute_snapshot = self.get_attributes();
// Does this need to validate anything further at all? The UUID
// will be checked as part of the schema migration on startup, so I think
// just that all the content is sane is fine.
for class in class_snapshot.values() {
// report the class we are checking
for a in &class.systemmay {
// report the attribute.
/*
audit_log!(
audit,
"validate systemmay class:attr -> {}:{}",
class.name,
a
);
*/
if !attribute_snapshot.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
for a in &class.may {
// report the attribute.
/*
audit_log!(audit, "validate may class:attr -> {}:{}", class.name, a);
*/
if !attribute_snapshot.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
for a in &class.systemmust {
// report the attribute.
/*
audit_log!(
audit,
"validate systemmust class:attr -> {}:{}",
class.name,
a
);
*/
if !attribute_snapshot.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
for a in &class.must {
// report the attribute.
/*
audit_log!(audit, "validate must class:attr -> {}:{}", class.name, a);
*/
if !attribute_snapshot.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
}
res
}
fn is_multivalue(&self, attr: &str) -> Result<bool, SchemaError> {
self.get_inner().is_multivalue(attr)
match self.get_attributes().get(attr) {
Some(a_schema) => Ok(a_schema.multivalue),
None => {
debug!("Attribute does not exist?!");
Err(SchemaError::InvalidAttribute)
}
}
}
fn normalise_attr_name(&self, an: &str) -> String {
@ -500,45 +595,113 @@ pub trait SchemaTransaction {
// Probably need something like get_classes or similar
// so that externals can call and use this data.
fn get_classes(&self) -> &HashMap<String, SchemaClass> {
&self.get_inner().classes
}
fn get_attributes(&self) -> &HashMap<String, SchemaAttribute> {
&self.get_inner().attributes
}
fn get_reference_types(&self) -> HashMap<&String, &SchemaAttribute> {
self.get_attributes()
fn get_reference_types(&self) -> BTreeMap<String, SchemaAttribute> {
let snapshot = self.get_attributes();
snapshot
.iter()
.filter(|(_, sa)| match &sa.syntax {
SyntaxType::REFERENCE_UUID => true,
_ => false,
})
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
fn get_idxmeta(&self) -> BTreeSet<(String, IndexType)> {
// TODO: We could cache this in the schema and recalc on reload instead to avoid
// so much cloning?
// for each attribute, if indexed, yield and flatten the attr + type.
self.get_inner().idxmeta.clone()
fn get_idxmeta_set(&self) -> BTreeSet<(String, IndexType)> {
self.get_idxmeta()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
}
impl SchemaInner {
pub fn new(audit: &mut AuditScope) -> Result<Self, OperationError> {
let mut au = AuditScope::new("schema_new");
impl SchemaInner {}
impl<'a> SchemaWriteTransaction<'a> {
// Schema probably needs to be part of the backend, so that commits are wholly atomic
// but in the current design, we need to open be first, then schema, but we have to commit be
// first, then schema to ensure that the be content matches our schema. Saying this, if your
// schema commit fails we need to roll back still .... How great are transactions.
// At the least, this is what validation is for!
pub fn commit(self) -> Result<(), OperationError> {
let SchemaWriteTransaction {
classes,
attributes,
idxmeta,
} = self;
classes.commit();
attributes.commit();
idxmeta.commit();
Ok(())
}
pub fn update_attributes(
&mut self,
attributetypes: Vec<SchemaAttribute>,
) -> Result<(), OperationError> {
// purge all old attributes.
self.attributes.clear();
// Update with new ones.
// Do we need to check for dups?
// No, they'll over-write each other ... but we do need name uniqueness.
attributetypes.into_iter().for_each(|a| {
self.attributes.insert(a.name.clone(), a);
});
// Now update the idxmeta
self.reload_idxmeta();
Ok(())
}
pub fn update_classes(
&mut self,
attributetypes: Vec<SchemaClass>,
) -> Result<(), OperationError> {
// purge all old attributes.
self.classes.clear();
// Update with new ones.
// Do we need to check for dups?
// No, they'll over-write each other ... but we do need name uniqueness.
attributetypes.into_iter().for_each(|a| {
self.classes.insert(a.name.clone(), a);
});
Ok(())
}
fn reload_idxmeta(&mut self) {
self.idxmeta.clear();
self.idxmeta.extend(self.attributes.values().flat_map(|a| {
a.index
.iter()
.map(move |itype: &IndexType| (a.name.clone(), (*itype).clone()))
}));
}
pub fn to_entries(&self) -> Vec<Entry<EntryValid, EntryNew>> {
let r: Vec<_> = self
.attributes
.values()
.map(Entry::<EntryValid, EntryNew>::from)
.chain(
self.classes
.values()
.map(Entry::<EntryValid, EntryNew>::from),
)
.collect();
r
}
pub fn generate_in_memory(&mut self, audit: &mut AuditScope) -> Result<(), OperationError> {
let mut au = AuditScope::new("generate_in_memory");
let r = audit_segment!(au, || {
//
let mut s = SchemaInner {
classes: HashMap::new(),
attributes: HashMap::new(),
idxmeta: BTreeSet::new(),
};
self.classes.clear();
self.attributes.clear();
self.idxmeta.clear();
// Bootstrap in definitions of our own schema types
// First, add all the needed core attributes for schema parsing
s.attributes.insert(
self.attributes.insert(
String::from("class"),
SchemaAttribute {
name: String::from("class"),
@ -551,7 +714,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("uuid"),
SchemaAttribute {
name: String::from("uuid"),
@ -566,7 +729,7 @@ impl SchemaInner {
syntax: SyntaxType::UUID,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("name"),
SchemaAttribute {
name: String::from("name"),
@ -579,7 +742,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("spn"),
SchemaAttribute {
name: String::from("spn"),
@ -594,7 +757,7 @@ impl SchemaInner {
syntax: SyntaxType::SERVICE_PRINCIPLE_NAME,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("attributename"),
SchemaAttribute {
name: String::from("attributename"),
@ -607,7 +770,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("classname"),
SchemaAttribute {
name: String::from("classname"),
@ -620,7 +783,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("description"),
SchemaAttribute {
name: String::from("description"),
@ -633,7 +796,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING,
},
);
s.attributes.insert(String::from("multivalue"), SchemaAttribute {
self.attributes.insert(String::from("multivalue"), SchemaAttribute {
name: String::from("multivalue"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_MULTIVALUE).expect("unable to parse static uuid"),
description: String::from("If true, this attribute is able to store multiple values rather than just a single value."),
@ -642,7 +805,7 @@ impl SchemaInner {
index: vec![],
syntax: SyntaxType::BOOLEAN,
});
s.attributes.insert(String::from("unique"), SchemaAttribute {
self.attributes.insert(String::from("unique"), SchemaAttribute {
name: String::from("unique"),
uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_UNIQUE).expect("unable to parse static uuid"),
description: String::from("If true, this attribute must store a unique value through out the database."),
@ -651,7 +814,7 @@ impl SchemaInner {
index: vec![],
syntax: SyntaxType::BOOLEAN,
});
s.attributes.insert(
self.attributes.insert(
String::from("index"),
SchemaAttribute {
name: String::from("index"),
@ -666,7 +829,7 @@ impl SchemaInner {
syntax: SyntaxType::INDEX_ID,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("syntax"),
SchemaAttribute {
name: String::from("syntax"),
@ -681,7 +844,7 @@ impl SchemaInner {
syntax: SyntaxType::SYNTAX_ID,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("systemmay"),
SchemaAttribute {
name: String::from("systemmay"),
@ -696,7 +859,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("may"),
SchemaAttribute {
name: String::from("may"),
@ -711,7 +874,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("systemmust"),
SchemaAttribute {
name: String::from("systemmust"),
@ -726,7 +889,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("must"),
SchemaAttribute {
name: String::from("must"),
@ -743,7 +906,7 @@ impl SchemaInner {
);
// SYSINFO attrs
// ACP attributes.
s.attributes.insert(
self.attributes.insert(
String::from("acp_enable"),
SchemaAttribute {
name: String::from("acp_enable"),
@ -757,7 +920,7 @@ impl SchemaInner {
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_receiver"),
SchemaAttribute {
name: String::from("acp_receiver"),
@ -772,7 +935,7 @@ impl SchemaInner {
syntax: SyntaxType::JSON_FILTER,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_targetscope"),
SchemaAttribute {
name: String::from("acp_targetscope"),
@ -787,7 +950,7 @@ impl SchemaInner {
syntax: SyntaxType::JSON_FILTER,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_search_attr"),
SchemaAttribute {
name: String::from("acp_search_attr"),
@ -800,7 +963,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_create_class"),
SchemaAttribute {
name: String::from("acp_create_class"),
@ -815,7 +978,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_create_attr"),
SchemaAttribute {
name: String::from("acp_create_attr"),
@ -831,7 +994,7 @@ impl SchemaInner {
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_modify_removedattr"),
SchemaAttribute {
name: String::from("acp_modify_removedattr"),
@ -844,7 +1007,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_modify_presentattr"),
SchemaAttribute {
name: String::from("acp_modify_presentattr"),
@ -857,7 +1020,7 @@ impl SchemaInner {
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("acp_modify_class"),
SchemaAttribute {
name: String::from("acp_modify_class"),
@ -871,7 +1034,7 @@ impl SchemaInner {
},
);
// MO/Member
s.attributes.insert(
self.attributes.insert(
String::from("memberof"),
SchemaAttribute {
name: String::from("memberof"),
@ -884,7 +1047,7 @@ impl SchemaInner {
syntax: SyntaxType::REFERENCE_UUID,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("directmemberof"),
SchemaAttribute {
name: String::from("directmemberof"),
@ -897,7 +1060,7 @@ impl SchemaInner {
syntax: SyntaxType::REFERENCE_UUID,
},
);
s.attributes.insert(
self.attributes.insert(
String::from("member"),
SchemaAttribute {
name: String::from("member"),
@ -911,7 +1074,7 @@ impl SchemaInner {
},
);
// Migration related
s.attributes.insert(
self.attributes.insert(
String::from("version"),
SchemaAttribute {
name: String::from("version"),
@ -927,7 +1090,7 @@ impl SchemaInner {
},
);
// Domain for sysinfo
s.attributes.insert(
self.attributes.insert(
String::from("domain"),
SchemaAttribute {
name: String::from("domain"),
@ -941,7 +1104,7 @@ impl SchemaInner {
},
);
s.classes.insert(
self.classes.insert(
String::from("attributetype"),
SchemaClass {
name: String::from("attributetype"),
@ -961,7 +1124,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("classtype"),
SchemaClass {
name: String::from("classtype"),
@ -983,7 +1146,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("object"),
SchemaClass {
name: String::from("object"),
@ -998,7 +1161,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("memberof"),
SchemaClass {
name: String::from("memberof"),
@ -1014,7 +1177,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("extensibleobject"),
SchemaClass {
name: String::from("extensibleobject"),
@ -1030,7 +1193,7 @@ impl SchemaInner {
},
);
/* These two classes are core to the entry lifecycle for recycling and tombstoning */
s.classes.insert(
self.classes.insert(
String::from("recycled"),
SchemaClass {
name: String::from("recycled"),
@ -1042,7 +1205,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("tombstone"),
SchemaClass {
name: String::from("tombstone"),
@ -1058,7 +1221,7 @@ impl SchemaInner {
},
);
// sysinfo
s.classes.insert(
self.classes.insert(
String::from("system_info"),
SchemaClass {
name: String::from("system_info"),
@ -1077,7 +1240,7 @@ impl SchemaInner {
},
);
// ACP
s.classes.insert(
self.classes.insert(
String::from("access_control_profile"),
SchemaClass {
name: String::from("access_control_profile"),
@ -1090,7 +1253,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("access_control_search"),
SchemaClass {
name: String::from("access_control_search"),
@ -1103,7 +1266,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("access_control_delete"),
SchemaClass {
name: String::from("access_control_delete"),
@ -1116,7 +1279,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("access_control_modify"),
SchemaClass {
name: String::from("access_control_modify"),
@ -1133,7 +1296,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("access_control_create"),
SchemaClass {
name: String::from("access_control_create"),
@ -1149,7 +1312,7 @@ impl SchemaInner {
must: vec![],
},
);
s.classes.insert(
self.classes.insert(
String::from("system"),
SchemaClass {
name: String::from("system"),
@ -1163,10 +1326,10 @@ impl SchemaInner {
},
);
let r = s.validate(&mut au);
let r = self.validate(&mut au);
if r.is_empty() {
s.reload_idxmeta();
Ok(s)
self.reload_idxmeta();
Ok(())
} else {
Err(OperationError::ConsistencyError(r))
}
@ -1175,205 +1338,65 @@ impl SchemaInner {
audit.append_scope(au);
r
}
fn reload_idxmeta(&mut self) {
let mut idxmeta_new: BTreeSet<_> = self
.attributes
.values()
.flat_map(|a| {
a.index
.iter()
.map(move |itype: &IndexType| (a.name.clone(), (*itype).clone()))
})
.collect();
std::mem::swap(&mut self.idxmeta, &mut idxmeta_new);
}
pub fn validate(&self, _audit: &mut AuditScope) -> Vec<Result<(), ConsistencyError>> {
let mut res = Vec::new();
// Does this need to validate anything further at all? The UUID
// will be checked as part of the schema migration on startup, so I think
// just that all the content is sane is fine.
for class in self.classes.values() {
// report the class we are checking
for a in &class.systemmay {
// report the attribute.
/*
audit_log!(
audit,
"validate systemmay class:attr -> {}:{}",
class.name,
a
);
*/
if !self.attributes.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
for a in &class.may {
// report the attribute.
/*
audit_log!(audit, "validate may class:attr -> {}:{}", class.name, a);
*/
if !self.attributes.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
for a in &class.systemmust {
// report the attribute.
/*
audit_log!(
audit,
"validate systemmust class:attr -> {}:{}",
class.name,
a
);
*/
if !self.attributes.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
for a in &class.must {
// report the attribute.
/*
audit_log!(audit, "validate must class:attr -> {}:{}", class.name, a);
*/
if !self.attributes.contains_key(a) {
res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
class.name.clone(),
a.clone(),
)))
}
}
}
res
}
fn is_multivalue(&self, attr_name: &str) -> Result<bool, SchemaError> {
match self.attributes.get(attr_name) {
Some(a_schema) => Ok(a_schema.multivalue),
None => {
debug!("Attribute does not exist?!");
Err(SchemaError::InvalidAttribute)
}
}
}
}
pub struct Schema {
inner: CowCell<SchemaInner>,
}
pub struct SchemaWriteTransaction<'a> {
inner: CowCellWriteTxn<'a, SchemaInner>,
}
impl<'a> SchemaWriteTransaction<'a> {
// Schema probably needs to be part of the backend, so that commits are wholly atomic
// but in the current design, we need to open be first, then schema, but we have to commit be
// first, then schema to ensure that the be content matches our schema. Saying this, if your
// schema commit fails we need to roll back still .... How great are transactions.
// At the least, this is what validation is for!
pub fn commit(self) -> Result<(), OperationError> {
self.inner.commit();
Ok(())
}
pub fn update_attributes(
&mut self,
attributetypes: Vec<SchemaAttribute>,
) -> Result<(), OperationError> {
// purge all old attributes.
self.inner.attributes.clear();
// Update with new ones.
// Do we need to check for dups?
// No, they'll over-write each other ... but we do need name uniqueness.
attributetypes.into_iter().for_each(|a| {
self.inner.attributes.insert(a.name.clone(), a);
});
// Now update the idxmeta
self.inner.reload_idxmeta();
Ok(())
}
pub fn update_classes(
&mut self,
attributetypes: Vec<SchemaClass>,
) -> Result<(), OperationError> {
// purge all old attributes.
self.inner.classes.clear();
// Update with new ones.
// Do we need to check for dups?
// No, they'll over-write each other ... but we do need name uniqueness.
attributetypes.into_iter().for_each(|a| {
self.inner.classes.insert(a.name.clone(), a);
});
Ok(())
}
pub fn to_entries(&self) -> Vec<Entry<EntryValid, EntryNew>> {
let r: Vec<_> = self
.inner
.attributes
.values()
.map(Entry::<EntryValid, EntryNew>::from)
.chain(
self.inner
.classes
.values()
.map(Entry::<EntryValid, EntryNew>::from),
)
.collect();
r
}
}
impl<'a> SchemaTransaction for SchemaWriteTransaction<'a> {
fn get_inner(&self) -> &SchemaInner {
// Does this deref the CowCell for us?
&self.inner
fn get_classes(&self) -> BptreeMapReadSnapshot<String, SchemaClass> {
self.classes.to_snapshot()
}
fn get_attributes(&self) -> BptreeMapReadSnapshot<String, SchemaAttribute> {
self.attributes.to_snapshot()
}
fn get_idxmeta(&self) -> BptreeMapReadSnapshot<String, IndexType> {
self.idxmeta.to_snapshot()
}
}
pub struct SchemaReadTransaction {
inner: CowCellReadTxn<SchemaInner>,
}
impl SchemaTransaction for SchemaReadTransaction {
fn get_inner(&self) -> &SchemaInner {
// Does this deref the CowCell for us?
&self.inner
fn get_classes(&self) -> BptreeMapReadSnapshot<String, SchemaClass> {
self.classes.to_snapshot()
}
fn get_attributes(&self) -> BptreeMapReadSnapshot<String, SchemaAttribute> {
self.attributes.to_snapshot()
}
fn get_idxmeta(&self) -> BptreeMapReadSnapshot<String, IndexType> {
self.idxmeta.to_snapshot()
}
}
impl Schema {
pub fn new(audit: &mut AuditScope) -> Result<Self, OperationError> {
SchemaInner::new(audit).map(|si| Schema {
inner: CowCell::new(si),
})
let s = Schema {
classes: BptreeMap::new(),
attributes: BptreeMap::new(),
idxmeta: BptreeMap::new(),
};
let mut sw = s.write();
let r1 = sw.generate_in_memory(audit);
debug_assert!(r1.is_ok());
r1?;
let r2 = sw.commit().map(|_| s);
debug_assert!(r2.is_ok());
r2
}
pub fn read(&self) -> SchemaReadTransaction {
SchemaReadTransaction {
inner: self.inner.read(),
classes: self.classes.read(),
attributes: self.attributes.read(),
idxmeta: self.idxmeta.read(),
}
}
pub fn write(&self) -> SchemaWriteTransaction {
SchemaWriteTransaction {
inner: self.inner.write(),
classes: self.classes.write(),
attributes: self.attributes.write(),
idxmeta: self.idxmeta.write(),
}
}
}

View file

@ -95,7 +95,7 @@ pub trait QueryServerTransaction {
// NOTE: Filters are validated in event conversion.
let schema = self.get_schema();
let idxmeta = schema.get_idxmeta();
let idxmeta = schema.get_idxmeta_set();
// Now resolve all references and indexes.
let vfr = try_audit!(au, se.filter.resolve(&se.event, Some(&idxmeta)));
@ -132,7 +132,7 @@ pub trait QueryServerTransaction {
let mut audit_be = AuditScope::new("backend_exists");
let schema = self.get_schema();
let idxmeta = schema.get_idxmeta();
let idxmeta = schema.get_idxmeta_set();
let vfr = try_audit!(au, ee.filter.resolve(&ee.event, Some(&idxmeta)));
let res = self
@ -715,7 +715,7 @@ impl QueryServer {
pub fn write(&self) -> QueryServerWriteTransaction {
// Feed the current schema index metadata to the be write transaction.
let schema_write = self.schema.write();
let idxmeta = schema_write.get_idxmeta();
let idxmeta = schema_write.get_idxmeta_set();
QueryServerWriteTransaction {
// I think this is *not* needed, because commit is mut self which should