Executor abstraction (#384)

This change adds a layer of abstraction over poolmgr. Poolmgr is now just one of the ways to turn a function into a service; other implementations will be added. The executor abstraction is a uniform API over all these implementations.

* Executor layer added on top of pool manager

* Removed the external server for executor

* Minor changes to keep existing semantics as much possible

* Separating the executor vs. poolmgr backend functionality and associated data members

* Executor logic separated from Poolmgr backend completely, placeholder for new backend

* Changed references to poolmgr in tests

* Moved poolmgr to it's package, as a side effect moved Cache to its's package (was causing cyclical dependency) and had to make some data structures exposed outside package

* Rebased from master and changed references to tpr -> crd

* Executor layer added on top of pool manager

* Executor logic separated from Poolmgr backend completely, placeholder for new backend

* Changed podName to a generic objectReference in fscache (#391)

Changed podName to a generic objectReference in function service cache implementation.

* Moved poolmgr to it's package, as a side effect moved Cache to its's package (was causing cyclical dependency) and had to make some data structures exposed outside package

* Rebased from master and changed references to tpr -> crd

* Merged from master with latest changes

* Removed stale executor service & deployment from previous merge

* Addressed review comments, still testing some areas
This commit is contained in:
Vishal
2017-11-20 20:51:14 -08:00
committed by Soam Vasani
parent 7bb397dfee
commit da820186f0
20 changed files with 476 additions and 458 deletions
+8 -9
View File
@@ -226,16 +226,16 @@ spec:
image: "{{ .Values.image }}:{{ .Values.imageTag }}" image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }} imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"] command: ["/fission-bundle"]
args: ["--routerPort", "8888", "--poolmgrUrl", "http://poolmgr.{{ .Release.Namespace }}"] args: ["--routerPort", "8888", "--executorUrl", "http://executor.{{ .Release.Namespace }}"]
serviceAccount: fission-svc serviceAccount: fission-svc
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: poolmgr name: executor
labels: labels:
svc: poolmgr svc: executor
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec: spec:
type: ClusterIP type: ClusterIP
@@ -243,13 +243,13 @@ spec:
- port: 80 - port: 80
targetPort: 8888 targetPort: 8888
selector: selector:
svc: poolmgr svc: executor
--- ---
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: poolmgr name: executor
labels: labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec: spec:
@@ -257,14 +257,14 @@ spec:
template: template:
metadata: metadata:
labels: labels:
svc: poolmgr svc: executor
spec: spec:
containers: containers:
- name: poolmgr - name: executor
image: "{{ .Values.image }}:{{ .Values.imageTag }}" image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }} imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"] command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888", "--namespace", "{{ .Values.functionNamespace }}", "--fission-namespace", "{{ .Release.Namespace }}"] args: ["--executorPort", "8888", "--namespace", "{{ .Values.functionNamespace }}", "--fission-namespace", "{{ .Release.Namespace }}"]
env: env:
- name: FETCHER_IMAGE - name: FETCHER_IMAGE
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}" value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
@@ -273,7 +273,6 @@ spec:
- name: RUNTIME_IMAGE_PULL_POLICY - name: RUNTIME_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}" value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc serviceAccount: fission-svc
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
@@ -226,16 +226,16 @@ spec:
image: "{{ .Values.image }}:{{ .Values.imageTag }}" image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }} imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"] command: ["/fission-bundle"]
args: ["--routerPort", "8888", "--poolmgrUrl", "http://poolmgr.{{ .Release.Namespace }}"] args: ["--routerPort", "8888", "--executorUrl", "http://executor.{{ .Release.Namespace }}"]
serviceAccount: fission-svc serviceAccount: fission-svc
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: poolmgr name: executor
labels: labels:
svc: poolmgr svc: executor
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec: spec:
type: ClusterIP type: ClusterIP
@@ -243,13 +243,13 @@ spec:
- port: 80 - port: 80
targetPort: 8888 targetPort: 8888
selector: selector:
svc: poolmgr svc: executor
--- ---
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
metadata: metadata:
name: poolmgr name: executor
labels: labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec: spec:
@@ -257,14 +257,14 @@ spec:
template: template:
metadata: metadata:
labels: labels:
svc: poolmgr svc: executor
spec: spec:
containers: containers:
- name: poolmgr - name: executor
image: "{{ .Values.image }}:{{ .Values.imageTag }}" image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }} imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"] command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888", "--namespace", "{{ .Values.functionNamespace }}", "--fission-namespace", "{{ .Release.Namespace }}"] args: ["--executorPort", "8888", "--namespace", "{{ .Values.functionNamespace }}", "--fission-namespace", "{{ .Release.Namespace }}"]
env: env:
- name: FETCHER_IMAGE - name: FETCHER_IMAGE
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}" value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
+105
View File
@@ -0,0 +1,105 @@
/*
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 executor
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission"
)
func (executor *Executor) getServiceForFunctionApi(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request", 500)
return
}
// get function metadata
m := metav1.ObjectMeta{}
err = json.Unmarshal(body, &m)
if err != nil {
http.Error(w, "Failed to parse request", 400)
return
}
serviceName, err := executor.getServiceForFunction(&m)
if err != nil {
code, msg := fission.GetHTTPError(err)
log.Printf("Error: %v: %v", code, msg)
http.Error(w, msg, code)
return
}
w.Write([]byte(serviceName))
}
func (executor *Executor) getServiceForFunction(m *metav1.ObjectMeta) (string, error) {
// Check function -> svc cache
log.Printf("[%v] Checking for cached function service", m.Name)
fsvc, err := executor.fsCache.GetByFunction(m)
if err == nil {
// Cached, return svc address
return fsvc.Address, nil
}
respChan := make(chan *createFuncServiceResponse)
executor.requestChan <- &createFuncServiceRequest{
funcMeta: m,
respChan: respChan,
}
resp := <-respChan
return resp.funcSvc.Address, resp.err
}
// find funcSvc and update its atime
func (executor *Executor) tapService(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request", 500)
return
}
svcName := string(body)
svcHost := strings.TrimPrefix(svcName, "http://")
err = executor.fsCache.TouchByAddress(svcHost)
if err != nil {
log.Printf("funcSvc tap error: %v", err)
http.Error(w, "Not found", 404)
return
}
w.WriteHeader(http.StatusOK)
}
func (executor *Executor) Serve(port int) {
r := mux.NewRouter()
r.HandleFunc("/v2/getServiceForFunction", executor.getServiceForFunctionApi).Methods("POST")
r.HandleFunc("/v2/tapService", executor.tapService).Methods("POST")
address := fmt.Sprintf(":%v", port)
log.Printf("starting executor at port %v", port)
log.Fatal(http.ListenAndServe(address, handlers.LoggingHandler(os.Stdout, r)))
}
@@ -32,14 +32,14 @@ import (
) )
type Client struct { type Client struct {
poolmgrUrl string executorUrl string
tappedByUrl map[string]bool tappedByUrl map[string]bool
requestChan chan string requestChan chan string
} }
func MakeClient(poolmgrUrl string) *Client { func MakeClient(executorUrl string) *Client {
c := &Client{ c := &Client{
poolmgrUrl: strings.TrimSuffix(poolmgrUrl, "/"), executorUrl: strings.TrimSuffix(executorUrl, "/"),
tappedByUrl: make(map[string]bool), tappedByUrl: make(map[string]bool),
requestChan: make(chan string), requestChan: make(chan string),
} }
@@ -48,14 +48,14 @@ func MakeClient(poolmgrUrl string) *Client {
} }
func (c *Client) GetServiceForFunction(metadata *metav1.ObjectMeta) (string, error) { func (c *Client) GetServiceForFunction(metadata *metav1.ObjectMeta) (string, error) {
poolmgrUrl := c.poolmgrUrl + "/v2/getServiceForFunction" executorUrl := c.executorUrl + "/v2/getServiceForFunction"
body, err := json.Marshal(metadata) body, err := json.Marshal(metadata)
if err != nil { if err != nil {
return "", err return "", err
} }
resp, err := http.Post(poolmgrUrl, "application/json", bytes.NewReader(body)) resp, err := http.Post(executorUrl, "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -100,9 +100,9 @@ func (c *Client) TapService(serviceUrl *url.URL) {
} }
func (c *Client) _tapService(serviceUrlStr string) error { func (c *Client) _tapService(serviceUrlStr string) error {
poolmgrUrl := c.poolmgrUrl + "/v2/tapService" executorUrl := c.executorUrl + "/v2/tapService"
resp, err := http.Post(poolmgrUrl, "application/octet-stream", bytes.NewReader([]byte(serviceUrlStr))) resp, err := http.Post(executorUrl, "application/octet-stream", bytes.NewReader([]byte(serviceUrlStr)))
if err != nil { if err != nil {
return err return err
} }
+22
View File
@@ -0,0 +1,22 @@
/*
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 deploymgr
func GetFuncSvc() (*funcSvc, error) {
return nil, nil
}
+195
View File
@@ -0,0 +1,195 @@
/*
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 executor
import (
"log"
"os"
"sync"
"time"
"github.com/dchest/uniuri"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission/cache"
"github.com/fission/fission/crd"
"github.com/fission/fission/executor/fscache"
"github.com/fission/fission/executor/poolmgr"
)
type (
Executor struct {
gpm *poolmgr.GenericPoolManager
functionEnv *cache.Cache
fissionClient *crd.FissionClient
fsCache *fscache.FunctionServiceCache
requestChan chan *createFuncServiceRequest
fsCreateWg map[string]*sync.WaitGroup
}
createFuncServiceRequest struct {
funcMeta *metav1.ObjectMeta
respChan chan *createFuncServiceResponse
}
createFuncServiceResponse struct {
funcSvc *fscache.FuncSvc
err error
}
)
func MakeExecutor(gpm *poolmgr.GenericPoolManager, fissionClient *crd.FissionClient, fsCache *fscache.FunctionServiceCache) *Executor {
executor := &Executor{
gpm: gpm,
functionEnv: cache.MakeCache(10*time.Second, 0),
fissionClient: fissionClient,
fsCache: fsCache,
requestChan: make(chan *createFuncServiceRequest),
fsCreateWg: make(map[string]*sync.WaitGroup),
}
go executor.serveCreateFuncServices()
return executor
}
// All non-cached function service requests go through this goroutine
// serially. It parallelizes requests for different functions, and
// ensures that for a given function, only one request causes a pod to
// get specialized. In other words, it ensures that when there's an
// ongoing request for a certain function, all other requests wait for
// that request to complete.
func (executor *Executor) serveCreateFuncServices() {
for {
req := <-executor.requestChan
m := req.funcMeta
// Cache miss -- is this first one to request the func?
wg, found := executor.fsCreateWg[crd.CacheKey(m)]
if !found {
// create a waitgroup for other requests for
// the same function to wait on
wg := &sync.WaitGroup{}
wg.Add(1)
executor.fsCreateWg[crd.CacheKey(m)] = wg
// launch a goroutine for each request, to parallelize
// the specialization of different functions
go func() {
fsvc, err := executor.createServiceForFunction(m)
req.respChan <- &createFuncServiceResponse{
funcSvc: fsvc,
err: err,
}
delete(executor.fsCreateWg, crd.CacheKey(m))
wg.Done()
}()
} else {
// There's an existing request for this function, wait for it to finish
go func() {
log.Printf("Waiting for concurrent request for the same function: %v", m)
wg.Wait()
// get the function service from the cache
fsvc, err := executor.fsCache.GetByFunction(m)
req.respChan <- &createFuncServiceResponse{
funcSvc: fsvc,
err: err,
}
}()
}
}
}
func (executor *Executor) createServiceForFunction(m *metav1.ObjectMeta) (*fscache.FuncSvc, error) {
log.Printf("[%v] No cached function service found, creating one", m.Name)
env, err := executor.getFunctionEnv(m)
if err != nil {
return nil, err
}
// Appropriate backend handles the service creation
backend := os.Getenv("EXECUTOR_BACKEND")
switch backend {
case "DEPLOY":
return nil, nil
default:
pool, err := executor.gpm.GetPool(env)
if err != nil {
return nil, err
}
// from GenericPool -> get one function container
// (this also adds to the cache)
log.Printf("[%v] getting function service from pool", m.Name)
fsvc, err := pool.GetFuncSvc(m)
if err != nil {
return nil, err
}
return fsvc, nil
}
}
func (executor *Executor) getFunctionEnv(m *metav1.ObjectMeta) (*crd.Environment, error) {
var env *crd.Environment
// Cached ?
result, err := executor.functionEnv.Get(crd.CacheKey(m))
if err == nil {
env = result.(*crd.Environment)
return env, nil
}
// Cache miss -- get func from controller
f, err := executor.fissionClient.Functions(m.Namespace).Get(m.Name)
if err != nil {
return nil, err
}
// Get env from metadata
log.Printf("[%v] getting env", m)
env, err = executor.fissionClient.Environments(f.Spec.Environment.Namespace).Get(f.Spec.Environment.Name)
if err != nil {
return nil, err
}
// cache for future lookups
executor.functionEnv.Set(crd.CacheKey(m), env)
return env, nil
}
// StartExecutor Starts executor and the backend components that executor uses such as Poolmgr,
// deploymgr and potential future backends
func StartExecutor(fissionNamespace string, functionNamespace string, port int) error {
fissionClient, kubernetesClient, _, err := crd.MakeFissionClient()
if err != nil {
log.Printf("Failed to get kubernetes client: %v", err)
return err
}
instanceID := uniuri.NewLen(8)
poolmgr.CleanupOldPoolmgrResources(kubernetesClient, functionNamespace, instanceID)
fsCache := fscache.MakeFunctionServiceCache()
gpm := poolmgr.MakeGenericPoolManager(
fissionClient, kubernetesClient, fissionNamespace,
functionNamespace, fsCache, instanceID)
api := MakeExecutor(gpm, fissionClient, fsCache)
go api.Serve(port)
return nil
}
@@ -20,7 +20,7 @@
// Here's how I run this on my setup, with minikube: // Here's how I run this on my setup, with minikube:
// TEST_SPECIALIZE_URL=http://192.168.99.100:30002/specialize TEST_FETCHER_URL=http://192.168.99.100:30001 FETCHER_IMAGE=minikube/fetcher:testing KUBECONFIG=/Users/soam/.kube/config go test -v . // TEST_SPECIALIZE_URL=http://192.168.99.100:30002/specialize TEST_FETCHER_URL=http://192.168.99.100:30001 FETCHER_IMAGE=minikube/fetcher:testing KUBECONFIG=/Users/soam/.kube/config go test -v .
package poolmgr package executor
import ( import (
"fmt" "fmt"
@@ -40,7 +40,7 @@ import (
"github.com/fission/fission" "github.com/fission/fission"
"github.com/fission/fission/crd" "github.com/fission/fission/crd"
"github.com/fission/fission/poolmgr/client" "github.com/fission/fission/executor/client"
) )
// return the number of pods in the given namespace matching the given labels // return the number of pods in the given namespace matching the given labels
@@ -104,7 +104,7 @@ func httpGet(url string) string {
return string(body) return string(body)
} }
func TestPoolmgr(t *testing.T) { func TestExecutor(t *testing.T) {
// run in a random namespace so we can have concurrent tests // run in a random namespace so we can have concurrent tests
// on a given cluster // on a given cluster
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
@@ -160,7 +160,7 @@ func TestPoolmgr(t *testing.T) {
// create poolmgr // create poolmgr
port := 9999 port := 9999
err = StartPoolmgr(fissionNs, functionNs, port) err = StartExecutor(fissionNs, functionNs, port)
if err != nil { if err != nil {
log.Panicf("failed to start poolmgr: %v", err) log.Panicf("failed to start poolmgr: %v", err)
} }
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package poolmgr package fscache
import ( import (
"log" "log"
@@ -44,18 +44,18 @@ const (
) )
type ( type (
funcSvc struct { FuncSvc struct {
function *metav1.ObjectMeta // function this pod/service is for Function *metav1.ObjectMeta // function this pod/service is for
environment *crd.Environment // function's environment Environment *crd.Environment // function's environment
address string // Host:Port or IP:Port that the function's service can be reached at. Address string // Host:Port or IP:Port that the function's service can be reached at.
kubernetesObject api.ObjectReference // Kubernetes Object (within the function namespace) KubernetesObject api.ObjectReference // Kubernetes Object (within the function namespace)
backend backendType Backend backendType
ctime time.Time Ctime time.Time
atime time.Time Atime time.Time
} }
functionServiceCache struct { FunctionServiceCache struct {
byFunction *cache.Cache // function-key -> funcSvc : map[string]*funcSvc byFunction *cache.Cache // function-key -> funcSvc : map[string]*funcSvc
byAddress *cache.Cache // address -> function : map[string]metav1.ObjectMeta byAddress *cache.Cache // address -> function : map[string]metav1.ObjectMeta
byKubeObject *cache.Cache // obj -> function : map[api.ObjectReference]metav1.ObjectMeta byKubeObject *cache.Cache // obj -> function : map[api.ObjectReference]metav1.ObjectMeta
@@ -77,8 +77,8 @@ type (
} }
) )
func MakeFunctionServiceCache() *functionServiceCache { func MakeFunctionServiceCache() *FunctionServiceCache {
fsc := &functionServiceCache{ fsc := &FunctionServiceCache{
byFunction: cache.MakeCache(0, 0), byFunction: cache.MakeCache(0, 0),
byAddress: cache.MakeCache(0, 0), byAddress: cache.MakeCache(0, 0),
byKubeObject: cache.MakeCache(0, 0), byKubeObject: cache.MakeCache(0, 0),
@@ -88,7 +88,7 @@ func MakeFunctionServiceCache() *functionServiceCache {
return fsc return fsc
} }
func (fsc *functionServiceCache) service() { func (fsc *FunctionServiceCache) service() {
for { for {
req := <-fsc.requestChannel req := <-fsc.requestChannel
resp := &fscResponse{} resp := &fscResponse{}
@@ -106,9 +106,9 @@ func (fsc *functionServiceCache) service() {
if err != nil { if err != nil {
resp.error = err resp.error = err
} else { } else {
fsvc := fsvcI.(*funcSvc) fsvc := fsvcI.(*FuncSvc)
if fsvc.environment.Metadata.UID == req.env.UID && if fsvc.Environment.Metadata.UID == req.env.UID &&
time.Now().Sub(fsvc.atime) > req.age { time.Now().Sub(fsvc.Atime) > req.age {
obj := objI.(api.ObjectReference) obj := objI.(api.ObjectReference)
kubeObjects = append(kubeObjects, obj) kubeObjects = append(kubeObjects, obj)
@@ -120,8 +120,8 @@ func (fsc *functionServiceCache) service() {
funcCopy := fsc.byFunction.Copy() funcCopy := fsc.byFunction.Copy()
log.Printf("Cache has %v entries", len(funcCopy)) log.Printf("Cache has %v entries", len(funcCopy))
for key, fsvcI := range funcCopy { for key, fsvcI := range funcCopy {
fsvc := fsvcI.(*funcSvc) fsvc := fsvcI.(*FuncSvc)
log.Printf("%v\t%v\t%v", key, fsvc.kubernetesObject.Kind, fsvc.kubernetesObject.Name) log.Printf("%v\t%v\t%v", key, fsvc.KubernetesObject.Kind, fsvc.KubernetesObject.Name)
} }
case DELETE_BY_OBJECT: case DELETE_BY_OBJECT:
resp.deleted, resp.error = fsc._deleteByKubeObject(req.kubernetesObject, req.age) resp.deleted, resp.error = fsc._deleteByKubeObject(req.kubernetesObject, req.age)
@@ -130,7 +130,7 @@ func (fsc *functionServiceCache) service() {
} }
} }
func (fsc *functionServiceCache) GetByFunction(m *metav1.ObjectMeta) (*funcSvc, error) { func (fsc *FunctionServiceCache) GetByFunction(m *metav1.ObjectMeta) (*FuncSvc, error) {
key := crd.CacheKey(m) key := crd.CacheKey(m)
fsvcI, err := fsc.byFunction.Get(key) fsvcI, err := fsc.byFunction.Get(key)
@@ -139,20 +139,20 @@ func (fsc *functionServiceCache) GetByFunction(m *metav1.ObjectMeta) (*funcSvc,
} }
// update atime // update atime
fsvc := fsvcI.(*funcSvc) fsvc := fsvcI.(*FuncSvc)
fsvc.atime = time.Now() fsvc.Atime = time.Now()
fsvcCopy := *fsvc fsvcCopy := *fsvc
return &fsvcCopy, nil return &fsvcCopy, nil
} }
// TODO: error should be second return // TODO: error should be second return
func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) { func (fsc *FunctionServiceCache) Add(fsvc FuncSvc) (error, *FuncSvc) {
err, existing := fsc.byFunction.Set(crd.CacheKey(fsvc.function), &fsvc) err, existing := fsc.byFunction.Set(crd.CacheKey(fsvc.Function), &fsvc)
if err != nil { if err != nil {
if existing != nil { if existing != nil {
f := existing.(*funcSvc) f := existing.(*FuncSvc)
err2 := fsc.TouchByAddress(f.address) err2 := fsc.TouchByAddress(f.Address)
if err2 != nil { if err2 != nil {
return err2, nil return err2, nil
} }
@@ -162,12 +162,12 @@ func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) {
return err, nil return err, nil
} }
now := time.Now() now := time.Now()
fsvc.ctime = now fsvc.Ctime = now
fsvc.atime = now fsvc.Atime = now
// Add to byAddress and byKubernetesObject caches. Ignore NameExists errors // Add to byAddress and byKubernetesObject caches. Ignore NameExists errors
// because of multiple-specialization. See issue #331. // because of multiple-specialization. See issue #331.
err, _ = fsc.byAddress.Set(fsvc.address, *fsvc.function) err, _ = fsc.byAddress.Set(fsvc.Address, *fsvc.Function)
if err != nil { if err != nil {
if fe, ok := err.(fission.Error); ok { if fe, ok := err.(fission.Error); ok {
if fe.Code == fission.ErrorNameExists { if fe.Code == fission.ErrorNameExists {
@@ -177,7 +177,7 @@ func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) {
log.Printf("error caching fsvc: %v", err) log.Printf("error caching fsvc: %v", err)
return err, nil return err, nil
} }
err, _ = fsc.byKubeObject.Set(fsvc.kubernetesObject, *fsvc.function) err, _ = fsc.byKubeObject.Set(fsvc.KubernetesObject, *fsvc.Function)
if err != nil { if err != nil {
if fe, ok := err.(fission.Error); ok { if fe, ok := err.(fission.Error); ok {
if fe.Code == fission.ErrorNameExists { if fe.Code == fission.ErrorNameExists {
@@ -190,7 +190,7 @@ func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) {
return nil, nil return nil, nil
} }
func (fsc *functionServiceCache) TouchByAddress(address string) error { func (fsc *FunctionServiceCache) TouchByAddress(address string) error {
responseChannel := make(chan *fscResponse) responseChannel := make(chan *fscResponse)
fsc.requestChannel <- &fscRequest{ fsc.requestChannel <- &fscRequest{
requestType: TOUCH, requestType: TOUCH,
@@ -201,7 +201,7 @@ func (fsc *functionServiceCache) TouchByAddress(address string) error {
return resp.error return resp.error
} }
func (fsc *functionServiceCache) _touchByAddress(address string) error { func (fsc *FunctionServiceCache) _touchByAddress(address string) error {
mI, err := fsc.byAddress.Get(address) mI, err := fsc.byAddress.Get(address)
if err != nil { if err != nil {
return err return err
@@ -211,12 +211,12 @@ func (fsc *functionServiceCache) _touchByAddress(address string) error {
if err != nil { if err != nil {
return err return err
} }
fsvc := fsvcI.(*funcSvc) fsvc := fsvcI.(*FuncSvc)
fsvc.atime = time.Now() fsvc.Atime = time.Now()
return nil return nil
} }
func (fsc *functionServiceCache) DeleteByKubeObject(obj api.ObjectReference, minAge time.Duration) (bool, error) { func (fsc *FunctionServiceCache) DeleteByKubeObject(obj api.ObjectReference, minAge time.Duration) (bool, error) {
responseChannel := make(chan *fscResponse) responseChannel := make(chan *fscResponse)
fsc.requestChannel <- &fscRequest{ fsc.requestChannel <- &fscRequest{
requestType: DELETE_BY_OBJECT, requestType: DELETE_BY_OBJECT,
@@ -230,7 +230,7 @@ func (fsc *functionServiceCache) DeleteByKubeObject(obj api.ObjectReference, min
// _deleteByKubeObject deletes the entry keyed by Kubernetes Object, but only if it is // _deleteByKubeObject deletes the entry keyed by Kubernetes Object, but only if it is
// at least minAge old. // at least minAge old.
func (fsc *functionServiceCache) _deleteByKubeObject(obj api.ObjectReference, minAge time.Duration) (bool, error) { func (fsc *FunctionServiceCache) _deleteByKubeObject(obj api.ObjectReference, minAge time.Duration) (bool, error) {
mI, err := fsc.byKubeObject.Get(obj) mI, err := fsc.byKubeObject.Get(obj)
if err != nil { if err != nil {
return false, err return false, err
@@ -240,19 +240,20 @@ func (fsc *functionServiceCache) _deleteByKubeObject(obj api.ObjectReference, mi
if err != nil { if err != nil {
return false, err return false, err
} }
fsvc := fsvcI.(*funcSvc) fsvc := fsvcI.(*FuncSvc)
if time.Now().Sub(fsvc.atime) < minAge { if time.Now().Sub(fsvc.Atime) < minAge {
return false, nil return false, nil
} }
fsc.byFunction.Delete(crd.CacheKey(&m)) fsc.byFunction.Delete(crd.CacheKey(&m))
fsc.byAddress.Delete(fsvc.address) fsc.byAddress.Delete(fsvc.Address)
fsc.byKubeObject.Delete(obj) fsc.byKubeObject.Delete(obj)
return true, nil return true, nil
} }
func (fsc *functionServiceCache) ListOld(env *metav1.ObjectMeta, age time.Duration) ([]api.ObjectReference, error) { func (fsc *FunctionServiceCache) ListOld(env *metav1.ObjectMeta, age time.Duration) ([]api.ObjectReference, error) {
responseChannel := make(chan *fscResponse) responseChannel := make(chan *fscResponse)
fsc.requestChannel <- &fscRequest{ fsc.requestChannel <- &fscRequest{
requestType: LISTOLD, requestType: LISTOLD,
@@ -264,7 +265,7 @@ func (fsc *functionServiceCache) ListOld(env *metav1.ObjectMeta, age time.Durati
return resp.objects, resp.error return resp.objects, resp.error
} }
func (fsc *functionServiceCache) Log() { func (fsc *FunctionServiceCache) Log() {
log.Printf("--- FunctionService Cache Contents") log.Printf("--- FunctionService Cache Contents")
responseChannel := make(chan *fscResponse) responseChannel := make(chan *fscResponse)
fsc.requestChannel <- &fscRequest{ fsc.requestChannel <- &fscRequest{
@@ -1,4 +1,4 @@
package poolmgr package fscache
import ( import (
"log" "log"
@@ -18,15 +18,15 @@ func TestFunctionServiceCache(t *testing.T) {
log.Panicf("error creating cache") log.Panicf("error creating cache")
} }
var fsvc *funcSvc var fsvc *FuncSvc
now := time.Now() now := time.Now()
fsvc = &funcSvc{ fsvc = &FuncSvc{
function: &metav1.ObjectMeta{ Function: &metav1.ObjectMeta{
Name: "foo", Name: "foo",
UID: "1212", UID: "1212",
}, },
environment: &crd.Environment{ Environment: &crd.Environment{
Metadata: metav1.ObjectMeta{ Metadata: metav1.ObjectMeta{
Name: "foo-env", Name: "foo-env",
UID: "2323", UID: "2323",
@@ -39,15 +39,15 @@ func TestFunctionServiceCache(t *testing.T) {
Builder: fission.Builder{}, Builder: fission.Builder{},
}, },
}, },
address: "xxx", Address: "xxx",
kubernetesObject: api.ObjectReference{ KubernetesObject: api.ObjectReference{
Kind: "pod", Kind: "pod",
Name: "xxx", Name: "xxx",
APIVersion: "v1", APIVersion: "v1",
Namespace: "fission-function", Namespace: "fission-function",
}, },
ctime: now, Ctime: now,
atime: now, Atime: now,
} }
err, _ := fsc.Add(*fsvc) err, _ := fsc.Add(*fsvc)
if err != nil { if err != nil {
@@ -55,25 +55,25 @@ func TestFunctionServiceCache(t *testing.T) {
log.Panicf("Failed to add fsvc: %v", err) log.Panicf("Failed to add fsvc: %v", err)
} }
f, err := fsc.GetByFunction(fsvc.function) f, err := fsc.GetByFunction(fsvc.Function)
if err != nil { if err != nil {
fsc.Log() fsc.Log()
log.Panicf("Failed to get fsvc: %v", err) log.Panicf("Failed to get fsvc: %v", err)
} }
fsvc.atime = f.atime fsvc.Atime = f.Atime
fsvc.ctime = f.ctime fsvc.Ctime = f.Ctime
if *f != *fsvc { if *f != *fsvc {
fsc.Log() fsc.Log()
log.Panicf("Incorrect fsvc \n(expected: %#v)\n (found: %#v)", fsvc, f) log.Panicf("Incorrect fsvc \n(expected: %#v)\n (found: %#v)", fsvc, f)
} }
err = fsc.TouchByAddress(fsvc.address) err = fsc.TouchByAddress(fsvc.Address)
if err != nil { if err != nil {
fsc.Log() fsc.Log()
log.Panicf("Failed to touch fsvc: %v", err) log.Panicf("Failed to touch fsvc: %v", err)
} }
deleted, err := fsc.DeleteByKubeObject(fsvc.kubernetesObject, 0) deleted, err := fsc.DeleteByKubeObject(fsvc.KubernetesObject, 0)
if err != nil { if err != nil {
fsc.Log() fsc.Log()
log.Panicf("Failed to delete fsvc: %v", err) log.Panicf("Failed to delete fsvc: %v", err)
@@ -83,7 +83,7 @@ func TestFunctionServiceCache(t *testing.T) {
log.Panicf("Did not delete fsvc") log.Panicf("Did not delete fsvc")
} }
_, err = fsc.GetByFunction(fsvc.function) _, err = fsc.GetByFunction(fsvc.Function)
if err == nil { if err == nil {
fsc.Log() fsc.Log()
log.Panicf("found fsvc while expecting empty cache: %v", err) log.Panicf("found fsvc while expecting empty cache: %v", err)
@@ -26,7 +26,7 @@ import (
// cleanupOldPoolmgrResources looks for resources created by an old // cleanupOldPoolmgrResources looks for resources created by an old
// poolmgr instance and cleans them up. // poolmgr instance and cleans them up.
func cleanupOldPoolmgrResources(client *kubernetes.Clientset, namespace string, instanceId string) { func CleanupOldPoolmgrResources(client *kubernetes.Clientset, namespace string, instanceId string) {
go func() { go func() {
err := cleanup(client, namespace, instanceId) err := cleanup(client, namespace, instanceId)
if err != nil { if err != nil {
+20 -30
View File
@@ -44,6 +44,7 @@ import (
"github.com/fission/fission/crd" "github.com/fission/fission/crd"
"github.com/fission/fission/environments/fetcher" "github.com/fission/fission/environments/fetcher"
fetcherClient "github.com/fission/fission/environments/fetcher/client" fetcherClient "github.com/fission/fission/environments/fetcher/client"
"github.com/fission/fission/executor/fscache"
) )
const POOLMGR_INSTANCEID_LABEL string = "poolmgrInstanceId" const POOLMGR_INSTANCEID_LABEL string = "poolmgrInstanceId"
@@ -52,14 +53,14 @@ const POD_PHASE_RUNNING string = "Running"
type ( type (
GenericPool struct { GenericPool struct {
env *crd.Environment env *crd.Environment
replicas int32 // num idle pods replicas int32 // num idle pods
deployment *v1beta1.Deployment // kubernetes deployment deployment *v1beta1.Deployment // kubernetes deployment
namespace string // namespace to keep our resources namespace string // namespace to keep our resources
podReadyTimeout time.Duration // timeout for generic pods to become ready podReadyTimeout time.Duration // timeout for generic pods to become ready
idlePodReapTime time.Duration // pods unused for idlePodReapTime are deleted idlePodReapTime time.Duration // pods unused for idlePodReapTime are deleted
fsCache *functionServiceCache // cache funcSvc's by function, address and podname fsCache *fscache.FunctionServiceCache // cache funcSvc's by function, address and podname
useSvc bool // create k8s service for specialized pods useSvc bool // create k8s service for specialized pods
poolInstanceId string // small random string to uniquify pod names poolInstanceId string // small random string to uniquify pod names
fetcherImage string fetcherImage string
fetcherImagePullPolicy apiv1.PullPolicy fetcherImagePullPolicy apiv1.PullPolicy
runtimeImagePullPolicy apiv1.PullPolicy // pull policy for generic pool to created env deployment runtimeImagePullPolicy apiv1.PullPolicy // pull policy for generic pool to created env deployment
@@ -99,7 +100,7 @@ func MakeGenericPool(
env *crd.Environment, env *crd.Environment,
initialReplicas int32, initialReplicas int32,
namespace string, namespace string,
fsCache *functionServiceCache, fsCache *fscache.FunctionServiceCache,
instanceId string) (*GenericPool, error) { instanceId string) (*GenericPool, error) {
log.Printf("Creating pool for environment %v", env.Metadata) log.Printf("Creating pool for environment %v", env.Metadata)
@@ -515,7 +516,7 @@ func (gp *GenericPool) createSvc(name string, labels map[string]string) (*apiv1.
return svc, err return svc, err
} }
func (gp *GenericPool) GetFuncSvc(m *metav1.ObjectMeta) (*funcSvc, error) { func (gp *GenericPool) GetFuncSvc(m *metav1.ObjectMeta) (*fscache.FuncSvc, error) {
log.Printf("[%v] Choosing pod from pool", m.Name) log.Printf("[%v] Choosing pod from pool", m.Name)
newLabels := gp.labelsForFunction(m) newLabels := gp.labelsForFunction(m)
@@ -566,29 +567,18 @@ func (gp *GenericPool) GetFuncSvc(m *metav1.ObjectMeta) (*funcSvc, error) {
UID: pod.ObjectMeta.UID, UID: pod.ObjectMeta.UID,
} }
fsvc := &funcSvc{ fsvc := &fscache.FuncSvc{
function: m, Function: m,
environment: gp.env, Environment: gp.env,
address: svcHost, Address: svcHost,
kubernetesObject: kubeObjRef, KubernetesObject: kubeObjRef,
backend: POOLMGR, Backend: fscache.POOLMGR,
ctime: time.Now(), Ctime: time.Now(),
atime: time.Now(), Atime: time.Now(),
} }
err, existingFsvc := gp.fsCache.Add(*fsvc) err, _ = gp.fsCache.Add(*fsvc)
if err != nil { if err != nil {
if fe, ok := err.(fission.Error); ok {
if fe.Code == fission.ErrorNameExists {
// Some other thread beat us to it -- return the other thread's fsvc and clean up
// our own.
log.Printf("func svc already exists: %v", existingFsvc.kubernetesObject.Name)
go func() {
gp.kubernetesClient.CoreV1().Pods(gp.namespace).Delete(fsvc.kubernetesObject.Name, nil)
}()
return existingFsvc, nil
}
}
return nil, err return nil, err
} }
return fsvc, nil return fsvc, nil
+7 -5
View File
@@ -25,6 +25,7 @@ import (
"github.com/fission/fission" "github.com/fission/fission"
"github.com/fission/fission/crd" "github.com/fission/fission/crd"
"github.com/fission/fission/executor/fscache"
) )
type requestType int type requestType int
@@ -39,10 +40,11 @@ type (
pools map[string]*GenericPool pools map[string]*GenericPool
kubernetesClient *kubernetes.Clientset kubernetesClient *kubernetes.Clientset
namespace string namespace string
fissionClient *crd.FissionClient
fsCache *functionServiceCache fissionClient *crd.FissionClient
instanceId string fsCache *fscache.FunctionServiceCache
requestChannel chan *request instanceId string
requestChannel chan *request
} }
request struct { request struct {
requestType requestType
@@ -61,7 +63,7 @@ func MakeGenericPoolManager(
kubernetesClient *kubernetes.Clientset, kubernetesClient *kubernetes.Clientset,
fissionNamespace string, fissionNamespace string,
functionNamespace string, functionNamespace string,
fsCache *functionServiceCache, fsCache *fscache.FunctionServiceCache,
instanceId string) *GenericPoolManager { instanceId string) *GenericPoolManager {
gpm := &GenericPoolManager{ gpm := &GenericPoolManager{
+19 -18
View File
@@ -6,11 +6,12 @@ import (
"strconv" "strconv"
"github.com/docopt/docopt-go" "github.com/docopt/docopt-go"
"github.com/fission/fission/buildermgr" "github.com/fission/fission/buildermgr"
"github.com/fission/fission/controller" "github.com/fission/fission/controller"
"github.com/fission/fission/executor"
"github.com/fission/fission/kubewatcher" "github.com/fission/fission/kubewatcher"
"github.com/fission/fission/mqtrigger" "github.com/fission/fission/mqtrigger"
"github.com/fission/fission/poolmgr"
"github.com/fission/fission/router" "github.com/fission/fission/router"
"github.com/fission/fission/storagesvc" "github.com/fission/fission/storagesvc"
"github.com/fission/fission/timer" "github.com/fission/fission/timer"
@@ -21,15 +22,15 @@ func runController(port int) {
log.Fatalf("Error: Controller exited.") log.Fatalf("Error: Controller exited.")
} }
func runRouter(port int, poolmgrUrl string) { func runRouter(port int, executorUrl string) {
router.Start(port, poolmgrUrl) router.Start(port, executorUrl)
log.Fatalf("Error: Router exited.") log.Fatalf("Error: Router exited.")
} }
func runPoolmgr(port int, fissionNamespace, functionNamespace string) { func runExecutor(port int, fissionNamespace, functionNamespace string) {
err := poolmgr.StartPoolmgr(fissionNamespace, functionNamespace, port) err := executor.StartExecutor(fissionNamespace, functionNamespace, port)
if err != nil { if err != nil {
log.Fatalf("Error starting poolmgr: %v", err) log.Fatalf("Error starting executor: %v", err)
} }
} }
@@ -88,18 +89,18 @@ func getStringArgWithDefault(arg interface{}, defaultValue string) string {
} }
func main() { func main() {
usage := `fission-bundle: Package of all fission microservices: controller, router, poolmgr. usage := `fission-bundle: Package of all fission microservices: controller, router, executor.
Use it to start one or more of the fission servers: Use it to start one or more of the fission servers:
Controller is a stateless API frontend for fission resources. Controller is a stateless API frontend for fission resources.
Pool manager maintains a pool of generalized function containers, and Pool manager maintains a pool of generalized function containers, and
specializes them on-demand. Poolmgr must be run from a pod in a specializes them on-demand. Executor must be run from a pod in a
Kubernetes cluster. Kubernetes cluster.
Router implements HTTP triggers: it routes to running instances, Router implements HTTP triggers: it routes to running instances,
working with the controller and poolmgr. working with the controller and executor.
Kubewatcher implements Kubernetes Watch triggers: it watches Kubewatcher implements Kubernetes Watch triggers: it watches
Kubernetes resources and invokes functions described in the Kubernetes resources and invokes functions described in the
@@ -111,8 +112,8 @@ Use it to start one or more of the fission servers:
Usage: Usage:
fission-bundle --controllerPort=<port> fission-bundle --controllerPort=<port>
fission-bundle --routerPort=<port> [--poolmgrUrl=<url>] fission-bundle --routerPort=<port> [--executorUrl=<url>]
fission-bundle --poolmgrPort=<port> [--namespace=<namespace>] [--fission-namespace=<namespace>] fission-bundle --executorPort=<port> [--namespace=<namespace>] [--fission-namespace=<namespace>]
fission-bundle --kubewatcher [--routerUrl=<url>] fission-bundle --kubewatcher [--routerUrl=<url>]
fission-bundle --storageServicePort=<port> --filePath=<filePath> fission-bundle --storageServicePort=<port> --filePath=<filePath>
fission-bundle --builderMgrPort=<port> [--storageSvcUrl=<url>] [--envbuilder-namespace=<namespace>] fission-bundle --builderMgrPort=<port> [--storageSvcUrl=<url>] [--envbuilder-namespace=<namespace>]
@@ -121,10 +122,10 @@ Usage:
Options: Options:
--controllerPort=<port> Port that the controller should listen on. --controllerPort=<port> Port that the controller should listen on.
--routerPort=<port> Port that the router should listen on. --routerPort=<port> Port that the router should listen on.
--poolmgrPort=<port> Port that the poolmgr should listen on. --executorPort=<port> Port that the executor should listen on.
--storageServicePort=<port> Port that the storage service should listen on. --storageServicePort=<port> Port that the storage service should listen on.
--builderMgrPort=<port> Port that the buildermgr should listen on. --builderMgrPort=<port> Port that the buildermgr should listen on.
--poolmgrUrl=<url> Poolmgr URL. Not required if --poolmgrPort is specified. --executorUrl=<url> Executor URL. Not required if --executorPort is specified.
--routerUrl=<url> Router URL. --routerUrl=<url> Router URL.
--etcdUrl=<etcdUrl> Etcd URL. --etcdUrl=<etcdUrl> Etcd URL.
--storageSvcUrl=<url> StorageService URL. --storageSvcUrl=<url> StorageService URL.
@@ -143,7 +144,7 @@ Options:
fissionNs := getStringArgWithDefault(arguments["--fission-namespace"], "fission") fissionNs := getStringArgWithDefault(arguments["--fission-namespace"], "fission")
envBuilderNs := getStringArgWithDefault(arguments["--envbuilder-namespace"], "fission-builder") envBuilderNs := getStringArgWithDefault(arguments["--envbuilder-namespace"], "fission-builder")
poolmgrUrl := getStringArgWithDefault(arguments["--poolmgrUrl"], "http://poolmgr.fission") executorUrl := getStringArgWithDefault(arguments["--executorUrl"], "http://executor.fission")
routerUrl := getStringArgWithDefault(arguments["--routerUrl"], "http://router.fission") routerUrl := getStringArgWithDefault(arguments["--routerUrl"], "http://router.fission")
storageSvcUrl := getStringArgWithDefault(arguments["--storageSvcUrl"], "http://storagesvc.fission") storageSvcUrl := getStringArgWithDefault(arguments["--storageSvcUrl"], "http://storagesvc.fission")
@@ -154,12 +155,12 @@ Options:
if arguments["--routerPort"] != nil { if arguments["--routerPort"] != nil {
port := getPort(arguments["--routerPort"]) port := getPort(arguments["--routerPort"])
runRouter(port, poolmgrUrl) runRouter(port, executorUrl)
} }
if arguments["--poolmgrPort"] != nil { if arguments["--executorPort"] != nil {
port := getPort(arguments["--poolmgrPort"]) port := getPort(arguments["--executorPort"])
runPoolmgr(port, fissionNs, functionNs) runExecutor(port, fissionNs, functionNs)
} }
if arguments["--kubewatcher"] == true { if arguments["--kubewatcher"] == true {
+2 -2
View File
@@ -20,6 +20,6 @@ fi
go test -v -i $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go') go test -v -i $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
# The poolmgr unit test only works with NodePort-type services for # The executor unit test only works with NodePort-type services for
# now. So disable it for our travis ci tests. # now. So disable it for our travis ci tests.
go test -v $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go' | grep -v poolmgr) go test -v $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go' | grep -v executor)
-253
View File
@@ -1,253 +0,0 @@
/*
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 poolmgr
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission"
"github.com/fission/fission/cache"
"github.com/fission/fission/crd"
)
type (
createFuncServiceRequest struct {
funcMeta *metav1.ObjectMeta
respChan chan *createFuncServiceResponse
}
createFuncServiceResponse struct {
address string
err error
}
Poolmgr struct {
gpm *GenericPoolManager
functionEnv *cache.Cache // map[string]crd.Environment
fsCache *functionServiceCache
fissionClient *crd.FissionClient
fsCreateChannels map[string]*sync.WaitGroup // xxx no channels here, rename this
requestChan chan *createFuncServiceRequest
}
)
func MakePoolmgr(gpm *GenericPoolManager, fissionClient *crd.FissionClient, fissionNs string, fsCache *functionServiceCache) *Poolmgr {
poolMgr := &Poolmgr{
gpm: gpm,
functionEnv: cache.MakeCache(10*time.Second, 0),
fsCache: fsCache,
fissionClient: fissionClient,
fsCreateChannels: make(map[string]*sync.WaitGroup),
requestChan: make(chan *createFuncServiceRequest),
}
go poolMgr.serveCreateFuncServices()
return poolMgr
}
// All non-cached function service requests go through this goroutine
// serially. It parallelizes requests for different functions, and
// ensures that for a given function, only one request causes a pod to
// get specialized. In other words, it ensures that when there's an
// ongoing request for a certain function, all other requests wait for
// that request to complete.
func (poolMgr *Poolmgr) serveCreateFuncServices() {
for {
req := <-poolMgr.requestChan
m := req.funcMeta
// Cache miss -- is this first one to request the func?
wg, found := poolMgr.fsCreateChannels[crd.CacheKey(m)]
if !found {
// create a waitgroup for other requests for
// the same function to wait on
wg := &sync.WaitGroup{}
wg.Add(1)
poolMgr.fsCreateChannels[crd.CacheKey(m)] = wg
// launch a goroutine for each request, to parallelize
// the specialization of different functions
go func() {
address, err := poolMgr.createServiceForFunction(m)
req.respChan <- &createFuncServiceResponse{
address: address,
err: err,
}
delete(poolMgr.fsCreateChannels, crd.CacheKey(m))
wg.Done()
}()
} else {
// There's an existing request for this function, wait for it to finish
go func() {
log.Printf("Waiting for concurrent request for the same function: %v", m)
wg.Wait()
// get the function service from the cache
fsvc, err := poolMgr.fsCache.GetByFunction(m)
address := ""
if err == nil {
address = fsvc.address
}
req.respChan <- &createFuncServiceResponse{
address: address,
err: err,
}
}()
}
}
}
func (poolMgr *Poolmgr) getServiceForFunctionApi(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request", 500)
return
}
// get function metadata
m := metav1.ObjectMeta{}
err = json.Unmarshal(body, &m)
if err != nil {
http.Error(w, "Failed to parse request", 400)
return
}
serviceName, err := poolMgr.getServiceForFunction(&m)
if err != nil {
code, msg := fission.GetHTTPError(err)
log.Printf("Error: %v: %v", code, msg)
http.Error(w, msg, code)
return
}
w.Write([]byte(serviceName))
}
func (poolMgr *Poolmgr) getFunctionEnv(m *metav1.ObjectMeta) (*crd.Environment, error) {
var env *crd.Environment
// Cached ?
result, err := poolMgr.functionEnv.Get(crd.CacheKey(m))
if err == nil {
env = result.(*crd.Environment)
return env, nil
}
// Cache miss -- get func from controller
f, err := poolMgr.fissionClient.Functions(m.Namespace).Get(m.Name)
if err != nil {
return nil, err
}
// Get env from metadata
log.Printf("[%v] getting env", m)
env, err = poolMgr.fissionClient.Environments(f.Spec.Environment.Namespace).Get(f.Spec.Environment.Name)
if err != nil {
return nil, err
}
// cache for future lookups
poolMgr.functionEnv.Set(crd.CacheKey(m), env)
return env, nil
}
func (poolMgr *Poolmgr) getServiceForFunction(m *metav1.ObjectMeta) (string, error) {
// Check function -> svc cache
log.Printf("[%v] Checking for cached function service", m.Name)
fsvc, err := poolMgr.fsCache.GetByFunction(m)
if err == nil {
// Cached, return svc address
return fsvc.address, nil
}
respChan := make(chan *createFuncServiceResponse)
poolMgr.requestChan <- &createFuncServiceRequest{
funcMeta: m,
respChan: respChan,
}
resp := <-respChan
return resp.address, resp.err
}
func (poolMgr *Poolmgr) createServiceForFunction(m *metav1.ObjectMeta) (string, error) {
// None exists, so create a new funcSvc:
log.Printf("[%v] No cached function service found, creating one", m.Name)
// from Func -> get Env
log.Printf("[%v] getting environment for function", m.Name)
env, err := poolMgr.getFunctionEnv(m)
if err != nil {
return "", err
}
// from Env -> get GenericPool
log.Printf("[%v] getting generic pool for env", m.Name)
pool, err := poolMgr.gpm.GetPool(env)
if err != nil {
return "", err
}
// from GenericPool -> get one function container
// (this also adds to the cache)
log.Printf("[%v] getting function service from pool", m.Name)
fsvc, err := pool.GetFuncSvc(m)
if err != nil {
return "", err
}
return fsvc.address, nil
}
// find funcSvc and update its atime
func (poolMgr *Poolmgr) tapService(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request", 500)
return
}
svcName := string(body)
svcHost := strings.TrimPrefix(svcName, "http://")
err = poolMgr.fsCache.TouchByAddress(svcHost)
if err != nil {
log.Printf("funcSvc tap error: %v", err)
http.Error(w, "Not found", 404)
return
}
w.WriteHeader(http.StatusOK)
}
func (poolMgr *Poolmgr) Serve(port int) {
r := mux.NewRouter()
r.HandleFunc("/v2/getServiceForFunction", poolMgr.getServiceForFunctionApi).Methods("POST")
r.HandleFunc("/v2/tapService", poolMgr.tapService).Methods("POST")
address := fmt.Sprintf(":%v", port)
log.Printf("starting poolmgr at port %v", port)
log.Fatal(http.ListenAndServe(address, handlers.LoggingHandler(os.Stdout, r)))
}
-47
View File
@@ -1,47 +0,0 @@
/*
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 poolmgr
import (
"log"
"github.com/dchest/uniuri"
"github.com/fission/fission/crd"
)
// Start the poolmgr service.
func StartPoolmgr(fissionNamespace string, functionNamespace string, port int) error {
fissionClient, kubernetesClient, _, err := crd.MakeFissionClient()
if err != nil {
log.Printf("Failed to get kubernetes client: %v", err)
return err
}
instanceId := uniuri.NewLen(8)
cleanupOldPoolmgrResources(kubernetesClient, functionNamespace, instanceId)
fsCache := MakeFunctionServiceCache()
gpm := MakeGenericPoolManager(
fissionClient, kubernetesClient, fissionNamespace,
functionNamespace, fsCache, instanceId)
api := MakePoolmgr(gpm, fissionClient, fissionNamespace, fsCache)
go api.Serve(port)
return nil
}
+7 -7
View File
@@ -28,18 +28,18 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
poolmgrClient "github.com/fission/fission/poolmgr/client" executorClient "github.com/fission/fission/executor/client"
) )
type functionHandler struct { type functionHandler struct {
fmap *functionServiceMap fmap *functionServiceMap
poolmgr *poolmgrClient.Client executor *executorClient.Client
function *metav1.ObjectMeta function *metav1.ObjectMeta
} }
func (fh *functionHandler) getServiceForFunction() (*url.URL, error) { func (fh *functionHandler) getServiceForFunction() (*url.URL, error) {
// call poolmgr, get a url for a function // call executor, get a url for a function
svcName, err := fh.poolmgr.GetServiceForFunction(fh.function) svcName, err := fh.executor.GetServiceForFunction(fh.function)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -83,10 +83,10 @@ func (rrt RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, er
} }
func (fh *functionHandler) tapService(serviceUrl *url.URL) { func (fh *functionHandler) tapService(serviceUrl *url.URL) {
if fh.poolmgr == nil { if fh.executor == nil {
return return
} }
fh.poolmgr.TapService(serviceUrl) fh.executor.TapService(serviceUrl)
} }
func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *http.Request) { func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *http.Request) {
@@ -121,7 +121,7 @@ func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *
fh.fmap.assign(fh.function, serviceUrl) fh.fmap.assign(fh.function, serviceUrl)
} else { } else {
// if we're using our cache, asynchronously tell // if we're using our cache, asynchronously tell
// poolmgr we're using this service // executor we're using this service
go fh.tapService(serviceUrl) go fh.tapService(serviceUrl)
} }
+6 -6
View File
@@ -30,14 +30,14 @@ import (
"github.com/fission/fission" "github.com/fission/fission"
"github.com/fission/fission/crd" "github.com/fission/fission/crd"
poolmgrClient "github.com/fission/fission/poolmgr/client" executorClient "github.com/fission/fission/executor/client"
) )
type HTTPTriggerSet struct { type HTTPTriggerSet struct {
*functionServiceMap *functionServiceMap
*mutableRouter *mutableRouter
fissionClient *crd.FissionClient fissionClient *crd.FissionClient
poolmgr *poolmgrClient.Client executor *executorClient.Client
resolver *functionReferenceResolver resolver *functionReferenceResolver
crdClient *rest.RESTClient crdClient *rest.RESTClient
triggers []crd.HTTPTrigger triggers []crd.HTTPTrigger
@@ -49,12 +49,12 @@ type HTTPTriggerSet struct {
} }
func makeHTTPTriggerSet(fmap *functionServiceMap, fissionClient *crd.FissionClient, func makeHTTPTriggerSet(fmap *functionServiceMap, fissionClient *crd.FissionClient,
poolmgr *poolmgrClient.Client, crdClient *rest.RESTClient) (*HTTPTriggerSet, k8sCache.Store, k8sCache.Store) { executor *executorClient.Client, crdClient *rest.RESTClient) (*HTTPTriggerSet, k8sCache.Store, k8sCache.Store) {
httpTriggerSet := &HTTPTriggerSet{ httpTriggerSet := &HTTPTriggerSet{
functionServiceMap: fmap, functionServiceMap: fmap,
triggers: []crd.HTTPTrigger{}, triggers: []crd.HTTPTrigger{},
fissionClient: fissionClient, fissionClient: fissionClient,
poolmgr: poolmgr, executor: executor,
crdClient: crdClient, crdClient: crdClient,
} }
var tStore, fnStore k8sCache.Store var tStore, fnStore k8sCache.Store
@@ -114,7 +114,7 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
fh := &functionHandler{ fh := &functionHandler{
fmap: ts.functionServiceMap, fmap: ts.functionServiceMap,
function: rr.functionMetadata, function: rr.functionMetadata,
poolmgr: ts.poolmgr, executor: ts.executor,
} }
muxRouter.HandleFunc(trigger.Spec.RelativeURL, fh.handler).Methods(trigger.Spec.Method) muxRouter.HandleFunc(trigger.Spec.RelativeURL, fh.handler).Methods(trigger.Spec.Method)
if trigger.Spec.RelativeURL == "/" && trigger.Spec.Method == "GET" { if trigger.Spec.RelativeURL == "/" && trigger.Spec.Method == "GET" {
@@ -139,7 +139,7 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
fh := &functionHandler{ fh := &functionHandler{
fmap: ts.functionServiceMap, fmap: ts.functionServiceMap,
function: &m, function: &m,
poolmgr: ts.poolmgr, executor: ts.executor,
} }
muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name), fh.handler) muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name), fh.handler)
} }
+7 -4
View File
@@ -51,7 +51,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/fission/fission/crd" "github.com/fission/fission/crd"
poolmgrClient "github.com/fission/fission/poolmgr/client" executorClient "github.com/fission/fission/executor/client"
) )
// request url ---[mux]---> Function(name,uid) ----[fmap]----> k8s service url // request url ---[mux]---> Function(name,uid) ----[fmap]----> k8s service url
@@ -71,17 +71,20 @@ func serve(ctx context.Context, port int, httpTriggerSet *HTTPTriggerSet, resolv
http.ListenAndServe(url, handlers.LoggingHandler(os.Stdout, mr)) http.ListenAndServe(url, handlers.LoggingHandler(os.Stdout, mr))
} }
func Start(port int, poolmgrUrl string) { func Start(port int, executorUrl string) {
fmap := makeFunctionServiceMap(time.Minute) fmap := makeFunctionServiceMap(time.Minute)
fissionClient, _, _, err := crd.MakeFissionClient() fissionClient, _, _, err := crd.MakeFissionClient()
if err != nil { if err != nil {
log.Fatalf("Error connecting to kubernetes API: %v", err) log.Fatalf("Error connecting to kubernetes API: %v", err)
} }
restClient := fissionClient.GetCrdClient() restClient := fissionClient.GetCrdClient()
poolmgr := poolmgrClient.MakeClient(poolmgrUrl)
triggers, _, fnStore := makeHTTPTriggerSet(fmap, fissionClient, poolmgr, restClient) executor := executorClient.MakeClient(executorUrl)
triggers, _, fnStore := makeHTTPTriggerSet(fmap, fissionClient, executor, restClient)
resolver := makeFunctionReferenceResolver(fnStore) resolver := makeFunctionReferenceResolver(fnStore)
log.Printf("Starting router at port %v\n", port) log.Printf("Starting router at port %v\n", port)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
+1 -1
View File
@@ -262,8 +262,8 @@ dump_logs() {
dump_env_pods $fns dump_env_pods $fns
dump_fission_logs $ns $fns controller dump_fission_logs $ns $fns controller
dump_fission_logs $ns $fns router dump_fission_logs $ns $fns router
dump_fission_logs $ns $fns poolmgr
dump_fission_logs $ns $fns buildermgr dump_fission_logs $ns $fns buildermgr
dump_fission_logs $ns $fns executor
dump_function_pod_logs $ns $fns dump_function_pod_logs $ns $fns
dump_fission_crds dump_fission_crds
} }