Skip to content

Commit

Permalink
Restructure repository
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Sep 28, 2020
1 parent bbfa3fd commit 35245ab
Show file tree
Hide file tree
Showing 46 changed files with 184 additions and 174 deletions.
13 changes: 1 addition & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,2 @@
/artifacts
/target

*Cargo.lock

.idea
*.iml

.terraform
*.tfstate*
*.tfvars*

*.zip
*.iml
165 changes: 4 additions & 161 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,165 +1,8 @@
# Description

The intention of this project is to build a skeleton of VRP REST API for quick prototyping using Rust and AWS. It uses
another Rust project which implements a rich VRP solver functionality, you can find it [here](https://github.com/reinterpretcat/vrp).
This repo contains various experiments with technologies, languages, tools in connection to VRP topic.

# Overview
# Content

This repo contains two implementations of REST API:

* `server` a traditional server approach built using [actix-web framework](https://github.com/actix/actix-web)
* `serverless` a serverless approach using AWS lambdas

## Server approach


## Serverless approach

### Architecture

On API level, there are two public endpoints:

- __submit problem__: responsible for submitting VRP in [pragmatic format](https://reinterpretcat.github.io/vrp/concepts/pragmatic/index.html)
- __poll solution__: provides way to get calculated VRP solution back

Essentially, the flow can be described as follows:

![architecture](docs/architecture.png "VRP API")

1. User create POST request to `/problem` resource which is exposed via AWS API Gateway.
2. API Gateway triggers AWS `submit_problem` lambda function. See its source code inside `./lambdas/gateway/submit` folder.
3. If problem definition is ok, the lambda function uploads it to S3 bucket as `problem.json`. As result, user gets
`submit id` which is the name of the bucket.
4. The `trigger_solver` lambda function is triggered once `problem.json` is uploaded. Its source code is inside
`./lambdas/triggers/batch` folder.
5. This lambda triggers a AWS Batch job for solving VRP.
6. The Batch job pulls problem definition from S3 bucket, solves VRP, and uploads `solution.json`
7. Any time user can poll solution by calling `/solution` resource using GET method. This resource is also exposed
via AWS API Gateway.
8. API Gateway triggers AWS `poll_problem` lambda function. See its source code inside `./lambdas/gateway/poll` folder.
9. The lambda function downloads `state.json` file (see a note below) and, depending on its content, returns solution
or submission state.

Notes:
- additionally, there is `state.json` file in S3 bucket. It is used to track the state of submission and, potentially,
return some information regarding execution progress
- actually, batch job can be created by submit lambda, but this would limit extensibility: in real world, there
should be an extra step to request routing matrix information. It might be expensive in terms of performance and time,
so another logic might be needed here and it is better to avoid coupling problem submission and calling the solver.


## Source code structure

- __Rust code__:
- `./common`: contains shared code used by different crates in the project
- `./lambdas`: contains code for AWS lambda functions used by `serverless` approach
- `./server`: contains code for server approach
- `./solver`: contains a binary crate for solving VRP problem
- __Build & Deployment & Test__
- `./terraform`: terraform configuration to deploy AWS resources
- `./scripts`: various scripts to build, deploy, and test (see next section for details)
- `./solver/image/Dockerfile`: a docker file for solver image

# How to use

## Prerequisites

Once source code is pulled from github, make sure that you have the following prerequisites:

- linux compatible environment
- rust
* install rust toolchain using instructions from [official site](https://www.rust-lang.org/tools/install)
* install cross compilation tool from [github](https://github.com/rust-embedded/cross) which will be used to build
code to be in AWS:
```shell script
# see https://github.com/awslabs/aws-lambda-rust-runtime/issues/17
cargo install --force --git https://github.com/rust-embedded/cross
```
- AWS
* create an account in [AWS](https://aws.amazon.com/resources/create-account)
* create Elastic Container Repository with `vrp-solver` name which will be used to publish docker image
* set environment variables:
```shell script
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
export AWS_DEFAULT_REGION="eu-west-1" # or another one
```
- Terraform
* install terraform using instructions from [official site](https://learn.hashicorp.com/terraform/getting-started/install.html)
* create `private.tfvars` file inside `terraform` directory and put the following:
```
vpc_subnet_ids = ["subnet-12340a1b"]
vpc_security_group_ids = ["sg-12340a2b"]
batch_container_image = "012345678901.dkr.ecr.eu-west-1.amazonaws.com/vrp-solver"
```
Use proper subnet and security group ids (e.g. default ones) and change `012345678901` to your account id (12 digits).

## Build

Use the following script to build the code:

```shell script
./scripts/build.sh
```

It builds rust code and copies build artifacts into `artifacts` folder.

## Deploy

- build a docker image of the VRP solver and publish it in Elastic Container Repository:

```shell script
./scripts/deploy.sh
```

- create AWS resources using terraform:

```shell script
cd terraform
terraform init
terraform apply -var-file="private.tfvars"
```

## Test serverless

If you decided to use different AWS region, adjust url in tests scripts according to
[AWS documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html).
Then determine api gateway id which you can find by looking into AWS console or terraform output.

To post the test problem, run the following command:

```shell script
./scripts/test_submit_problem.sh api_gateway_id
```

Replace `api_gateway_id` with real one.

If everything is ok, you will get json response like:

```json
{
"id": "ecc9c997-5063-4eb3-8099-1c3c1743b4f1"
}
```

You can get solution or its processing status by running:

```shell script
./scripts/test_poll_solution.sh api_gateway_id ecc9c997-5063-4eb3-8099-1c3c1743b4f1
```

Result is one of the following responses:

- `200 OK`: solution is calculated and returned in response body
- `204 No content`: problem is accepted, but solution is not yet calculated
- `404 Not found`: no problem is found
- `409 Conflict`: an error is occurred while calculating solution. Response body contains additional information


# Further improvements

- limit resources accessed by each service
- add openapi spec for API Gateway
- add authorization
- add unit tests
- ...
* **serverless-api**: experimenting with serverless computing on AWS + Rust
* **server-k8s-api**: experimenting with kubernetes + REST server on Rust
File renamed without changes.
2 changes: 2 additions & 0 deletions server/src/main.rs → server-k8s-api/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod routes;

use actix_web::{get, web, App, HttpServer, Responder};

#[get("/{id}/{name}/index.html")]
Expand Down
Empty file added server-k8s-api/src/models.rs
Empty file.
15 changes: 15 additions & 0 deletions server-k8s-api/src/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use actix_web::{web, HttpResponse};

pub fn configure(cfg: &mut web::ServiceConfig) {
cfg
.service(web::resource("/problems").route(web::post().to_async(submit_problem)))
.service(web::resource("/problems/{id}/solutions").route(web::get().to_async(get_solutions)));
}

async fn submit_problem(path: web::Path<String>) -> HttpResponse {

}

async fn get_solutions(path: web::Path<String>) -> HttpResponse {

}
10 changes: 10 additions & 0 deletions serverless-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/artifacts
/target

*Cargo.lock

.terraform
*.tfstate*
*.tfvars*

*.zip
1 change: 0 additions & 1 deletion Cargo.toml → serverless-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
members = [
"common",
"lambdas",
"server",
"solver",
]

Expand Down
152 changes: 152 additions & 0 deletions serverless-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Description

The intention of this project is to build a skeleton of VRP REST API for quick prototyping using Rust and AWS. It uses
another Rust project which implements a rich VRP solver functionality, you can find it [here](https://github.com/reinterpretcat/vrp).

# Architecture

On API level, there are two public endpoints:

- __submit problem__: responsible for submitting VRP in [pragmatic format](https://reinterpretcat.github.io/vrp/concepts/pragmatic/index.html)
- __poll solution__: provides way to get calculated VRP solution back

Essentially, the flow can be described as follows:

![architecture](docs/architecture.png "VRP API")

1. User create POST request to `/problem` resource which is exposed via AWS API Gateway.
2. API Gateway triggers AWS `submit_problem` lambda function. See its source code inside `./lambdas/gateway/submit` folder.
3. If problem definition is ok, the lambda function uploads it to S3 bucket as `problem.json`. As result, user gets
`submit id` which is the name of the bucket.
4. The `trigger_solver` lambda function is triggered once `problem.json` is uploaded. Its source code is inside
`./lambdas/triggers/batch` folder.
5. This lambda triggers a AWS Batch job for solving VRP.
6. The Batch job pulls problem definition from S3 bucket, solves VRP, and uploads `solution.json`
7. Any time user can poll solution by calling `/solution` resource using GET method. This resource is also exposed
via AWS API Gateway.
8. API Gateway triggers AWS `poll_problem` lambda function. See its source code inside `./lambdas/gateway/poll` folder.
9. The lambda function downloads `state.json` file (see a note below) and, depending on its content, returns solution
or submission state.

Notes:
- additionally, there is `state.json` file in S3 bucket. It is used to track the state of submission and, potentially,
return some information regarding execution progress
- actually, batch job can be created by submit lambda, but this would limit extensibility: in real world, there
should be an extra step to request routing matrix information. It might be expensive in terms of performance and time,
so another logic might be needed here and it is better to avoid coupling problem submission and calling the solver.


# Source code structure

- __Rust code__:
- `./common`: contains shared code used by different crates in the project
- `./lambdas`: contains code for AWS lambda functions
- `./solver`: contains a binary crate for solving VRP problem
- __Build & Deployment & Test__
- `./terraform`: terraform configuration to deploy AWS resources
- `./scripts`: various scripts to build, deploy, and test (see next section for details)
- `./solver/image/Dockerfile`: a docker file for solver image

# How to use

## Prerequisites

Once source code is pulled from github, make sure that you have the following prerequisites:

- linux compatible environment
- rust
* install rust toolchain using instructions from [official site](https://www.rust-lang.org/tools/install)
* install cross compilation tool from [github](https://github.com/rust-embedded/cross) which will be used to build
code to be in AWS:
```shell script
# see https://github.com/awslabs/aws-lambda-rust-runtime/issues/17
cargo install --force --git https://github.com/rust-embedded/cross
```
- AWS
* create an account in [AWS](https://aws.amazon.com/resources/create-account)
* create Elastic Container Repository with `vrp-solver` name which will be used to publish docker image
* set environment variables:
```shell script
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
export AWS_DEFAULT_REGION="eu-west-1" # or another one
```
- Terraform
* install terraform using instructions from [official site](https://learn.hashicorp.com/terraform/getting-started/install.html)
* create `private.tfvars` file inside `terraform` directory and put the following:
```
vpc_subnet_ids = ["subnet-12340a1b"]
vpc_security_group_ids = ["sg-12340a2b"]
batch_container_image = "012345678901.dkr.ecr.eu-west-1.amazonaws.com/vrp-solver"
```
Use proper subnet and security group ids (e.g. default ones) and change `012345678901` to your account id (12 digits).

## Build

Use the following script to build the code:

```shell script
./scripts/build.sh
```

It builds rust code and copies build artifacts into `artifacts` folder.

## Deploy

- build a docker image of the VRP solver and publish it in Elastic Container Repository:

```shell script
./scripts/deploy.sh
```

- create AWS resources using terraform:

```shell script
cd terraform
terraform init
terraform apply -var-file="private.tfvars"
```

## Test

If you decided to use different AWS region, adjust url in tests scripts according to
[AWS documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-call-api.html).
Then determine api gateway id which you can find by looking into AWS console or terraform output.

To post the test problem, run the following command:

```shell script
./scripts/test_submit_problem.sh api_gateway_id
```

Replace `api_gateway_id` with real one.

If everything is ok, you will get json response like:

```json
{
"id": "ecc9c997-5063-4eb3-8099-1c3c1743b4f1"
}
```

You can get solution or its processing status by running:

```shell script
./scripts/test_poll_solution.sh api_gateway_id ecc9c997-5063-4eb3-8099-1c3c1743b4f1
```

Result is one of the following responses:

- `200 OK`: solution is calculated and returned in response body
- `204 No content`: problem is accepted, but solution is not yet calculated
- `404 Not found`: no problem is found
- `409 Conflict`: an error is occurred while calculating solution. Response body contains additional information


# Further improvements

- limit resources accessed by each service
- add openapi spec for API Gateway
- add authorization
- add unit tests
- ...
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 35245ab

Please sign in to comment.