use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fs::{rename, File};
use std::io::Write;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use super::{AdminServiceEvent, EventIter};
use super::{
AdminServiceStore, AdminServiceStoreError, AuthorizationType, Circuit, CircuitBuilder,
CircuitNode, CircuitNodeBuilder, CircuitPredicate, CircuitProposal, CircuitProposalBuilder,
CircuitStatus, DurabilityType, PersistenceType, ProposalType, ProposedCircuit,
ProposedCircuitBuilder, ProposedNode, ProposedNodeBuilder, ProposedService,
ProposedServiceBuilder, RouteType, Service, ServiceBuilder, ServiceId, Vote, VoteRecord,
VoteRecordBuilder,
};
use crate::admin::messages;
use crate::error::{
ConstraintViolationError, ConstraintViolationType, InternalError, InvalidStateError,
};
use crate::hex::{parse_hex, to_hex};
use crate::public_key::PublicKey;
#[derive(Clone)]
pub struct YamlAdminServiceStore {
circuit_file_path: String,
proposal_file_path: String,
state: Arc<Mutex<YamlState>>,
}
impl YamlAdminServiceStore {
pub fn new(
circuit_file_path: String,
proposal_file_path: String,
) -> Result<Self, AdminServiceStoreError> {
let mut store = YamlAdminServiceStore {
circuit_file_path: circuit_file_path.to_string(),
proposal_file_path: proposal_file_path.to_string(),
state: Arc::new(Mutex::new(YamlState::default())),
};
let circuit_file_path_buf = PathBuf::from(circuit_file_path);
let proposal_file_path_buf = PathBuf::from(proposal_file_path);
if circuit_file_path_buf.is_file() && proposal_file_path_buf.is_file() {
store.read_state()?;
} else if circuit_file_path_buf.is_file() {
store.read_circuit_state()?;
store.write_proposal_state()?;
} else if proposal_file_path_buf.is_file() {
store.write_circuit_state()?;
store.read_proposal_state()?;
} else {
store.write_state()?;
}
Ok(store)
}
fn read_circuit_state(&mut self) -> Result<(), AdminServiceStoreError> {
let circuit_file = File::open(&self.circuit_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to open YAML circuit state file".to_string(),
))
})?;
let yaml_state_circuits: YamlCircuitState = serde_yaml::from_reader(&circuit_file)
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to read YAML circuit state file".to_string(),
))
})?;
let yaml_state = CircuitState::try_from(yaml_state_circuits)
.map_err(AdminServiceStoreError::InvalidStateError)?;
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock poisoned".to_string(),
))
})?;
for (circuit_id, circuit) in yaml_state.circuits.iter() {
for service in circuit.roster() {
let service_id =
ServiceId::new(service.service_id().to_string(), circuit_id.to_string());
state.service_directory.insert(service_id, service.clone());
}
}
state.circuit_state = yaml_state;
Ok(())
}
fn read_proposal_state(&mut self) -> Result<(), AdminServiceStoreError> {
let proposal_file = File::open(&self.proposal_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to open YAML proposal state file".to_string(),
))
})?;
let yaml_proposals_state: YamlProposalState = serde_yaml::from_reader(&proposal_file)
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to read YAML proposal state file".to_string(),
))
})?;
let proposals_state = ProposalState::try_from(yaml_proposals_state)
.map_err(AdminServiceStoreError::InvalidStateError)?;
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock poisoned".to_string(),
))
})?;
state.proposal_state = proposals_state;
Ok(())
}
fn read_state(&mut self) -> Result<(), AdminServiceStoreError> {
let circuit_file = File::open(&self.circuit_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to open YAML circuit state file".to_string(),
))
})?;
let yaml_state_circuits: YamlCircuitState = serde_yaml::from_reader(&circuit_file)
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to read YAML circuit state file".to_string(),
))
})?;
let yaml_state = CircuitState::try_from(yaml_state_circuits)
.map_err(AdminServiceStoreError::InvalidStateError)?;
let proposal_file = File::open(&self.proposal_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to open YAML proposal state file".to_string(),
))
})?;
let yaml_proposals_state: YamlProposalState = serde_yaml::from_reader(&proposal_file)
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to read YAML proposal state file".to_string(),
))
})?;
let proposals_state = ProposalState::try_from(yaml_proposals_state)
.map_err(AdminServiceStoreError::InvalidStateError)?;
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock poisoned".to_string(),
))
})?;
for (circuit_id, circuit) in yaml_state.circuits.iter() {
for service in circuit.roster() {
let service_id =
ServiceId::new(service.service_id().to_string(), circuit_id.to_string());
state.service_directory.insert(service_id, service.clone());
}
}
state.circuit_state = yaml_state;
state.proposal_state = proposals_state;
Ok(())
}
fn write_circuit_state(&self) -> Result<(), AdminServiceStoreError> {
let state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock poisoned".to_string(),
))
})?;
let circuit_output = serde_yaml::to_vec(&YamlCircuitState::from(
state.circuit_state.clone(),
))
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to write circuit state to YAML".to_string(),
))
})?;
let temp_circuit_file = format!("{}.temp", self.circuit_file_path);
let mut circuit_file = File::create(&temp_circuit_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to open YAML circuit state file '{}'",
temp_circuit_file
),
))
})?;
circuit_file.write_all(&circuit_output).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML circuit state file '{}'",
temp_circuit_file
),
))
})?;
writeln!(circuit_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML circuit file '{}'",
temp_circuit_file
),
))
})?;
rename(&temp_circuit_file, &self.circuit_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to rename temp circuit state file to final location'{}'",
temp_circuit_file
),
))
})?;
Ok(())
}
fn write_proposal_state(&self) -> Result<(), AdminServiceStoreError> {
let state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock poisoned".to_string(),
))
})?;
let proposal_output = serde_yaml::to_vec(&YamlProposalState::from(
state.proposal_state.clone(),
))
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to write proposals state to YAML".to_string(),
))
})?;
let temp_proposal_file = format!("{}.temp", self.proposal_file_path);
let mut proposal_file = File::create(&temp_proposal_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to open YAML proposal state file '{}'",
temp_proposal_file
),
))
})?;
proposal_file.write_all(&proposal_output).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write YAML proposal state file '{}'",
temp_proposal_file
),
))
})?;
writeln!(proposal_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML proposal file '{}'",
temp_proposal_file
),
))
})?;
rename(&temp_proposal_file, &self.proposal_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to rename temp proposal state file to final location '{}'",
temp_proposal_file
),
))
})?;
Ok(())
}
fn write_state(&self) -> Result<(), AdminServiceStoreError> {
let state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock poisoned".to_string(),
))
})?;
let circuit_output = serde_yaml::to_vec(&YamlCircuitState::from(
state.circuit_state.clone(),
))
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to write circuit state to YAML".to_string(),
))
})?;
let temp_circuit_file = format!("{}.temp", self.circuit_file_path);
let mut circuit_file = File::create(&temp_circuit_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to open YAML circuit state file '{}'",
temp_circuit_file
),
))
})?;
circuit_file.write_all(&circuit_output).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML circuit state file '{}'",
temp_circuit_file
),
))
})?;
writeln!(circuit_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML circuit file '{}'",
temp_circuit_file
),
))
})?;
rename(&temp_circuit_file, &self.circuit_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to rename temp circuit state file to final location '{}'",
temp_circuit_file
),
))
})?;
let proposal_output = serde_yaml::to_vec(&YamlProposalState::from(
state.proposal_state.clone(),
))
.map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Failed to write proposals state to YAML".to_string(),
))
})?;
let temp_proposal_file = format!("{}.temp", self.proposal_file_path);
let mut proposal_file = File::create(&temp_proposal_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to open YAML proposal state file '{}'",
temp_proposal_file
),
))
})?;
proposal_file.write_all(&proposal_output).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML proposal state file '{}'",
temp_proposal_file
),
))
})?;
writeln!(proposal_file).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to write to YAML proposal file '{}'",
self.proposal_file_path
),
))
})?;
rename(&temp_proposal_file, &self.proposal_file_path).map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
format!(
"Failed to rename temp proposal state file to final location '{}'",
temp_proposal_file
),
))
})?;
Ok(())
}
}
impl AdminServiceStore for YamlAdminServiceStore {
fn add_proposal(&self, proposal: CircuitProposal) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if state
.proposal_state
.proposals
.contains_key(proposal.circuit_id())
{
return Err(AdminServiceStoreError::ConstraintViolationError(
ConstraintViolationError::with_violation_type(ConstraintViolationType::Unique),
));
} else {
state
.proposal_state
.proposals
.insert(proposal.circuit_id().to_string(), proposal);
}
}
self.write_proposal_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write proposal state yaml file".to_string(),
))
})
}
fn update_proposal(&self, proposal: CircuitProposal) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if state
.proposal_state
.proposals
.contains_key(proposal.circuit_id())
{
state
.proposal_state
.proposals
.insert(proposal.circuit_id().to_string(), proposal);
} else {
return Err(AdminServiceStoreError::InvalidStateError(
InvalidStateError::with_message(format!(
"A proposal with ID {} does not exist",
proposal.circuit_id()
)),
));
}
}
self.write_proposal_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write proposal state yaml file".to_string(),
))
})
}
fn remove_proposal(&self, proposal_id: &str) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if state.proposal_state.proposals.contains_key(proposal_id) {
state.proposal_state.proposals.remove(proposal_id);
} else {
return Err(AdminServiceStoreError::InvalidStateError(
InvalidStateError::with_message(format!(
"A proposal with ID {} does not exist",
proposal_id
)),
));
}
}
self.write_proposal_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write proposal state yaml file".to_string(),
))
})
}
fn get_proposal(
&self,
proposal_id: &str,
) -> Result<Option<CircuitProposal>, AdminServiceStoreError> {
Ok(self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.proposal_state
.proposals
.get(proposal_id)
.cloned())
}
fn list_proposals(
&self,
predicates: &[CircuitPredicate],
) -> Result<Box<dyn ExactSizeIterator<Item = CircuitProposal>>, AdminServiceStoreError> {
let mut proposals: Vec<CircuitProposal> = self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.proposal_state
.proposals
.iter()
.map(|(_, proposal)| proposal.clone())
.collect::<Vec<CircuitProposal>>();
proposals.retain(|proposal| {
predicates
.iter()
.all(|predicate| predicate.apply_to_proposals(proposal))
});
Ok(Box::new(proposals.into_iter()))
}
fn count_proposals(
&self,
predicates: &[CircuitPredicate],
) -> Result<u32, AdminServiceStoreError> {
let count = self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.proposal_state
.proposals
.iter()
.filter(|(_, proposal)| {
predicates
.iter()
.all(|predicate| predicate.apply_to_proposals(proposal))
})
.count();
u32::try_from(count).map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"The number of proposals is larger than the max u32".to_string(),
))
})
}
fn add_circuit(
&self,
circuit: Circuit,
nodes: Vec<CircuitNode>,
) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if state
.circuit_state
.circuits
.contains_key(circuit.circuit_id())
{
return Err(AdminServiceStoreError::ConstraintViolationError(
ConstraintViolationError::with_violation_type(ConstraintViolationType::Unique),
));
} else {
for service in circuit.roster() {
let service_id = ServiceId::new(
service.service_id().to_string(),
circuit.circuit_id().to_string(),
);
state.service_directory.insert(service_id, service.clone());
}
for node in nodes.into_iter() {
if !state.circuit_state.nodes.contains_key(node.node_id()) {
state
.circuit_state
.nodes
.insert(node.node_id().to_string(), node);
}
}
state
.circuit_state
.circuits
.insert(circuit.circuit_id().to_string(), circuit);
}
}
self.write_circuit_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write circuit state yaml file".to_string(),
))
})
}
fn update_circuit(&self, circuit: Circuit) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if state
.circuit_state
.circuits
.contains_key(circuit.circuit_id())
{
state
.circuit_state
.circuits
.insert(circuit.circuit_id().to_string(), circuit);
} else {
return Err(AdminServiceStoreError::InvalidStateError(
InvalidStateError::with_message(format!(
"A circuit with ID {} does not exist",
circuit.circuit_id()
)),
));
}
}
self.write_circuit_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write circuit state yaml file".to_string(),
))
})
}
fn remove_circuit(&self, circuit_id: &str) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if state.circuit_state.circuits.contains_key(circuit_id) {
let circuit = state.circuit_state.circuits.remove(circuit_id);
if let Some(circuit) = circuit {
for service in circuit.roster() {
let service_id = ServiceId::new(
service.service_id().to_string(),
circuit_id.to_string(),
);
state.service_directory.remove(&service_id);
}
}
} else {
return Err(AdminServiceStoreError::InvalidStateError(
InvalidStateError::with_message(format!(
"A circuit with ID {} does not exist",
circuit_id
)),
));
}
}
self.write_circuit_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write circuit state yaml file".to_string(),
))
})
}
fn get_circuit(&self, circuit_id: &str) -> Result<Option<Circuit>, AdminServiceStoreError> {
Ok(self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.circuit_state
.circuits
.get(circuit_id)
.cloned())
}
fn list_circuits(
&self,
predicates: &[CircuitPredicate],
) -> Result<Box<dyn ExactSizeIterator<Item = Circuit>>, AdminServiceStoreError> {
let mut circuits: Vec<Circuit> = self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.circuit_state
.circuits
.iter()
.map(|(_, circuit)| circuit.clone())
.collect();
circuits.retain(|circuit| {
predicates
.iter()
.all(|predicate| predicate.apply_to_circuit(circuit))
});
Ok(Box::new(circuits.into_iter()))
}
fn count_circuits(
&self,
predicates: &[CircuitPredicate],
) -> Result<u32, AdminServiceStoreError> {
let count = self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.circuit_state
.circuits
.iter()
.filter(|(_, circuit)| {
predicates
.iter()
.all(|predicate| predicate.apply_to_circuit(circuit))
})
.count();
u32::try_from(count).map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"The number of circuits is larger than the max u32".to_string(),
))
})
}
fn upgrade_proposal_to_circuit(&self, circuit_id: &str) -> Result<(), AdminServiceStoreError> {
{
let mut state = self.state.lock().map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?;
if let Some(proposal) = state.proposal_state.proposals.remove(circuit_id) {
let nodes = proposal.circuit().members().to_vec();
let services = proposal.circuit().roster().to_vec();
let circuit = Circuit::from(proposal.circuit().clone());
state
.circuit_state
.circuits
.insert(circuit.circuit_id().to_string(), circuit);
for service in services.into_iter() {
let service_id =
ServiceId::new(service.service_id().to_string(), circuit_id.to_string());
state
.service_directory
.insert(service_id, Service::from(service));
}
for node in nodes.into_iter() {
if !state.circuit_state.nodes.contains_key(node.node_id()) {
state
.circuit_state
.nodes
.insert(node.node_id().to_string(), CircuitNode::from(node));
}
}
} else {
return Err(AdminServiceStoreError::InvalidStateError(
InvalidStateError::with_message(format!(
"A circuit proposal with ID {} does not exist",
circuit_id
)),
));
}
}
self.write_state().map_err(|err| {
AdminServiceStoreError::InternalError(InternalError::from_source_with_prefix(
Box::new(err),
"Unable to write circuit state yaml file".to_string(),
))
})
}
fn get_node(&self, node_id: &str) -> Result<Option<CircuitNode>, AdminServiceStoreError> {
Ok(self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.circuit_state
.nodes
.get(node_id)
.cloned())
}
fn list_nodes(
&self,
) -> Result<Box<dyn ExactSizeIterator<Item = CircuitNode>>, AdminServiceStoreError> {
let nodes: Box<dyn ExactSizeIterator<Item = CircuitNode>> = Box::new(
self.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.circuit_state
.nodes
.iter()
.map(|(_, node)| node.clone())
.collect::<Vec<_>>()
.into_iter(),
);
Ok(nodes)
}
fn get_service(
&self,
service_id: &ServiceId,
) -> Result<Option<Service>, AdminServiceStoreError> {
Ok(self
.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.service_directory
.get(service_id)
.cloned())
}
fn list_services(
&self,
circuit_id: &str,
) -> Result<Box<dyn ExactSizeIterator<Item = Service>>, AdminServiceStoreError> {
let services: Vec<Service> =
self.state
.lock()
.map_err(|_| {
AdminServiceStoreError::InternalError(InternalError::with_message(
"YAML admin service store's internal lock was poisoned".to_string(),
))
})?
.circuit_state
.circuits
.get(circuit_id)
.ok_or_else(|| {
AdminServiceStoreError::InvalidStateError(InvalidStateError::with_message(
format!("A circuit with ID {} does not exist", circuit_id),
))
})?
.roster()
.to_vec();
Ok(Box::new(services.into_iter()))
}
fn add_event(
&self,
_event: messages::AdminServiceEvent,
) -> Result<AdminServiceEvent, AdminServiceStoreError> {
unimplemented!()
}
fn list_events_since(&self, _start: i64) -> Result<EventIter, AdminServiceStoreError> {
unimplemented!()
}
fn list_events_by_management_type_since(
&self,
_management_type: String,
_start: i64,
) -> Result<EventIter, AdminServiceStoreError> {
unimplemented!()
}
fn clone_boxed(&self) -> Box<dyn AdminServiceStore> {
Box::new(self.clone())
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
struct YamlCircuit {
id: String,
roster: Vec<YamlService>,
members: Vec<String>,
auth: YamlAuthorizationType,
persistence: YamlPersistenceType,
durability: YamlDurabilityType,
routes: YamlRouteType,
circuit_management_type: String,
display_name: Option<String>,
#[serde(default = "default_circuit_value")]
circuit_version: i32,
#[serde(default = "default_circuit_status")]
circuit_status: YamlCircuitStatus,
}
impl TryFrom<YamlCircuit> for Circuit {
type Error = InvalidStateError;
fn try_from(circuit: YamlCircuit) -> Result<Self, Self::Error> {
let mut builder = CircuitBuilder::new()
.with_circuit_id(&circuit.id)
.with_roster(
&circuit
.roster
.into_iter()
.map(Service::try_from)
.collect::<Result<Vec<Service>, InvalidStateError>>()?,
)
.with_members(
&circuit
.members
.into_iter()
.map(|node| {
CircuitNodeBuilder::new()
.with_node_id(&node)
.with_endpoints(&[])
.build()
})
.collect::<Result<Vec<CircuitNode>, InvalidStateError>>()?,
)
.with_authorization_type(&AuthorizationType::from(circuit.auth))
.with_persistence(&PersistenceType::from(circuit.persistence))
.with_durability(&DurabilityType::from(circuit.durability))
.with_routes(&RouteType::from(circuit.routes))
.with_circuit_management_type(&circuit.circuit_management_type)
.with_circuit_version(circuit.circuit_version)
.with_circuit_status(&CircuitStatus::from(circuit.circuit_status));
if let Some(display_name) = &circuit.display_name {
builder = builder.with_display_name(display_name);
}
builder.build()
}
}
impl From<Circuit> for YamlCircuit {
fn from(circuit: Circuit) -> Self {
YamlCircuit {
id: circuit.circuit_id().into(),
roster: circuit
.roster()
.iter()
.map(|service| YamlService::from(service.clone()))
.collect(),
members: circuit
.members()
.iter()
.map(|node| node.node_id().to_string())
.collect(),
auth: circuit.authorization_type().clone().into(),
persistence: circuit.persistence().clone().into(),
durability: circuit.durability().clone().into(),
routes: circuit.routes().clone().into(),
circuit_management_type: circuit.circuit_management_type().into(),
display_name: circuit.display_name().clone(),
circuit_version: circuit.circuit_version(),
circuit_status: circuit.circuit_status().clone().into(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
struct YamlService {
service_id: String,
service_type: String,
allowed_nodes: Vec<String>,
arguments: BTreeMap<String, String>,
}
impl TryFrom<YamlService> for Service {
type Error = InvalidStateError;
fn try_from(service: YamlService) -> Result<Self, Self::Error> {
ServiceBuilder::new()
.with_service_id(&service.service_id)
.with_service_type(&service.service_type)
.with_node_id(service.allowed_nodes.get(0).ok_or_else(|| {
InvalidStateError::with_message("Must contain 1 node ID".to_string())
})?)
.with_arguments(
&service
.arguments
.iter()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect::<Vec<(String, String)>>(),
)
.build()
}
}
impl From<Service> for YamlService {
fn from(service: Service) -> Self {
YamlService {
service_id: service.service_id().into(),
service_type: service.service_type().into(),
allowed_nodes: vec![service.node_id().into()],
arguments: service
.arguments()
.iter()
.map(|(key, value)| (key.into(), value.into()))
.collect(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
struct YamlCircuitState {
nodes: BTreeMap<String, YamlCircuitNode>,
circuits: BTreeMap<String, YamlCircuit>,
}
impl TryFrom<YamlCircuitState> for CircuitState {
type Error = InvalidStateError;
fn try_from(state: YamlCircuitState) -> Result<Self, Self::Error> {
Ok(CircuitState {
nodes: state
.nodes
.into_iter()
.map(|(id, node)| CircuitNode::try_from(node).map(|node| (id, node)))
.collect::<Result<BTreeMap<String, CircuitNode>, InvalidStateError>>()?,
circuits: state
.circuits
.into_iter()
.map(|(id, circuit)| match Circuit::try_from(circuit) {
Ok(circuit) => Ok((id, circuit)),
Err(err) => Err(err),
})
.collect::<Result<BTreeMap<String, Circuit>, InvalidStateError>>()?,
})
}
}
impl From<CircuitState> for YamlCircuitState {
fn from(state: CircuitState) -> Self {
YamlCircuitState {
nodes: state
.nodes
.into_iter()
.map(|(id, node)| (id, node.into()))
.collect::<BTreeMap<String, YamlCircuitNode>>(),
circuits: state
.circuits
.into_iter()
.map(|(id, circuit)| (id, YamlCircuit::from(circuit)))
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
struct CircuitState {
nodes: BTreeMap<String, CircuitNode>,
circuits: BTreeMap<String, Circuit>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct YamlCircuitProposal {
proposal_type: YamlProposalType,
circuit_id: String,
circuit_hash: String,
circuit: YamlProposedCircuit,
votes: Vec<YamlVoteRecord>,
requester: String,
requester_node_id: String,
}
impl From<ProposalState> for YamlProposalState {
fn from(state: ProposalState) -> Self {
YamlProposalState {
proposals: state
.proposals
.into_iter()
.map(|(id, proposal)| (id, YamlCircuitProposal::from(proposal)))
.collect(),
}
}
}
impl TryFrom<YamlProposalState> for ProposalState {
type Error = InvalidStateError;
fn try_from(state: YamlProposalState) -> Result<Self, Self::Error> {
Ok(ProposalState {
proposals: state
.proposals
.into_iter()
.map(|(id, proposal)| match CircuitProposal::try_from(proposal) {
Ok(proposal) => Ok((id, proposal)),
Err(err) => Err(err),
})
.collect::<Result<BTreeMap<String, CircuitProposal>, InvalidStateError>>()?,
})
}
}
impl TryFrom<YamlCircuitProposal> for CircuitProposal {
type Error = InvalidStateError;
fn try_from(proposal: YamlCircuitProposal) -> Result<Self, Self::Error> {
CircuitProposalBuilder::new()
.with_circuit_id(&proposal.circuit_id)
.with_proposal_type(&ProposalType::from(proposal.proposal_type))
.with_circuit_hash(&proposal.circuit_hash)
.with_circuit(&ProposedCircuit::try_from(proposal.circuit)?)
.with_votes(
&proposal
.votes
.into_iter()
.map(VoteRecord::try_from)
.collect::<Result<Vec<VoteRecord>, InvalidStateError>>()?,
)
.with_requester(&PublicKey::from_bytes(
parse_hex(&proposal.requester).map_err(|_| {
InvalidStateError::with_message(
"Requester public key is not valid hex".to_string(),
)
})?,
))
.with_requester_node_id(&proposal.requester_node_id)
.build()
}
}
impl From<CircuitProposal> for YamlCircuitProposal {
fn from(proposal: CircuitProposal) -> Self {
YamlCircuitProposal {
circuit_id: proposal.circuit_id().into(),
proposal_type: proposal.proposal_type().clone().into(),
circuit_hash: proposal.circuit_hash().into(),
circuit: YamlProposedCircuit::from(proposal.circuit().clone()),
votes: proposal
.votes()
.iter()
.map(|vote| YamlVoteRecord::from(vote.clone()))
.collect(),
requester: to_hex(proposal.requester().as_slice()),
requester_node_id: proposal.requester_node_id().into(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum YamlProposalType {
Create,
UpdateRoster,
AddNode,
RemoveNode,
Disband,
}
impl From<YamlProposalType> for ProposalType {
fn from(proposal_type: YamlProposalType) -> Self {
match proposal_type {
YamlProposalType::Create => ProposalType::Create,
YamlProposalType::UpdateRoster => ProposalType::UpdateRoster,
YamlProposalType::AddNode => ProposalType::AddNode,
YamlProposalType::RemoveNode => ProposalType::RemoveNode,
YamlProposalType::Disband => ProposalType::Disband,
}
}
}
impl From<ProposalType> for YamlProposalType {
fn from(proposal_type: ProposalType) -> Self {
match proposal_type {
ProposalType::Create => YamlProposalType::Create,
ProposalType::UpdateRoster => YamlProposalType::UpdateRoster,
ProposalType::AddNode => YamlProposalType::AddNode,
ProposalType::RemoveNode => YamlProposalType::RemoveNode,
ProposalType::Disband => YamlProposalType::Disband,
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct YamlVoteRecord {
public_key: String,
vote: YamlVote,
voter_node_id: String,
}
impl TryFrom<YamlVoteRecord> for VoteRecord {
type Error = InvalidStateError;
fn try_from(vote: YamlVoteRecord) -> Result<Self, Self::Error> {
VoteRecordBuilder::new()
.with_public_key(&PublicKey::from_bytes(
parse_hex(&vote.public_key).map_err(|_| {
InvalidStateError::with_message(
"Requester public key is not valid hex".to_string(),
)
})?,
))
.with_vote(&Vote::from(vote.vote))
.with_voter_node_id(&vote.voter_node_id)
.build()
}
}
impl From<VoteRecord> for YamlVoteRecord {
fn from(vote: VoteRecord) -> Self {
YamlVoteRecord {
public_key: to_hex(vote.public_key().as_slice()),
vote: vote.vote().clone().into(),
voter_node_id: vote.voter_node_id().into(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
struct YamlProposedCircuit {
circuit_id: String,
roster: Vec<YamlProposedService>,
members: Vec<YamlProposedNode>,
authorization_type: YamlAuthorizationType,
persistence: YamlPersistenceType,
durability: YamlDurabilityType,
routes: YamlRouteType,
circuit_management_type: String,
application_metadata: Option<String>,
comments: Option<String>,
display_name: Option<String>,
#[serde(default = "default_circuit_value")]
circuit_version: i32,
#[serde(default = "default_circuit_status")]
circuit_status: YamlCircuitStatus,
}
impl TryFrom<YamlProposedCircuit> for ProposedCircuit {
type Error = InvalidStateError;
fn try_from(circuit: YamlProposedCircuit) -> Result<Self, Self::Error> {
let mut builder = ProposedCircuitBuilder::new()
.with_circuit_id(&circuit.circuit_id)
.with_roster(
&circuit
.roster
.into_iter()
.map(ProposedService::try_from)
.collect::<Result<Vec<ProposedService>, InvalidStateError>>()?,
)
.with_members(
&circuit
.members
.into_iter()
.map(ProposedNode::try_from)
.collect::<Result<Vec<ProposedNode>, InvalidStateError>>()?,
)
.with_authorization_type(&AuthorizationType::from(circuit.authorization_type))
.with_persistence(&PersistenceType::from(circuit.persistence))
.with_durability(&DurabilityType::from(circuit.durability))
.with_routes(&RouteType::from(circuit.routes))
.with_circuit_management_type(&circuit.circuit_management_type)
.with_circuit_version(circuit.circuit_version)
.with_circuit_status(&CircuitStatus::from(circuit.circuit_status));
if let Some(application_metadata) = circuit.application_metadata {
builder = builder.with_application_metadata(&parse_hex(&application_metadata).map_err(
|_| {
InvalidStateError::with_message(
"Requester application metadataca is not valid hex".to_string(),
)
},
)?)
}
if let Some(comments) = &circuit.comments {
builder = builder.with_comments(comments);
}
if let Some(display_name) = &circuit.display_name {
builder = builder.with_display_name(display_name);
}
builder.build()
}
}
impl From<ProposedCircuit> for YamlProposedCircuit {
fn from(circuit: ProposedCircuit) -> Self {
let application_metadata = circuit
.application_metadata()
.as_ref()
.map(|app_metadata| to_hex(app_metadata));
YamlProposedCircuit {
circuit_id: circuit.circuit_id().into(),
roster: circuit
.roster()
.iter()
.cloned()
.map(YamlProposedService::from)
.collect(),
members: circuit
.members()
.iter()
.cloned()
.map(YamlProposedNode::from)
.collect(),
authorization_type: circuit.authorization_type().clone().into(),
persistence: circuit.persistence().clone().into(),
durability: circuit.durability().clone().into(),
routes: circuit.routes().clone().into(),
circuit_management_type: circuit.circuit_management_type().into(),
application_metadata,
comments: circuit.comments().clone(),
display_name: circuit.display_name().clone(),
circuit_version: circuit.circuit_version(),
circuit_status: circuit.circuit_status().clone().into(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default, Eq)]
pub struct YamlProposedService {
service_id: String,
service_type: String,
allowed_nodes: Vec<String>,
arguments: Vec<(String, String)>,
}
impl TryFrom<YamlProposedService> for ProposedService {
type Error = InvalidStateError;
fn try_from(service: YamlProposedService) -> Result<Self, Self::Error> {
ProposedServiceBuilder::new()
.with_service_id(&service.service_id)
.with_service_type(&service.service_type)
.with_node_id(service.allowed_nodes.get(0).ok_or_else(|| {
InvalidStateError::with_message("Must contain 1 node ID".to_string())
})?)
.with_arguments(
&service
.arguments
.iter()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect::<Vec<(String, String)>>(),
)
.build()
}
}
impl From<ProposedService> for YamlProposedService {
fn from(service: ProposedService) -> Self {
YamlProposedService {
service_id: service.service_id().into(),
service_type: service.service_type().into(),
allowed_nodes: vec![service.node_id().to_string()],
arguments: service
.arguments()
.iter()
.map(|(key, value)| (key.into(), value.into()))
.collect(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct YamlProposedNode {
node_id: String,
endpoints: Vec<String>,
}
impl TryFrom<YamlProposedNode> for ProposedNode {
type Error = InvalidStateError;
fn try_from(node: YamlProposedNode) -> Result<Self, Self::Error> {
ProposedNodeBuilder::new()
.with_node_id(&node.node_id)
.with_endpoints(&node.endpoints)
.build()
}
}
impl From<ProposedNode> for YamlProposedNode {
fn from(node: ProposedNode) -> Self {
YamlProposedNode {
node_id: node.node_id().into(),
endpoints: node.endpoints().into(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum YamlAuthorizationType {
Trust,
Challenge,
}
impl From<AuthorizationType> for YamlAuthorizationType {
fn from(authorization_type: AuthorizationType) -> Self {
match authorization_type {
AuthorizationType::Trust => YamlAuthorizationType::Trust,
AuthorizationType::Challenge => YamlAuthorizationType::Challenge,
}
}
}
impl From<YamlAuthorizationType> for AuthorizationType {
fn from(yaml_authorization_type: YamlAuthorizationType) -> Self {
match yaml_authorization_type {
YamlAuthorizationType::Trust => AuthorizationType::Trust,
YamlAuthorizationType::Challenge => AuthorizationType::Challenge,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum YamlPersistenceType {
Any,
}
impl From<PersistenceType> for YamlPersistenceType {
fn from(persistence_type: PersistenceType) -> Self {
match persistence_type {
PersistenceType::Any => YamlPersistenceType::Any,
}
}
}
impl From<YamlPersistenceType> for PersistenceType {
fn from(yaml_persistence_type: YamlPersistenceType) -> Self {
match yaml_persistence_type {
YamlPersistenceType::Any => PersistenceType::Any,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum YamlDurabilityType {
NoDurability,
}
impl From<DurabilityType> for YamlDurabilityType {
fn from(durability_type: DurabilityType) -> Self {
match durability_type {
DurabilityType::NoDurability => YamlDurabilityType::NoDurability,
}
}
}
impl From<YamlDurabilityType> for DurabilityType {
fn from(yaml_durability_type: YamlDurabilityType) -> Self {
match yaml_durability_type {
YamlDurabilityType::NoDurability => DurabilityType::NoDurability,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum YamlRouteType {
Any,
}
impl From<RouteType> for YamlRouteType {
fn from(route_type: RouteType) -> Self {
match route_type {
RouteType::Any => YamlRouteType::Any,
}
}
}
impl From<YamlRouteType> for RouteType {
fn from(yaml_route_type: YamlRouteType) -> Self {
match yaml_route_type {
YamlRouteType::Any => RouteType::Any,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct YamlCircuitNode {
id: String,
endpoints: Vec<String>,
}
impl From<CircuitNode> for YamlCircuitNode {
fn from(circuit_node: CircuitNode) -> Self {
YamlCircuitNode {
id: circuit_node.node_id().to_string(),
endpoints: circuit_node.endpoints().to_vec(),
}
}
}
impl TryFrom<YamlCircuitNode> for CircuitNode {
type Error = InvalidStateError;
fn try_from(yaml_circuit_node: YamlCircuitNode) -> Result<Self, Self::Error> {
CircuitNodeBuilder::new()
.with_node_id(&yaml_circuit_node.id)
.with_endpoints(&yaml_circuit_node.endpoints)
.build()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum YamlVote {
Accept,
Reject,
}
impl From<Vote> for YamlVote {
fn from(vote: Vote) -> Self {
match vote {
Vote::Accept => YamlVote::Accept,
Vote::Reject => YamlVote::Reject,
}
}
}
impl From<YamlVote> for Vote {
fn from(vote: YamlVote) -> Self {
match vote {
YamlVote::Accept => Vote::Accept,
YamlVote::Reject => Vote::Reject,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
struct YamlProposalState {
proposals: BTreeMap<String, YamlCircuitProposal>,
}
#[derive(Debug, Clone, PartialEq, Default)]
struct ProposalState {
proposals: BTreeMap<String, CircuitProposal>,
}
#[derive(Debug, Clone, Default)]
struct YamlState {
circuit_state: CircuitState,
proposal_state: ProposalState,
service_directory: BTreeMap<ServiceId, Service>,
}
fn default_circuit_value() -> i32 {
1
}
fn default_circuit_status() -> YamlCircuitStatus {
YamlCircuitStatus::Active
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum YamlCircuitStatus {
Active,
Disbanded,
Abandoned,
}
impl From<CircuitStatus> for YamlCircuitStatus {
fn from(circuit_status: CircuitStatus) -> Self {
match circuit_status {
CircuitStatus::Active => YamlCircuitStatus::Active,
CircuitStatus::Disbanded => YamlCircuitStatus::Disbanded,
CircuitStatus::Abandoned => YamlCircuitStatus::Abandoned,
}
}
}
impl From<YamlCircuitStatus> for CircuitStatus {
fn from(yaml_circuit_status: YamlCircuitStatus) -> Self {
match yaml_circuit_status {
YamlCircuitStatus::Active => CircuitStatus::Active,
YamlCircuitStatus::Disbanded => CircuitStatus::Disbanded,
YamlCircuitStatus::Abandoned => CircuitStatus::Abandoned,
}
}
}
#[cfg(test)]
mod tests {
use std::io::Read;
use tempdir::TempDir;
use super::*;
use crate::admin::store::{
CircuitNodeBuilder, CircuitProposalBuilder, ProposalType, ProposedCircuitBuilder,
ProposedNodeBuilder, ProposedServiceBuilder, Vote, VoteRecordBuilder,
};
use crate::hex::parse_hex;
const CIRCUIT_STATE: &[u8] = b"---
nodes:
acme-node-000:
id: acme-node-000
endpoints:
- \"tcps://splinterd-node-acme:8044\"
bubba-node-000:
id: bubba-node-000
endpoints:
- \"tcps://splinterd-node-bubba:8044\"
circuits:
WBKLF-AAAAA:
id: WBKLF-AAAAA
auth: Trust
members:
- bubba-node-000
- acme-node-000
roster:
- service_id: a000
service_type: scabbard
allowed_nodes:
- acme-node-000
arguments:
admin_keys: '[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]'
peer_services: '[\"a001\"]'
- service_id: a001
service_type: scabbard
allowed_nodes:
- bubba-node-000
arguments:
admin_keys: '[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]'
peer_services: '[\"a000\"]'
persistence: Any
durability: NoDurability
routes: Any
circuit_management_type: gameroom
circuit_status: Active";
const PROPOSAL_STATE: &[u8] = b"---
proposals:
WBKLF-BBBBB:
proposal_type: Create
circuit_id: WBKLF-BBBBB
circuit_hash: 7ddc426972710adc0b2ecd49e89a9dd805fb9206bf516079724c887bedbcdf1d
circuit:
circuit_id: WBKLF-BBBBB
roster:
- service_id: a000
service_type: scabbard
allowed_nodes:
- acme-node-000
arguments:
- - peer_services
- '[\"a001\"]'
- - admin_keys
- '[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]'
- service_id: a001
service_type: scabbard
allowed_nodes:
- bubba-node-000
arguments:
- - peer_services
- '[\"a000\"]'
- - admin_keys
- '[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]'
members:
- node_id: bubba-node-000
endpoints:
- \"tcps://splinterd-node-bubba:8044\"
- node_id: acme-node-000
endpoints:
- \"tcps://splinterd-node-acme:8044\"
authorization_type: Trust
persistence: Any
durability: NoDurability
routes: Any
circuit_management_type: gameroom
display_name: \"test_display\"
circuit_status: Active
votes: []
requester: 0283a14e0a17cb7f665311e9b5560f4cde2b502f17e2d03223e15d90d9318d7482
requester_node_id: acme-node-000";
#[test]
fn test_write_new_files() {
let temp_dir = TempDir::new("test_write_new_files").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
assert!(!PathBuf::from(circuit_path.clone()).is_file());
assert!(!PathBuf::from(proposals_path.clone()).is_file());
let _store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
assert!(PathBuf::from(circuit_path.clone()).is_file());
assert!(PathBuf::from(proposals_path.clone()).is_file());
}
#[test]
fn test_read_existing_files() {
let temp_dir = TempDir::new("test_read_existing_files").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
write_file(CIRCUIT_STATE, &circuit_path);
write_file(PROPOSAL_STATE, &proposals_path);
let store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
assert!(store
.get_proposal("WBKLF-BBBBB")
.expect("unable to fetch proposals")
.is_some());
assert!(store
.get_circuit("WBKLF-AAAAA")
.expect("unable to fetch circuits")
.is_some());
}
#[test]
fn test_proposals() {
let temp_dir = TempDir::new("test_proposals").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
write_file(CIRCUIT_STATE, &circuit_path);
write_file(PROPOSAL_STATE, &proposals_path);
let store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
let proposal = store
.get_proposal("WBKLF-BBBBB")
.expect("unable to fetch proposals")
.expect("Expected proposal, got none");
assert_eq!(proposal, create_expected_proposal());
assert!(store
.get_proposal("WBKLF-BADD")
.expect("unable to fetch proposals")
.is_none());
let updated_proposal = proposal
.builder()
.with_votes(&vec![VoteRecordBuilder::new()
.with_public_key(&PublicKey::from_bytes(
parse_hex("035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550")
.unwrap(),
))
.with_vote(&Vote::Accept)
.with_voter_node_id("bubba-node-000")
.build()
.expect("Unable to build vote record")])
.build()
.expect("Unable to build updated proposal");
store
.update_proposal(updated_proposal.clone())
.expect("Unable to update proposal");
let new_proposal = new_proposal();
assert!(
store.update_proposal(new_proposal.clone()).is_err(),
"Updating new proposal should fail"
);
store
.add_proposal(new_proposal.clone())
.expect("Unable to add proposal");
assert_eq!(
store
.list_proposals(&vec![])
.expect("Unable to get list of proposals")
.collect::<Vec<CircuitProposal>>(),
vec![updated_proposal, new_proposal.clone()]
);
store
.remove_proposal("WBKLF-BBBBB")
.expect("Unable to remove proposals");
let mut yaml_state = BTreeMap::new();
yaml_state.insert(
new_proposal.circuit_id().to_string(),
YamlCircuitProposal::from(new_proposal),
);
let mut yaml_state_vec = serde_yaml::to_vec(&YamlProposalState {
proposals: yaml_state,
})
.unwrap();
yaml_state_vec.append(&mut "\n".as_bytes().to_vec());
let mut contents = vec![];
File::open(proposals_path.clone())
.unwrap()
.read_to_end(&mut contents)
.expect("Unable to read proposals");
assert_eq!(yaml_state_vec, contents)
}
#[test]
fn test_circuit() {
let temp_dir = TempDir::new("test_circuit").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
write_file(CIRCUIT_STATE, &circuit_path);
write_file(PROPOSAL_STATE, &proposals_path);
let store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
let circuit = store
.get_circuit("WBKLF-AAAAA")
.expect("unable to fetch circuit")
.expect("Expected circuit, got none");
assert_eq!(circuit, create_expected_circuit());
assert!(store
.get_circuit("WBKLF-BADD")
.expect("unable to fetch circuit")
.is_none());
let updated_circuit = CircuitBuilder::default()
.with_circuit_id("WBKLF-AAAAA")
.with_roster(&vec![
ServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id("acme-node-000")
.with_arguments(&vec![
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()),
("peer_services".into(), "[\"a001\"]".into()),
])
.build()
.expect("Unable to build service"),
ServiceBuilder::default()
.with_service_id("a001")
.with_service_type("scabbard")
.with_node_id("bubba-node-000")
.with_arguments(&vec![(
"admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()
),(
"peer_services".into(), "[\"a000\"]".into()
)])
.build()
.expect("Unable to build service"),
])
.with_members(&vec![
CircuitNodeBuilder::new()
.with_node_id("bubba-node-000")
.with_endpoints(&[])
.build()
.expect("Unable to build circuit node"),
CircuitNodeBuilder::new()
.with_node_id("acme-node-000")
.with_endpoints(&[])
.build()
.expect("Unable to build circuit node"),
])
.with_circuit_management_type("test")
.with_circuit_status(&CircuitStatus::default())
.build()
.expect("Unable to build circuit");
store
.update_circuit(updated_circuit.clone())
.expect("Unable to update circuit");
let (new_circuit, new_node) = new_circuit();
assert!(
store.update_circuit(new_circuit.clone()).is_err(),
"Updating new cirucit should fail"
);
store
.add_circuit(new_circuit.clone(), vec![new_node.clone()])
.expect("Unable to add cirucit");
assert_eq!(
store
.list_circuits(&vec![])
.expect("Unable to get list of circuits")
.collect::<Vec<Circuit>>(),
vec![updated_circuit, new_circuit.clone()]
);
store
.remove_circuit("WBKLF-AAAAA")
.expect("Unable to remove circuit");
let mut yaml_circuits = BTreeMap::new();
let mut yaml_nodes = BTreeMap::new();
yaml_circuits.insert(
new_circuit.circuit_id().to_string(),
YamlCircuit::from(new_circuit),
);
yaml_nodes.insert(
"acme-node-000".to_string(),
YamlCircuitNode::from(
CircuitNodeBuilder::new()
.with_node_id("acme-node-000")
.with_endpoints(&["tcps://splinterd-node-acme:8044".into()])
.build()
.expect("Unable to build circuit node"),
),
);
yaml_nodes.insert(
"bubba-node-000".to_string(),
YamlCircuitNode::from(
CircuitNodeBuilder::new()
.with_node_id("bubba-node-000")
.with_endpoints(&["tcps://splinterd-node-bubba:8044".into()])
.build()
.expect("Unable to build circuit node"),
),
);
yaml_nodes.insert(
new_node.node_id().to_string(),
YamlCircuitNode::from(new_node),
);
let mut yaml_state_vec = serde_yaml::to_vec(&YamlCircuitState {
circuits: yaml_circuits,
nodes: yaml_nodes,
})
.unwrap();
yaml_state_vec.append(&mut "\n".as_bytes().to_vec());
let mut contents = vec![];
File::open(circuit_path.clone())
.unwrap()
.read_to_end(&mut contents)
.expect("Unable to read proposals");
assert_eq!(yaml_state_vec, contents)
}
#[test]
fn test_node() {
let temp_dir = TempDir::new("test_node").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
write_file(CIRCUIT_STATE, &circuit_path);
write_file(PROPOSAL_STATE, &proposals_path);
let store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
let node = store
.get_node("acme-node-000")
.expect("Unable to fetch node")
.expect("expected node, got none");
assert_eq!(
node,
CircuitNodeBuilder::new()
.with_node_id("acme-node-000")
.with_endpoints(&["tcps://splinterd-node-acme:8044".into()])
.build()
.expect("Unable to build circuit node"),
);
assert_eq!(
store.list_nodes().unwrap().collect::<Vec<CircuitNode>>(),
vec![
CircuitNodeBuilder::new()
.with_node_id("acme-node-000")
.with_endpoints(&["tcps://splinterd-node-acme:8044".into()])
.build()
.expect("Unable to build circuit node"),
CircuitNodeBuilder::new()
.with_node_id("bubba-node-000")
.with_endpoints(&["tcps://splinterd-node-bubba:8044".into()])
.build()
.expect("Unable to build circuit node"),
]
);
}
#[test]
fn test_service() {
let temp_dir = TempDir::new("test_service").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
write_file(CIRCUIT_STATE, &circuit_path);
write_file(PROPOSAL_STATE, &proposals_path);
let service_id = ServiceId::new("a000".to_string(), "WBKLF-AAAAA".to_string());
let store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
let service = store
.get_service(&service_id)
.expect("Unable to fetch service")
.expect("unable to get expected service, got none");
assert_eq!(
service,
ServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id("acme-node-000")
.with_arguments(&vec![
(
"admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()
),
("peer_services".into(), "[\"a001\"]".into()),
])
.build()
.expect("Unable to build service"),
);
assert_eq!(
store
.list_services("WBKLF-AAAAA")
.unwrap()
.collect::<Vec<Service>>(),
vec![
ServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id("acme-node-000")
.with_arguments(&vec![
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()),
("peer_services".into(), "[\"a001\"]".into()),
])
.build()
.expect("Unable to build service"),
ServiceBuilder::default()
.with_service_id("a001")
.with_service_type("scabbard")
.with_node_id("bubba-node-000")
.with_arguments(&vec![
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()),
("peer_services".into(), "[\"a000\"]".into()),
])
.build()
.expect("Unable to build service")
]
);
}
#[test]
fn test_upgrading_proposals_to_circuit() {
let temp_dir =
TempDir::new("est_upgrading_proposals_to_circuit").expect("Failed to create temp dir");
let circuit_path = temp_dir
.path()
.join("circuits.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
let proposals_path = temp_dir
.path()
.join("circuit_proposals.yaml")
.to_str()
.expect("Failed to get path")
.to_string();
write_file(PROPOSAL_STATE, &proposals_path);
let store = YamlAdminServiceStore::new(circuit_path.clone(), proposals_path.clone())
.expect("Unable to create yaml admin store");
let service_id = ServiceId::new("a000".to_string(), "WBKLF-BBBBB".to_string());
assert_eq!(store.get_circuit("WBKLF-BBBBB").unwrap(), None);
assert_eq!(store.get_node("acme-node-000").unwrap(), None);
assert_eq!(store.get_service(&service_id).unwrap(), None);
store
.upgrade_proposal_to_circuit("WBKLF-BBBBB")
.expect("Unable to upgrade proposalto circuit");
assert_eq!(store.list_proposals(&vec![]).unwrap().next(), None);
assert!(store.get_circuit("WBKLF-BBBBB").unwrap().is_some());
assert!(store.get_node("acme-node-000").unwrap().is_some());
assert!(store.get_service(&service_id).unwrap().is_some());
}
fn write_file(data: &[u8], file_path: &str) {
let mut file = File::create(file_path).expect("Error creating test yaml file.");
file.write_all(data)
.expect("unable to write test file to temp dir")
}
fn create_expected_proposal() -> CircuitProposal {
CircuitProposalBuilder::default()
.with_proposal_type(&ProposalType::Create)
.with_circuit_id("WBKLF-BBBBB")
.with_circuit_hash(
"7ddc426972710adc0b2ecd49e89a9dd805fb9206bf516079724c887bedbcdf1d")
.with_circuit(
&ProposedCircuitBuilder::default()
.with_circuit_id("WBKLF-BBBBB")
.with_roster(&vec![
ProposedServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id(&"acme-node-000")
.with_arguments(&vec![
("peer_services".into(), "[\"a001\"]".into()),
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]".into())
])
.build().expect("Unable to build service"),
ProposedServiceBuilder::default()
.with_service_id("a001")
.with_service_type("scabbard")
.with_node_id(&"bubba-node-000")
.with_arguments(&vec![
("peer_services".into(), "[\"a000\"]".into()),
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]".into())
])
.build().expect("Unable to build service")
])
.with_members(
&vec![
ProposedNodeBuilder::default()
.with_node_id("bubba-node-000".into())
.with_endpoints(&vec!["tcps://splinterd-node-bubba:8044".into()])
.build().expect("Unable to build node"),
ProposedNodeBuilder::default()
.with_node_id("acme-node-000".into())
.with_endpoints(&vec!["tcps://splinterd-node-acme:8044".into()])
.build().expect("Unable to build node"),
]
)
.with_circuit_management_type("gameroom")
.with_display_name("test_display")
.with_circuit_status(&CircuitStatus::default())
.build().expect("Unable to build circuit")
)
.with_requester(
&PublicKey::from_bytes(parse_hex(
"0283a14e0a17cb7f665311e9b5560f4cde2b502f17e2d03223e15d90d9318d7482").unwrap()))
.with_requester_node_id("acme-node-000")
.build().expect("Unable to build proposals")
}
fn create_expected_circuit() -> Circuit {
CircuitBuilder::default()
.with_circuit_id("WBKLF-AAAAA")
.with_roster(&vec![
ServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id("acme-node-000")
.with_arguments(&vec![
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()),
("peer_services".into(), "[\"a001\"]".into()),
])
.build()
.expect("Unable to build service"),
ServiceBuilder::default()
.with_service_id("a001")
.with_service_type("scabbard")
.with_node_id("bubba-node-000")
.with_arguments(&vec![(
"admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]"
.into()
),(
"peer_services".into(), "[\"a000\"]".into()
)])
.build()
.expect("Unable to build service"),
])
.with_members(&vec![
CircuitNodeBuilder::new()
.with_node_id("bubba-node-000")
.with_endpoints(&[])
.build()
.expect("Unable to build circuit node"),
CircuitNodeBuilder::new()
.with_node_id("acme-node-000")
.with_endpoints(&[])
.build()
.expect("Unable to build circuit node"),
])
.with_circuit_management_type("gameroom")
.with_circuit_version(1)
.with_circuit_status(&CircuitStatus::default())
.build()
.expect("Unable to build circuit")
}
fn new_proposal() -> CircuitProposal {
CircuitProposalBuilder::default()
.with_proposal_type(&ProposalType::Create)
.with_circuit_id("WBKLF-CCCCC")
.with_circuit_hash(
"7ddc426972710adc0b2ecd49e89a9dd805fb9206bf516079724c887bedbcdf1d")
.with_circuit(
&ProposedCircuitBuilder::default()
.with_circuit_id("WBKLF-PqfoE")
.with_roster(&vec![
ProposedServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id("acme-node-000")
.with_arguments(&vec![
("peer_services".into(), "[\"a001\"]".into()),
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]".into())
])
.build().expect("Unable to build service"),
ProposedServiceBuilder::default()
.with_service_id("a001")
.with_service_type("scabbard")
.with_node_id("bubba-node-000")
.with_arguments(&vec![
("peer_services".into(), "[\"a000\"]".into()),
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]".into())
])
.build().expect("Unable to build service")
])
.with_members(
&vec![
ProposedNodeBuilder::default()
.with_node_id("bubba-node-000".into())
.with_endpoints(&vec!["tcps://splinterd-node-bubba:8044".into()])
.build().expect("Unable to build node"),
ProposedNodeBuilder::default()
.with_node_id("acme-node-000".into())
.with_endpoints(&vec!["tcps://splinterd-node-acme:8044".into()])
.build().expect("Unable to build node"),
]
)
.with_circuit_management_type("test")
.with_circuit_status(&CircuitStatus::default())
.build().expect("Unable to build circuit")
)
.with_requester(
&PublicKey::from_bytes(parse_hex(
"0283a14e0a17cb7f665311e9b5560f4cde2b502f17e2d03223e15d90d9318d7482").unwrap()))
.with_requester_node_id("acme-node-000")
.build().expect("Unable to build proposals")
}
fn new_circuit() -> (Circuit, CircuitNode) {
(CircuitBuilder::default()
.with_circuit_id("WBKLF-DDDDD")
.with_roster(&vec![
ServiceBuilder::default()
.with_service_id("a000")
.with_service_type("scabbard")
.with_node_id("acme-node-000")
.with_arguments(&vec![
("peer_services".into(), "[\"a001\"]".into()),
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]".into())
])
.build().expect("Unable to build service"),
ServiceBuilder::default()
.with_service_id("a001")
.with_service_type("scabbard")
.with_node_id("bubba-node-000")
.with_arguments(&vec![
("peer_services".into(), "[\"a000\"]".into()),
("admin_keys".into(),
"[\"035724d11cae47c8907f8bfdf510488f49df8494ff81b63825bad923733c4ac550\"]".into())
])
.build().expect("Unable to build service")
])
.with_members(
&vec![
CircuitNodeBuilder::new()
.with_node_id("acme-node-000")
.with_endpoints(&[])
.build()
.expect("Unable to build circuit node"),
CircuitNodeBuilder::new()
.with_node_id("bubba-node-000")
.with_endpoints(&[])
.build()
.expect("Unable to build circuit node"),
CircuitNodeBuilder::default()
.with_node_id("new-node-000".into())
.with_endpoints(&vec![])
.build().expect("Unable to build node")
]
)
.with_circuit_management_type("test")
.with_circuit_status(&CircuitStatus::default())
.build().expect("Unable to build circuit"),
CircuitNodeBuilder::default()
.with_node_id("new-node-000".into())
.with_endpoints(&vec!["tcps://splinterd-node-new:8044".into()])
.build().expect("Unable to build node"))
}
}