feat: with sled db

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-08-27 16:49:53 +02:00
parent 9e61ed7ef7
commit 757d1081bd
Signed by: kjuulh
GPG Key ID: 9AA7BC13CE474394
12 changed files with 682 additions and 31 deletions

133
Cargo.lock generated
View File

@ -217,6 +217,21 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "capnp"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95e65021d89250bbfe7c2791789ced2c4bdc21b0e8bb59c64f3fd6145a5fd678"
[[package]]
name = "capnpc"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbbc3763fb3e6635188e9cc51ee11a26f8777c553ca377430818dbebaaf6042b"
dependencies = [
"capnp",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.79"
@ -262,6 +277,17 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "churn-capnp"
version = "0.1.0"
dependencies = [
"anyhow",
"capnp",
"capnpc",
"churn-domain",
"uuid",
]
[[package]] [[package]]
name = "churn-domain" name = "churn-domain"
version = "0.1.0" version = "0.1.0"
@ -283,12 +309,15 @@ name = "churn-server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"axum", "axum",
"churn-capnp",
"churn-domain", "churn-domain",
"clap", "clap",
"dotenv", "dotenv",
"serde", "serde",
"serde_json", "serde_json",
"sled",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -400,6 +429,28 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -671,6 +722,16 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.28" version = "0.3.28"
@ -760,6 +821,15 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@ -1021,6 +1091,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "1.0.11" version = "1.0.11"
@ -1117,6 +1196,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"
@ -1246,6 +1334,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -1253,7 +1352,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core 0.9.8",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
] ]
[[package]] [[package]]
@ -1643,6 +1756,22 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.10.0" version = "1.10.0"
@ -1789,7 +1918,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"num_cpus", "num_cpus",
"parking_lot", "parking_lot 0.12.1",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2 0.5.3", "socket2 0.5.3",

View File

@ -17,6 +17,7 @@ churn = { path = "crates/churn" }
churn-agent = { path = "crates/churn-agent" } churn-agent = { path = "crates/churn-agent" }
churn-server = { path = "crates/churn-server" } churn-server = { path = "crates/churn-server" }
churn-domain = { path = "crates/churn-domain", version = "0.1.0" } churn-domain = { path = "crates/churn-domain", version = "0.1.0" }
churn-capnp = { path = "crates/churn-capnp", version = "0.1.0" }
anyhow = { version = "1.0.71" } anyhow = { version = "1.0.71" }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
@ -30,3 +31,5 @@ serde = {version = "1", features = ["derive"]}
serde_json = "1" serde_json = "1"
reqwest = {version = "0.11.20", features = ["json"]} reqwest = {version = "0.11.20", features = ["json"]}
uuid = {version = "1.4.1", features = ["v4", "serde"]} uuid = {version = "1.4.1", features = ["v4", "serde"]}
sled = "0.34.7"

View File

@ -0,0 +1,20 @@
[package]
name = "churn-capnp"
repository.workspace = true
description.workspace = true
readme.workspace = true
license-file.workspace = true
authors.workspace = true
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
churn-domain.workspace = true
uuid.workspace = true
anyhow.workspace = true
capnp = "0.17.2"
[build-dependencies]
capnpc = "0.17.2"

View File

@ -0,0 +1,10 @@
extern crate capnpc;
fn main() {
capnpc::CompilerCommand::new()
.output_path("src/")
.src_prefix("schemas/")
.file("schemas/models.capnp")
.run()
.unwrap();
}

View File

@ -0,0 +1,7 @@
@0xf23adf24ffd8aca4;
struct LogEvent {
id @0 :Text;
author @1 :Text;
content @2 :Text;
}

View File

