-
Notifications
You must be signed in to change notification settings - Fork 16
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
1 parent
7b9168f
commit 5c168df
Showing
10 changed files
with
341 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 |
---|---|---|
@@ -1,2 +1,17 @@ | ||
# tinyFaaS | ||
A Lightweight FaaS Platform for Edge Environments | ||
|
||
## Build | ||
|
||
To start this tinyFaaS implementation, simply build and start the management service in a Docker container. It will then create the gateway in a separate container. | ||
|
||
To build the management service container, run: | ||
`docker build -t tinyfaas-mgmt .` | ||
|
||
Then start the container with: | ||
`docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 --name tinyfaas-mgmt -d tinyfaas-mgmt tinyfaas-mgmt` | ||
|
||
This ensures that the management service has access to Docker on the host and it will then expose port 8080 to accept incoming request. | ||
|
||
To deploy a function (e.g. the "Sieve of Erasthostenes"), run: | ||
`curl http://localhost:8080 --data '{"path": "sieve-of-erasthostenes", "resource": "/sieve/primes", "entry": "sieve.js", "threads": 4}' -v` |
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,12 @@ | ||
FROM python:3 | ||
|
||
WORKDIR /usr/src/app | ||
|
||
RUN pip install --no-cache-dir tornado | ||
RUN pip install --no-cache-dir docker | ||
|
||
COPY . . | ||
EXPOSE 8080 | ||
|
||
ENTRYPOINT [ "python", "./management-service.py" ] | ||
CMD [] |
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,12 @@ | ||
FROM golang | ||
|
||
EXPOSE 80/tcp | ||
EXPOSE 5683/udp | ||
|
||
WORKDIR /go/src/app | ||
COPY . . | ||
|
||
RUN go get -d -v ./... | ||
RUN go install -v ./... | ||
|
||
CMD ["app"] |
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,110 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"math/rand" | ||
"net" | ||
"net/http" | ||
"bytes" | ||
|
||
"github.com/dustin/go-coap" | ||
) | ||
|
||
var functions map[string][]string | ||
|
||
type function_info struct { | ||
Function_resource string `json:"function_resource"` | ||
Function_containers []string `json:"function_containers"` | ||
} | ||
|
||
func handleFunctionCall(l *net.UDPConn, a *net.UDPAddr, m *coap.Message) *coap.Message { | ||
|
||
if m.IsConfirmable() { | ||
|
||
handler, ok := functions[m.PathString()] | ||
|
||
if ok { | ||
// call function and return results | ||
resp, err := http.Get("http://" + handler[rand.Intn(len(handler))] + ":8000") | ||
|
||
if err != nil { | ||
return &coap.Message{ | ||
Type: coap.Acknowledgement, | ||
Code: coap.InternalServerError, | ||
} | ||
} | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
|
||
if err != nil { | ||
return &coap.Message{ | ||
Type: coap.Acknowledgement, | ||
Code: coap.InternalServerError, | ||
} | ||
} | ||
|
||
res := &coap.Message{ | ||
Type: coap.Acknowledgement, | ||
Code: coap.Content, | ||
MessageID: m.MessageID, | ||
Token: m.Token, | ||
Payload: []byte(body), | ||
} | ||
|
||
res.SetOption(coap.ContentFormat, coap.TextPlain) | ||
|
||
return res | ||
} else { | ||
return &coap.Message{ | ||
Type: coap.Acknowledgement, | ||
Code: coap.NotFound, | ||
} | ||
} | ||
|
||
} | ||
|
||
return nil | ||
} | ||
|
||
func main() { | ||
functions = make(map[string][]string) | ||
|
||
mux := coap.NewServeMux() | ||
mux.Handle("/functions", coap.FuncHandler(handleFunctionCall)) | ||
|
||
go func() { | ||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
if r.Method == "POST" { | ||
buf := new(bytes.Buffer) | ||
buf.ReadFrom(r.Body) | ||
newStr := buf.String() | ||
|
||
var f function_info | ||
err := json.Unmarshal([]byte(newStr), &f) | ||
|
||
if err != nil { | ||
return | ||
} | ||
|
||
if f.Function_resource[0] == '/' { | ||
f.Function_resource = f.Function_resource[1:] | ||
} | ||
|
||
functions[f.Function_resource] = f.Function_containers | ||
|
||
mux.Handle(f.Function_resource, coap.FuncHandler(handleFunctionCall)) | ||
|
||
return | ||
|
||
} | ||
}) | ||
|
||
http.ListenAndServe(":80", nil) | ||
}() | ||
|
||
func() { | ||
coap.ListenAndServe("udp", ":5683", mux) | ||
}() | ||
|
||
} |
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,7 @@ | ||
{ | ||
"name": "sieve-of-erasthostenes", | ||
"version": "1.0.0", | ||
"description": "Computer Prime Numbers between 1 and 1000", | ||
"main": "sieve.js", | ||
"author": "Tobias Pfandzelter" | ||
} |
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,19 @@ | ||
module.exports = { | ||
eventhandler: function () { | ||
const max = 1000; | ||
let sieve = [], i, j, primes = []; | ||
for (i = 2; i <= max; ++i) { | ||
if (!sieve[i]) { | ||
primes.push(i); | ||
for (j = i << 1; j <= max; j += i) { | ||
sieve[j] = true; | ||
} | ||
} | ||
} | ||
|
||
return JSON.stringify({ | ||
response_code: "2.05", | ||
payload: primes.toString() | ||
}); | ||
} | ||
} |
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,132 @@ | ||
import asyncio | ||
import tornado.ioloop | ||
import tornado.web | ||
|
||
import docker | ||
import json | ||
import uuid | ||
import shutil | ||
import urllib | ||
import sys | ||
|
||
CONFIG_PORT = 8080 | ||
endpoint_container = {} | ||
function_handlers = {} | ||
|
||
def create_endpoint(meta_container): | ||
client = docker.from_env() | ||
endpoint_image = client.images.build(path='./endpoint/', rm=True)[0] | ||
|
||
endpoint_network = client.networks.create('endpoint-net', driver='bridge') | ||
|
||
endpoint_container['container'] = client.containers.run(endpoint_image, network=endpoint_network.name, ports={'5683/udp': 5683}, detach=True) | ||
# getting IP address of the handler container by inspecting the network and converting CIDR to IPv4 address notation (very dirtily, removing the last 3 chars -> i.e. '/20', so let's hope we don't have a /8 subnet mask) | ||
endpoint_container['ipaddr'] = docker.APIClient().inspect_network(endpoint_network.id)['Containers'][endpoint_container['container'].id]['IPv4Address'][:-3] | ||
|
||
endpoint_network.connect(meta_container) | ||
|
||
class FunctionHandler(): | ||
def __init__(self, function_name, function_resource, function_path, function_entry, function_threads): | ||
self.client = docker.from_env() | ||
self.function_resource = function_resource | ||
self.name = function_name | ||
|
||
# copy all files in ./templates/functionhandler to handler-runtime/[function_path] | ||
shutil.copytree('./templates/functionhandler', './handler-runtime/' + self.name) | ||
|
||
# copy the folder ./handlers/[function_path] to handler-runtime/[function_path] | ||
shutil.copytree('./handlers/' + function_path, './handler-runtime/' + self.name + '/' + function_path) | ||
|
||
# use the Dockerfile.template to create a custom Dockerfile with function_path | ||
with open('./templates/Dockerfile.template', 'rt') as fin: | ||
with open('./handler-runtime/' + self.name + '/Dockerfile', 'wt') as fout: | ||
for line in fin: | ||
fout.write(line.replace('%%%HANDLERPATH%%%', function_path)) | ||
|
||
# use the functionhandler.js.template to create a custom functionhandler.js with function_path as a module name | ||
with open('./templates/functionhandler.js.template', 'rt') as fin: | ||
with open('./handler-runtime/' + self.name + '/functionhandler.js', 'wt') as fout: | ||
for line in fin: | ||
fout.write(line.replace('%%%PACKAGENAME%%%', function_path)) | ||
|
||
self.this_image = self.client.images.build(path='./handler-runtime/' + self.name, rm=True)[0] | ||
|
||
self.thread_count = function_threads | ||
|
||
# connect handler container(s) to endpoint on a dedicated subnet | ||
self.this_network = self.client.networks.create(self.name + '-net', driver='bridge') | ||
|
||
self.this_network.connect(endpoint_container['container'].name) | ||
|
||
self.this_handler_ips = list([None]*self.thread_count) | ||
|
||
# create handler container(s) | ||
self.this_containers = list([None]*self.thread_count) | ||
|
||
for i in range(0, self.thread_count): | ||
self.this_containers[i] = self.client.containers.run(self.this_image, network=self.this_network.name, detach=True) | ||
# getting IP address of the handler container by inspecting the network and converting CIDR to IPv4 address notation (very dirtily, removing the last 3 chars -> i.e. '/20', so let's hope we don't have a /8 subnet mask) | ||
self.this_handler_ips[i] = docker.APIClient().inspect_network(self.this_network.id)['Containers'][self.this_containers[i].id]['IPv4Address'][:-3] | ||
|
||
# tell endpoint about new function | ||
function_handler = { | ||
"function_resource": self.function_resource, | ||
"function_containers": self.this_handler_ips | ||
} | ||
|
||
data = json.dumps(function_handler).encode('ascii') | ||
|
||
urllib.request.urlopen(url='http://' + endpoint_container['ipaddr'] + ':80', data=data) | ||
|
||
class EndpointHandler(tornado.web.RequestHandler): | ||
async def post(self): | ||
try: | ||
# expected post body | ||
# { | ||
# path: 'handler-path', | ||
# resource: 'han/dler', | ||
# entry: 'handler.js', | ||
# threads: 2 | ||
# } | ||
# | ||
|
||
function_data = tornado.escape.json_decode(self.request.body) | ||
|
||
function_path = function_data['path'] | ||
function_resource = function_data['resource'] | ||
function_entry = function_data['entry'] | ||
function_threads = function_data['threads'] | ||
|
||
function_name = str(uuid.uuid4()) + '-' + function_path + '-handler' | ||
|
||
function_handlers[function_name] = FunctionHandler(function_name, function_resource, function_path, function_entry, function_threads) | ||
|
||
|
||
except Exception as e: | ||
raise | ||
|
||
def main(args): | ||
# read config data | ||
# exactly one argument should be provided: meta_container | ||
if len(args) != 2: | ||
raise ValueError('Too many or too little arguments provided:\n' + json.dumps(args)) | ||
|
||
meta_container = args[1] | ||
|
||
try: | ||
docker.from_env().containers.get(meta_container) | ||
except: | ||
raise ValueError('Provided container name does not match a running container') | ||
|
||
# create endpoint | ||
endpoint_container = create_endpoint(meta_container) | ||
|
||
# accept incoming configuration requests and create handlers based on that | ||
app = tornado.web.Application([ | ||
(r'/', EndpointHandler), | ||
]) | ||
app.listen(CONFIG_PORT) | ||
tornado.ioloop.IOLoop.current().start() | ||
|
||
if __name__ == '__main__': | ||
main(sys.argv) |
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,16 @@ | ||
#https://nodejs.org/en/docs/guides/nodejs-docker-webapp/ | ||
FROM node:8 | ||
|
||
EXPOSE 8000 | ||
|
||
# Create app directory | ||
WORKDIR /usr/src/app | ||
|
||
COPY . . | ||
RUN npm install ./%%%HANDLERPATH%%% | ||
|
||
# Install app dependencies | ||
# A wildcard is used to ensure both package.json AND package-lock.json are copied | ||
# where available (npm@5+) | ||
|
||
CMD [ "node", "functionhandler.js" ] |
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,13 @@ | ||
'use strict'; | ||
|
||
const handler = require('%%%PACKAGENAME%%%'); | ||
const http = require('http'); | ||
|
||
const server = http.createServer((req, res) => { | ||
if(req.url == '/run') { | ||
res.end(handler.eventhandler()); | ||
} else { | ||
res.end() | ||
} | ||
}); | ||
server.listen(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,5 @@ | ||
{ | ||
"name": "functionhandler", | ||
"version": "1.0.0", | ||
"main": "functionhandler.js" | ||
} |