Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create a control plane interface (part 1) #436

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions runtimes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
members = [
"legacy",
"next",
"proto",
"wasm"
]
5 changes: 5 additions & 0 deletions runtimes/legacy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.62"
async-trait = "0.1.58"
clap ={ version = "4.0.18", features = ["derive"] }
thiserror = "1.0.37"
Expand All @@ -18,6 +19,10 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
version = "0.7.0"
path = "../../common"

[dependencies.shuttle-runtime-proto]
version = "0.1.0"
path = "../proto"

[dependencies.shuttle-service]
version = "0.7.0"
default-features = false
Expand Down
11 changes: 9 additions & 2 deletions runtimes/legacy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

Load and run an .so library that implements `shuttle_service::Service`.

To load and run, pass the path to the .so file to load as an argument to the shuttle-next binary:
To test, first start this binary using:

```bash
cargo run -- -f "src/libhello_world.so"
cargo run --
```

Then in another shell, load a `.so` file and start it up:

``` bash
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "../../examples/rocket/hello-world/target/debug/libhello_world.so"}' localhost:8000 runtime.Runtime/load
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:8000 runtime.Runtime/start
```
4 changes: 0 additions & 4 deletions runtimes/legacy/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ use tonic::transport::Endpoint;

#[derive(Parser, Debug)]
pub struct Args {
/// Uri to the `.so` file to load
#[arg(long, short)]
pub file_path: String,

/// Address to reach provisioner at
#[clap(long, default_value = "localhost:5000")]
pub provisioner_address: Endpoint,
Expand Down
2 changes: 2 additions & 0 deletions runtimes/legacy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub enum Error {
Load(#[from] LoaderError),
#[error("Run error: {0}")]
Run(#[from] shuttle_service::Error),
#[error("Start error: {0}")]
Start(#[from] shuttle_service::error::CustomError),
}

pub type Result<T> = std::result::Result<T, Error>;
149 changes: 149 additions & 0 deletions runtimes/legacy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,151 @@
use std::{
collections::BTreeMap,
net::{Ipv4Addr, SocketAddr},
path::PathBuf,
str::FromStr,
sync::Mutex,
};

use anyhow::anyhow;
use async_trait::async_trait;
use shuttle_common::{database, LogItem};
use shuttle_runtime_proto::runtime::{
runtime_server::Runtime, LoadRequest, LoadResponse, StartRequest, StartResponse,
};
use shuttle_service::{
loader::{LoadedService, Loader},
Factory, Logger, ServiceName,
};
use tokio::sync::mpsc::{self, UnboundedReceiver};
use tonic::{Request, Response, Status};
use tracing::{info, instrument, trace};

pub mod args;
pub mod error;

pub struct Legacy {
// Mutexes are for interior mutability
so_path: Mutex<Option<PathBuf>>,
port: Mutex<Option<u16>>,
}

impl Legacy {
pub fn new() -> Self {
Self {
so_path: Mutex::new(None),
port: Mutex::new(None),
}
}
}

#[async_trait]
impl Runtime for Legacy {
async fn load(&self, request: Request<LoadRequest>) -> Result<Response<LoadResponse>, Status> {
let so_path = request.into_inner().path;
trace!(so_path, "loading");

let so_path = PathBuf::from(so_path);
*self.so_path.lock().unwrap() = Some(so_path);

let message = LoadResponse { success: true };
Ok(Response::new(message))
}

async fn start(
&self,
_request: Request<StartRequest>,
) -> Result<Response<StartResponse>, Status> {
let port = 8001;
let address = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port);
let mut factory = DummyFactory::new();
let (logger, _rx) = get_logger();
let so_path = self
.so_path
.lock()
.unwrap()
.as_ref()
.ok_or_else(|| -> error::Error {
error::Error::Start(anyhow!("trying to start a service that was not loaded"))
})
.map_err(|err| Status::from_error(Box::new(err)))?
.clone();

trace!(%address, "starting");
let service = load_service(address, so_path, &mut factory, logger)
.await
.unwrap();

_ = tokio::spawn(run(service, address));

*self.port.lock().unwrap() = Some(port);

let message = StartResponse {
success: true,
port: Some(port as u32),
};

Ok(Response::new(message))
}
}

#[instrument(skip(service))]
async fn run(service: LoadedService, addr: SocketAddr) {
let (handle, library) = service;

info!("starting deployment on {}", addr);
handle.await.unwrap().unwrap();

tokio::spawn(async move {
trace!("closing .so file");
library.close().unwrap();
});
}

#[instrument(skip(addr, so_path, factory, logger))]
async fn load_service(
addr: SocketAddr,
so_path: PathBuf,
factory: &mut dyn Factory,
logger: Logger,
) -> error::Result<LoadedService> {
let loader = Loader::from_so_file(so_path)?;

Ok(loader.load(factory, addr, logger).await?)
}

struct DummyFactory {
service_name: ServiceName,
}

impl DummyFactory {
fn new() -> Self {
Self {
service_name: ServiceName::from_str("legacy").unwrap(),
}
}
}

#[async_trait]
impl Factory for DummyFactory {
fn get_service_name(&self) -> ServiceName {
self.service_name.clone()
}

async fn get_db_connection_string(
&mut self,
_: database::Type,
) -> Result<String, shuttle_service::Error> {
todo!()
}

async fn get_secrets(&mut self) -> Result<BTreeMap<String, String>, shuttle_service::Error> {
todo!()
}
}

fn get_logger() -> (Logger, UnboundedReceiver<LogItem>) {
let (tx, rx) = mpsc::unbounded_channel();
let logger = Logger::new(tx, Default::default());

(logger, rx)
}
90 changes: 10 additions & 80 deletions runtimes/legacy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
use std::{collections::BTreeMap, net::SocketAddr, path::PathBuf, str::FromStr};
use std::net::{Ipv4Addr, SocketAddr};

use async_trait::async_trait;
use clap::Parser;
use shuttle_common::{database, LogItem};
use shuttle_legacy::args::Args;
use shuttle_service::{
loader::{LoadedService, Loader},
Factory, Logger, ServiceName,
};
use tokio::sync::mpsc::{self, UnboundedReceiver};
use tracing::{info, instrument, trace};
use shuttle_legacy::{args::Args, Legacy};
use shuttle_runtime_proto::runtime::runtime_server::RuntimeServer;
use tonic::transport::Server;
use tracing::trace;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

#[tokio::main(flavor = "multi_thread")]
Expand All @@ -28,76 +23,11 @@ async fn main() {

trace!(args = ?args, "parsed args");

let address: SocketAddr = "127.0.0.1:8000".parse().unwrap();
let mut factory = DummyFactory::new();
let (logger, _rx) = get_logger();
let so_path = PathBuf::from(args.file_path.as_str());

let service = load_service(address, so_path, &mut factory, logger)
let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 8000);
let legacy = Legacy::new();
Server::builder()
.add_service(RuntimeServer::new(legacy))
.serve(addr)
.await
.unwrap();

_ = tokio::spawn(run(service, address)).await;
}

#[instrument(skip(service))]
async fn run(service: LoadedService, addr: SocketAddr) {
let (handle, library) = service;

info!("starting deployment on {}", addr);
handle.await.unwrap().unwrap();

tokio::spawn(async move {
trace!("closing .so file");
library.close().unwrap();
});
}

#[instrument(skip(addr, so_path, factory, logger))]
async fn load_service(
addr: SocketAddr,
so_path: PathBuf,
factory: &mut dyn Factory,
logger: Logger,
) -> shuttle_legacy::error::Result<LoadedService> {
let loader = Loader::from_so_file(so_path)?;

Ok(loader.load(factory, addr, logger).await?)
}

struct DummyFactory {
service_name: ServiceName,
}

impl DummyFactory {
fn new() -> Self {
Self {
service_name: ServiceName::from_str("next").unwrap(),
}
}
}

#[async_trait]
impl Factory for DummyFactory {
fn get_service_name(&self) -> ServiceName {
self.service_name.clone()
}

async fn get_db_connection_string(
&mut self,
_: database::Type,
) -> Result<String, shuttle_service::Error> {
todo!()
}

async fn get_secrets(&mut self) -> Result<BTreeMap<String, String>, shuttle_service::Error> {
todo!()
}
}

fn get_logger() -> (Logger, UnboundedReceiver<LogItem>) {
let (tx, rx) = mpsc::unbounded_channel();
let logger = Logger::new(tx, Default::default());

(logger, rx)
}
6 changes: 6 additions & 0 deletions runtimes/next/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ async-trait = "0.1.58"
clap ={ version = "4.0.18", features = ["derive"] }
tokio = { version = "1.20.1", features = [ "full" ] }
tonic = "0.8.0"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }

cap-std = "*"
wasmtime = "*"
wasmtime-wasi = "*"
wasi-common = "*"

serenity = { version = "0.11.5", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }

[dependencies.shuttle-runtime-proto]
version = "0.1.0"
path = "../proto"
9 changes: 8 additions & 1 deletion runtimes/next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

```bash
$ cd ..; make wasm
$ DISCORD_TOKEN=xxx BOT_SRC=bot.wasm cargo run
$ DISCORD_TOKEN=xxx cargo run
```

In another terminal:

``` bash
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic", "path": "bot.wasm"}' localhost:8000 runtime.Runtime/load
grpcurl -plaintext -import-path ../proto -proto runtime.proto -d '{"service_name": "Tonic"}' localhost:8000 runtime.Runtime/start
```

## Running the tests
Expand Down
Loading