Showcase version commit

This commit is contained in:
Gregor Reitzenstein 2020-02-18 16:55:19 +01:00
parent 796e957b27
commit 9c4144ac66
9 changed files with 368 additions and 184 deletions

View File

@ -1,5 +1,5 @@
[package] [package]
name = "bffh" name = "diflouroborane"
version = "0.1.0" version = "0.1.0"
authors = ["Gregor Reitzenstein <me@dequbed.space>"] authors = ["Gregor Reitzenstein <me@dequbed.space>"]
edition = "2018" edition = "2018"

View File

@ -5,37 +5,50 @@ use slog::Logger;
use casbin::prelude::*; use casbin::prelude::*;
use super::config::Config;
use futures_signals::signal::Mutable; use futures_signals::signal::Mutable;
use crate::api::api; use crate::api::api;
use crate::config::Config;
use crate::auth::Authentication; use crate::auth::Authentication;
use crate::error::Result; use crate::error::Result;
use std::rc::Rc;
use async_std::sync::{Arc, RwLock};
use std::ops::Deref;
pub struct PermissionsProvider {
log: Logger,
pdb: Enforcer,
}
impl PermissionsProvider {
pub fn new(log: Logger, pdb: Enforcer) -> Self {
Self { log, pdb }
}
pub fn enforce(&self, actor: &str, object: &str, action: &str) -> Result<bool> {
let b = self.pdb.enforce(vec![actor, object, action])?;
Ok(b)
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct Permissions { pub struct Permissions {
log: Logger, inner: Arc<RwLock<PermissionsProvider>>,
pdb: Mutable<Enforcer>, auth: Rc<Authentication>,
auth: Authentication,
} }
impl Permissions { impl Permissions {
pub fn new(log: Logger, pdb: Mutable<Enforcer>, auth: Authentication) -> Self { pub fn new(inner: Arc<RwLock<PermissionsProvider>>, auth: Rc<Authentication>) -> Self {
Self { log, pdb, auth } Self { inner, auth }
} }
pub fn enforce(&self, object: &str, action: &str) -> bool { pub async fn enforce(&self, object: &str, action: &str) -> Result<bool> {
if let Some(actor) = self.auth.get_authzid() { if let Some(actor) = self.auth.state.read().await.deref() {
trace!(self.log, "Checking permission {} for {} on {}", action, actor, object); self.inner.read().await.enforce(&actor, object, action)
let r = self.pdb.lock_ref().enforce(vec![&actor,object,action]).unwrap();
if !r {
info!(self.log, "Failed permission {} for {} on {}", action, actor, object);
}
return r;
} else { } else {
info!(self.log, "Attempted anonymous access: {} on {}", action, object); Ok(false)
false
} }
} }
} }
@ -45,11 +58,11 @@ impl api::permissions::Server for Permissions {
} }
/// This line documents init /// This line documents init
pub async fn init(config: &Config) -> std::result::Result<Enforcer, Box<dyn std::error::Error>> { pub async fn init(log: Logger, config: &Config) -> std::result::Result<PermissionsProvider, Box<dyn std::error::Error>> {
let model = Model::from_file(config.access.model.clone()).await?; let model = Model::from_file(config.access.model.clone()).await?;
let adapter = Box::new(FileAdapter::new(config.access.policy.clone())); let adapter = Box::new(FileAdapter::new(config.access.policy.clone()));
let e = Enforcer::new(model, adapter).await?; let e = Enforcer::new(model, adapter).await?;
return Ok(e); return Ok(PermissionsProvider::new(log, e));
} }

View File

@ -7,13 +7,20 @@ pub mod api {
use std::default::Default; use std::default::Default;
use async_std::net::TcpStream; use async_std::net::TcpStream;
use futures::task::Spawn;
use futures::FutureExt;
use futures_signals::signal::Mutable; use futures_signals::signal::Mutable;
use casbin::Enforcer; use casbin::Enforcer;
use casbin::MgmtApi; use casbin::MgmtApi;
use crate::machine::Machines; use slog::Logger;
use crate::auth::Authentication;
use crate::access::Permissions; use std::rc::Rc;
use async_std::sync::{Arc, RwLock};
use crate::machine::{MachinesProvider, Machines};
use crate::auth::{AuthenticationProvider, Authentication};
use crate::access::{PermissionsProvider, Permissions};
use capnp::{Error}; use capnp::{Error};
use capnp::capability::Promise; use capnp::capability::Promise;
@ -21,35 +28,76 @@ use capnp_rpc::RpcSystem;
use capnp_rpc::twoparty::VatNetwork; use capnp_rpc::twoparty::VatNetwork;
use capnp_rpc::rpc_twoparty_capnp::Side; use capnp_rpc::rpc_twoparty_capnp::Side;
use std::ops::Deref;
use api::diflouroborane; use api::diflouroborane;
pub fn init() { #[derive(Clone)]
pub struct API<S> {
auth: Arc<RwLock<AuthenticationProvider>>,
perm: Arc<RwLock<PermissionsProvider>>,
mach: Arc<RwLock<MachinesProvider>>,
spawner: S,
}
impl<S: Spawn> API<S> {
pub fn new(auth: AuthenticationProvider,
perm: PermissionsProvider,
mach: MachinesProvider,
spawner: S)
-> Self
{
let auth = Arc::new(RwLock::new(auth));
let perm = Arc::new(RwLock::new(perm));
let mach = Arc::new(RwLock::new(mach));
Self { auth, perm, mach, spawner }
}
pub fn into_connection(self) -> Bootstrap {
let auth = Rc::new(Authentication::new(self.auth));
let perm = Rc::new(Permissions::new(self.perm, auth.clone()));
let mach = Machines::new(self.mach, perm.clone());
Bootstrap {
auth: auth,
perm: perm,
mach: mach,
}
}
} }
pub async fn process_socket(auth: Authentication, perm: Permissions, mach: Machines, socket: TcpStream) pub async fn handle_connection<S: Spawn>(api: API<S>, log: Logger, socket: TcpStream) -> Result<(), Error> {
-> Result<(), Error> info!(log, "A new connection");
{ let client = api.into_connection();
let api = Api { auth, perm, mach }; let a = api::diflouroborane::ToClient::new(client).into_client::<capnp_rpc::Server>();
let a = api::diflouroborane::ToClient::new(api).into_client::<capnp_rpc::Server>();
let netw = VatNetwork::new(socket.clone(), socket, Side::Server, Default::default()); let netw = VatNetwork::new(socket.clone(), socket, Side::Server, Default::default());
let rpc = RpcSystem::new(Box::new(netw), Some(a.clone().client));
rpc.await let rpc = RpcSystem::new(Box::new(netw), Some(a.clone().client)).map(|_| ());
rpc.await;
Ok(())
} }
struct Api { /// Bootstrap capability of the Diflouroborane API
auth: Authentication, ///
perm: Permissions, /// This is the starting point for any client connecting
#[derive(Clone)]
pub struct Bootstrap {
auth: Rc<Authentication>,
perm: Rc<Permissions>,
mach: Machines, mach: Machines,
} }
impl diflouroborane::Server for Api { impl diflouroborane::Server for Bootstrap {
fn authentication(&mut self, fn authentication(&mut self,
_params: diflouroborane::AuthenticationParams, _params: diflouroborane::AuthenticationParams,
mut results: diflouroborane::AuthenticationResults) mut results: diflouroborane::AuthenticationResults)
-> Promise<(), Error> -> Promise<(), Error>
{ {
let mut b = results.get(); let mut b = results.get();
let auth = api::authentication::ToClient::new(self.auth.clone()).into_client::<capnp_rpc::Server>(); let auth = api::authentication::ToClient::new(self.auth.deref().clone()).into_client::<capnp_rpc::Server>();
b.set_auth(auth); b.set_auth(auth);
Promise::ok(()) Promise::ok(())
} }
@ -59,9 +107,9 @@ impl diflouroborane::Server for Api {
mut results: diflouroborane::PermissionsResults) mut results: diflouroborane::PermissionsResults)
-> Promise<(), Error> -> Promise<(), Error>
{ {
let mut b = results.get(); //let mut b = results.get();
let perm = api::permissions::ToClient::new(self.perm.clone()).into_client::<capnp_rpc::Server>(); //let perm = api::permissions::ToClient::new(self.perm).into_client::<capnp_rpc::Server>();
b.set_perm(perm); //b.set_perm(perm);
Promise::ok(()) Promise::ok(())
} }

View File

@ -11,6 +11,9 @@ use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::ops::Deref; use std::ops::Deref;
use async_std::sync::{Arc, RwLock};
use capnp::capability::Promise;
use futures_signals::signal::Mutable; use futures_signals::signal::Mutable;
use casbin::{Enforcer, Model, FileAdapter}; use casbin::{Enforcer, Model, FileAdapter};
@ -133,24 +136,16 @@ impl AuthenticationProvider {
#[derive(Clone)] #[derive(Clone)]
pub struct Authentication { pub struct Authentication {
state: Mutable<Option<String>>, pub state: Arc<RwLock<Option<String>>>,
provider: Mutable<AuthenticationProvider>, provider: Arc<RwLock<AuthenticationProvider>>,
} }
impl Authentication { impl Authentication {
pub fn new(provider: Mutable<AuthenticationProvider>) -> Self { pub fn new(provider: Arc<RwLock<AuthenticationProvider>>) -> Self {
Self { Self {
state: Mutable::new(None), state: Arc::new(RwLock::new(None)),
provider: provider, provider: provider,
} }
} }
pub fn get_authzid(&self) -> Option<String> {
self.state.lock_ref().clone()
}
pub fn mechs(&self) -> Vec<&'static str> {
self.provider.lock_ref().mechs()
}
} }
@ -162,15 +157,19 @@ impl api::authentication::Server for Authentication {
mut results: api::authentication::AvailableMechanismsResults) mut results: api::authentication::AvailableMechanismsResults)
-> ::capnp::capability::Promise<(), ::capnp::Error> -> ::capnp::capability::Promise<(), ::capnp::Error>
{ {
let m = self.mechs(); let p = self.provider.clone();
let mut b = results.get() let f = async move {
.init_mechanisms(m.len() as u32); let m = p.read().await.mechs();
for (i, mech) in m.iter().enumerate() { let mut b = results.get()
let mut bldr = b.reborrow(); .init_mechanisms(m.len() as u32);
bldr.set(i as u32, mech); for (i, mech) in m.iter().enumerate() {
} let mut bldr = b.reborrow();
bldr.set(i as u32, mech);
}
Ok(())
};
::capnp::capability::Promise::ok(()) ::capnp::capability::Promise::from_future(f)
} }
fn initialize_authentication(&mut self, fn initialize_authentication(&mut self,
@ -178,42 +177,47 @@ impl api::authentication::Server for Authentication {
mut results: api::authentication::InitializeAuthenticationResults) mut results: api::authentication::InitializeAuthenticationResults)
-> ::capnp::capability::Promise<(), ::capnp::Error> -> ::capnp::capability::Promise<(), ::capnp::Error>
{ {
let params = pry!(params.get()); let prov = self.provider.clone();
let mechanism = pry!(params.get_mechanism()); let stat = self.state.clone();
match mechanism {
"PLAIN" => {
use api::authentication::maybe_data::Which;
let data = pry!(params.get_initial_data()); Promise::from_future(async move {
if let Ok(Which::Some(data)) = data.which() { let params = params.get()?;
let data = pry!(data); let mechanism = params.get_mechanism()?;
if let Ok((b, name)) = self.provider.lock_ref().plain.step(data) {
// If login was successful, also set the current authzid match mechanism {
if b { "PLAIN" => {
self.state.lock_mut().replace(name.to_string()); use api::authentication::maybe_data::Which;
let data = params.get_initial_data()?;
if let Ok(Which::Some(data)) = data.which() {
let data = data?;
if let Ok((b, name)) = prov.read().await.plain.step(data) {
// If login was successful set the authzid
if b {
stat.write().await.replace(name.to_string());
}
let outcome = Outcome::value(b);
results
.get()
.init_response()
.set_outcome(api::authentication::outcome::ToClient::new(outcome)
.into_client::<::capnp_rpc::Server>());
} }
Ok(())
let outcome = Outcome::value(b); } else {
results Err(::capnp::Error::unimplemented(
.get() "SASL PLAIN requires initial data set".to_string()))
.init_response()
.set_outcome(api::authentication::outcome::ToClient::new(outcome)
.into_client::<::capnp_rpc::Server>());
} }
::capnp::capability::Promise::ok(()) },
} else { m => {
return Err(::capnp::Error::unimplemented(
::capnp::capability::Promise::err(::capnp::Error::unimplemented( format!("SASL Mechanism {} is not implemented", m)
"SASL PLAIN requires initial data set".to_string())); ))
} }
},
m => {
return
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
format!("SASL Mechanism {} is not implemented", m)));
} }
} })
} }
fn get_authzid(&mut self, fn get_authzid(&mut self,
@ -221,12 +225,18 @@ impl api::authentication::Server for Authentication {
mut results: api::authentication::GetAuthzidResults) mut results: api::authentication::GetAuthzidResults)
-> ::capnp::capability::Promise<(), ::capnp::Error> -> ::capnp::capability::Promise<(), ::capnp::Error>
{ {
if let Some(zid) = self.state.lock_ref().deref() { let state = self.state.clone();
results.get().set_authzid(zid); let f = async move {
} else { if let Some(zid) = state.read().await.deref() {
results.get().set_authzid(""); results.get().set_authzid(&zid);
} } else {
::capnp::capability::Promise::ok(()) results.get().set_authzid("");
}
Ok(())
};
Promise::from_future(f)
} }
} }