@ -0,0 +1,59 @@
use capnp::message::{Builder, HeapAllocator};
use capnp::message::{ReaderOptions, TypedReader};
use capnp::serialize::{self, OwnedSegments};
use capnp::traits::{FromPointerReader, Owned};
use churn_domain::LogEvent;
mod models_capnp;
pub trait CapnpPackExt {
type Return;
fn serialize_capnp(&self) -> String;
fn deserialize_capnp(content: &str) -> anyhow::Result<Self::Return>;
fn capnp_to_string(builder: &Builder<HeapAllocator>) -> String {
let msg = serialize::write_message_to_words(builder);
std::str::from_utf8(msg.as_slice())
.expect("to be able to parse capnp as utf8")
.to_string()
}
fn string_to_capnp<'a, S>(content: &'a str) -> TypedReader<OwnedSegments, S>
where
S: Owned,
{
let log_event = serialize::read_message(content.as_bytes(), ReaderOptions::new()).unwrap();
let log_event = log_event.into_typed::<S>();
log_event
}
}
impl CapnpPackExt for LogEvent {
type Return = Self;
fn serialize_capnp(&self) -> String {
let mut builder = Builder::new_default();
let mut log_event = builder.init_root::<models_capnp::log_event::Builder>();
log_event.set_id(&self.id.to_string());
log_event.set_author(&self.author);
log_event.set_content(&self.content);
Self::capnp_to_string(&builder)
}
fn deserialize_capnp(content: &str) -> anyhow::Result<Self> {
let log_event = Self::string_to_capnp::<models_capnp::log_event::Owned>(content);
let log_event = log_event.get()?;
Ok(Self {
id: uuid::Uuid::parse_str(log_event.get_id()?)?,
author: log_event.get_author()?.into(),
content: log_event.get_content()?.into(),
})
}
}

View File

