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
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) => {
avas.iter()
.map(|av| {
@ -219,6 +219,9 @@ impl<STATE> Entry<EntryInvalid, STATE> {
None => avas.clone(),
};
// Ensure they are ordered property.
avas_normal.sort_unstable();
// Should never fail!
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 {
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> {

View file

@ -2,7 +2,7 @@ use super::filter::{Filter, FilterInvalid};
use super::proto_v1::Entry as ProtoEntry;
use super::proto_v1::{
AuthRequest, AuthResponse, AuthStatus, CreateRequest, DeleteRequest, ModifyRequest, Response,
SearchRequest, SearchResponse
SearchRequest, SearchResponse, SearchRecycledRequest, ReviveRecycledRequest
};
use actix::prelude::*;
use entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
@ -94,21 +94,21 @@ impl SearchEvent {
pub fn new_impersonate(filter: Filter<FilterInvalid>) -> Self {
SearchEvent {
internal: false,
filter: filter,
class: (),
}
}
pub fn from_rec_request(request: SearchRecycledRequest) -> Self {
SearchEvent {
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
]),
Filter::Eq(
"class".to_string(),
"recycled".to_string(),
),
Filter::from(&request.filter)
]),
internal: false,
class: (),
}
}
@ -200,7 +200,21 @@ impl Message for DeleteEvent {
impl DeleteEvent {
pub fn from_request(request: DeleteRequest) -> Self {
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,
}
}
@ -309,3 +323,28 @@ impl 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 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 event::{
AuthEvent, AuthResult, CreateEvent, DeleteEvent, ExistsEvent, ModifyEvent, OpResult,
SearchEvent, SearchResult, PurgeEvent,
SearchEvent, SearchResult, PurgeEvent, ReviveRecycledEvent
};
use filter::{Filter, FilterInvalid};
use log::EventLog;
use modify::ModifyList;
use modify::{ModifyList, Modify};
use plugins::Plugins;
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
// server. This is why ExtensibleObject can only take schema that
// exists in the server, not arbitrary attr names.
audit_log!(au, "search: filter -> {:?}", se.filter);
// TODO: Normalise the filter
@ -471,14 +472,75 @@ impl<'a> QueryServerWriteTransaction<'a> {
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
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> {
// Revive an entry to live.
unimplemented!()
// Should this take a revive event?
pub fn revive_recycled(&self, au: &mut AuditScope, re: &ReviveRecycledEvent) -> Result<(), OperationError> {
// 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> {
@ -629,6 +691,19 @@ impl<'a> QueryServerWriteTransaction<'a> {
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.
// These just wrap the fn create/search etc, but they allow
// 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::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
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::log;
use super::super::modify::{Modify, ModifyList};
use super::super::proto_v1::Entry as ProtoEntry;
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::ModifyList as ProtoModifyList;
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(())
})
}
}