View File

@ -1,25 +1,28 @@
use std::str::FromStr; use std::str::FromStr;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use std::io::Read;
use std::fs::File;
use crate::error::Result; use crate::error::Result;
pub fn read() -> Result<Config> { use std::default::Default;
Ok(Config {
machinedb: PathBuf::from_str("/tmp/machines.db").unwrap(), pub fn read(path: &Path) -> Result<Config> {
access: Access { let mut fp = File::open(path)?;
model: PathBuf::from_str("/tmp/model.conf").unwrap(), let mut contents = String::new();
policy: PathBuf::from_str("/tmp/policy.csv").unwrap(), fp.read_to_string(&mut contents)?;
},
passdb: PathBuf::from_str("/tmp/passwd.db").unwrap(), let config = toml::from_str(&contents)?;
}) Ok(config)
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config { pub struct Config {
pub(crate) access: Access,
pub machinedb: PathBuf, pub machinedb: PathBuf,
pub passdb: PathBuf, pub passdb: PathBuf,
pub(crate) access: Access,
pub listen: Box<[Listen]>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -27,3 +30,33 @@ pub struct Access {
pub(crate) model: PathBuf, pub(crate) model: PathBuf,
pub(crate) policy: PathBuf pub(crate) policy: PathBuf
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Listen {
pub address: String,
pub port: Option<u16>,
}
impl Default for Config {
fn default() -> Self {
Config {
machinedb: PathBuf::from_str("/tmp/machines.db").unwrap(),
access: Access {
model: PathBuf::from_str("/tmp/model.conf").unwrap(),
policy: PathBuf::from_str("/tmp/policy.csv").unwrap(),
},
passdb: PathBuf::from_str("/tmp/passwd.db").unwrap(),
listen: Box::new([Listen {
address: "127.0.0.1".to_string(),
port: Some(DEFAULT_PORT)
},
Listen {
address: "::1".to_string(),
port: Some(DEFAULT_PORT)
}]),
}
}
}
// The default port in the non-assignable i.e. free-use area
pub const DEFAULT_PORT: u16 = 59661;

View File

@ -9,6 +9,7 @@ pub enum Error {
TomlSer(toml::ser::Error), TomlSer(toml::ser::Error),
SASL(SASLError), SASL(SASLError),
IO(io::Error), IO(io::Error),
Boxed(Box<dyn std::error::Error>),
} }
impl From<SASLError> for Error { impl From<SASLError> for Error {
@ -35,4 +36,10 @@ impl From<toml::ser::Error> for Error {
} }
} }
impl From<Box<dyn std::error::Error>> for Error {
fn from(e: Box<dyn std::error::Error>) -> Error {
Error::Boxed(e)
}
}
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;

View File

@ -1,8 +1,9 @@
use slog::{Drain, Logger}; use slog::{Drain, Logger};
use slog_async; use slog_async;
use slog_term::{TermDecorator, FullFormat}; use slog_term::{TermDecorator, FullFormat};
use crate::config::Config;
pub fn init() -> Logger { pub fn init(_config: &Config) -> Logger {
let decorator = TermDecorator::new().build(); let decorator = TermDecorator::new().build();
let drain = FullFormat::new(decorator).build().fuse(); let drain = FullFormat::new(decorator).build().fuse();
let drain = slog_async::Async::new(drain).build().fuse(); let drain = slog_async::Async::new(drain).build().fuse();

View File

@ -15,6 +15,9 @@ use crate::config::Config;
use crate::api::api; use crate::api::api;
use crate::access::Permissions; use crate::access::Permissions;
use std::rc::Rc;
use async_std::sync::{Arc, RwLock};
use capnp::capability::Promise; use capnp::capability::Promise;
use capnp::Error; use capnp::Error;
use capnp_rpc::Server; use capnp_rpc::Server;
@ -32,16 +35,64 @@ pub enum Status {
Blocked, Blocked,
} }
#[derive(Clone)] pub struct MachinesProvider {
pub struct Machines {
log: Logger, log: Logger,
mdb: Mutable<MachineDB>, mdb: MachineDB,
perm: Permissions
} }
impl MachinesProvider {
pub fn new(log: Logger, mdb: MachineDB) -> Self {
Self { log, mdb }
}
pub fn use_(&mut self, uuid: &Uuid) -> std::result::Result<(), capnp::Error> {
if let Some(m) = self.mdb.get_mut(uuid) {
match m.status {
Status::Free => {
trace!(self.log, "Granted use on machine {}", uuid);
m.status = Status::Occupied;
Ok(())
},
Status::Occupied => {
info!(self.log, "Attempted use on an occupied machine {}", uuid);
Err(Error::failed("Machine is occupied".to_string()))
},
Status::Blocked => {
info!(self.log, "Attempted use on a blocked machine {}", uuid);
Err(Error::failed("Machine is blocked".to_string()))
}
}
} else {
info!(self.log, "Attempted use on invalid machine {}", uuid);
Err(Error::failed("No such machine".to_string()))
}
}
pub fn give_back(&mut self, uuid: &Uuid) -> std::result::Result<(), capnp::Error> {
if let Some(m) = self.mdb.get_mut(uuid) {
m.status = Status::Free;
} else {
warn!(self.log, "A giveback was issued for a unknown machine {}", uuid);
}
Ok(())
}
pub fn get_perm_req(&self, uuid: &Uuid) -> Option<String> {
self.mdb.get(uuid).map(|m| m.perm.clone())
}
}
#[derive(Clone)]
pub struct Machines {
inner: Arc<RwLock<MachinesProvider>>,
perm: Rc<Permissions>,
}
impl Machines { impl Machines {
pub fn new(log: Logger, mdb: Mutable<MachineDB>, perm: Permissions) -> Self { pub fn new(inner: Arc<RwLock<MachinesProvider>>, perm: Rc<Permissions>) -> Self {
Self { log, mdb, perm } Self { inner, perm }
} }
} }
impl api::machines::Server for Machines { impl api::machines::Server for Machines {
@ -52,79 +103,95 @@ impl api::machines::Server for Machines {
{ {
let params = pry!(params.get()); let params = pry!(params.get());
let uuid_s = pry!(params.get_uuid()); let uuid_s = pry!(params.get_uuid());
let uuid = uuid_from_api(uuid_s); let uuid = uuid_from_api(uuid_s);
let db = self.mdb.lock_ref(); // We need to copy the Arc here because we don't have access to it from within the closure
// witout moving it out of self.
let i = self.inner.clone();
let p = self.perm.clone();
if let Some(m) = db.get(&uuid) { let f = async move {
let manager = MachineManager::new(uuid, self.mdb.clone()); // We only need a read lock at first there's no reason to aquire a write lock.
let i_lock = i.read().await;
if self.perm.enforce(&m.perm, "manage") { if let Some(ps) = i_lock.get_perm_req(&uuid) {
let mut b = results.get(); // drop the lock as soon as possible to prevent locking as much as possible
let mngr = api::machines::manage::ToClient::new(manager).into_client::<Server>(); drop(i_lock);
b.set_manage(mngr); if let Ok(true) = p.enforce(&ps, "manage").await {
trace!(self.log, "Granted manage on machine {}", uuid); // We're here and have not returned an error yet - that means we're free to
Promise::ok(()) // send a successful manage back.
} else { let mut b = results.get();
Promise::err(Error::failed("Permission denied".to_string()))
// Magic incantation to get a capability to send
// Also since we move i in here we at this point *must* have dropped
// all locks we may still have on it.
b.set_manage(api::machines::give_back::ToClient::new(
MachineManager::new(i, uuid)).into_client::<Server>());
}
} }
} else { Ok(())
info!(self.log, "Attempted manage on invalid machine {}", uuid); };
Promise::err(Error::failed("No such machine".to_string()))
} Promise::from_future(f)
} }
fn use_(&mut self, fn use_(&mut self,
params: api::machines::UseParams, params: api::machines::UseParams,
mut results: api::machines::UseResults) mut results: api::machines::UseResults)
-> Promise<(), Error> -> Promise<(), capnp::Error>
{ {
let params = pry!(params.get()); let params = pry!(params.get());
let uuid_s = pry!(params.get_uuid()); let uuid_s = pry!(params.get_uuid());
let uuid = uuid_from_api(uuid_s); let uuid = uuid_from_api(uuid_s);
let mdb = self.mdb.lock_ref(); // We need to copy the Arc here because we don't have access to it from within the closure
if let Some(m) = mdb.get(&uuid) { // witout moving it out of self.
match m.status { let i = self.inner.clone();
Status::Free => { let p = self.perm.clone();
trace!(self.log, "Granted use on machine {}", uuid);
let f = async move {
// We only need a read lock at first there's no reason to aquire a write lock.
let i_lock = i.read().await;
if let Some(ps) = i_lock.get_perm_req(&uuid) {
// drop the lock as soon as possible to prevent locking as much as possible
drop(i_lock);
if let Ok(true) = p.enforce(&ps, "write").await {
{
// If use_() returns an error that is our error. If it doesn't that means we can use
// the machine
// Using a subscope to again make the time the lock is valid as short as
// possible. Less locking == more good
let mut i_lock = i.write().await;
i_lock.use_(&uuid)?;
}
// We're here and have not returned an error yet - that means we're free to
// send a successful use back.
let mut b = results.get(); let mut b = results.get();
let gb = api::machines::give_back::ToClient::new( // Magic incantation to get a capability to send
GiveBack::new(self.log.new(o!()), uuid, self.mdb.clone()) // Also since we move i in here we at this point *must* have dropped
).into_client::<Server>(); // all locks we may still have on it.
b.set_giveback(api::machines::give_back::ToClient::new(
b.set_giveback(gb); GiveBack::new(i, uuid)).into_client::<Server>());
Promise::ok(())
},
Status::Occupied => {
info!(self.log, "Attempted use on an occupied machine {}", uuid);
Promise::err(Error::failed("Machine is occupied".to_string()))
},
Status::Blocked => {
info!(self.log, "Attempted use on a blocked machine {}", uuid);
Promise::err(Error::failed("Machine is blocked".to_string()))
} }
} }
} else { Ok(())
info!(self.log, "Attempted use on invalid machine {}", uuid); };
Promise::err(Error::failed("No such machine".to_string()))
} Promise::from_future(f)
} }
} }
#[derive(Clone)]
pub struct GiveBack { pub struct GiveBack {
log: Logger, mdb: Arc<RwLock<MachinesProvider>>,
mdb: Mutable<MachineDB>,
uuid: Uuid, uuid: Uuid,
} }
impl GiveBack { impl GiveBack {
pub fn new(log: Logger, uuid: Uuid, mdb: Mutable<MachineDB>) -> Self { pub fn new(mdb: Arc<RwLock<MachinesProvider>>, uuid: Uuid) -> Self {
trace!(log, "Giveback initialized for {}", uuid); Self { mdb, uuid }
Self { log, mdb, uuid }
} }
} }
@ -134,19 +201,16 @@ impl api::machines::give_back::Server for GiveBack {
_results: api::machines::give_back::GivebackResults) _results: api::machines::give_back::GivebackResults)
-> Promise<(), Error> -> Promise<(), Error>
{ {
trace!(log, "Returning {}...", uuid); let mdb = self.mdb.clone();
let mut mdb = self.mdb.lock_mut(); let uuid = self.uuid.clone();
if let Some(m) = mdb.get_mut(&self.uuid) { let f = async move {
m.status = Status::Free; mdb.write().await.give_back(&uuid)
} else { };
warn!(self.log, "A giveback was issued for a unknown machine {}", self.uuid);
}
Promise::ok(()) Promise::from_future(f)
} }
} }
// FIXME: Test this exhaustively!
fn uuid_from_api(uuid: api::u_u_i_d::Reader) -> Uuid { fn uuid_from_api(uuid: api::u_u_i_d::Reader) -> Uuid {
let uuid0 = uuid.get_uuid0() as u128; let uuid0 = uuid.get_uuid0() as u128;
let uuid1 = uuid.get_uuid1() as u128; let uuid1 = uuid.get_uuid1() as u128;
@ -163,12 +227,12 @@ fn api_from_uuid(uuid: Uuid, mut wr: api::u_u_i_d::Builder) {
#[derive(Clone)] #[derive(Clone)]
pub struct MachineManager { pub struct MachineManager {
mdb: Mutable<MachineDB>, mdb: Arc<RwLock<MachinesProvider>>,
uuid: Uuid, uuid: Uuid,
} }
impl MachineManager { impl MachineManager {
pub fn new(uuid: Uuid, mdb: Mutable<MachineDB>) -> Self { pub fn new(uuid: Uuid, mdb: Arc<RwLock<MachineDB>>) -> Self {
Self { mdb, uuid } Self { mdb, uuid }
} }
} }
@ -222,16 +286,18 @@ impl Machine {
pub type MachineDB = HashMap<Uuid, Machine>; pub type MachineDB = HashMap<Uuid, Machine>;
pub fn init(config: &Config) -> Result<MachineDB> { pub async fn init(log: Logger, config: &Config) -> Result<MachinesProvider> {
if config.machinedb.is_file() { let mdb = if config.machinedb.is_file() {
let mut fp = File::open(&config.machinedb)?; let mut fp = File::open(&config.machinedb)?;
let mut content = String::new(); let mut content = String::new();
fp.read_to_string(&mut content)?; fp.read_to_string(&mut content)?;
let map = toml::from_str(&content)?; let map = toml::from_str(&content)?;
return Ok(map); map
} else { } else {
return Ok(HashMap::new()); HashMap::new()
} };
Ok(MachinesProvider::new(log, mdb))
} }
pub fn save(config: &Config, mdb: &MachineDB) -> Result<()> { pub fn save(config: &Config, mdb: &MachineDB) -> Result<()> {

View File

@ -26,7 +26,7 @@ use futures::prelude::*;
use futures::executor::{LocalPool, ThreadPool}; use futures::executor::{LocalPool, ThreadPool};
use futures::compat::Stream01CompatExt; use futures::compat::Stream01CompatExt;
use futures::join; use futures::join;
use futures::task::SpawnExt; use futures::task::{SpawnExt, LocalSpawn};
use capnp_rpc::twoparty::{VatNetwork, VatId}; use capnp_rpc::twoparty::{VatNetwork, VatId};
use capnp_rpc::rpc_twoparty_capnp::Side; use capnp_rpc::rpc_twoparty_capnp::Side;
@ -43,6 +43,8 @@ use std::sync::Arc;
use error::Error; use error::Error;
use api::API;
// Returning a `Result` from `main` allows us to use the `?` shorthand. // Returning a `Result` from `main` allows us to use the `?` shorthand.
// In the case of an Err it will be printed using `fmt::Debug` // In the case of an Err it will be printed using `fmt::Debug`
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
@ -106,7 +108,7 @@ fn main() -> Result<(), Error> {
// Start loading the machine database, authentication system and permission system // Start loading the machine database, authentication system and permission system
// All of those get a custom logger so the source of a log message can be better traced and // All of those get a custom logger so the source of a log message can be better traced and
// filtered // filtered
let machinedb_f = machine::init(log.new(o!("system" => "machinedb")), &config); let machinedb_f = machine::init(log.new(o!("system" => "machines")), &config);
let permission_f = access::init(log.new(o!("system" => "permissions")), &config); let permission_f = access::init(log.new(o!("system" => "permissions")), &config);
let authentication_f = auth::init(log.new(o!("system" => "authentication")), config.clone()); let authentication_f = auth::init(log.new(o!("system" => "authentication")), config.clone());
@ -132,14 +134,14 @@ fn main() -> Result<(), Error> {
} }
}).collect(); }).collect();
let (mdb, pdb, auth) = exec.run_until(async { let (mach, pdb, auth) = exec.run_until(async {
// Rull all futures to completion in parallel. // Rull all futures to completion in parallel.
// This will block until all three are done starting up. // This will block until all three are done starting up.
join!(machinedb_f, permission_f, authentication_f) join!(machinedb_f, permission_f, authentication_f)
}); });
// Error out if any of the subsystems failed to start. // Error out if any of the subsystems failed to start.
let mdb = mdb?; let mach = mach?;
let pdb = pdb.unwrap(); let pdb = pdb.unwrap();
let auth = auth?; let auth = auth?;
@ -157,6 +159,12 @@ fn main() -> Result<(), Error> {
info!(stop_log.new(o!("system" => "threadpool")), "Stopping Thread <{}>", i) info!(stop_log.new(o!("system" => "threadpool")), "Stopping Thread <{}>", i)
}) })
.create()?; .create()?;
let local_spawn = exec.spawner();
// The API has access to all subsystems it needs and the Threadpool as capability to spawn new
// tasks for CPU-intensive work
let api = API::new(auth, pdb, mach, pool);
// Closure inefficiencies. Lucky cloning an Arc is pretty cheap. // Closure inefficiencies. Lucky cloning an Arc is pretty cheap.
let inner_log = log.clone(); let inner_log = log.clone();
@ -167,9 +175,6 @@ fn main() -> Result<(), Error> {
let listeners = listeners_s.await; let listeners = listeners_s.await;
let incoming = stream::select_all(listeners.iter().map(|l| l.incoming())); let incoming = stream::select_all(listeners.iter().map(|l| l.incoming()));
// Spawner is a handle to the shared ThreadPool forwarded into each connection
let spawner = pool.clone();
// For each incoming connection start a new task to handle it and throw it on the thread // For each incoming connection start a new task to handle it and throw it on the thread
// pool // pool
let handle_sockets = incoming.map(|socket| { let handle_sockets = incoming.map(|socket| {
@ -191,7 +196,7 @@ fn main() -> Result<(), Error> {
// We handle the error using map_err, `let _` is used to quiet the compiler // We handle the error using map_err, `let _` is used to quiet the compiler
// warning // warning
let f = api::handle_connection(log.clone(), socket, spawner.clone()) let f = api::handle_connection(api.clone(), log.clone(), socket)
.map_err(move |e| { .map_err(move |e| {
error!(log, "Error occured during protocol handling: {}", e); error!(log, "Error occured during protocol handling: {}", e);
}) })
@ -199,7 +204,8 @@ fn main() -> Result<(), Error> {
.map(|_| ()); .map(|_| ());
// In this case only the error is relevant since the Value is always () // In this case only the error is relevant since the Value is always ()
if let Err(e) = pool.spawn(f) { // The future is Boxed to make it the `LocalFutureObj` that LocalSpawn expects
if let Err(e) = local_spawn.spawn_local_obj(Box::new(f).into()) {
error!(elog, "Failed to spawn connection handler: {}", e); error!(elog, "Failed to spawn connection handler: {}", e);
// Failing to spawn a handler means we are most likely overloaded // Failing to spawn a handler means we are most likely overloaded
return LoopResult::Overloaded; return LoopResult::Overloaded;