@ -0,0 +1,288 @@
// @generated by the capnpc-rust plugin to the Cap'n Proto schema compiler.
// DO NOT EDIT.
// source: models.capnp
pub mod log_event {
#[derive(Copy, Clone)]
pub struct Owned(());
impl ::capnp::introspect::Introspect for Owned { fn introspect() -> ::capnp::introspect::Type { ::capnp::introspect::TypeVariant::Struct(::capnp::introspect::RawBrandedStructSchema { generic: &_private::RAW_SCHEMA, field_types: _private::get_field_types, annotation_types: _private::get_annotation_types }).into() } }
impl ::capnp::traits::Owned for Owned { type Reader<'a> = Reader<'a>; type Builder<'a> = Builder<'a>; }
impl ::capnp::traits::OwnedStruct for Owned { type Reader<'a> = Reader<'a>; type Builder<'a> = Builder<'a>; }
impl ::capnp::traits::Pipelined for Owned { type Pipeline = Pipeline; }
pub struct Reader<'a> { reader: ::capnp::private::layout::StructReader<'a> }
impl <'a,> ::core::marker::Copy for Reader<'a,> {}
impl <'a,> ::core::clone::Clone for Reader<'a,> {
fn clone(&self) -> Self { *self }
}
impl <'a,> ::capnp::traits::HasTypeId for Reader<'a,> {
const TYPE_ID: u64 = _private::TYPE_ID;
}
impl <'a,> ::core::convert::From<::capnp::private::layout::StructReader<'a>> for Reader<'a,> {
fn from(reader: ::capnp::private::layout::StructReader<'a>) -> Self {
Self { reader, }
}
}
impl <'a,> ::core::convert::From<Reader<'a,>> for ::capnp::dynamic_value::Reader<'a> {
fn from(reader: Reader<'a,>) -> Self {
Self::Struct(::capnp::dynamic_struct::Reader::new(reader.reader, ::capnp::schema::StructSchema::new(::capnp::introspect::RawBrandedStructSchema { generic: &_private::RAW_SCHEMA, field_types: _private::get_field_types::<>, annotation_types: _private::get_annotation_types::<>})))
}
}
impl <'a,> ::core::fmt::Debug for Reader<'a,> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::result::Result<(), ::core::fmt::Error> {
core::fmt::Debug::fmt(&::core::convert::Into::<::capnp::dynamic_value::Reader<'_>>::into(*self), f)
}
}
impl <'a,> ::capnp::traits::FromPointerReader<'a> for Reader<'a,> {
fn get_from_pointer(reader: &::capnp::private::layout::PointerReader<'a>, default: ::core::option::Option<&'a [::capnp::Word]>) -> ::capnp::Result<Self> {
::core::result::Result::Ok(reader.get_struct(default)?.into())
}
}
impl <'a,> ::capnp::traits::IntoInternalStructReader<'a> for Reader<'a,> {
fn into_internal_struct_reader(self) -> ::capnp::private::layout::StructReader<'a> {
self.reader
}
}
impl <'a,> ::capnp::traits::Imbue<'a> for Reader<'a,> {
fn imbue(&mut self, cap_table: &'a ::capnp::private::layout::CapTable) {
self.reader.imbue(::capnp::private::layout::CapTableReader::Plain(cap_table))
}
}
impl <'a,> Reader<'a,> {
pub fn reborrow(&self) -> Reader<'_,> {
Self { .. *self }
}
pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> {
self.reader.total_size()
}
#[inline]
pub fn get_id(self) -> ::capnp::Result<::capnp::text::Reader<'a>> {
::capnp::traits::FromPointerReader::get_from_pointer(&self.reader.get_pointer_field(0), ::core::option::Option::None)
}
#[inline]
pub fn has_id(&self) -> bool {
!self.reader.get_pointer_field(0).is_null()
}
#[inline]
pub fn get_author(self) -> ::capnp::Result<::capnp::text::Reader<'a>> {
::capnp::traits::FromPointerReader::get_from_pointer(&self.reader.get_pointer_field(1), ::core::option::Option::None)
}
#[inline]
pub fn has_author(&self) -> bool {
!self.reader.get_pointer_field(1).is_null()
}
#[inline]
pub fn get_content(self) -> ::capnp::Result<::capnp::text::Reader<'a>> {
::capnp::traits::FromPointerReader::get_from_pointer(&self.reader.get_pointer_field(2), ::core::option::Option::None)
}
#[inline]
pub fn has_content(&self) -> bool {
!self.reader.get_pointer_field(2).is_null()
}
}
pub struct Builder<'a> { builder: ::capnp::private::layout::StructBuilder<'a> }
impl <'a,> ::capnp::traits::HasStructSize for Builder<'a,> {
const STRUCT_SIZE: ::capnp::private::layout::StructSize = ::capnp::private::layout::StructSize { data: 0, pointers: 3 };
}
impl <'a,> ::capnp::traits::HasTypeId for Builder<'a,> {
const TYPE_ID: u64 = _private::TYPE_ID;
}
impl <'a,> ::core::convert::From<::capnp::private::layout::StructBuilder<'a>> for Builder<'a,> {
fn from(builder: ::capnp::private::layout::StructBuilder<'a>) -> Self {
Self { builder, }
}
}
impl <'a,> ::core::convert::From<Builder<'a,>> for ::capnp::dynamic_value::Builder<'a> {
fn from(builder: Builder<'a,>) -> Self {
Self::Struct(::capnp::dynamic_struct::Builder::new(builder.builder, ::capnp::schema::StructSchema::new(::capnp::introspect::RawBrandedStructSchema { generic: &_private::RAW_SCHEMA, field_types: _private::get_field_types::<>, annotation_types: _private::get_annotation_types::<>})))
}
}
impl <'a,> ::capnp::traits::ImbueMut<'a> for Builder<'a,> {
fn imbue_mut(&mut self, cap_table: &'a mut ::capnp::private::layout::CapTable) {
self.builder.imbue(::capnp::private::layout::CapTableBuilder::Plain(cap_table))
}
}
impl <'a,> ::capnp::traits::FromPointerBuilder<'a> for Builder<'a,> {
fn init_pointer(builder: ::capnp::private::layout::PointerBuilder<'a>, _size: u32) -> Self {
builder.init_struct(<Self as ::capnp::traits::HasStructSize>::STRUCT_SIZE).into()
}
fn get_from_pointer(builder: ::capnp::private::layout::PointerBuilder<'a>, default: ::core::option::Option<&'a [::capnp::Word]>) -> ::capnp::Result<Self> {
::core::result::Result::Ok(builder.get_struct(<Self as ::capnp::traits::HasStructSize>::STRUCT_SIZE, default)?.into())
}
}
impl <'a,> ::capnp::traits::SetPointerBuilder for Reader<'a,> {
fn set_pointer_builder(mut pointer: ::capnp::private::layout::PointerBuilder<'_>, value: Self, canonicalize: bool) -> ::capnp::Result<()> { pointer.set_struct(&value.reader, canonicalize) }
}
impl <'a,> Builder<'a,> {
pub fn into_reader(self) -> Reader<'a,> {
self.builder.into_reader().into()
}
pub fn reborrow(&mut self) -> Builder<'_,> {
Builder { builder: self.builder.reborrow() }
}
pub fn reborrow_as_reader(&self) -> Reader<'_,> {
self.builder.as_reader().into()
}
pub fn total_size(&self) -> ::capnp::Result<::capnp::MessageSize> {
self.builder.as_reader().total_size()
}
#[inline]
pub fn get_id(self) -> ::capnp::Result<::capnp::text::Builder<'a>> {
::capnp::traits::FromPointerBuilder::get_from_pointer(self.builder.get_pointer_field(0), ::core::option::Option::None)
}
#[inline]
pub fn set_id(&mut self, value: ::capnp::text::Reader<'_>) {
self.builder.reborrow().get_pointer_field(0).set_text(value);
}
#[inline]
pub fn init_id(self, size: u32) -> ::capnp::text::Builder<'a> {
self.builder.get_pointer_field(0).init_text(size)
}
#[inline]
pub fn has_id(&self) -> bool {
!self.builder.is_pointer_field_null(0)
}
#[inline]
pub fn get_author(self) -> ::capnp::Result<::capnp::text::Builder<'a>> {
::capnp::traits::FromPointerBuilder::get_from_pointer(self.builder.get_pointer_field(1), ::core::option::Option::None)
}
#[inline]
pub fn set_author(&mut self, value: ::capnp::text::Reader<'_>) {
self.builder.reborrow().get_pointer_field(1).set_text(value);
}
#[inline]
pub fn init_author(self, size: u32) -> ::capnp::text::Builder<'a> {
self.builder.get_pointer_field(1).init_text(size)
}
#[inline]
pub fn has_author(&self) -> bool {
!self.builder.is_pointer_field_null(1)
}
#[inline]
pub fn get_content(self) -> ::capnp::Result<::capnp::text::Builder<'a>> {
::capnp::traits::FromPointerBuilder::get_from_pointer(self.builder.get_pointer_field(2), ::core::option::Option::None)
}
#[inline]
pub fn set_content(&mut self, value: ::capnp::text::Reader<'_>) {
self.builder.reborrow().get_pointer_field(2).set_text(value);
}
#[inline]
pub fn init_content(self, size: u32) -> ::capnp::text::Builder<'a> {
self.builder.get_pointer_field(2).init_text(size)
}
#[inline]
pub fn has_content(&self) -> bool {
!self.builder.is_pointer_field_null(2)
}
}
pub struct Pipeline { _typeless: ::capnp::any_pointer::Pipeline }
impl ::capnp::capability::FromTypelessPipeline for Pipeline {
fn new(typeless: ::capnp::any_pointer::Pipeline) -> Self {
Self { _typeless: typeless, }
}
}
impl Pipeline {
}
mod _private {
pub static ENCODED_NODE: [::capnp::Word; 62] = [
::capnp::word(0, 0, 0, 0, 5, 0, 6, 0),
::capnp::word(50, 25, 14, 89, 91, 12, 143, 231),
::capnp::word(13, 0, 0, 0, 1, 0, 0, 0),
::capnp::word(164, 172, 216, 255, 36, 223, 58, 242),
::capnp::word(3, 0, 7, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(21, 0, 0, 0, 178, 0, 0, 0),
::capnp::word(29, 0, 0, 0, 7, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(25, 0, 0, 0, 175, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(109, 111, 100, 101, 108, 115, 46, 99),
::capnp::word(97, 112, 110, 112, 58, 76, 111, 103),
::capnp::word(69, 118, 101, 110, 116, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 1, 0, 1, 0),
::capnp::word(12, 0, 0, 0, 3, 0, 4, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 1, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(69, 0, 0, 0, 26, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(64, 0, 0, 0, 3, 0, 1, 0),
::capnp::word(76, 0, 0, 0, 2, 0, 1, 0),
::capnp::word(1, 0, 0, 0, 1, 0, 0, 0),
::capnp::word(0, 0, 1, 0, 1, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(73, 0, 0, 0, 58, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(68, 0, 0, 0, 3, 0, 1, 0),
::capnp::word(80, 0, 0, 0, 2, 0, 1, 0),
::capnp::word(2, 0, 0, 0, 2, 0, 0, 0),
::capnp::word(0, 0, 1, 0, 2, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(77, 0, 0, 0, 66, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(72, 0, 0, 0, 3, 0, 1, 0),
::capnp::word(84, 0, 0, 0, 2, 0, 1, 0),
::capnp::word(105, 100, 0, 0, 0, 0, 0, 0),
::capnp::word(12, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(12, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(97, 117, 116, 104, 111, 114, 0, 0),
::capnp::word(12, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(12, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(99, 111, 110, 116, 101, 110, 116, 0),
::capnp::word(12, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(12, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
::capnp::word(0, 0, 0, 0, 0, 0, 0, 0),
];
pub fn get_field_types(index: u16) -> ::capnp::introspect::Type {
match index {
0 => <::capnp::text::Owned as ::capnp::introspect::Introspect>::introspect(),
1 => <::capnp::text::Owned as ::capnp::introspect::Introspect>::introspect(),
2 => <::capnp::text::Owned as ::capnp::introspect::Introspect>::introspect(),
_ => panic!("invalid field index {}", index),
}
}
pub fn get_annotation_types(child_index: Option<u16>, index: u32) -> ::capnp::introspect::Type {
panic!("invalid annotation indices ({:?}, {}) ", child_index, index)
}
pub static RAW_SCHEMA: ::capnp::introspect::RawStructSchema = ::capnp::introspect::RawStructSchema {
encoded_node: &ENCODED_NODE,
nonunion_members: NONUNION_MEMBERS,
members_by_discriminant: MEMBERS_BY_DISCRIMINANT,
};
pub static NONUNION_MEMBERS : &[u16] = &[0,1,2];
pub static MEMBERS_BY_DISCRIMINANT : &[u16] = &[];
pub const TYPE_ID: u64 = 0xe78f_0c5b_590e_1932;
}
}

View File

@ -23,3 +23,20 @@ pub struct ServerMonitorResp {
pub cursor: Option<uuid::Uuid>, pub cursor: Option<uuid::Uuid>,
pub logs: Vec<String>, pub logs: Vec<String>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LogEvent {
pub id: uuid::Uuid,
pub author: String,
pub content: String,
}
impl LogEvent {
pub fn new(author: impl Into<String>, content: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4(),
author: author.into(),
content: content.into(),
}
}
}

View File

@ -11,6 +11,7 @@ publish.workspace = true
[dependencies] [dependencies]
churn-domain.workspace = true churn-domain.workspace = true
churn-capnp.workspace = true
anyhow.workspace = true anyhow.workspace = true
tokio.workspace = true tokio.workspace = true
@ -22,3 +23,6 @@ axum.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
uuid.workspace = true uuid.workspace = true
async-trait.workspace = true
sled.workspace = true

View File

@ -0,0 +1,79 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use async_trait::async_trait;
use churn_domain::ServerEnrollReq;
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct Db(Arc<dyn DbTrait + Send + Sync + 'static>);
impl Db {
pub fn new_sled(path: &Path) -> Self {
Self(Arc::new(DefaultDb::new(path)))
}
}
impl std::ops::Deref for Db {
type Target = Arc<dyn DbTrait + Send + Sync + 'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for Db {
fn default() -> Self {
Self(Arc::new(DefaultDb::default()))
}
}
struct DefaultDb {
db: sled::Db,
}
impl Default for DefaultDb {
fn default() -> Self {
Self::new(&PathBuf::from("churn-server.sled"))
}
}
impl DefaultDb {
pub fn new(path: &Path) -> Self {
Self {
db: sled::open(path).expect("to be able open a sled path"),
}
}
}
#[async_trait]
pub trait DbTrait {
async fn insert(&self, namespace: &str, key: &str, value: &str) -> anyhow::Result<()>;
async fn get_all(&self, namespace: &str) -> anyhow::Result<Vec<String>>;
}
#[async_trait]
impl DbTrait for DefaultDb {
async fn insert(&self, namespace: &str, key: &str, value: &str) -> anyhow::Result<()> {
let tree = self.db.open_tree(namespace)?;
tree.insert(key, value)?;
tree.flush_async().await?;
Ok(())
}
async fn get_all(&self, namespace: &str) -> anyhow::Result<Vec<String>> {
let tree = self.db.open_tree(namespace)?;
Ok(tree
.iter()
.flatten()
.map(|(_, val)| val)
.flat_map(|v| v.iter().map(|v| v.to_string()).collect::<Vec<String>>())
.collect::<Vec<_>>())
}
}

View File

@ -4,13 +4,23 @@ use std::sync::Arc;
use axum::async_trait; use axum::async_trait;
use churn_domain::ServerEnrollReq; use churn_domain::{LogEvent, ServerEnrollReq};
use serde::{ser::SerializeStruct, Deserialize, Serialize}; use serde::{ser::SerializeStruct, Deserialize, Serialize};
use tokio::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
use churn_capnp::CapnpPackExt;
use crate::db::Db;
#[derive(Clone)] #[derive(Clone)]
pub struct EventService(Arc<dyn EventServiceTrait + Send + Sync + 'static>); pub struct EventService(Arc<dyn EventServiceTrait + Send + Sync + 'static>);
impl EventService {
pub fn new(db: Db) -> Self {
Self(Arc::new(DefaultEventService::new(db)))
}
}
impl std::ops::Deref for EventService { impl std::ops::Deref for EventService {
type Target = Arc<dyn EventServiceTrait + Send + Sync + 'static>; type Target = Arc<dyn EventServiceTrait + Send + Sync + 'static>;
@ -27,23 +37,12 @@ impl Default for EventService {
#[derive(Default)] #[derive(Default)]
struct DefaultEventService { struct DefaultEventService {
agents: Arc<RwLock<Vec<LogEvent>>>, db: Db,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] impl DefaultEventService {
pub struct LogEvent { pub fn new(db: Db) -> Self {
pub id: uuid::Uuid, Self { db }
pub author: String,
pub content: String,
}
impl LogEvent {
pub fn new(author: impl Into<String>, content: impl Into<String>) -> Self {
Self {
id: uuid::Uuid::new_v4(),
author: author.into(),
content: content.into(),
}
} }
} }
@ -57,22 +56,34 @@ pub trait EventServiceTrait {
#[async_trait] #[async_trait]
impl EventServiceTrait for DefaultEventService { impl EventServiceTrait for DefaultEventService {
async fn append(&self, req: LogEvent) -> anyhow::Result<()> { async fn append(&self, req: LogEvent) -> anyhow::Result<()> {
let mut events = self.agents.write().await; self.db
events.push(req); .insert("events_log", &req.id.to_string(), &req.serialize_capnp())
.await;
Ok(()) Ok(())
} }
async fn get_from_cursor(&self, cursor: uuid::Uuid) -> anyhow::Result<Vec<LogEvent>> { async fn get_from_cursor(&self, cursor: uuid::Uuid) -> anyhow::Result<Vec<LogEvent>> {
let events = self.agents.read().await; let events = self.db.get_all("events_log").await?;
let items = events
let events = events
.iter() .iter()
.map(|e| LogEvent::deserialize_capnp(e))
.flatten()
.skip_while(|item| item.id != cursor) .skip_while(|item| item.id != cursor)
.skip(1) .skip(1)
.cloned() .collect();
.collect::<Vec<_>>();
Ok(items) Ok(events)
} }
async fn get_from_beginning(&self) -> anyhow::Result<Vec<LogEvent>> { async fn get_from_beginning(&self) -> anyhow::Result<Vec<LogEvent>> {
let events = self.agents.read().await; let events = self.db.get_all("events_log").await?;
Ok(events.iter().cloned().collect())
let events = events
.iter()
.map(|e| LogEvent::deserialize_capnp(e))
.flatten()
.collect();
Ok(events)
} }
} }

View File

@ -1,8 +1,10 @@
mod agent; mod agent;
mod db;
mod event; mod event;
mod lease; mod lease;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf;
use agent::AgentService; use agent::AgentService;
use anyhow::Error; use anyhow::Error;
@ -11,18 +13,37 @@ use axum::http::StatusCode;
use axum::response::{IntoResponse, Response}; use axum::response::{IntoResponse, Response};
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::{Json, Router}; use axum::{Json, Router};
use churn_domain::{LeaseResp, ServerEnrollReq, ServerMonitorResp}; use churn_domain::{LeaseResp, LogEvent, ServerEnrollReq, ServerMonitorResp};
use clap::{Parser, Subcommand}; use clap::{Args, Parser, Subcommand, ValueEnum};
use event::{EventService, LogEvent}; use event::EventService;
use lease::LeaseService; use lease::LeaseService;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use crate::db::Db;
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, about, long_about = None, subcommand_required = true)] #[command(author, version, about, long_about = None, subcommand_required = true)]
struct Command { struct Command {
#[command(subcommand)] #[command(subcommand)]
command: Option<Commands>, command: Option<Commands>,
#[clap(flatten)]
global: GlobalArgs,
}
#[derive(Args)]
struct GlobalArgs {
#[arg(env = "CHURN_DATABASE", long, default_value = "sled")]
database: DatabaseType,
#[arg(env = "CHURN_SLED_PATH", long, default_value = "churn-server.sled")]
sled_path: PathBuf,
}
#[derive(ValueEnum, Clone)]
enum DatabaseType {
Sled,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -55,6 +76,9 @@ async fn main() -> anyhow::Result<()> {
match cli.command { match cli.command {
Some(Commands::Serve { host }) => { Some(Commands::Serve { host }) => {
tracing::info!("Starting churn server"); tracing::info!("Starting churn server");
let db = match cli.global.database {
DatabaseType::Sled => Db::new_sled(&cli.global.sled_path),
};
let app = Router::new() let app = Router::new()
.route("/ping", get(ping)) .route("/ping", get(ping))
@ -70,7 +94,7 @@ async fn main() -> anyhow::Result<()> {
.with_state(AppState { .with_state(AppState {
agent: AgentService::default(), agent: AgentService::default(),
leases: LeaseService::default(), leases: LeaseService::default(),
events: EventService::default(), events: EventService::new(db),
}); });
tracing::info!("churn server listening on {}", host); tracing::info!("churn server listening on {}", host);