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/code cleanup #45

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7c61327
code: break run function into multiple little ones
MurielParaire May 2, 2024
8caaef3
docs: add readme (#38)
thomas-mauran May 2, 2024
17a5f47
chore: fix example path (#42)
thomas-mauran May 2, 2024
42f947c
feat: agent is now build through command line and not just
MurielParaire May 2, 2024
8808865
fix: refactor code & vmm commands streams script output
MurielParaire May 3, 2024
a186dde
fix(vmm): cleanup code
MurielParaire May 3, 2024
cfdb10d
fix(vmm): cleanup code
MurielParaire May 3, 2024
3c3bceb
feat: get internet access (#39)
sylvain-pierrot May 2, 2024
450bd3f
feat: add struct for image repo, name and tag, download from any publ…
sea-gull-diana Apr 27, 2024
4963696
fix: wrap errors (#10)
Mathias-Boulay Apr 29, 2024
0ab14fa
feat: use multiple container registries (docker hub, ghcr, gitlab...)
sea-gull-diana Apr 29, 2024
b288bb1
feat: remove registries json, get auth link and service directly from…
sea-gull-diana Apr 30, 2024
b921b70
feat: extract registry from image name, remove -r cli argument
sea-gull-diana Apr 30, 2024
cb775aa
feat: support docker.io alias for docker hub, check that registry lin…
sea-gull-diana May 1, 2024
af1ff7f
refactor: make xz use all available cpus
BioTheWolff May 2, 2024
68734f5
feat: add basic auth to access private image repositories
sea-gull-diana May 1, 2024
ac22ee0
feat: allow passing password via stdin
sea-gull-diana May 1, 2024
bc88531
feat: add '--insecure' option for invalid tls certs, allow writing im…
sea-gull-diana May 2, 2024
149571d
Agent: sigterm children (#36)
remi-espie May 3, 2024
7b4ffdd
fix(vmm): file name bugs
MurielParaire May 3, 2024
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
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ run:
'RUST_BACKTRACE=1 '$CARGO_PATH' run --bin vmm -- cli --memory 512 --cpus 1 \
--kernel tools/kernel/linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
--iface-host-addr 172.29.0.1 --netmask 255.255.0.0 --iface-guest-addr 172.29.0.2 \
--initramfs=tools/rootfs/initramfs.img'
--initramfs=../virt-do/initramfs.img'

build-kernel:
#!/bin/bash
Expand Down
141 changes: 141 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<div style="text-align:center">
<h1> Cloudlet</h1>
<p>The almost fast FaaS</p>
<img src="./assets/demo.gif" alt="Demo" />
</div>

## Table of Contents

- [Table of Contents](#table-of-contents)
- [Prerequisites](#prerequisites)
- [Run Locally](#run-locally)
- [Clone the project](#clone-the-project)
- [Setup](#setup)
- [Start the VMM](#start-the-vmm)
- [Run the API](#run-the-api)
- [Send the request using the CLI](#send-the-request-using-the-cli)
- [Architecture](#architecture)
- [Config file](#config-file)

## Prerequisites

Install the dependencies. On Debian/Ubuntu:

```bash
apt install build-essential cmake pkg-config libssl-dev flex bison libelf-dev
```

Then, configure the Rust toolchain and install [Just](https://github.com/casey/just):

```bash
rustup target add x86_64-unknown-linux-musl
cargo install just
```

Finally, install [the protobuf compiler](https://github.com/protocolbuffers/protobuf?tab=readme-ov-file#protobuf-compiler-installation).

## Run Locally

### Clone the project

```bash
git clone https://github.com/virt-do/cloudlet
```

### Setup

Go to the project directory:

```bash
cd cloudlet
```

Create a TOML config file or update the [existing one](./src/cli/examples/config.toml):

```bash
cat << EOF > src/cli/examples/config.toml
workload-name = "fibonacci"
language = "rust"
action = "prepare-and-run"

[server]
address = "localhost"
port = 50051

[build]
source-code-path = "$(readlink -f ./src/cli/examples/main.rs)"
release = true
EOF
```

Make sure to update the `source-code-path` to the path of the source code you want to run.
Use an absolute path.

[Here](#config-file) are more informations about each field

### Start the VMM

> [!WARNING]
> Make sure to replace `CARGO_PATH` environment variable with the path to your cargo binary
>
> ```bash
> export CARGO_PATH=$(which cargo)
> ```

```bash
sudo -E capsh --keep=1 --user=$USER --inh=cap_net_admin --addamb=cap_net_admin -- -c 'RUST_BACKTRACE=1 '$CARGO_PATH' run --bin vmm -- grpc'
```

### Run the API

```bash
cargo run --bin api
```

### Send the request using the CLI

```bash
cargo run --bin cli -- run --config-path src/cli/examples/config.toml
```

> [!NOTE]
> If it's your first time running the request, `cloudlet` will have to compile a kernel and an initramfs image.
> This will take a while, so make sure you do something else while you wait...

## Architecture

Here is a simple sequence diagram of Cloudlet:

```mermaid
sequenceDiagram
participant CLI
participant API
participant VMM
participant Agent

CLI->>API: HTTP Request /run
API->>VMM: gRPC Request to create VM
VMM->>Agent: Creation of the VM
VMM->>Agent: gRPC Request to the agent
Agent->>Agent: Build and run code
Agent-->>VMM: Stream Response
VMM-->>API: Stream Response
API-->>CLI: HTTP Response
```

1. The CLI sends an HTTP request to the API which in turn sends a gRPC request to the VMM
2. The VMM then creates a VM
3. When a VM starts it boots on the agent which holds another gRPC server to handle requests
4. The agent then builds and runs the code
5. The response is streamed back to the VMM and then to the API and finally to the CLI.

## Config file
| Field | Description | Type |
| --- | --- | --- |
| workload-name | Name of the workload you wanna run | String |
| language | Language of the source code | String enum: rust, python node |
| action | Action to perform | String enum: prepare-and-run |
| server.address | Address of the server (currently not used) | String |
| server.port | Port of the server (currently not used) | Integer |
| build.source-code-path | Path to the source code on your local machine | String |
| build.release | Build the source code in release mode | Boolean |
Binary file added assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ name = "agent"
path = "src/lib.rs"

[dependencies]
async-trait = "0.1.80"
clap = { version = "4.5.4", features = ["derive", "env"] }
nix = { version = "0.28.0", features = ["signal"] }
once_cell = "1.19.0"
prost = "0.12.4"
rand = "0.8.5"
serde = { version = "1.0.197", features = ["derive"] }
Expand Down
9 changes: 7 additions & 2 deletions src/agent/src/agents/debug.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use super::AgentOutput;
use crate::agents::Agent;
use crate::{workload, AgentResult};
use async_trait::async_trait;
use std::collections::HashSet;
use std::fs::create_dir_all;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::Mutex;

pub struct DebugAgent {
workload_config: workload::config::Config,
Expand All @@ -14,8 +18,9 @@ impl From<workload::config::Config> for DebugAgent {
}
}

#[async_trait]
impl Agent for DebugAgent {
fn prepare(&self) -> AgentResult<AgentOutput> {
async fn prepare(&self, _: Arc<Mutex<HashSet<u32>>>) -> AgentResult<AgentOutput> {
let dir = format!("/tmp/{}", self.workload_config.workload_name);

println!("Function directory: {}", dir);
Expand All @@ -39,7 +44,7 @@ impl Agent for DebugAgent {
})
}

fn run(&self) -> AgentResult<AgentOutput> {
async fn run(&self, _: Arc<Mutex<HashSet<u32>>>) -> AgentResult<AgentOutput> {
let dir = format!("/tmp/{}", self.workload_config.workload_name);

let content = std::fs::read_to_string(format!("{}/debug.txt", &dir))
Expand Down
9 changes: 7 additions & 2 deletions src/agent/src/agents/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::{AgentError, AgentResult};
use async_trait::async_trait;
use serde::Deserialize;
use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::Mutex;

#[cfg(feature = "debug-agent")]
pub mod debug;
Expand All @@ -12,9 +16,10 @@ pub struct AgentOutput {
pub stderr: String,
}

#[async_trait]
pub trait Agent {
fn prepare(&self) -> AgentResult<AgentOutput>;
fn run(&self) -> AgentResult<AgentOutput>;
async fn prepare(&self, child_processes: Arc<Mutex<HashSet<u32>>>) -> AgentResult<AgentOutput>;
async fn run(&self, child_processes: Arc<Mutex<HashSet<u32>>>) -> AgentResult<AgentOutput>;
}

#[derive(Debug, Clone, Deserialize)]
Expand Down
57 changes: 45 additions & 12 deletions src/agent/src/agents/rust.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use super::{Agent, AgentOutput};
use crate::{workload, AgentError, AgentResult};
use async_trait::async_trait;
use rand::distributions::{Alphanumeric, DistString};
use serde::Deserialize;
use std::{fs::create_dir_all, process::Command};
use std::collections::HashSet;
use std::fs::create_dir_all;
use std::sync::Arc;
use tokio::process::Command;
use tokio::sync::Mutex;

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
Expand All @@ -21,27 +26,45 @@ pub struct RustAgent {
}

impl RustAgent {
fn build(&self, function_dir: &String) -> AgentResult<AgentOutput> {
async fn build(
&self,
function_dir: &str,
child_processes: Arc<Mutex<HashSet<u32>>>,
) -> AgentResult<AgentOutput> {
if self.rust_config.build.release {
let output = Command::new("cargo")
let child = Command::new("cargo")
.arg("build")
.arg("--release")
.current_dir(function_dir)
.output()
.spawn()
.expect("Failed to build function");

{
child_processes.lock().await.insert(child.id().unwrap());
}

let output = child
.wait_with_output()
.await
.expect("Failed to wait on child");

Ok(AgentOutput {
exit_code: output.status.code().unwrap(),
stdout: std::str::from_utf8(&output.stdout).unwrap().to_string(),
stderr: std::str::from_utf8(&output.stderr).unwrap().to_string(),
})
} else {
let output = Command::new("cargo")
let child = Command::new("cargo")
.arg("build")
.current_dir(function_dir)
.output()
.spawn()
.expect("Failed to build function");

let output = child
.wait_with_output()
.await
.expect("Failed to wait on child");

Ok(AgentOutput {
exit_code: output.status.code().unwrap(),
stdout: std::str::from_utf8(&output.stdout).unwrap().to_string(),
Expand All @@ -63,8 +86,9 @@ impl From<workload::config::Config> for RustAgent {
}
}

#[async_trait]
impl Agent for RustAgent {
fn prepare(&self) -> AgentResult<AgentOutput> {
async fn prepare(&self, child_processes: Arc<Mutex<HashSet<u32>>>) -> AgentResult<AgentOutput> {
let function_dir = format!(
"/tmp/{}",
Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
Expand All @@ -85,15 +109,15 @@ impl Agent for RustAgent {
[package]
name = "{}"
version = "0.1.0"
edition = "2018"
edition = "2021"
"#,
self.workload_config.workload_name
);

std::fs::write(format!("{}/Cargo.toml", &function_dir), cargo_toml)
.expect("Unable to write Cargo.toml file");

let result = self.build(&function_dir)?;
let result = self.build(&function_dir, child_processes).await?;

if result.exit_code != 0 {
println!("Build failed: {:?}", result);
Expand Down Expand Up @@ -131,11 +155,20 @@ impl Agent for RustAgent {
})
}

fn run(&self) -> AgentResult<AgentOutput> {
let output = Command::new(format!("/tmp/{}", self.workload_config.workload_name))
.output()
async fn run(&self, child_processes: Arc<Mutex<HashSet<u32>>>) -> AgentResult<AgentOutput> {
let child = Command::new(format!("/tmp/{}", self.workload_config.workload_name))
.spawn()
.expect("Failed to run function");

{
child_processes.lock().await.insert(child.id().unwrap());
}

let output = child
.wait_with_output()
.await
.expect("Failed to wait on child");

let agent_output = AgentOutput {
exit_code: output.status.code().unwrap(),
stdout: std::str::from_utf8(&output.stdout).unwrap().to_string(),
Expand Down
Loading
Loading