-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a2d3d08
Showing
10 changed files
with
1,511 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
cni.conf | ||
cni-bin | ||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
run: | ||
@go run . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# Windhoek | ||
|
||
Windhoek is a small API written in Golang that exposes an invoke route we can call to run a function within a Firecracker VM. | ||
|
||
# How to run | ||
## Install dependencies | ||
|
||
### Install Golang | ||
```bash | ||
pushd /tmp | ||
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz | ||
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz | ||
echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc | ||
popd | ||
``` | ||
|
||
### Install Firecracker | ||
```bash | ||
pushd /tmp | ||
ARCH="$(uname -m)" | ||
release_url="https://github.com/firecracker-microvm/firecracker/releases" | ||
latest=$(basename $(curl -fsSLI -o /dev/null -w %{url_effective} ${release_url}/latest)) | ||
curl -L ${release_url}/download/${latest}/firecracker-${latest}-${ARCH}.tgz \ | ||
| tar -xz | ||
|
||
# Rename the binary to "firecracker" | ||
mv release-${latest}-$(uname -m)/firecracker-${latest}-${ARCH} /usr/local/bin/firecracker | ||
popd | ||
``` | ||
|
||
### Install CNI plugins | ||
```bash | ||
apt-get install make | ||
git clone https://github.com/containernetworking/plugins.git /tmp/cni-plugins | ||
|
||
# Move to the plugins directory | ||
pushd /tmp/cni-plugins | ||
|
||
# Build the CNI tools | ||
./build_linux.sh | ||
|
||
mv bin/* /opt/cni/bin | ||
|
||
git clone https://github.com/awslabs/tc-redirect-tap | ||
pushd tc-redirect-tap/ | ||
make install | ||
mv tc-redirect-tap /opt/cni/bin | ||
popd | ||
popd | ||
``` | ||
|
||
### Create required directories | ||
|
||
```bash | ||
mkdir /root/fckernels /root/fcsockets /root/fcruntimes | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
module github.com/runvelocity/windhoek | ||
|
||
go 1.21.5 | ||
|
||
require ( | ||
github.com/firecracker-microvm/firecracker-go-sdk v1.0.0 | ||
github.com/labstack/echo/v4 v4.11.4 | ||
) | ||
|
||
require ( | ||
github.com/PuerkitoBio/purell v1.1.1 // indirect | ||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect | ||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect | ||
github.com/containerd/fifo v1.0.0 // indirect | ||
github.com/containernetworking/cni v1.0.1 // indirect | ||
github.com/containernetworking/plugins v1.0.1 // indirect | ||
github.com/go-openapi/analysis v0.21.2 // indirect | ||
github.com/go-openapi/errors v0.20.2 // indirect | ||
github.com/go-openapi/jsonpointer v0.19.5 // indirect | ||
github.com/go-openapi/jsonreference v0.19.6 // indirect | ||
github.com/go-openapi/loads v0.21.1 // indirect | ||
github.com/go-openapi/runtime v0.24.0 // indirect | ||
github.com/go-openapi/spec v0.20.4 // indirect | ||
github.com/go-openapi/strfmt v0.21.2 // indirect | ||
github.com/go-openapi/swag v0.21.1 // indirect | ||
github.com/go-openapi/validate v0.22.0 // indirect | ||
github.com/go-stack/stack v1.8.1 // indirect | ||
github.com/google/uuid v1.3.0 // indirect | ||
github.com/hashicorp/errwrap v1.0.0 // indirect | ||
github.com/hashicorp/go-multierror v1.1.1 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/labstack/gommon v0.4.2 // indirect | ||
github.com/mailru/easyjson v0.7.7 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mitchellh/mapstructure v1.4.3 // indirect | ||
github.com/oklog/ulid v1.3.1 // indirect | ||
github.com/opentracing/opentracing-go v1.2.0 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
github.com/sirupsen/logrus v1.8.1 // indirect | ||
github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
github.com/valyala/fasttemplate v1.2.2 // indirect | ||
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 // indirect | ||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect | ||
go.mongodb.org/mongo-driver v1.8.3 // indirect | ||
golang.org/x/crypto v0.17.0 // indirect | ||
golang.org/x/net v0.19.0 // indirect | ||
golang.org/x/sys v0.15.0 // indirect | ||
golang.org/x/text v0.14.0 // indirect | ||
gopkg.in/yaml.v2 v2.4.0 // indirect | ||
) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/labstack/echo/v4" | ||
"github.com/runvelocity/windhoek/routes" | ||
"github.com/runvelocity/windhoek/utils" | ||
) | ||
|
||
type PingResponse struct { | ||
Ok bool `json:"ok"` | ||
} | ||
|
||
func main() { | ||
e := echo.New() | ||
|
||
if err := utils.WriteCNIConfWithHostLocalSubnet(fmt.Sprintf("/etc/cni/conf.d/%s.conflist", utils.FC_NETWORK_NAME)); err != nil { | ||
e.Logger.Fatal(err.Error()) | ||
} | ||
e.GET("/ping", func(c echo.Context) error { | ||
return c.JSON(http.StatusOK, PingResponse{Ok: true}) | ||
}) | ||
|
||
e.POST("/invoke", routes.InvokeFunctionHandler) | ||
e.Logger.Fatal(e.Start(":8000")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package routes | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"time" | ||
|
||
"github.com/labstack/echo/v4" | ||
"github.com/runvelocity/windhoek/utils" | ||
) | ||
|
||
const ( | ||
MAXRETRIES = 10 | ||
BACKOFFTIME = 500 | ||
) | ||
|
||
func InvokeFunctionHandler(c echo.Context) error { | ||
var vmRequest utils.FirecrackerVmRequest | ||
if err := c.Bind(&vmRequest); err != nil { | ||
return echo.NewHTTPError(http.StatusBadRequest, err.Error()) | ||
} | ||
vmRequest.SocketPath = fmt.Sprintf("%s/firecracker-%s.sock", utils.FC_SOCKETS_PATH, vmRequest.FunctionId) | ||
m, ctx, err := utils.CreateVm(vmRequest) | ||
defer func() { | ||
err := m.Shutdown(ctx) | ||
if err != nil { | ||
log.Println("An error occured while shutting down vm", err) | ||
} | ||
err = os.Remove(vmRequest.SocketPath) | ||
if err != nil { | ||
log.Println("Error deleting socket file", vmRequest.SocketPath) | ||
} | ||
}() | ||
if err != nil { | ||
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("error occured while creating vm. %s", err.Error())) | ||
} | ||
fmt.Println(m.Cfg.NetworkInterfaces[0].StaticConfiguration.IPConfiguration.IPAddr.IP.String()) | ||
url := fmt.Sprintf("http://%s:3000/invoke", m.Cfg.NetworkInterfaces[0].StaticConfiguration.IPConfiguration.IPAddr.IP.String()) | ||
argsJSON, err := json.Marshal(vmRequest.InvokePayload.Args) | ||
if err != nil { | ||
fmt.Println("Error marshaling args to JSON:", err) | ||
errObj := utils.ErrorResponse{ | ||
Message: err.Error(), | ||
} | ||
return c.JSON(http.StatusInternalServerError, errObj) | ||
} | ||
payload := fmt.Sprintf(`{"args": %s,"handler": "%s","codeLocation": "%s"}`, argsJSON, vmRequest.InvokePayload.Handler, vmRequest.CodeLocation) | ||
|
||
for i := 0; i < MAXRETRIES; i++ { | ||
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(payload))) | ||
if err != nil { | ||
fmt.Println("Error creating request:", err) | ||
errObj := utils.ErrorResponse{ | ||
Message: err.Error(), | ||
} | ||
return c.JSON(http.StatusInternalServerError, errObj) | ||
} | ||
|
||
// Set headers if needed | ||
req.Header.Set("Content-Type", "application/json") | ||
|
||
req.Close = true | ||
|
||
var client = &http.Client{} | ||
res, err := client.Do(req) | ||
|
||
if err != nil { | ||
time.Sleep(BACKOFFTIME * time.Millisecond) | ||
} else { | ||
if res.StatusCode != 200 { | ||
var resObj map[string]interface{} | ||
err = json.NewDecoder(res.Body).Decode(&resObj) | ||
if err != nil { | ||
fmt.Println("Error decoding JSON:", err) | ||
errObj := utils.ErrorResponse{ | ||
Message: err.Error(), | ||
} | ||
return c.JSON(http.StatusInternalServerError, errObj) | ||
} | ||
if resObj["error"] != nil { | ||
errObj := utils.ErrorResponse{ | ||
Message: resObj["error"].(string), | ||
} | ||
return c.JSON(res.StatusCode, errObj) | ||
} | ||
return c.JSON(res.StatusCode, resObj) | ||
} | ||
var resObj utils.FunctionInvokeResponse | ||
err = json.NewDecoder(res.Body).Decode(&resObj) | ||
if err != nil { | ||
fmt.Println("Error decoding JSON:", err) | ||
errObj := utils.ErrorResponse{ | ||
Message: err.Error(), | ||
} | ||
return c.JSON(http.StatusInternalServerError, errObj) | ||
} | ||
return c.JSON(http.StatusOK, resObj) | ||
} | ||
} | ||
|
||
if err != nil { | ||
fmt.Println("Error invoking function:", err) | ||
errObj := utils.ErrorResponse{ | ||
Message: err.Error(), | ||
} | ||
return c.JSON(http.StatusInternalServerError, errObj) | ||
} | ||
|
||
return nil | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package utils | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/firecracker-microvm/firecracker-go-sdk" | ||
"github.com/firecracker-microvm/firecracker-go-sdk/client/models" | ||
) | ||
|
||
var ( | ||
ROOTFS_PATH = "/root/fcrootfs" | ||
KERNEL_IMAGE_PATH = "/root/fckernels/vmlinux" | ||
FC_SOCKETS_PATH = "/root/fcsockets" | ||
FC_NETWORK_NAME = "fcnet" | ||
FC_IF_NAME = "veth0" | ||
KERNEL_ARGS = "console=ttyS0 reboot=k panic=1 pci=off" | ||
NETWORK_MASK = "/24" | ||
SUBNET = "192.168.127.0" + NETWORK_MASK | ||
DEFAULT_CPU_COUNT int64 = 1 | ||
DEFAULT_MEMORY_COUNT int64 = 512 | ||
AWS_REGION = "us-east-1" | ||
RUNTIMES map[string]string = map[string]string{ | ||
"nodejs": "/root/fcruntimes/nodejs-runtime.ext4", | ||
} | ||
) | ||
|
||
func WriteCNIConfWithHostLocalSubnet(path string) error { | ||
return os.WriteFile(path, []byte(fmt.Sprintf( | ||
`{ | ||
"cniVersion": "1.0.0", | ||
"name": "%s", | ||
"plugins": [ | ||
{ | ||
"type": "ptp", | ||
"ipMasq": true, | ||
"ipam": { | ||
"type": "host-local", | ||
"subnet": "%s" | ||
} | ||
}, | ||
{ | ||
"type": "firewall" | ||
}, | ||
{ | ||
"type": "tc-redirect-tap" | ||
} | ||
] | ||
}`, FC_NETWORK_NAME, SUBNET)), 0644) | ||
} | ||
|
||
func GetVmConfig(vmRequest FirecrackerVmRequest) firecracker.Config { | ||
drives := firecracker.NewDrivesBuilder(RUNTIMES[vmRequest.Runtime]). | ||
// drives := firecracker.NewDrivesBuilder("/root/fcrootfs/rootfs.ext4"). | ||
Build() | ||
networkInterface := firecracker.NetworkInterface{ | ||
CNIConfiguration: &firecracker.CNIConfiguration{ | ||
NetworkName: FC_NETWORK_NAME, | ||
IfName: FC_IF_NAME, | ||
}, | ||
} | ||
cfg := firecracker.Config{ | ||
SocketPath: vmRequest.SocketPath, | ||
KernelImagePath: KERNEL_IMAGE_PATH, | ||
KernelArgs: KERNEL_ARGS, | ||
Drives: drives, | ||
MachineCfg: models.MachineConfiguration{ | ||
VcpuCount: firecracker.Int64(DEFAULT_CPU_COUNT), | ||
MemSizeMib: firecracker.Int64(DEFAULT_MEMORY_COUNT), | ||
}, | ||
NetworkInterfaces: []firecracker.NetworkInterface{ | ||
networkInterface, | ||
}, | ||
} | ||
|
||
return cfg | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package utils | ||
|
||
import ( | ||
"context" | ||
"os" | ||
|
||
"github.com/firecracker-microvm/firecracker-go-sdk" | ||
) | ||
|
||
func CreateVm(vmRequest FirecrackerVmRequest) (*firecracker.Machine, context.Context, error) { | ||
ctx := context.Background() | ||
|
||
cfg := GetVmConfig(vmRequest) | ||
|
||
// Check if kernel image is readable | ||
f, err := os.Open(cfg.KernelImagePath) | ||
if err != nil { | ||
return nil, ctx, err | ||
} | ||
defer f.Close() | ||
|
||
cmd := firecracker.VMCommandBuilder{}.WithSocketPath(cfg.SocketPath).WithBin("/usr/local/bin/firecracker").Build(ctx) | ||
|
||
m, err := firecracker.NewMachine(ctx, cfg, firecracker.WithProcessRunner(cmd)) | ||
if err != nil { | ||
return nil, ctx, err | ||
} | ||
|
||
if err := m.Start(ctx); err != nil { | ||
return nil, ctx, err | ||
} | ||
|
||
return m, ctx, nil | ||
} |
Oops, something went wrong.