From 2b0b2e28ae4ba1f4c15aa1613f2c750176b09b89 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Tue, 30 Aug 2016 16:41:16 -0700 Subject: [PATCH] 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 +