Refactor machines somewhat

This commit is contained in:
Gregor Reitzenstein 2020-11-30 14:08:03 +01:00
parent 65841f5046
commit cc40cde831
9 changed files with 130 additions and 79 deletions

View File

@ -40,17 +40,17 @@ impl Machine {
if let Some(state) = self.db.machine.get_state(&self.id) { if let Some(state) = self.db.machine.get_state(&self.id) {
match state.state { match state.state {
Status::Free => builder.set_state(State::Free), Status::Free => builder.set_state(State::Free),
Status::InUse(_u) => { Status::InUse(_u, _p) => {
builder.set_state(State::InUse); builder.set_state(State::InUse);
} }
Status::ToCheck(_u) => { Status::ToCheck(_u, _p) => {
builder.set_state(State::ToCheck); builder.set_state(State::ToCheck);
} }
Status::Blocked(_u) => { Status::Blocked(_u, _p) => {
builder.set_state(State::Blocked); builder.set_state(State::Blocked);
} }
Status::Disabled => builder.set_state(State::Disabled), Status::Disabled => builder.set_state(State::Disabled),
Status::Reserved(_u) => { Status::Reserved(_u, _p) => {
builder.set_state(State::Reserved); builder.set_state(State::Reserved);
} }
} }

View File

@ -37,6 +37,7 @@ pub mod internal;
use internal::Internal; use internal::Internal;
pub type MachineIdentifier = Uuid; pub type MachineIdentifier = Uuid;
pub type Priority = u64;
/// Status of a Machine /// Status of a Machine
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
@ -44,15 +45,15 @@ pub enum Status {
/// Not currently used by anybody /// Not currently used by anybody
Free, Free,
/// Used by somebody /// Used by somebody
InUse(UserId), InUse(UserId, Priority),
/// Was used by somebody and now needs to be checked for cleanliness /// Was used by somebody and now needs to be checked for cleanliness
ToCheck(UserId), ToCheck(UserId, Priority),
/// Not used by anybody but also can not be used. E.g. down for maintenance /// Not used by anybody but also can not be used. E.g. down for maintenance
Blocked(UserId), Blocked(UserId, Priority),
/// Disabled for some other reason /// Disabled for some other reason
Disabled, Disabled,
/// Reserved /// Reserved
Reserved(UserId), Reserved(UserId, Priority),
} }
pub fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid { pub fn uuid_from_api(uuid: crate::schema::api_capnp::u_u_i_d::Reader) -> Uuid {
@ -75,6 +76,24 @@ pub struct MachineState {
pub state: Status, pub state: Status,
} }
impl MachineState {
/// Check if the given priority is higher than one's own.
///
/// If `self` does not have a priority then this function always returns `true`
pub fn is_higher_priority(&self, priority: u64) -> bool {
match self.state {
Status::Disabled | Status::Free => { true },
Status::Blocked(_, self_prio) |
Status::InUse(_, self_prio) |
Status::ToCheck(_, self_prio) |
Status::Reserved(_, self_prio) =>
{
priority > self_prio
}
}
}
}
pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>) -> Result<Internal> { pub fn init(log: Logger, config: &Settings, env: Arc<lmdb::Environment>) -> Result<Internal> {
let mut machine_descriptions = MachineDescription::load_file(&config.machines)?; let mut machine_descriptions = MachineDescription::load_file(&config.machines)?;
let mut flags = lmdb::DatabaseFlags::empty(); let mut flags = lmdb::DatabaseFlags::empty();

View File

@ -66,11 +66,25 @@ pub struct UserData {
/// Persons are only ever given roles, not permissions directly /// Persons are only ever given roles, not permissions directly
pub roles: Vec<RoleIdentifier>, pub roles: Vec<RoleIdentifier>,
#[serde(skip_serializing_if = "is_zero")]
#[serde(default = "default_priority")]
/// A priority number, defaulting to 0.
///
/// The higher, the higher the priority. Higher priority users overwrite lower priority ones.
pub priority: u64,
/// Additional data storage /// Additional data storage
#[serde(flatten)] #[serde(flatten)]
kv: HashMap<Box<[u8]>, Box<[u8]>>, kv: HashMap<Box<[u8]>, Box<[u8]>>,
} }
fn is_zero(i: &u64) -> bool {
*i == 0
}
const fn default_priority() -> u64 {
0
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -24,7 +24,8 @@ pub enum Error {
MQTT(mqtt::Error), MQTT(mqtt::Error),
Config(config::ConfigError), Config(config::ConfigError),
BadVersion((u32,u32)), BadVersion((u32,u32)),
Argon2(argon2::Error) Argon2(argon2::Error),
Denied,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -72,6 +73,9 @@ impl fmt::Display for Error {
Error::BadVersion((major,minor)) => { Error::BadVersion((major,minor)) => {
write!(f, "Peer uses API version {}.{} which is incompatible!", major, minor) write!(f, "Peer uses API version {}.{} which is incompatible!", major, minor)
} }
Error::Denied => {
write!(f, "You do not have the permission required to do that.")
}
} }
} }
} }

