Skip to content

Commit

Permalink
carlo: add upload and create commands
Browse files Browse the repository at this point in the history
Uses reqwest::blocking to implement a Client type that uses the
carol_http provided response types to handle serialization.
  • Loading branch information
nothingmuch committed Jun 2, 2023
1 parent 4ad4c43 commit c94e4b7
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 16 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ carol = { path = "crates/carol" }
carol_core = { path = "crates/carol_core", default-features = false }
carol_http = { path = "crates/carol_http" }
carol_bls = { path = "crates/carol_bls" }
reqwest = { version = "0.11.16", features = ["json", "rustls-tls"], default-features = false }
reqwest = { version = "0.11.16", features = ["json", "rustls-tls", "blocking"], default-features = false }
hyper = { version = "0.14", default-features = false }
http_crate = { package = "http", version = "0.2" }
clap = { version = "4", features = ["derive"] }
Expand Down
18 changes: 3 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,7 @@ Then upload it to carol:

``` sh
carol_url=http://localhost:8000
curl -vv -XPOST --data-binary "@${wasm_output}" "${carol_url}/binaries"
```

The response will return something like:

```
"d1e3e462c1e61dd99c7c05c3c818134721a0fb5c33280085e6c4acab96835d5c"}
binary_id=$( cargo run -p carlo -- upload --carol-url "${carol_url}" --binary "${wasm_output}" )
```

Note this `id` is just a hash of the binary. In general it's intended for client software to
Expand All @@ -71,13 +65,7 @@ server.
Carol machines are created from a binary and a parameterization array. Most machines will have an empty parameterization for now so we make an empty POST request to t

``` sh
curl -vv -XPOST "${carol_url}/binaries/3ac00e3d0a37e4255bddb4f3389c7acfb51f7dcfa771f70da05b83ea893c7646"
```

This will return a response like:

``` json
{"id":"7f5389c82658b4792e8b32b91817300b5169458fb82fd1eab63f33f36e1decaf"}
machine_id=$( cargo run -p carlo -- create --carol-url "${carol_url}" --binary-id "${binary_id}" )
```

Note this `id` is a hash of the binary and the (empty) parameterization vector. In general it's
Expand All @@ -96,7 +84,7 @@ Let's send a HTTP request to the machine which will activate the `attest_to_pric


```sh
curl -v -XPOST --data-binary '{"time" : "2023-04-16T12:30:00Z", "symbol" : ".BXBT"}' "${carol_url}/machines/7f5389c82658b4792e8b32b91817300b5169458fb82fd1eab63f33f36e1decaf/activate/attest_to_price_at_minute"
curl -v -XPOST --data-binary '{"time" : "2023-04-16T12:30:00Z", "symbol" : ".BXBT"}' "${carol_url}/machines/${machine_id}/activate/attest_to_price_at_minute"
```

which at the time of writing returns:
Expand Down
5 changes: 5 additions & 0 deletions crates/carlo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ edition = "2021"
[dependencies]
anyhow.workspace = true
cargo_metadata = "0.15.4"
carol_core = { workspace = true, features = [ "std" ] }
carol_host.workspace = true
carol_http.workspace = true
clap = { workspace = true }
clap-cargo = { version = "0.10.0", features = [ "cargo_metadata" ] }
serde = { workspace = true }
serde_json = { workspace = true }
reqwest = { workspace = true }
wit-component.workspace = true
80 changes: 80 additions & 0 deletions crates/carlo/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use anyhow::{anyhow, Context};
pub use carol_core::BinaryId;
pub use carol_http::api::{BinaryCreated, MachineCreated};

pub struct Client {
base: String,
http_client: reqwest::blocking::Client,
}

impl Client {
pub fn new(base: String) -> Self {
Self {
base,
http_client: reqwest::blocking::Client::new(), // blocking::get() does builder().build()?
}
}

pub fn upload_binary<B: Into<reqwest::blocking::Body>>(
&self,
binary_id: &BinaryId,
binary: B,
) -> anyhow::Result<BinaryCreated> {
let http_response = self
.post("binaries") // TODO idempotent HTTP PUT?
.body(binary)
.send()
.context("Uploading compiled WASM file to {carol_url}")?;

let api_response: BinaryCreated = self
.decode_response(http_response)
.context("Parse response")?;

if api_response.id != *binary_id {
return Err(anyhow!(
"Locally computed binary ID {} doesn't match server reported value {}",
binary_id,
api_response.id,
));
}

Ok(api_response)
}

pub fn create_machine(&self, binary_id: &BinaryId) -> anyhow::Result<MachineCreated> {
let http_response = self
.post(&format!("binaries/{binary_id}"))
.send()
.context("Creating machine from {binary_id} on {carol_url}")?;

self.decode_response::<MachineCreated>(http_response)
.context("Parse response")
}

fn post(&self, path: &str) -> reqwest::blocking::RequestBuilder {
self.http_client
.post(format!("{}/{}", self.base, path))
.header(reqwest::header::ACCEPT, "application/json")
}

fn decode_response<B>(&self, response: reqwest::blocking::Response) -> anyhow::Result<B>
where
B: for<'de> serde::Deserialize<'de>,
{
let response = response.error_for_status()?;

// FIXME error if no content type specified?
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.map(|h| h.to_str().unwrap_or(""))
.unwrap_or("");

if !content_type.is_empty() && content_type != "application/json" {
return Err(anyhow!("Unsupported response content-type {content_type}"));
}

let body = response.bytes().context("Reading server response")?;
serde_json::from_slice(&body).context("Decoding response body")
}
}
42 changes: 42 additions & 0 deletions crates/carlo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use anyhow::{anyhow, Context};
use cargo_metadata::camino::Utf8PathBuf;
use cargo_metadata::Message;
use carol_core::BinaryId;
use carol_host::Executor;
use clap::{Parser, Subcommand};
use clap_cargo::Workspace;
use std::process::{Command, Stdio};
use wit_component::ComponentEncoder;

mod client;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
Expand All @@ -20,6 +24,18 @@ enum Commands {
/// Package to compile to a Carol WASM component (see `cargo help pkgid`)
package: Option<String>, // real one has Vec<String>
},
Upload {
#[arg(long)]
carol_url: String,
#[arg(long)]
binary: Utf8PathBuf,
},
Create {
#[arg(long)]
carol_url: String,
#[arg(long)]
binary_id: BinaryId,
},
}

fn main() -> anyhow::Result<()> {
Expand Down Expand Up @@ -120,6 +136,32 @@ fn main() -> anyhow::Result<()> {

println!("{component_target}");
}
Commands::Upload { binary, carol_url } => {
let client = client::Client::new(carol_url.clone());

// Validate and derive BinaryId
let binary_id = Executor::new()
.load_binary_from_wasm_file(binary)
.context("Loading compiled binary")?
.binary_id();

let file = std::fs::File::open(binary)
.context(format!("Reading compiled WASM file {}", binary))?;

let response = client.upload_binary(&binary_id, file)?;
let binary_id = response.id;
println!("{binary_id}");
}
Commands::Create {
binary_id,
carol_url,
} => {
let client = client::Client::new(carol_url.clone());

let response = client.create_machine(binary_id)?;
let machine_id = response.id;
print!("{machine_id}");
}
}

Ok(())
Expand Down

0 comments on commit c94e4b7

Please sign in to comment.