Recycle lifecycle mostly done

This commit is contained in:
William Brown 2019-02-24 14:15:28 +10:00
parent ea5af4f369
commit 341f7cd0c5
4 changed files with 336 additions and 26 deletions

View file

@ -207,7 +207,7 @@ impl<STATE> Entry<EntryInvalid, STATE> {
// Get the needed schema type // Get the needed schema type
let schema_a_r = schema_attributes.get(&attr_name_normal); let schema_a_r = schema_attributes.get(&attr_name_normal);
let avas_normal: Vec<String> = match schema_a_r { let mut avas_normal: Vec<String> = match schema_a_r {
Some(schema_a) => { Some(schema_a) => {
avas.iter() avas.iter()
.map(|av| { .map(|av| {
@ -219,6 +219,9 @@ impl<STATE> Entry<EntryInvalid, STATE> {
None => avas.clone(), None => avas.clone(),
}; };
// Ensure they are ordered property.
avas_normal.sort_unstable();
// Should never fail! // Should never fail!
let _ = new_attrs.insert(attr_name_normal, avas_normal); let _ = new_attrs.insert(attr_name_normal, avas_normal);
} }
@ -375,6 +378,24 @@ impl Entry<EntryValid, EntryCommitted> {
pub fn compare(&self, rhs: &Entry<EntryValid, EntryNew>) -> bool { pub fn compare(&self, rhs: &Entry<EntryValid, EntryNew>) -> bool {
self.attrs == rhs.attrs self.attrs == rhs.attrs
} }
pub fn to_tombstone(&self) -> Self {
// Duplicate this to a tombstone entry.
let uuid_ava = self.get_ava(&String::from("uuid")).expect("Corrupted entry!");
let class_ava = vec!["object".to_string(), "tombstone".to_string()];
let mut attrs_new: BTreeMap<String, Vec<String>> = BTreeMap::new();
attrs_new.insert("uuid".to_string(), uuid_ava.clone());
attrs_new.insert("class".to_string(), class_ava);
Entry {
valid: EntryValid,
state: EntryCommitted,
id: self.id,
attrs: attrs_new,
}
}
} }
impl<STATE> Entry<EntryValid, STATE> { impl<STATE> Entry<EntryValid, STATE> {

View file

@ -2,7 +2,7 @@ use super::filter::{Filter, FilterInvalid};
use super::proto_v1::Entry as ProtoEntry; use super::proto_v1::Entry as ProtoEntry;
use super::proto_v1::{ use super::proto_v1::{
AuthRequest, AuthResponse, AuthStatus, CreateRequest, DeleteRequest, ModifyRequest, Response, AuthRequest, AuthResponse, AuthStatus, CreateRequest, DeleteRequest, ModifyRequest, Response,
SearchRequest, SearchResponse SearchRequest, SearchResponse, SearchRecycledRequest, ReviveRecycledRequest
}; };
use actix::prelude::*; use actix::prelude::*;
use entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; use entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
@ -94,21 +94,21 @@ impl SearchEvent {
pub fn new_impersonate(filter: Filter<FilterInvalid>) -> Self { pub fn new_impersonate(filter: Filter<FilterInvalid>) -> Self {
SearchEvent { SearchEvent {
internal: false, internal: false,
filter: filter,
class: (),
}
}
pub fn from_rec_request(request: SearchRecycledRequest) -> Self {
SearchEvent {
filter: Filter::And(vec![ filter: Filter::And(vec![
Filter::AndNot(Box::new( Filter::Eq(
Filter::Or(vec![ "class".to_string(),
Filter::Eq( "recycled".to_string(),
"class".to_string(), ),
"tombstone".to_string(), Filter::from(&request.filter)
), ]),
Filter::Eq( internal: false,
"class".to_string(),
"recycled".to_string(),
)
])
)),
filter
]),
class: (), class: (),
} }
} }
@ -200,7 +200,21 @@ impl Message for DeleteEvent {
impl DeleteEvent { impl DeleteEvent {
pub fn from_request(request: DeleteRequest) -> Self { pub fn from_request(request: DeleteRequest) -> Self {
DeleteEvent { DeleteEvent {
filter: Filter::from(&request.filter), filter: Filter::And(vec![
Filter::AndNot(Box::new(
Filter::Or(vec![
Filter::Eq(
"class".to_string(),
"tombstone".to_string(),
),
Filter::Eq(
"class".to_string(),
"recycled".to_string(),
)
])
)),
Filter::from(&request.filter)
]),
internal: false, internal: false,
} }
} }
@ -309,3 +323,28 @@ impl PurgeEvent {
PurgeEvent {} PurgeEvent {}
} }
} }
#[derive(Debug)]
pub struct ReviveRecycledEvent {
pub filter: Filter<FilterInvalid>,
pub internal: bool,
}
impl Message for ReviveRecycledEvent {
type Result = ();
}
impl ReviveRecycledEvent {
pub fn from_request(request: ReviveRecycledRequest) -> Self {
ReviveRecycledEvent {
filter: Filter::And(vec![
Filter::Eq(
"class".to_string(),
"recycled".to_string(),
),
Filter::from(&request.filter)
]),
internal: false,
}
}
}

View file

@ -168,3 +168,31 @@ pub enum AuthStatus {
pub struct AuthResponse { pub struct AuthResponse {
pub status: AuthStatus, pub status: AuthStatus,
} }
/* Recycle Requests area */
// Only two actions on recycled is possible. Search and Revive.
pub struct SearchRecycledRequest {
pub filter: Filter,
}
impl SearchRecycledRequest {
pub fn new(filter: Filter) -> Self {
SearchRecycledRequest { filter: filter }
}
}
// Need a search response here later.
pub struct ReviveRecycledRequest {
pub filter: Filter,
}
impl ReviveRecycledRequest {
pub fn new(filter: Filter) -> Self {
ReviveRecycledRequest { filter: filter }
}
}

View file

@ -14,11 +14,11 @@ use entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
use error::{OperationError, SchemaError}; use error::{OperationError, SchemaError};
use event::{ use event::{
AuthEvent, AuthResult, CreateEvent, DeleteEvent, ExistsEvent, ModifyEvent, OpResult, AuthEvent, AuthResult, CreateEvent, DeleteEvent, ExistsEvent, ModifyEvent, OpResult,
SearchEvent, SearchResult, PurgeEvent, SearchEvent, SearchResult, PurgeEvent, ReviveRecycledEvent
}; };
use filter::{Filter, FilterInvalid}; use filter::{Filter, FilterInvalid};
use log::EventLog; use log::EventLog;
use modify::ModifyList; use modify::{ModifyList, Modify};
use plugins::Plugins; use plugins::Plugins;
use schema::{Schema, SchemaReadTransaction, SchemaTransaction, SchemaWriteTransaction}; use schema::{Schema, SchemaReadTransaction, SchemaTransaction, SchemaWriteTransaction};
@ -103,6 +103,7 @@ pub trait QueryServerReadTransaction {
// performing un-indexed searches on attr's that don't exist in the // performing un-indexed searches on attr's that don't exist in the
// server. This is why ExtensibleObject can only take schema that // server. This is why ExtensibleObject can only take schema that
// exists in the server, not arbitrary attr names. // exists in the server, not arbitrary attr names.
audit_log!(au, "search: filter -> {:?}", se.filter);
// TODO: Normalise the filter // TODO: Normalise the filter
@ -471,14 +472,75 @@ impl<'a> QueryServerWriteTransaction<'a> {
res res
} }
pub fn purge_recycle(&self) -> Result<(), OperationError> { pub fn purge_recycled(&self, au: &mut AuditScope) -> Result<(), OperationError> {
// Send everything that is recycled to tombstone // Send everything that is recycled to tombstone
unimplemented!() // Search all recycled
let rc = match self.internal_search(
au,
Filter::Eq("class".to_string(), "recycled".to_string())
) {
Ok(r) => {
r
}
Err(e) => {
return Err(e)
}
};
// Modify them to strip all avas except uuid
let tombstone_cand = rc.iter().map(|e| {
e.to_tombstone()
}).collect();
// Backend Modify
let mut audit_be = AuditScope::new("backend_modify");
let res = self
.be_txn
.modify(&mut audit_be, &tombstone_cand)
.map(|_| ())
.map_err(|e| match e {
BackendError::EmptyRequest => OperationError::EmptyRequest,
BackendError::EntryMissingId => OperationError::InvalidRequestState,
});
au.append_scope(audit_be);
if res.is_err() {
// be_txn is dropped, ie aborted here.
audit_log!(au, "Purge recycled operation failed (backend), {:?}", res);
return res;
}
// return
audit_log!(au, "Purge recycled operation success");
res
} }
pub fn revive_recycled(&self) -> Result<(), OperationError> { // Should this take a revive event?
// Revive an entry to live. pub fn revive_recycled(&self, au: &mut AuditScope, re: &ReviveRecycledEvent) -> Result<(), OperationError> {
unimplemented!() // Revive an entry to live. This is a specialised (limited)
// modify proxy.
//
// impersonate modify will require ability to search the class=recycled
// and the ability to remove that from the object.
// create the modify
// tl;dr, remove the class=recycled
let modlist = ModifyList::new_list(vec![
Modify::Removed(
"class".to_string(),
"recycled".to_string(),
),
]);
// Now impersonate the modify
self.impersonate_modify(
au,
re.filter.clone(),
modlist
)
} }
pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> { pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> {
@ -629,6 +691,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
res res
} }
pub fn impersonate_modify(
&self,
audit: &mut AuditScope,
filter: Filter<FilterInvalid>,
modlist: ModifyList,
) -> Result<(), OperationError> {
let mut audit_int = AuditScope::new("impersonate_modify");
let me = ModifyEvent::new_internal(filter, modlist);
let res = self.modify(&mut audit_int, &me);
audit.append_scope(audit_int);
res
}
// internal server operation types. // internal server operation types.
// These just wrap the fn create/search etc, but they allow // These just wrap the fn create/search etc, but they allow
// creating the needed create event with the correct internal flags // creating the needed create event with the correct internal flags
@ -968,13 +1043,13 @@ mod tests {
use super::super::be::{Backend, BackendTransaction}; use super::super::be::{Backend, BackendTransaction};
use super::super::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; use super::super::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
use super::super::error::OperationError; use super::super::error::OperationError;
use super::super::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent}; use super::super::event::{CreateEvent, DeleteEvent, ModifyEvent, SearchEvent, ReviveRecycledEvent};
use super::super::filter::Filter; use super::super::filter::Filter;
use super::super::log; use super::super::log;
use super::super::modify::{Modify, ModifyList}; use super::super::modify::{Modify, ModifyList};
use super::super::proto_v1::Entry as ProtoEntry; use super::super::proto_v1::Entry as ProtoEntry;
use super::super::proto_v1::Filter as ProtoFilter; use super::super::proto_v1::Filter as ProtoFilter;
use super::super::proto_v1::{CreateRequest, SearchRequest, DeleteRequest, ModifyRequest}; use super::super::proto_v1::{CreateRequest, SearchRequest, DeleteRequest, ModifyRequest, SearchRecycledRequest, ReviveRecycledRequest};
use super::super::proto_v1::Modify as ProtoModify; use super::super::proto_v1::Modify as ProtoModify;
use super::super::proto_v1::ModifyList as ProtoModifyList; use super::super::proto_v1::ModifyList as ProtoModifyList;
use super::super::schema::Schema; use super::super::schema::Schema;
@ -1425,4 +1500,151 @@ mod tests {
}) })
} }
#[test]
fn test_qs_recycle_simple() {
run_test!(|_log, mut server: QueryServer, audit: &mut AuditScope| {
let mut server_txn = server.write();
let filt_rc = ProtoFilter::Eq(
String::from("class"),
String::from("recycled")
);
let filt_i_rc = Filter::Eq(
String::from("class"),
String::from("recycled")
);
let filt_i_ts = Filter::Eq(
String::from("class"),
String::from("tombstone")
);
let filt_i_per = Filter::Eq(
String::from("class"),
String::from("person")
);
// Create fake external requests. Probably from admin later
let me_rc = ModifyEvent::from_request(
ModifyRequest::new(
filt_rc.clone(),
ProtoModifyList::new_list(vec![
ProtoModify::Present(String::from("class"), String::from("recycled")),
]),
)
);
let de_rc = DeleteEvent::from_request(DeleteRequest::new(filt_rc.clone()));
let se_rc = SearchEvent::from_request(SearchRequest::new(filt_rc.clone()));
let sre_rc = SearchEvent::from_rec_request(
SearchRecycledRequest::new(
filt_rc.clone()
)
);
let rre_rc = ReviveRecycledEvent::from_request(
ReviveRecycledRequest::new(
ProtoFilter::Eq(
"name".to_string(),
"testperson1".to_string(),
)
)
);
// Create some recycled objects
let e1: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
"valid": null,
"state": null,
"attrs": {
"class": ["object", "person", "recycled"],
"name": ["testperson1"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
"description": ["testperson"],
"displayname": ["testperson1"]
}
}"#,
)
.unwrap();
let e2: Entry<EntryInvalid, EntryNew> = serde_json::from_str(
r#"{
"valid": null,
"state": null,
"attrs": {
"class": ["object", "person", "recycled"],
"name": ["testperson2"],
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63932"],
"description": ["testperson"],
"displayname": ["testperson2"]
}
}"#,
)
.unwrap();
let ce = CreateEvent::from_vec(vec![e1, e2]);
let cr = server_txn.create(audit, &ce);
assert!(cr.is_ok());
// Can it be seen (external search)
let r1 = server_txn.search(audit, &se_rc).unwrap();
assert!(r1.len() == 0);
// Can it be deleted (external delete)
// Should be err-no candidates.
assert!(server_txn.delete(audit, &de_rc).is_err());
// Can it be modified? (external modify)
// Should be err-no candidates
assert!(server_txn.modify(audit, &me_rc).is_err());
// Can in be seen by special search? (external recycle search)
let r2 = server_txn.search(audit, &sre_rc).unwrap();
assert!(r2.len() == 2);
// Can it be seen (internal search)
// Internal search should see it.
let r2 = server_txn.internal_search(audit, filt_i_rc.clone()).unwrap();
assert!(r2.len() == 2);
// There are now two options
// revival
assert!(server_txn.revive_recycled(audit, &rre_rc).is_ok());
// purge to tombstone
assert!(server_txn.purge_recycled(audit).is_ok());
// Should be no recycled objects.
let r3 = server_txn.internal_search(audit, filt_i_rc.clone()).unwrap();
assert!(r3.len() == 0);
// There should be one tombstone
let r4 = server_txn.internal_search(audit, filt_i_ts.clone()).unwrap();
assert!(r4.len() == 1);
// There should be one entry
let r5 = server_txn.internal_search(audit, filt_i_per.clone()).unwrap();
assert!(r5.len() == 1);
assert!(server_txn.commit(audit).is_ok());
future::ok(())
})
}
// The delete test above should be unaffected by recycle anyway
#[test]
fn test_qs_recycle_advanced() {
run_test!(|_log, mut server: QueryServer, audit: &mut AuditScope| {
let mut server_txn = server.write();
// Create items
// Delete and ensure they became recycled.
// After a delete -> recycle, create duplicate name etc.
// Create dup uuid (rej)
assert!(server_txn.commit(audit).is_ok());
future::ok(())
})
}
} }