View File

@ -1,4 +1,8 @@
use std::path::Path; use std::path::Path;
use std::task::{Poll, Context};
use std::pin::Pin;
use std::future::Future;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
@ -10,7 +14,7 @@ use futures_signals::signal::Mutable;
use uuid::Uuid; use uuid::Uuid;
use crate::error::Result; use crate::error::{Result, Error};
use crate::db::access; use crate::db::access;
use crate::db::machine::{MachineIdentifier, Status, MachineState}; use crate::db::machine::{MachineIdentifier, Status, MachineState};
@ -34,14 +38,21 @@ pub struct Machine {
/// This is a Signal generator. Subscribers to this signal will be notified of changes. In the /// This is a Signal generator. Subscribers to this signal will be notified of changes. In the
/// case of an actor it should then make sure that the real world matches up with the set state /// case of an actor it should then make sure that the real world matches up with the set state
state: Mutable<MachineState>, state: Mutable<MachineState>,
reset: Option<MachineState>,
rx: Option<futures::channel::oneshot::Receiver<()>>,
access: access::AccessControl,
} }
impl Machine { impl Machine {
pub fn new(id: MachineIdentifier, desc: MachineDescription, perm: access::PermIdentifier) -> Machine { pub fn new(id: MachineIdentifier, desc: MachineDescription, access: access::AccessControl, state: MachineState) -> Machine {
Machine { Machine {
id: id, id: id,
desc: desc, desc: desc,
state: Mutable::new(MachineState { state: Status::Free}), state: Mutable::new(state),
reset: None,
rx: None,
access: access,
} }
} }
@ -57,27 +68,68 @@ impl Machine {
Box::pin(self.state.signal_cloned().dedupe_cloned()) Box::pin(self.state.signal_cloned().dedupe_cloned())
} }
/// Requests to use a machine. Returns `true` if successful. /// Requests to use a machine. Returns a return token if successful.
/// ///
/// This will update the internal state of the machine, notifying connected actors, if any. /// This will update the internal state of the machine, notifying connected actors, if any.
pub async fn request_use /// The return token is a channel that considers the machine 'returned' if anything is sent
( &mut self /// along it or if the sending end gets dropped. Anybody who holds this token needs to check if
, access: access::AccessControl /// the receiving end was canceled which indicates that the machine has been taken off their
, who: &User /// hands.
) -> Result<bool> pub async fn request_state_change(&mut self, who: &User, new_state: MachineState)
-> Result<ReturnToken>
{ {
// TODO: Check different levels if self.access.check(&who.data, &self.desc.privs.write).await? {
if access.check(&who.data, &self.desc.privs.write).await? { if self.state.lock_ref().is_higher_priority(who.data.priority) {
self.state.set(MachineState { state: Status::InUse(who.id.clone()) }); let (tx, rx) = futures::channel::oneshot::channel();
return Ok(true); let old_state = self.state.replace(new_state);
} else { self.reset.replace(old_state);
return Ok(false); // Also this drops the old receiver, which will signal to the initiator that the
// machine has been taken off their hands.
self.rx.replace(rx);
return Ok(tx);
} }
} }
return Err(Error::Denied);
}
pub fn set_state(&mut self, state: Status) { pub fn set_state(&mut self, state: Status) {
self.state.set(MachineState { state }) self.state.set(MachineState { state })
} }
pub fn get_signal(&self) -> impl Signal {
self.state.signal_cloned().dedupe_cloned()
}
pub fn reset_state(&mut self) {
if let Some(state) = self.reset.take() {
self.state.replace(state);
}
}
}
type ReturnToken = futures::channel::oneshot::Sender<()>;
impl Future for Machine {
type Output = MachineState;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut this = &mut *self;
// TODO Return this on exit
if false {
return Poll::Ready(self.state.get_cloned());
}
if let Some(mut rx) = this.rx.take() {
match Future::poll(Pin::new(&mut rx), cx) {
// Regardless if we were canceled or properly returned, reset.
Poll::Ready(_) => self.reset_state(),
Poll::Pending => { this.rx.replace(rx); },
}
}
Poll::Pending
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -217,11 +217,12 @@ fn main() -> Result<(), Error> {
let pdb = pdb?; let pdb = pdb?;
let mut ac = db::access::AccessControl::new(); let mut ac = db::access::AccessControl::new();
ac.add_source_unchecked("Internal".to_string(), Box::new(pdb)); ac.add_source_unchecked("Internal".to_string(), Box::new(pdb));
let machdb = Arc::new(machdb);
let passdb = db::pass::PassDB::init(log.new(o!("system" => "passwords")), env.clone()).unwrap(); let passdb = db::pass::PassDB::init(log.new(o!("system" => "passwords")), env.clone()).unwrap();
let db = db::Databases { let db = db::Databases {
access: Arc::new(db::access::AccessControl::new()), access: Arc::new(db::access::AccessControl::new()),
machine: Arc::new(machdb), machine: machdb.clone(),
passdb: Arc::new(passdb), passdb: Arc::new(passdb),
}; };
@ -245,7 +246,7 @@ fn main() -> Result<(), Error> {
// FIXME: implement notification so the modules can shut down cleanly instead of being killed // FIXME: implement notification so the modules can shut down cleanly instead of being killed
// without warning. // without warning.
let modlog = log.clone(); let modlog = log.clone();
let mut regs = Registries::new(); let mut regs = Registries::new(machdb.clone());
match exec.run_until(modules::init(modlog.new(o!("system" => "modules")), config.clone(), pool.clone(), regs.clone())) { match exec.run_until(modules::init(modlog.new(o!("system" => "modules")), config.clone(), pool.clone(), regs.clone())) {
Ok(()) => {} Ok(()) => {}
Err(e) => { Err(e) => {

View File

@ -100,7 +100,7 @@ impl Stream for Shelly {
info!(unpin.log, "Machine Status changed: {:?}", status); info!(unpin.log, "Machine Status changed: {:?}", status);
let topic = format!("shellies/{}/relay/0/command", unpin.name); let topic = format!("shellies/{}/relay/0/command", unpin.name);
let pl = match status { let pl = match status {
Status::InUse(_) => "on", Status::InUse(_, _) => "on",
_ => "off", _ => "off",
}; };
let msg = mqtt::Message::new(topic, pl, 0); let msg = mqtt::Message::new(topic, pl, 0);

View File

@ -1,8 +1,11 @@
use std::sync::Arc;
use crate::db::machine::MachineDB;
mod actuators; mod actuators;
mod sensors; mod sensors;
pub use actuators::{Actuator, ActBox, StatusSignal}; pub use actuators::{Actuator, ActBox, StatusSignal};
pub use sensors::{Sensor, SensBox};
#[derive(Clone)] #[derive(Clone)]
/// BFFH registries /// BFFH registries
@ -15,10 +18,10 @@ pub struct Registries {
} }
impl Registries { impl Registries {
pub fn new() -> Self { pub fn new(db: Arc<MachineDB>) -> Self {
Registries { Registries {
actuators: actuators::Actuators::new(), actuators: actuators::Actuators::new(),
sensors: sensors::Sensors::new(), sensors: sensors::Sensors::new(db),
} }
} }
} }

View File

@ -2,6 +2,9 @@ use std::pin::Pin;
use futures::task::{Context, Poll}; use futures::task::{Context, Poll};
use futures::{Future, Stream}; use futures::{Future, Stream};
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures_signals::signal::Signal;
use crate::db::user::UserId;
use crate::db::machine::MachineDB;
use std::sync::Arc; use std::sync::Arc;
use smol::lock::RwLock; use smol::lock::RwLock;
@ -10,64 +13,19 @@ use std::collections::HashMap;
#[derive(Clone)] #[derive(Clone)]
pub struct Sensors { pub struct Sensors {
inner: Arc<RwLock<Inner>>, inner: Arc<RwLock<Inner>>,
db: Arc<MachineDB>,
} }
impl Sensors { impl Sensors {
pub fn new() -> Self { pub fn new(db: Arc<MachineDB>) -> Self {
Sensors { Sensors {
inner: Arc::new(RwLock::new(Inner::new())), inner: Arc::new(RwLock::new(Inner::new())),
db: db,
} }
} }
} }
pub type SensBox = Box<dyn Sensor + Send + Sync>; pub type SensBox = Box<dyn Signal<Item=UserId> + Send + Sync>;
type Inner = HashMap<String, SensBox>; type Inner = HashMap<String, SensBox>;
// Implementing Sensors.
//
// Given the coroutine/task split stays as it is - Sensor input to machine update being one,
// machine update signal to actor doing thing being another, a Sensor implementation would send a
// Stream of futures - each future being an atomic Machine update.
#[async_trait]
/// BFFH Sensor
///
/// A sensor is anything that can forward an intent of an user to do something to bffh.
/// This may be a card reader connected to a machine, a website allowing users to select a machine
/// they want to use or something like QRHello
pub trait Sensor: Stream<Item = BoxFuture<'static, ()>> {
/// Setup the Sensor.
///
/// After this async function completes the Stream implementation should be able to generate
/// futures when polled.
/// Implementations can rely on this function being polled to completeion before the stream
/// is polled.
// TODO Is this sensible vs just having module-specific setup fns?
async fn setup(&mut self);
/// Shutdown the sensor gracefully
///
/// Implementations can rely on that the stream will not be polled after this function has been
/// called.
async fn shutdown(&mut self);
}
struct Dummy;
#[async_trait]
impl Sensor for Dummy {
async fn setup(&mut self) {
return;
}
async fn shutdown(&mut self) {
return;
}
}
impl Stream for Dummy {
type Item = BoxFuture<'static, ()>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
Poll::Ready(Some(Box::pin(futures::future::ready(()))))
}
}