From ad961d4f4d6fc7f4477f277acd535e6f0c384978 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Fri, 26 Aug 2016 13:50:51 -0700 Subject: [PATCH 01/14] Wrapper around mux to allow runtime route changes We want to be able to update routes without restarting the http server. mutableRouter is a very thin wrapper around github.com/gorilla/mux that enables safely switching to a new mux router without pausing requests. --- src/router/mutablemux.go | 54 +++++++++++++++++ src/router/mutablemux_test.go | 107 ++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/router/mutablemux.go create mode 100644 src/router/mutablemux_test.go diff --git a/src/router/mutablemux.go b/src/router/mutablemux.go new file mode 100644 index 00000000..a1053e3a --- /dev/null +++ b/src/router/mutablemux.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "net/http" + "sync/atomic" + "github.com/gorilla/mux" + "log" +) + +// +// mutableRouter wraps the mux router, and allows the router to be +// atomically changed. +// + +type mutableRouter struct { + router atomic.Value // mux.Router +} + +func NewMutableRouter(handler *mux.Router) (*mutableRouter) { + mr := mutableRouter{}; + mr.router.Store(handler) + return &mr +} + +func (mr *mutableRouter) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { + // Atomically grab the underlying mux router and call it. + routerValue := mr.router.Load() + router, ok := routerValue.(*mux.Router) + if (!ok) { + log.Panic("Invalid router type") + } + router.ServeHTTP(responseWriter, request) +} + +func (mr *mutableRouter) UpdateRouter(newHandler *mux.Router) { + log.Print("Updating router") + mr.router.Store(newHandler) +} diff --git a/src/router/mutablemux_test.go b/src/router/mutablemux_test.go new file mode 100644 index 00000000..de8d3820 --- /dev/null +++ b/src/router/mutablemux_test.go @@ -0,0 +1,107 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "testing" + "net/http" + "github.com/gorilla/mux" + "log" + "io/ioutil" + "time" +) + +func OldHandler(responseWriter http.ResponseWriter, request *http.Request) { + responseWriter.Write([]byte("old handler")) +} +func NewHandler(responseWriter http.ResponseWriter, request *http.Request) { + responseWriter.Write([]byte("new handler")) +} + +func verifyRequest(expectedResponse string) { + resp, err := http.Get("http://localhost:3333") + if (err != nil) { + log.Panic("failed make get request") + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if (err != nil) { + log.Panic("failed to read response") + } + + bodyStr := string(body) + log.Printf("Server responded with %v", bodyStr) + if (bodyStr != expectedResponse) { + log.Panic("Unexpected response") + } +} + +func startServer(mr *mutableRouter) { + http.ListenAndServe(":3333", mr) +} + +func spamServer() { + i := 0 + for { + i = i + 1 + resp, err := http.Get("http://localhost:3333") + if (err != nil) { + log.Panicf("failed make get request %v", i) + } + resp.Body.Close() + log.Printf("request count = %v", i) + } +} + +func TestMutableMux(t *testing.T) { + // make a simple mutable router + log.Print("Create mutable router") + muxRouter := mux.NewRouter() + muxRouter.HandleFunc("/", OldHandler) + mr := NewMutableRouter(muxRouter) + + // start http server + log.Print("Start http server") + go startServer(mr) + + // continuously make requests, panic if any fails + go spamServer() + go spamServer() + go spamServer() + + time.Sleep(5 * time.Millisecond) + + // connect and verify old handler + log.Print("Verify old handler") + verifyRequest("old handler") + + // change the muxer + log.Print("Change mux router") + newMuxRouter := mux.NewRouter() + newMuxRouter.HandleFunc("/", NewHandler) + mr.UpdateRouter(newMuxRouter) + + // connect and verify the new handler + log.Print("Verify new handler") + verifyRequest("new handler") + + time.Sleep(5 * time.Millisecond) + + // all done + log.Print("ok") +} From 828fa5f88c83961d8364d2f3fefed080077ab491 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Mon, 29 Aug 2016 15:30:04 -0700 Subject: [PATCH 02/14] Fix test shutdown Mutable mux test occasionally panics because the server is shut down before the client goroutine. Fix this by shutting down the client goroutines before shutting down the server. --- src/router/mutablemux_test.go | 36 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/router/mutablemux_test.go b/src/router/mutablemux_test.go index de8d3820..1b1417ba 100644 --- a/src/router/mutablemux_test.go +++ b/src/router/mutablemux_test.go @@ -55,16 +55,21 @@ func startServer(mr *mutableRouter) { http.ListenAndServe(":3333", mr) } -func spamServer() { + +func spamServer(quit chan bool) { i := 0 for { - i = i + 1 - resp, err := http.Get("http://localhost:3333") - if (err != nil) { - log.Panicf("failed make get request %v", i) + select { + case <- quit: + break + default: + i = i + 1 + resp, err := http.Get("http://localhost:3333") + if (err != nil) { + log.Panicf("failed make get request %v: %v", i, err) + } + resp.Body.Close() } - resp.Body.Close() - log.Printf("request count = %v", i) } } @@ -80,9 +85,13 @@ func TestMutableMux(t *testing.T) { go startServer(mr) // continuously make requests, panic if any fails - go spamServer() - go spamServer() - go spamServer() + time.Sleep(100 * time.Millisecond) + q1 := make(chan bool) + go spamServer(q1) + q2 := make(chan bool) + go spamServer(q2) + q3 := make(chan bool) + go spamServer(q3) time.Sleep(5 * time.Millisecond) @@ -101,7 +110,8 @@ func TestMutableMux(t *testing.T) { verifyRequest("new handler") time.Sleep(5 * time.Millisecond) - - // all done - log.Print("ok") + q1 <- true + q2 <- true + q3 <- true + time.Sleep(100 * time.Millisecond) } From d990a0b9af0d4e06a15da38fecd9aea12c75c926 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Mon, 29 Aug 2016 15:31:53 -0700 Subject: [PATCH 03/14] Function (name, uid) -> service URL map Concurrency-safe mapping from a function's (name, uid) identifier to a URL. This will be used to look up the service URL associated with a given function. --- src/router/functionServiceMap.go | 113 ++++++++++++++++++++++++++ src/router/functionServiceMap_test.go | 43 ++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/router/functionServiceMap.go create mode 100644 src/router/functionServiceMap_test.go diff --git a/src/router/functionServiceMap.go b/src/router/functionServiceMap.go new file mode 100644 index 00000000..98997ab9 --- /dev/null +++ b/src/router/functionServiceMap.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "errors" + "log" +) + +type requestType int +const ( + LOOKUP requestType = iota // lookup the map + ASSIGN // assign function + NEXT_GEN // increment current generation + SWEEP // delete all but the current generation +) + +type functionServiceMapResponse struct { + serviceUrl string + error +} +type functionServiceMapRequest struct { + function + serviceUrl string + requestType + responseChannel chan<- functionServiceMapResponse +} +type functionServiceMapEntry struct { + serviceUrl string + generation uint64 +} + +type functionServiceMap struct { + // map (funcname, uid) -> url + svc map[function]functionServiceMapEntry + currentGeneration uint64 + requestChannel chan *functionServiceMapRequest +} + +func makeFunctionServiceMap() (*functionServiceMap) { + fmap := &functionServiceMap{} + fmap.requestChannel = make(chan *functionServiceMapRequest) + fmap.svc = make(map[function]functionServiceMapEntry) + go fmap.functionServiceMapWork() + return fmap +} + +func (fmap *functionServiceMap) functionServiceMapWork() { + for { + req := <- fmap.requestChannel + switch req.requestType { + case LOOKUP: + e, present := fmap.svc[req.function] + if present { + req.responseChannel <- functionServiceMapResponse{ serviceUrl: e.serviceUrl } + } else { + req.responseChannel <- functionServiceMapResponse{ error: errors.New("not found") } + } + case ASSIGN: + fmap.svc[req.function] = + functionServiceMapEntry{ serviceUrl: req.serviceUrl, generation: fmap.currentGeneration } + // no response + case NEXT_GEN: + fmap.currentGeneration++ + // no response + case SWEEP: + log.Panic("not implemented") + default: + log.Panic("bad request") + } + } +} + +func (fmap *functionServiceMap) lookup(f *function) (string, error) { + respChannel := make(chan functionServiceMapResponse) + fmap.requestChannel <- + &functionServiceMapRequest{ function: *f, requestType: LOOKUP, responseChannel: respChannel } + resp := <-respChannel + if (resp.error != nil) { + return "", resp.error + } else { + return resp.serviceUrl, nil + } +} + +func (fmap *functionServiceMap) assign(f *function, serviceUrl string) { + fmap.requestChannel <- + &functionServiceMapRequest{ function: *f, serviceUrl: serviceUrl, requestType: ASSIGN } +} + +func (fmap *functionServiceMap) nextGen() { + fmap.requestChannel <- + &functionServiceMapRequest{ requestType: NEXT_GEN } +} + +func (fmap *functionServiceMap) sweep() { + fmap.requestChannel <- + &functionServiceMapRequest{ requestType: SWEEP } +} diff --git a/src/router/functionServiceMap_test.go b/src/router/functionServiceMap_test.go new file mode 100644 index 00000000..b5c9897b --- /dev/null +++ b/src/router/functionServiceMap_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "testing" +) + +func TestFunctionServiceMap(t *testing.T) { + m := makeFunctionServiceMap() + fn := &function{ name: "foo", uid: "012" } + url := "/foo012" + + m.assign(fn, url) + + v, err := m.lookup(fn) + if (err != nil) { + t.Errorf("Lookup error: %s", err) + } + if (v != url) { + t.Errorf("Expected %s, got %s", url, v) + } + + fn.name = "bar" + _, err2 := m.lookup(fn) + if (err2 == nil) { + t.Errorf("No error on missing entry") + } +} From b9b7f9bbfc96333716b4d931b6cb0a1b85add0c8 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Tue, 30 Aug 2016 16:39:39 -0700 Subject: [PATCH 04/14] Function service map: cache parsed URLs instead of strings --- src/router/functionServiceMap.go | 17 +++++++++-------- src/router/functionServiceMap_test.go | 14 +++++++++----- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/router/functionServiceMap.go b/src/router/functionServiceMap.go index 98997ab9..0f0db7d9 100644 --- a/src/router/functionServiceMap.go +++ b/src/router/functionServiceMap.go @@ -19,6 +19,7 @@ package router import ( "errors" "log" + "net/url" ) type requestType int @@ -30,17 +31,17 @@ const ( ) type functionServiceMapResponse struct { - serviceUrl string + serviceUrl url.URL error } type functionServiceMapRequest struct { function - serviceUrl string + serviceUrl url.URL requestType responseChannel chan<- functionServiceMapResponse } type functionServiceMapEntry struct { - serviceUrl string + serviceUrl url.URL generation uint64 } @@ -85,21 +86,21 @@ func (fmap *functionServiceMap) functionServiceMapWork() { } } -func (fmap *functionServiceMap) lookup(f *function) (string, error) { +func (fmap *functionServiceMap) lookup(f *function) (*url.URL, error) { respChannel := make(chan functionServiceMapResponse) fmap.requestChannel <- &functionServiceMapRequest{ function: *f, requestType: LOOKUP, responseChannel: respChannel } resp := <-respChannel if (resp.error != nil) { - return "", resp.error + return nil, resp.error } else { - return resp.serviceUrl, nil + return &resp.serviceUrl, nil } } -func (fmap *functionServiceMap) assign(f *function, serviceUrl string) { +func (fmap *functionServiceMap) assign(f *function, serviceUrl *url.URL) { fmap.requestChannel <- - &functionServiceMapRequest{ function: *f, serviceUrl: serviceUrl, requestType: ASSIGN } + &functionServiceMapRequest{ function: *f, serviceUrl: *serviceUrl, requestType: ASSIGN } } func (fmap *functionServiceMap) nextGen() { diff --git a/src/router/functionServiceMap_test.go b/src/router/functionServiceMap_test.go index b5c9897b..6aa420c7 100644 --- a/src/router/functionServiceMap_test.go +++ b/src/router/functionServiceMap_test.go @@ -18,21 +18,25 @@ package router import ( "testing" + "net/url" ) func TestFunctionServiceMap(t *testing.T) { m := makeFunctionServiceMap() fn := &function{ name: "foo", uid: "012" } - url := "/foo012" + u, err := url.Parse("/foo012") + if (err != nil) { + t.Errorf("can't parse url") + } - m.assign(fn, url) + m.assign(fn, u) v, err := m.lookup(fn) if (err != nil) { - t.Errorf("Lookup error: %s", err) + t.Errorf("Lookup error: %v", err) } - if (v != url) { - t.Errorf("Expected %s, got %s", url, v) + if (*v != *u) { + t.Errorf("Expected %#v, got %#v", u, v) } fn.name = "bar" From a33f4e48e97aefc877ff0dd4bfd10afe823f80c4 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Tue, 30 Aug 2016 16:40:20 -0700 Subject: [PATCH 05/14] Simplify test a bit --- src/router/mutablemux_test.go | 34 ++++++---------------------------- src/router/testUtil.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 src/router/testUtil.go diff --git a/src/router/mutablemux_test.go b/src/router/mutablemux_test.go index 1b1417ba..a20e5b8f 100644 --- a/src/router/mutablemux_test.go +++ b/src/router/mutablemux_test.go @@ -21,7 +21,6 @@ import ( "net/http" "github.com/gorilla/mux" "log" - "io/ioutil" "time" ) @@ -33,22 +32,8 @@ func NewHandler(responseWriter http.ResponseWriter, request *http.Request) { } func verifyRequest(expectedResponse string) { - resp, err := http.Get("http://localhost:3333") - if (err != nil) { - log.Panic("failed make get request") - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if (err != nil) { - log.Panic("failed to read response") - } - - bodyStr := string(body) - log.Printf("Server responded with %v", bodyStr) - if (bodyStr != expectedResponse) { - log.Panic("Unexpected response") - } + targetUrl := "http://localhost:3333" + testRequest(targetUrl, expectedResponse) } func startServer(mr *mutableRouter) { @@ -86,12 +71,8 @@ func TestMutableMux(t *testing.T) { // continuously make requests, panic if any fails time.Sleep(100 * time.Millisecond) - q1 := make(chan bool) - go spamServer(q1) - q2 := make(chan bool) - go spamServer(q2) - q3 := make(chan bool) - go spamServer(q3) + q := make(chan bool) + go spamServer(q) time.Sleep(5 * time.Millisecond) @@ -108,10 +89,7 @@ func TestMutableMux(t *testing.T) { // connect and verify the new handler log.Print("Verify new handler") verifyRequest("new handler") - - time.Sleep(5 * time.Millisecond) - q1 <- true - q2 <- true - q3 <- true + + q <- true time.Sleep(100 * time.Millisecond) } diff --git a/src/router/testUtil.go b/src/router/testUtil.go new file mode 100644 index 00000000..cd88dc0d --- /dev/null +++ b/src/router/testUtil.go @@ -0,0 +1,27 @@ +package router + +import ( + "net/http" + "log" + "io/ioutil" +) + +func testRequest(targetUrl string, expectedResponse string) { + resp, err := http.Get(targetUrl) + if (err != nil) { + log.Panic("failed make get request") + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if (err != nil) { + log.Panic("failed to read response") + } + + bodyStr := string(body) + log.Printf("Server responded with %v", bodyStr) + if (bodyStr != expectedResponse) { + log.Panic("Unexpected response") + } +} + From 2b0b2e28ae4ba1f4c15aa1613f2c750176b09b89 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Tue, 30 Aug 2016 16:41:16 -0700 Subject: [PATCH 06/14] HTTP Handler for functions The function handler will be the handler for each http trigger. It will look up the function service map to find a service for the function (and call the pool manager to create a new service if one doesn't already exist, though this isn't implemented yet). It'll then proxy the request to the service. --- src/router/functionHandler.go | 70 ++++++++++++++++++++++++++++++ src/router/functionHandler_test.go | 61 ++++++++++++++++++++++++++ src/router/router.go | 70 ++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 src/router/functionHandler.go create mode 100644 src/router/functionHandler_test.go create mode 100644 src/router/router.go diff --git a/src/router/functionHandler.go b/src/router/functionHandler.go new file mode 100644 index 00000000..791c4c51 --- /dev/null +++ b/src/router/functionHandler.go @@ -0,0 +1,70 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "log" + "net/http" + "net/http/httputil" + "errors" + "net/url" +) + +type functionHandler struct { + fmap *functionServiceMap + function +} + +func getService(f *function) (*url.URL, error) { + return nil, errors.New("not implemented") +} + +func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *http.Request) { + serviceUrl, err := fh.fmap.lookup(&fh.function) + if (err != nil) { + // Cache miss: request the Pool Manager to make a new service. + serviceUrl, poolErr := getService(&fh.function) + if (poolErr != nil) { + // now we're really screwed + log.Printf("Failed to get service for function (%v,%v): %v", + fh.function.name, fh.function.uid, poolErr); + responseWriter.WriteHeader(500) // TODO: make this smarter based on the actual error + return + } + + // add it to the map + fh.fmap.assign(&fh.function, serviceUrl) + } + + // Proxy off our request to the serviceUrl, and send the response back. + // TODO: As an optimization we may want to cache proxies too -- this would get us + // connection reuse and possibly better performance + director := func(req *http.Request) { + // send this request to serviceurl + req.URL.Scheme = serviceUrl.Scheme + req.URL.Host = serviceUrl.Host + req.URL.Path = serviceUrl.Path + // leave the query string intact (req.URL.RawQuery) + + if _, ok := req.Header["User-Agent"]; !ok { + // explicitly disable User-Agent so it's not set to default value + req.Header.Set("User-Agent", "") + } + } + proxy := &httputil.ReverseProxy{Director: director} + proxy.ServeHTTP(responseWriter, request) +} diff --git a/src/router/functionHandler_test.go b/src/router/functionHandler_test.go new file mode 100644 index 00000000..ca36cc87 --- /dev/null +++ b/src/router/functionHandler_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "log" + "testing" + "net/http" +// "net/http/httputil" + "net/http/httptest" + "net/url" +) + + +func createBackendService(testResponseString string) *url.URL { + backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(testResponseString)) + })) + + backendURL, err := url.Parse(backendServer.URL) + if err != nil { + panic("error parsing url") + } + return backendURL +} + +/* + 1. Create a service at some URL + 2. Add it to the function service map + 3. Create a http server with some trigger url pointed at function handler + 4. Send a request to that server, ensure it reaches the first service. +*/ +func TestFunctionProxying(t *testing.T) { + testResponseString := "hi" + backendURL := createBackendService(testResponseString) + log.Printf("Created backend svc at %v", backendURL) + + fn := &function{name: "foo", uid: "xxx"} + fmap := makeFunctionServiceMap() + fmap.assign(fn, backendURL) + + fh := &functionHandler{fmap: fmap, function: *fn} + functionHandlerServer := httptest.NewServer(http.HandlerFunc(fh.handler)) + fhURL := functionHandlerServer.URL + + testRequest(fhURL, testResponseString) +} diff --git a/src/router/router.go b/src/router/router.go new file mode 100644 index 00000000..8ef1509e --- /dev/null +++ b/src/router/router.go @@ -0,0 +1,70 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + +This is the Fission Router package. + +Its job is to: + + 1. Keep track of HTTP triggers and their mappings to functions + + Use the controller API to get and watch this state. + + 2. Given a function, get a reference to a routable function run service + + Use the ContainerPoolManager API to get a service backed by one + or more function run containers. The container(s) backing the + service may be newly created, or they might be reused. The only + requirement is that one or more containers backs the service. + + 3. Forward the request to the service, and send the response back. + + Plain ol HTTP. + +*/ + + +package router + +import ( +// "fmt" +// "net/http" +) + +type ( + function struct { + name string + uid string + } + + httptrigger struct { + urlPattern string + function + } + + options struct { + port int + poolManagerUrl string + //... + } + +) + +// request url ---[trigger]---> function(name,uid) ----[pool mgr]----> k8s service url + +// request url ---[trigger]---> function(name, deployment) ----[deployment]----> function(name, uid) ----[pool mgr]---> k8s service url + From 53eeff3c441d39009807a65210af778968e4c3e5 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Wed, 31 Aug 2016 11:00:42 -0700 Subject: [PATCH 07/14] Build test utils only in 'go test' --- src/router/{testUtil.go => util_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/router/{testUtil.go => util_test.go} (100%) diff --git a/src/router/testUtil.go b/src/router/util_test.go similarity index 100% rename from src/router/testUtil.go rename to src/router/util_test.go From 9bd586ce611d9fefe02a94cee55b6d5c9ee5a9ff Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Thu, 1 Sep 2016 14:34:33 -0700 Subject: [PATCH 08/14] Include PoolManager URL in function handler state and initializer --- src/router/functionHandler.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/router/functionHandler.go b/src/router/functionHandler.go index 791c4c51..325b7ed3 100644 --- a/src/router/functionHandler.go +++ b/src/router/functionHandler.go @@ -26,10 +26,11 @@ import ( type functionHandler struct { fmap *functionServiceMap + poolManagerUrl string function } -func getService(f *function) (*url.URL, error) { +func (*functionHandler) getServiceForFunction() (*url.URL, error) { return nil, errors.New("not implemented") } @@ -37,7 +38,7 @@ func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request * serviceUrl, err := fh.fmap.lookup(&fh.function) if (err != nil) { // Cache miss: request the Pool Manager to make a new service. - serviceUrl, poolErr := getService(&fh.function) + serviceUrl, poolErr := fh.getServiceForFunction() if (poolErr != nil) { // now we're really screwed log.Printf("Failed to get service for function (%v,%v): %v", @@ -67,4 +68,6 @@ func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request * } proxy := &httputil.ReverseProxy{Director: director} proxy.ServeHTTP(responseWriter, request) + + // TODO: handle failures and possibly retry here. } From 322ca405f6f08b72fd36e826b8f650eb7076905a Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Thu, 1 Sep 2016 14:34:56 -0700 Subject: [PATCH 09/14] Verify request success while testing http requests --- src/router/util_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/router/util_test.go b/src/router/util_test.go index cd88dc0d..39eb8861 100644 --- a/src/router/util_test.go +++ b/src/router/util_test.go @@ -9,10 +9,14 @@ import ( func testRequest(targetUrl string, expectedResponse string) { resp, err := http.Get(targetUrl) if (err != nil) { - log.Panic("failed make get request") + log.Panicf("failed to make get request: %v", err) } defer resp.Body.Close() + if (resp.StatusCode != 200) { + log.Panicf("response status: %v", resp.StatusCode) + } + body, err := ioutil.ReadAll(resp.Body) if (err != nil) { log.Panic("failed to read response") From 865a60110f9783ed0f9486b02e9dd9350ecb746a Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Thu, 1 Sep 2016 14:37:33 -0700 Subject: [PATCH 10/14] Rename mutablemux updateRouter method --- src/router/mutablemux.go | 2 +- src/router/mutablemux_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/router/mutablemux.go b/src/router/mutablemux.go index a1053e3a..e66c66c8 100644 --- a/src/router/mutablemux.go +++ b/src/router/mutablemux.go @@ -48,7 +48,7 @@ func (mr *mutableRouter) ServeHTTP(responseWriter http.ResponseWriter, request * router.ServeHTTP(responseWriter, request) } -func (mr *mutableRouter) UpdateRouter(newHandler *mux.Router) { +func (mr *mutableRouter) updateRouter(newHandler *mux.Router) { log.Print("Updating router") mr.router.Store(newHandler) } diff --git a/src/router/mutablemux_test.go b/src/router/mutablemux_test.go index a20e5b8f..cce8938e 100644 --- a/src/router/mutablemux_test.go +++ b/src/router/mutablemux_test.go @@ -51,7 +51,7 @@ func spamServer(quit chan bool) { i = i + 1 resp, err := http.Get("http://localhost:3333") if (err != nil) { - log.Panicf("failed make get request %v: %v", i, err) + log.Panicf("failed to make get request %v: %v", i, err) } resp.Body.Close() } @@ -84,7 +84,7 @@ func TestMutableMux(t *testing.T) { log.Print("Change mux router") newMuxRouter := mux.NewRouter() newMuxRouter.HandleFunc("/", NewHandler) - mr.UpdateRouter(newMuxRouter) + mr.updateRouter(newMuxRouter) // connect and verify the new handler log.Print("Verify new handler") From 2c872642f3a5e9eedaa1dbca542ca8a3be2bf971 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Thu, 1 Sep 2016 14:38:47 -0700 Subject: [PATCH 11/14] Put it all together --- src/router/router.go | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/router/router.go b/src/router/router.go index 8ef1509e..4e980959 100644 --- a/src/router/router.go +++ b/src/router/router.go @@ -41,8 +41,10 @@ Its job is to: package router import ( -// "fmt" -// "net/http" + "fmt" + "net/http" + "github.com/gorilla/mux" + flag "github.com/ogier/pflag" ) type ( @@ -59,12 +61,44 @@ type ( options struct { port int poolManagerUrl string + controllerUrl string //... } ) -// request url ---[trigger]---> function(name,uid) ----[pool mgr]----> k8s service url +// request url ---[mux]---> function(name,uid) ----[fmap]----> k8s service url // request url ---[trigger]---> function(name, deployment) ----[deployment]----> function(name, uid) ----[pool mgr]---> k8s service url +func router(httpTriggerSet *HTTPTriggerSet) (*mutableRouter) { + muxRouter := mux.NewRouter() + mr := NewMutableRouter(muxRouter) + httpTriggerSet.subscribeRouter(mr) + return mr +} + +func server(port int, httpTriggerSet *HTTPTriggerSet) { + mr := router(httpTriggerSet) + url := fmt.Sprintf(":%v", port) + http.ListenAndServe(url, mr) +} + +func getOptions() (*options) { + options := &options{} + + flag.IntVar(&options.port, "port", 80, "Port to listen on") + + // default to using dns service discovery + flag.StringVar(&options.poolManagerUrl, "poolmanager_url", "http://poolmanager/", "URL for the PoolManager service") + flag.StringVar(&options.controllerUrl, "controller_url", "http://controller/", "URL for the controller service") + + return options +} + +func main() { + options := getOptions() + fmap := makeFunctionServiceMap() + triggers := makeHTTPTriggerSet(fmap, options.controllerUrl, options.poolManagerUrl) + server(options.port, triggers) +} From 8c56818e0af6926d2e241322a56a56587cfa1b9f Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Thu, 1 Sep 2016 16:32:32 -0700 Subject: [PATCH 12/14] apply gofmt --- src/router/functionHandler.go | 12 ++++---- src/router/functionHandler_test.go | 19 ++++++------ src/router/functionServiceMap.go | 43 +++++++++++++-------------- src/router/functionServiceMap_test.go | 12 ++++---- src/router/mutablemux.go | 12 ++++---- src/router/mutablemux_test.go | 13 ++++---- src/router/router.go | 20 ++++++------- src/router/util_test.go | 15 +++++----- 8 files changed, 69 insertions(+), 77 deletions(-) diff --git a/src/router/functionHandler.go b/src/router/functionHandler.go index 325b7ed3..8b93d89d 100644 --- a/src/router/functionHandler.go +++ b/src/router/functionHandler.go @@ -17,15 +17,15 @@ limitations under the License. package router import ( + "errors" "log" "net/http" "net/http/httputil" - "errors" "net/url" ) type functionHandler struct { - fmap *functionServiceMap + fmap *functionServiceMap poolManagerUrl string function } @@ -36,13 +36,13 @@ func (*functionHandler) getServiceForFunction() (*url.URL, error) { func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *http.Request) { serviceUrl, err := fh.fmap.lookup(&fh.function) - if (err != nil) { + if err != nil { // Cache miss: request the Pool Manager to make a new service. serviceUrl, poolErr := fh.getServiceForFunction() - if (poolErr != nil) { + if poolErr != nil { // now we're really screwed log.Printf("Failed to get service for function (%v,%v): %v", - fh.function.name, fh.function.uid, poolErr); + fh.function.name, fh.function.uid, poolErr) responseWriter.WriteHeader(500) // TODO: make this smarter based on the actual error return } @@ -54,7 +54,7 @@ func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request * // Proxy off our request to the serviceUrl, and send the response back. // TODO: As an optimization we may want to cache proxies too -- this would get us // connection reuse and possibly better performance - director := func(req *http.Request) { + director := func(req *http.Request) { // send this request to serviceurl req.URL.Scheme = serviceUrl.Scheme req.URL.Host = serviceUrl.Host diff --git a/src/router/functionHandler_test.go b/src/router/functionHandler_test.go index ca36cc87..72bfa1e7 100644 --- a/src/router/functionHandler_test.go +++ b/src/router/functionHandler_test.go @@ -18,14 +18,13 @@ package router import ( "log" - "testing" "net/http" -// "net/http/httputil" + "testing" + // "net/http/httputil" "net/http/httptest" "net/url" ) - func createBackendService(testResponseString string) *url.URL { backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(testResponseString)) @@ -39,23 +38,23 @@ func createBackendService(testResponseString string) *url.URL { } /* - 1. Create a service at some URL - 2. Add it to the function service map - 3. Create a http server with some trigger url pointed at function handler - 4. Send a request to that server, ensure it reaches the first service. + 1. Create a service at some URL + 2. Add it to the function service map + 3. Create a http server with some trigger url pointed at function handler + 4. Send a request to that server, ensure it reaches the first service. */ func TestFunctionProxying(t *testing.T) { testResponseString := "hi" backendURL := createBackendService(testResponseString) log.Printf("Created backend svc at %v", backendURL) - fn := &function{name: "foo", uid: "xxx"} + fn := &function{name: "foo", uid: "xxx"} fmap := makeFunctionServiceMap() fmap.assign(fn, backendURL) - + fh := &functionHandler{fmap: fmap, function: *fn} functionHandlerServer := httptest.NewServer(http.HandlerFunc(fh.handler)) fhURL := functionHandlerServer.URL - + testRequest(fhURL, testResponseString) } diff --git a/src/router/functionServiceMap.go b/src/router/functionServiceMap.go index 0f0db7d9..d667de67 100644 --- a/src/router/functionServiceMap.go +++ b/src/router/functionServiceMap.go @@ -23,11 +23,12 @@ import ( ) type requestType int + const ( - LOOKUP requestType = iota // lookup the map - ASSIGN // assign function - NEXT_GEN // increment current generation - SWEEP // delete all but the current generation + LOOKUP requestType = iota // lookup the map + ASSIGN // assign function + NEXT_GEN // increment current generation + SWEEP // delete all but the current generation ) type functionServiceMapResponse struct { @@ -35,7 +36,7 @@ type functionServiceMapResponse struct { error } type functionServiceMapRequest struct { - function + function serviceUrl url.URL requestType responseChannel chan<- functionServiceMapResponse @@ -47,12 +48,12 @@ type functionServiceMapEntry struct { type functionServiceMap struct { // map (funcname, uid) -> url - svc map[function]functionServiceMapEntry + svc map[function]functionServiceMapEntry currentGeneration uint64 - requestChannel chan *functionServiceMapRequest + requestChannel chan *functionServiceMapRequest } -func makeFunctionServiceMap() (*functionServiceMap) { +func makeFunctionServiceMap() *functionServiceMap { fmap := &functionServiceMap{} fmap.requestChannel = make(chan *functionServiceMapRequest) fmap.svc = make(map[function]functionServiceMapEntry) @@ -62,25 +63,25 @@ func makeFunctionServiceMap() (*functionServiceMap) { func (fmap *functionServiceMap) functionServiceMapWork() { for { - req := <- fmap.requestChannel + req := <-fmap.requestChannel switch req.requestType { case LOOKUP: e, present := fmap.svc[req.function] if present { - req.responseChannel <- functionServiceMapResponse{ serviceUrl: e.serviceUrl } + req.responseChannel <- functionServiceMapResponse{serviceUrl: e.serviceUrl} } else { - req.responseChannel <- functionServiceMapResponse{ error: errors.New("not found") } + req.responseChannel <- functionServiceMapResponse{error: errors.New("not found")} } case ASSIGN: - fmap.svc[req.function] = - functionServiceMapEntry{ serviceUrl: req.serviceUrl, generation: fmap.currentGeneration } + fmap.svc[req.function] = + functionServiceMapEntry{serviceUrl: req.serviceUrl, generation: fmap.currentGeneration} // no response case NEXT_GEN: fmap.currentGeneration++ // no response case SWEEP: log.Panic("not implemented") - default: + default: log.Panic("bad request") } } @@ -88,10 +89,9 @@ func (fmap *functionServiceMap) functionServiceMapWork() { func (fmap *functionServiceMap) lookup(f *function) (*url.URL, error) { respChannel := make(chan functionServiceMapResponse) - fmap.requestChannel <- - &functionServiceMapRequest{ function: *f, requestType: LOOKUP, responseChannel: respChannel } + fmap.requestChannel <- &functionServiceMapRequest{function: *f, requestType: LOOKUP, responseChannel: respChannel} resp := <-respChannel - if (resp.error != nil) { + if resp.error != nil { return nil, resp.error } else { return &resp.serviceUrl, nil @@ -99,16 +99,13 @@ func (fmap *functionServiceMap) lookup(f *function) (*url.URL, error) { } func (fmap *functionServiceMap) assign(f *function, serviceUrl *url.URL) { - fmap.requestChannel <- - &functionServiceMapRequest{ function: *f, serviceUrl: *serviceUrl, requestType: ASSIGN } + fmap.requestChannel <- &functionServiceMapRequest{function: *f, serviceUrl: *serviceUrl, requestType: ASSIGN} } func (fmap *functionServiceMap) nextGen() { - fmap.requestChannel <- - &functionServiceMapRequest{ requestType: NEXT_GEN } + fmap.requestChannel <- &functionServiceMapRequest{requestType: NEXT_GEN} } func (fmap *functionServiceMap) sweep() { - fmap.requestChannel <- - &functionServiceMapRequest{ requestType: SWEEP } + fmap.requestChannel <- &functionServiceMapRequest{requestType: SWEEP} } diff --git a/src/router/functionServiceMap_test.go b/src/router/functionServiceMap_test.go index 6aa420c7..48b12c52 100644 --- a/src/router/functionServiceMap_test.go +++ b/src/router/functionServiceMap_test.go @@ -17,31 +17,31 @@ limitations under the License. package router import ( - "testing" "net/url" + "testing" ) func TestFunctionServiceMap(t *testing.T) { m := makeFunctionServiceMap() - fn := &function{ name: "foo", uid: "012" } + fn := &function{name: "foo", uid: "012"} u, err := url.Parse("/foo012") - if (err != nil) { + if err != nil { t.Errorf("can't parse url") } m.assign(fn, u) v, err := m.lookup(fn) - if (err != nil) { + if err != nil { t.Errorf("Lookup error: %v", err) } - if (*v != *u) { + if *v != *u { t.Errorf("Expected %#v, got %#v", u, v) } fn.name = "bar" _, err2 := m.lookup(fn) - if (err2 == nil) { + if err2 == nil { t.Errorf("No error on missing entry") } } diff --git a/src/router/mutablemux.go b/src/router/mutablemux.go index e66c66c8..0f1a4b5c 100644 --- a/src/router/mutablemux.go +++ b/src/router/mutablemux.go @@ -17,13 +17,13 @@ limitations under the License. package router import ( - "net/http" - "sync/atomic" "github.com/gorilla/mux" "log" + "net/http" + "sync/atomic" ) -// +// // mutableRouter wraps the mux router, and allows the router to be // atomically changed. // @@ -32,8 +32,8 @@ type mutableRouter struct { router atomic.Value // mux.Router } -func NewMutableRouter(handler *mux.Router) (*mutableRouter) { - mr := mutableRouter{}; +func NewMutableRouter(handler *mux.Router) *mutableRouter { + mr := mutableRouter{} mr.router.Store(handler) return &mr } @@ -42,7 +42,7 @@ func (mr *mutableRouter) ServeHTTP(responseWriter http.ResponseWriter, request * // Atomically grab the underlying mux router and call it. routerValue := mr.router.Load() router, ok := routerValue.(*mux.Router) - if (!ok) { + if !ok { log.Panic("Invalid router type") } router.ServeHTTP(responseWriter, request) diff --git a/src/router/mutablemux_test.go b/src/router/mutablemux_test.go index cce8938e..a8e7df31 100644 --- a/src/router/mutablemux_test.go +++ b/src/router/mutablemux_test.go @@ -17,10 +17,10 @@ limitations under the License. package router import ( - "testing" - "net/http" "github.com/gorilla/mux" "log" + "net/http" + "testing" "time" ) @@ -40,17 +40,16 @@ func startServer(mr *mutableRouter) { http.ListenAndServe(":3333", mr) } - func spamServer(quit chan bool) { i := 0 for { select { - case <- quit: + case <-quit: break default: i = i + 1 resp, err := http.Get("http://localhost:3333") - if (err != nil) { + if err != nil { log.Panicf("failed to make get request %v: %v", i, err) } resp.Body.Close() @@ -64,11 +63,11 @@ func TestMutableMux(t *testing.T) { muxRouter := mux.NewRouter() muxRouter.HandleFunc("/", OldHandler) mr := NewMutableRouter(muxRouter) - + // start http server log.Print("Start http server") go startServer(mr) - + // continuously make requests, panic if any fails time.Sleep(100 * time.Millisecond) q := make(chan bool) diff --git a/src/router/router.go b/src/router/router.go index 4e980959..c1e77e35 100644 --- a/src/router/router.go +++ b/src/router/router.go @@ -23,7 +23,7 @@ Its job is to: 1. Keep track of HTTP triggers and their mappings to functions Use the controller API to get and watch this state. - + 2. Given a function, get a reference to a routable function run service Use the ContainerPoolManager API to get a service backed by one @@ -37,41 +37,39 @@ Its job is to: */ - package router import ( "fmt" - "net/http" "github.com/gorilla/mux" flag "github.com/ogier/pflag" + "net/http" ) type ( function struct { name string - uid string + uid string } - + httptrigger struct { urlPattern string function } options struct { - port int + port int poolManagerUrl string - controllerUrl string + controllerUrl string //... } - ) // request url ---[mux]---> function(name,uid) ----[fmap]----> k8s service url // request url ---[trigger]---> function(name, deployment) ----[deployment]----> function(name, uid) ----[pool mgr]---> k8s service url -func router(httpTriggerSet *HTTPTriggerSet) (*mutableRouter) { +func router(httpTriggerSet *HTTPTriggerSet) *mutableRouter { muxRouter := mux.NewRouter() mr := NewMutableRouter(muxRouter) httpTriggerSet.subscribeRouter(mr) @@ -84,7 +82,7 @@ func server(port int, httpTriggerSet *HTTPTriggerSet) { http.ListenAndServe(url, mr) } -func getOptions() (*options) { +func getOptions() *options { options := &options{} flag.IntVar(&options.port, "port", 80, "Port to listen on") @@ -92,7 +90,7 @@ func getOptions() (*options) { // default to using dns service discovery flag.StringVar(&options.poolManagerUrl, "poolmanager_url", "http://poolmanager/", "URL for the PoolManager service") flag.StringVar(&options.controllerUrl, "controller_url", "http://controller/", "URL for the controller service") - + return options } diff --git a/src/router/util_test.go b/src/router/util_test.go index 39eb8861..86322b09 100644 --- a/src/router/util_test.go +++ b/src/router/util_test.go @@ -1,31 +1,30 @@ package router import ( - "net/http" - "log" "io/ioutil" + "log" + "net/http" ) func testRequest(targetUrl string, expectedResponse string) { resp, err := http.Get(targetUrl) - if (err != nil) { + if err != nil { log.Panicf("failed to make get request: %v", err) } defer resp.Body.Close() - if (resp.StatusCode != 200) { + if resp.StatusCode != 200 { log.Panicf("response status: %v", resp.StatusCode) } body, err := ioutil.ReadAll(resp.Body) - if (err != nil) { + if err != nil { log.Panic("failed to read response") } bodyStr := string(body) log.Printf("Server responded with %v", bodyStr) - if (bodyStr != expectedResponse) { + if bodyStr != expectedResponse { log.Panic("Unexpected response") - } + } } - From b03e7bad2df35401758947afdc6814466b7d7099 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Wed, 7 Sep 2016 13:41:26 -0700 Subject: [PATCH 13/14] HTTP trigger set This is currently: 1. A list of http triggers 2. A method to generate a mux router from that list This will be in the future: 1. A way to keep the set of triggers up to date, by watching the controller API 2. A way to update the router when the trigger set changes --- src/router/httpTriggers.go | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/router/httpTriggers.go diff --git a/src/router/httpTriggers.go b/src/router/httpTriggers.go new file mode 100644 index 00000000..7cdcb38b --- /dev/null +++ b/src/router/httpTriggers.go @@ -0,0 +1,62 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "github.com/gorilla/mux" +) + +type HTTPTriggerSet struct { + *functionServiceMap + *mutableRouter + controllerUrl string + poolManagerUrl string + triggers []httptrigger +} + +func makeHTTPTriggerSet(fmap *functionServiceMap, controllerUrl string, poolManagerUrl string) *HTTPTriggerSet { + triggers := make([]httptrigger, 1) + return &HTTPTriggerSet{ + functionServiceMap: fmap, + triggers: triggers, + controllerUrl: controllerUrl, + poolManagerUrl: poolManagerUrl, + } +} + +func (triggers *HTTPTriggerSet) subscribeRouter(mr *mutableRouter) { + triggers.mutableRouter = mr + mr.updateRouter(triggers.getRouterFromTriggers()) + go triggers.watchTriggers() +} + +func (triggers *HTTPTriggerSet) getRouterFromTriggers() *mux.Router { + muxRouter := mux.NewRouter() + for _, trigger := range triggers.triggers { + fh := &functionHandler{ + fmap: triggers.functionServiceMap, + function: trigger.function, + poolManagerUrl: triggers.poolManagerUrl, + } + muxRouter.HandleFunc(trigger.urlPattern, fh.handler) + } + return muxRouter +} + +func (triggers *HTTPTriggerSet) watchTriggers() { + // watch controller for updates to triggers and update the router accordingly +} From 993130d36b0ae42e93b31d41f70b26e0f84ce04b Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Wed, 7 Sep 2016 14:00:18 -0700 Subject: [PATCH 14/14] Router unit test --- src/router/router_test.go | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/router/router_test.go diff --git a/src/router/router_test.go b/src/router/router_test.go new file mode 100644 index 00000000..556a2849 --- /dev/null +++ b/src/router/router_test.go @@ -0,0 +1,44 @@ +/* +Copyright 2016 The Fission Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package router + +import ( + "fmt" + "testing" + "time" +) + +func TestRouter(t *testing.T) { + fmap := makeFunctionServiceMap() + fn := &function{name: "foo", uid: "xxx"} + + testResponseString := "hi" + testServiceUrl := createBackendService(testResponseString) + + fmap.assign(fn, testServiceUrl) + + triggers := makeHTTPTriggerSet(fmap, "", "") + triggerUrl := "/foo" + triggers.triggers = append(triggers.triggers, httptrigger{triggerUrl, *fn}) + + port := 4242 + go server(port, triggers) + time.Sleep(100 * time.Millisecond) + + testUrl := fmt.Sprintf("http://localhost:%v%v", port, triggerUrl) + testRequest(testUrl, testResponseString) +}