Feature request per pod (#1946)
This feature enables routing more than one request to a pod at the same time. This is the first draft of the work and might involve more optimizations later.
This commit is contained in:
@@ -353,6 +353,10 @@ type (
|
||||
// Maximum number of pods to be specialized which will serve requests
|
||||
// This is optional. If not specified default value will be taken as 5
|
||||
Concurrency int `json:"concurrency,omitempty"`
|
||||
|
||||
// RequestsPerPod indicates the maximum number of concurrent requests that can be served by a specialized pod
|
||||
// This is optional. If not specified default value will be taken as 1
|
||||
RequestsPerPod int `json:"requestsPerPod,omitempty"`
|
||||
}
|
||||
|
||||
// InvokeStrategy is a set of controls over how the function executes.
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
func Start(logger *zap.Logger, storageSvcUrl string, envBuilderNamespace string) error {
|
||||
bmLogger := logger.Named("builder_manager")
|
||||
|
||||
fissionClient, kubernetesClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubernetesClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get fission or kubernetes client")
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ func TestMain(m *testing.M) {
|
||||
return
|
||||
}
|
||||
|
||||
_, kubeClient, _, err := crd.GetKubernetesClient()
|
||||
_, kubeClient, _, _, err := crd.GetKubernetesClient()
|
||||
panicIf(err)
|
||||
|
||||
// testNS isolation for running multiple CI builds concurrently.
|
||||
|
||||
@@ -18,6 +18,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/fission/fission/pkg/crd"
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
func Start(logger *zap.Logger, port int, unitTestFlag bool) {
|
||||
cLogger := logger.Named("controller")
|
||||
|
||||
fc, kc, apiExtClient, err := crd.MakeFissionClient()
|
||||
fc, kc, apiExtClient, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
cLogger.Fatal("failed to connect to k8s API", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
func makeCRDBackedAPI(logger *zap.Logger) (*API, error) {
|
||||
fissionClient, kubernetesClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubernetesClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+14
-11
@@ -28,6 +28,7 @@ import (
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
|
||||
genClientset "github.com/fission/fission/pkg/apis/genclient/clientset/versioned"
|
||||
)
|
||||
@@ -41,7 +42,7 @@ type (
|
||||
// Get a kubernetes client using the kubeconfig file at the
|
||||
// environment var $KUBECONFIG, or an in-cluster config if that's
|
||||
// undefined.
|
||||
func GetKubernetesClient() (*rest.Config, *kubernetes.Clientset, *apiextensionsclient.Clientset, error) {
|
||||
func GetKubernetesClient() (*rest.Config, *kubernetes.Clientset, *apiextensionsclient.Clientset, *metricsclient.Clientset, error) {
|
||||
var config *rest.Config
|
||||
var err error
|
||||
|
||||
@@ -51,45 +52,47 @@ func GetKubernetesClient() (*rest.Config, *kubernetes.Clientset, *apiextensionsc
|
||||
if len(kubeConfig) != 0 {
|
||||
config, err = clientcmd.BuildConfigFromFlags("", kubeConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
} else {
|
||||
config, err = rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// creates the clientset
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
apiExtClientset, err := apiextensionsclient.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
return config, clientset, apiExtClientset, nil
|
||||
metricsClient, _ := metricsclient.NewForConfig(config)
|
||||
|
||||
return config, clientset, apiExtClientset, metricsClient, nil
|
||||
}
|
||||
|
||||
func MakeFissionClient() (*FissionClient, *kubernetes.Clientset, *apiextensionsclient.Clientset, error) {
|
||||
config, kubeClient, apiExtClient, err := GetKubernetesClient()
|
||||
func MakeFissionClient() (*FissionClient, *kubernetes.Clientset, *apiextensionsclient.Clientset, *metricsclient.Clientset, error) {
|
||||
config, kubeClient, apiExtClient, metricsClient, err := GetKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
// make a CRD REST client with the config
|
||||
crdClient, err := genClientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
fc := &FissionClient{
|
||||
Interface: crdClient,
|
||||
}
|
||||
return fc, kubeClient, apiExtClient, nil
|
||||
return fc, kubeClient, apiExtClient, metricsClient, nil
|
||||
}
|
||||
|
||||
func (fc *FissionClient) WaitForCRDs() error {
|
||||
|
||||
+1
-1
@@ -439,7 +439,7 @@ func TestCrd(t *testing.T) {
|
||||
|
||||
panicIf(err)
|
||||
|
||||
fc, kubeClient, apiExtClient, err := MakeFissionClient()
|
||||
fc, kubeClient, apiExtClient, _, err := MakeFissionClient()
|
||||
if err != nil {
|
||||
panicIf(err)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ var (
|
||||
Type: "integer",
|
||||
Description: "Concurrency specifies the maximum number of pods that can be specialized concurrently to serve requests.\n This is optional. If not specified default value will be taken as 5",
|
||||
},
|
||||
"requestsPerPod": {
|
||||
Type: "integer",
|
||||
Description: "RequestsPerPod indicates the maximum number of concurrent requests that can be served by a specialized pod.\n This is optional. If not specified default value will be taken as 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+58
-40
@@ -50,25 +50,60 @@ func (executor *Executor) getServiceForFunctionAPI(w http.ResponseWriter, r *htt
|
||||
}
|
||||
|
||||
t := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
|
||||
et, exists := executor.executorTypes[t]
|
||||
if !exists {
|
||||
http.Error(w, fmt.Sprintf("Unknown executor type '%v'", t), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
et := executor.executorTypes[t]
|
||||
|
||||
executor.logger.Debug(fmt.Sprintf("active instances: %v", et.GetTotalAvailable(fn)))
|
||||
conncurrency := fn.Spec.Concurrency
|
||||
if conncurrency == 0 {
|
||||
// set to default conncurrency
|
||||
conncurrency = 5
|
||||
executor.logger.Debug(fmt.Sprintf("concurrency specified in function: %v", fn.Spec.Concurrency))
|
||||
executor.logger.Debug("setting concurrency to 5")
|
||||
}
|
||||
if t == fv1.ExecutorTypePoolmgr && et.GetTotalAvailable(fn) >= conncurrency {
|
||||
errMsg := fmt.Sprintf("max concurrency reached for %v. All %v instance are active", fn.ObjectMeta.Name, conncurrency)
|
||||
executor.logger.Error("error occurred", zap.String("error", errMsg))
|
||||
http.Error(w, errMsg, http.StatusTooManyRequests)
|
||||
return
|
||||
// Check function -> svc cache
|
||||
executor.logger.Debug("checking for cached function service",
|
||||
zap.String("function_name", fn.ObjectMeta.Name),
|
||||
zap.String("function_namespace", fn.ObjectMeta.Namespace))
|
||||
if t == fv1.ExecutorTypePoolmgr {
|
||||
concurrency := fn.Spec.Concurrency
|
||||
if concurrency == 0 {
|
||||
concurrency = 5
|
||||
}
|
||||
requestsPerpod := fn.Spec.RequestsPerPod
|
||||
if requestsPerpod == 0 {
|
||||
requestsPerpod = 1
|
||||
}
|
||||
fsvc, active, err := et.GetFuncSvcFromPoolCache(fn, requestsPerpod)
|
||||
// check if its a cache hit (check if there is already specialized function pod that can serve another request)
|
||||
if err == nil {
|
||||
// if a pod is already serving request then it already exists else validated
|
||||
executor.logger.Debug("from cache", zap.Int("active", active))
|
||||
if active > 1 || et.IsValid(fsvc) {
|
||||
// Cached, return svc address
|
||||
executor.logger.Debug("served from cache", zap.String("name", fsvc.Name), zap.String("address", fsvc.Address))
|
||||
executor.writeResponse(w, fsvc.Address, fn.ObjectMeta.Name)
|
||||
return
|
||||
}
|
||||
executor.logger.Debug("deleting cache entry for invalid address",
|
||||
zap.String("function_name", fn.ObjectMeta.Name),
|
||||
zap.String("function_namespace", fn.ObjectMeta.Namespace),
|
||||
zap.String("address", fsvc.Address))
|
||||
et.DeleteFuncSvcFromCache(fsvc)
|
||||
active--
|
||||
}
|
||||
|
||||
if active >= concurrency {
|
||||
errMsg := fmt.Sprintf("max concurrency reached for %v. All %v instance are active", fn.ObjectMeta.Name, concurrency)
|
||||
executor.logger.Error("error occurred", zap.String("error", errMsg))
|
||||
http.Error(w, errMsg, http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fsvc, err := et.GetFuncSvcFromCache(fn)
|
||||
if err == nil {
|
||||
if et.IsValid(fsvc) {
|
||||
// Cached, return svc address
|
||||
executor.writeResponse(w, fsvc.Address, fn.ObjectMeta.Name)
|
||||
return
|
||||
}
|
||||
executor.logger.Debug("deleting cache entry for invalid address",
|
||||
zap.String("function_name", fn.ObjectMeta.Name),
|
||||
zap.String("function_namespace", fn.ObjectMeta.Namespace),
|
||||
zap.String("address", fsvc.Address))
|
||||
et.DeleteFuncSvcFromCache(fsvc)
|
||||
}
|
||||
}
|
||||
|
||||
serviceName, err := executor.getServiceForFunction(fn)
|
||||
@@ -81,12 +116,15 @@ func (executor *Executor) getServiceForFunctionAPI(w http.ResponseWriter, r *htt
|
||||
http.Error(w, msg, code)
|
||||
return
|
||||
}
|
||||
executor.writeResponse(w, serviceName, fn.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(serviceName))
|
||||
func (executor *Executor) writeResponse(w http.ResponseWriter, serviceName string, fnName string) {
|
||||
_, err := w.Write([]byte(serviceName))
|
||||
if err != nil {
|
||||
executor.logger.Error(
|
||||
"error writing HTTP response",
|
||||
zap.String("function", fn.ObjectMeta.Name),
|
||||
zap.String("function", fnName),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
@@ -102,26 +140,6 @@ func (executor *Executor) getServiceForFunctionAPI(w http.ResponseWriter, r *htt
|
||||
// To make it optimal, plan is to add an eager cache invalidator function that watches for pod deletion events and
|
||||
// invalidates the cache entry if the pod address was cached.
|
||||
func (executor *Executor) getServiceForFunction(fn *fv1.Function) (string, error) {
|
||||
t := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
|
||||
et := executor.executorTypes[t]
|
||||
// Check function -> svc cache
|
||||
executor.logger.Debug("checking for cached function service",
|
||||
zap.String("function_name", fn.ObjectMeta.Name),
|
||||
zap.String("function_namespace", fn.ObjectMeta.Namespace))
|
||||
|
||||
fsvc, err := et.GetFuncSvcFromCache(fn)
|
||||
if err == nil {
|
||||
if et.IsValid(fsvc) {
|
||||
// Cached, return svc address
|
||||
return fsvc.Address, nil
|
||||
}
|
||||
executor.logger.Debug("deleting cache entry for invalid address",
|
||||
zap.String("function_name", fn.ObjectMeta.Name),
|
||||
zap.String("function_namespace", fn.ObjectMeta.Namespace),
|
||||
zap.String("address", fsvc.Address))
|
||||
et.DeleteFuncSvcFromCache(fsvc)
|
||||
}
|
||||
|
||||
respChan := make(chan *createFuncServiceResponse)
|
||||
executor.requestChan <- &createFuncServiceRequest{
|
||||
function: fn,
|
||||
|
||||
@@ -238,7 +238,7 @@ func serveMetric(logger *zap.Logger) {
|
||||
// StartExecutor Starts executor and the executor components such as Poolmgr,
|
||||
// deploymgr and potential future executor types
|
||||
func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNamespace string, port int) error {
|
||||
fissionClient, kubernetesClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubernetesClient, _, metricsClient, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get kubernetes client")
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func StartExecutor(logger *zap.Logger, functionNamespace string, envBuilderNames
|
||||
|
||||
gpm := poolmgr.MakeGenericPoolManager(
|
||||
logger,
|
||||
fissionClient, kubernetesClient,
|
||||
fissionClient, kubernetesClient, metricsClient,
|
||||
functionNamespace, fetcherConfig, executorInstanceID)
|
||||
|
||||
ndm := newdeploy.MakeNewDeploy(
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestExecutor(t *testing.T) {
|
||||
|
||||
// connect to k8s
|
||||
// and get CRD client
|
||||
fissionClient, kubeClient, apiExtClient, err := crd.MakeFissionClient()
|
||||
fissionClient, kubeClient, apiExtClient, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
log.Panicf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ type ExecutorType interface {
|
||||
// GetFuncSvcFromCache retrieves function service from cache.
|
||||
GetFuncSvcFromCache(*fv1.Function) (*fscache.FuncSvc, error)
|
||||
|
||||
// GetFuncSvcFromPoolCache retrieves function service and number of active instances after filtering on requestsPerPod and CPULimit
|
||||
GetFuncSvcFromPoolCache(fn *fv1.Function, requestsPerPod int) (*fscache.FuncSvc, int, error)
|
||||
|
||||
// DeleteFuncSvcFromCache deletes function service entry in cache.
|
||||
DeleteFuncSvcFromCache(*fscache.FuncSvc)
|
||||
|
||||
@@ -60,7 +63,4 @@ type ExecutorType interface {
|
||||
|
||||
// CleanupOldExecutorObjects cleans up resources created by old executor instances
|
||||
CleanupOldExecutorObjects()
|
||||
|
||||
// getTotalAvailable returns total active instances of particular function
|
||||
GetTotalAvailable(*fv1.Function) int
|
||||
}
|
||||
|
||||
@@ -165,10 +165,10 @@ func (deploy *NewDeploy) UnTapService(key string, svcHost string) {
|
||||
// Not Implemented for NewDeployment. Will be used when support of concurrent specialization of same function is added.
|
||||
}
|
||||
|
||||
// GetTotalAvailable has not been implemented for NewDeployment.
|
||||
func (deploy *NewDeploy) GetTotalAvailable(fn *fv1.Function) int {
|
||||
// GetFuncSvcFromPoolCache has not been implemented for NewDeployment
|
||||
func (deploy *NewDeploy) GetFuncSvcFromPoolCache(fn *fv1.Function, requestsPerPod int) (*fscache.FuncSvc, int, error) {
|
||||
// Not Implemented for NewDeployment. Will be used when support of concurrent specialization of same function is added.
|
||||
return 0
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// TapService makes a TouchByAddress request to the cache.
|
||||
|
||||
@@ -20,9 +20,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
@@ -32,13 +34,16 @@ import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
k8sErrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
k8sTypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
||||
"github.com/fission/fission/pkg/crd"
|
||||
@@ -63,6 +68,7 @@ type (
|
||||
useIstio bool
|
||||
runtimeImagePullPolicy apiv1.PullPolicy // pull policy for generic pool to created env deployment
|
||||
kubernetesClient *kubernetes.Clientset
|
||||
metricsClient *metricsclient.Clientset
|
||||
fissionClient *crd.FissionClient
|
||||
fetcherConfig *fetcherConfig.Config
|
||||
stopReadyPodControllerCh chan struct{}
|
||||
@@ -71,6 +77,7 @@ type (
|
||||
readyPodQueue workqueue.DelayingInterface
|
||||
poolInstanceID string // small random string to uniquify pod names
|
||||
instanceID string // poolmgr instance id
|
||||
podFSVCMap sync.Map
|
||||
}
|
||||
)
|
||||
|
||||
@@ -79,6 +86,7 @@ func MakeGenericPool(
|
||||
logger *zap.Logger,
|
||||
fissionClient *crd.FissionClient,
|
||||
kubernetesClient *kubernetes.Clientset,
|
||||
metricsClient *metricsclient.Clientset,
|
||||
env *fv1.Environment,
|
||||
initialReplicas int32,
|
||||
namespace string,
|
||||
@@ -110,6 +118,7 @@ func MakeGenericPool(
|
||||
replicas: initialReplicas, // TODO make this an env param instead?
|
||||
fissionClient: fissionClient,
|
||||
kubernetesClient: kubernetesClient,
|
||||
metricsClient: metricsClient,
|
||||
namespace: namespace,
|
||||
functionNamespace: functionNamespace,
|
||||
podReadyTimeout: podReadyTimeout,
|
||||
@@ -120,6 +129,7 @@ func MakeGenericPool(
|
||||
stopReadyPodControllerCh: make(chan struct{}),
|
||||
poolInstanceID: uniuri.NewLen(8),
|
||||
instanceID: instanceID,
|
||||
podFSVCMap: sync.Map{},
|
||||
}
|
||||
|
||||
gp.runtimeImagePullPolicy = utils.GetImagePullPolicy(os.Getenv("RUNTIME_IMAGE_PULL_POLICY"))
|
||||
@@ -141,7 +151,7 @@ func MakeGenericPool(
|
||||
gpLogger.Info("deployment created", zap.Any("environment", env.ObjectMeta))
|
||||
|
||||
go gp.startReadyPodController()
|
||||
|
||||
go gp.updateCPUUtilizationSvc()
|
||||
return gp, nil
|
||||
}
|
||||
|
||||
@@ -161,6 +171,35 @@ func (gp *GenericPool) getDeployAnnotations() map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func (gp *GenericPool) updateCPUUtilizationSvc() {
|
||||
for {
|
||||
podMetricsList, err := gp.metricsClient.MetricsV1beta1().PodMetricses(gp.namespace).List(v1.ListOptions{
|
||||
LabelSelector: "managed=false",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
gp.logger.Error("failed to fetch pod metrics list", zap.Error(err))
|
||||
} else {
|
||||
gp.logger.Debug("pods found", zap.Any("length", len(podMetricsList.Items)))
|
||||
for _, val := range podMetricsList.Items {
|
||||
p, _ := resource.ParseQuantity("0m")
|
||||
for _, container := range val.Containers {
|
||||
p.Add(container.Usage["cpu"])
|
||||
}
|
||||
if value, ok := gp.podFSVCMap.Load(val.ObjectMeta.Name); ok {
|
||||
if valArray, ok1 := value.([]interface{}); ok1 {
|
||||
function, address := valArray[0], valArray[1]
|
||||
gp.fsCache.SetCPUUtilizaton(function.(string), address.(string), p)
|
||||
gp.logger.Info(fmt.Sprintf("updated function %s, address %s, cpuUsage %+v", function.(string), address.(string), p))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// choosePod picks a ready pod from the pool and relabels it, waiting if necessary.
|
||||
// returns the key and pod API object.
|
||||
func (gp *GenericPool) choosePod(newLabels map[string]string) (string, *apiv1.Pod, error) {
|
||||
@@ -564,7 +603,6 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
|
||||
gp.scheduleDeletePod(pod.ObjectMeta.Name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gp.logger.Info("specialized pod", zap.String("pod", pod.ObjectMeta.Name), zap.Any("function", fn.ObjectMeta))
|
||||
|
||||
var svcHost string
|
||||
@@ -623,6 +661,19 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
|
||||
UID: pod.ObjectMeta.UID,
|
||||
},
|
||||
}
|
||||
cpuUsage := resource.MustParse("0m")
|
||||
for _, container := range pod.Spec.Containers {
|
||||
val := *container.Resources.Limits.Cpu()
|
||||
cpuUsage.Add(val)
|
||||
}
|
||||
|
||||
// set cpuLimit to 85th percentage of the cpuUsage
|
||||
cpuLimit, err := gp.getPercent(cpuUsage, 0.85)
|
||||
if err != nil {
|
||||
gp.logger.Error("failed to get 85 of CPU usage", zap.Error(err))
|
||||
cpuLimit = cpuUsage
|
||||
}
|
||||
gp.logger.Debug("cpuLimit set to", zap.Any("cpulimit", cpuLimit))
|
||||
|
||||
m := fn.ObjectMeta // only cache necessary part
|
||||
fsvc := &fscache.FuncSvc{
|
||||
@@ -632,10 +683,12 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
|
||||
Address: svcHost,
|
||||
KubernetesObjects: kubeObjRefs,
|
||||
Executor: fv1.ExecutorTypePoolmgr,
|
||||
CPULimit: cpuLimit,
|
||||
Ctime: time.Now(),
|
||||
Atime: time.Now(),
|
||||
}
|
||||
|
||||
gp.podFSVCMap.Store(pod.ObjectMeta.Name, []interface{}{crd.CacheKey(fsvc.Function), fsvc.Address})
|
||||
gp.fsCache.AddFunc(*fsvc)
|
||||
|
||||
gp.fsCache.IncreaseColdStarts(fn.ObjectMeta.Name, string(fn.ObjectMeta.UID))
|
||||
@@ -643,6 +696,12 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
|
||||
return fsvc, nil
|
||||
}
|
||||
|
||||
// getPercent returns x percent of the quantity i.e multiple it x/100
|
||||
func (gp *GenericPool) getPercent(cpuUsage resource.Quantity, percentage float64) (resource.Quantity, error) {
|
||||
val := int64(math.Ceil(float64(cpuUsage.MilliValue()) * percentage))
|
||||
return resource.ParseQuantity(fmt.Sprintf("%dm", val))
|
||||
}
|
||||
|
||||
// destroys the pool -- the deployment, replicaset and pods
|
||||
func (gp *GenericPool) destroy() error {
|
||||
close(gp.stopReadyPodControllerCh)
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
k8sInformers "k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
k8sCache "k8s.io/client-go/tools/cache"
|
||||
metricsclient "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
||||
"github.com/fission/fission/pkg/cache"
|
||||
@@ -62,6 +63,7 @@ type (
|
||||
|
||||
pools map[string]*GenericPool
|
||||
kubernetesClient *kubernetes.Clientset
|
||||
metricsClient *metricsclient.Clientset
|
||||
namespace string
|
||||
|
||||
fissionClient *crd.FissionClient
|
||||
@@ -97,6 +99,7 @@ func MakeGenericPoolManager(
|
||||
logger *zap.Logger,
|
||||
fissionClient *crd.FissionClient,
|
||||
kubernetesClient *kubernetes.Clientset,
|
||||
metricsClient *metricsclient.Clientset,
|
||||
functionNamespace string,
|
||||
fetcherConfig *fetcherConfig.Config,
|
||||
instanceID string) executortype.ExecutorType {
|
||||
@@ -107,6 +110,7 @@ func MakeGenericPoolManager(
|
||||
logger: gpmLogger,
|
||||
pools: make(map[string]*GenericPool),
|
||||
kubernetesClient: kubernetesClient,
|
||||
metricsClient: metricsClient,
|
||||
namespace: functionNamespace,
|
||||
fissionClient: fissionClient,
|
||||
functionEnv: cache.MakeCache(10*time.Second, 0),
|
||||
@@ -172,7 +176,11 @@ func (gpm *GenericPoolManager) GetFuncSvc(ctx context.Context, fn *fv1.Function)
|
||||
}
|
||||
|
||||
func (gpm *GenericPoolManager) GetFuncSvcFromCache(fn *fv1.Function) (*fscache.FuncSvc, error) {
|
||||
return gpm.fsCache.GetFuncSvc(&fn.ObjectMeta)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (gpm *GenericPoolManager) GetFuncSvcFromPoolCache(fn *fv1.Function, requestsPerPod int) (*fscache.FuncSvc, int, error) {
|
||||
return gpm.fsCache.GetFuncSvc(&fn.ObjectMeta, requestsPerPod)
|
||||
}
|
||||
|
||||
func (gpm *GenericPoolManager) DeleteFuncSvcFromCache(fsvc *fscache.FuncSvc) {
|
||||
@@ -183,10 +191,6 @@ func (gpm *GenericPoolManager) UnTapService(key string, svcHost string) {
|
||||
gpm.fsCache.MarkAvailable(key, svcHost)
|
||||
}
|
||||
|
||||
func (gpm *GenericPoolManager) GetTotalAvailable(fn *fv1.Function) int {
|
||||
return gpm.fsCache.GetTotalAvailable(&fn.ObjectMeta)
|
||||
}
|
||||
|
||||
func (gpm *GenericPoolManager) TapService(svcHost string) error {
|
||||
err := gpm.fsCache.TouchByAddress(svcHost)
|
||||
if err != nil {
|
||||
@@ -457,7 +461,7 @@ func (gpm *GenericPoolManager) service() {
|
||||
}
|
||||
|
||||
pool, err = MakeGenericPool(gpm.logger,
|
||||
gpm.fissionClient, gpm.kubernetesClient, req.env, poolsize,
|
||||
gpm.fissionClient, gpm.kubernetesClient, gpm.metricsClient, req.env, poolsize,
|
||||
ns, gpm.namespace, gpm.fsCache, gpm.fetcherConfig, gpm.instanceID, gpm.enableIstio)
|
||||
if err != nil {
|
||||
req.responseChannel <- &response{error: err}
|
||||
|
||||
@@ -23,7 +23,6 @@ func (gp *GenericPool) startReadyPodController() {
|
||||
options.FieldSelector = "status.phase=Running"
|
||||
}
|
||||
readyPodWatcher := cache.NewFilteredListWatchFromClient(gp.kubernetesClient.CoreV1().RESTClient(), "pods", gp.namespace, optionsModifier)
|
||||
|
||||
gp.readyPodQueue = workqueue.NewDelayingQueue()
|
||||
gp.readyPodIndexer, gp.readyPodController = cache.NewIndexerInformer(readyPodWatcher, &apiv1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
@@ -30,7 +31,7 @@ import (
|
||||
"github.com/fission/fission/pkg/cache"
|
||||
"github.com/fission/fission/pkg/crd"
|
||||
ferror "github.com/fission/fission/pkg/error"
|
||||
poolcache "github.com/fission/fission/pkg/newcache"
|
||||
"github.com/fission/fission/pkg/poolcache"
|
||||
)
|
||||
|
||||
type fscRequestType int
|
||||
@@ -54,6 +55,7 @@ type (
|
||||
Address string // Host:Port or IP:Port that the function's service can be reached at.
|
||||
KubernetesObjects []apiv1.ObjectReference // Kubernetes Objects (within the function namespace)
|
||||
Executor fv1.ExecutorType
|
||||
CPULimit resource.Quantity
|
||||
|
||||
Ctime time.Time
|
||||
Atime time.Time
|
||||
@@ -147,8 +149,7 @@ func (fsc *FunctionServiceCache) service() {
|
||||
fscs := fsc.connFunctionCache.ListAvailableValue()
|
||||
funcObjects := make([]*FuncSvc, 0)
|
||||
for _, funcSvc := range fscs {
|
||||
fsvc := funcSvc.(*FuncSvc)
|
||||
if time.Since(fsvc.Atime) > req.age {
|
||||
if fsvc, ok := funcSvc.(*FuncSvc); ok && time.Since(fsvc.Atime) > req.age {
|
||||
funcObjects = append(funcObjects, fsvc)
|
||||
}
|
||||
}
|
||||
@@ -176,14 +177,14 @@ func (fsc *FunctionServiceCache) GetByFunction(m *metav1.ObjectMeta) (*FuncSvc,
|
||||
return &fsvcCopy, nil
|
||||
}
|
||||
|
||||
// GetFuncSvc gets a function service from pool cache using function key.
|
||||
func (fsc *FunctionServiceCache) GetFuncSvc(m *metav1.ObjectMeta) (*FuncSvc, error) {
|
||||
// GetFuncSvc gets a function service from pool cache using function key and returns number of active instances of function pod
|
||||
func (fsc *FunctionServiceCache) GetFuncSvc(m *metav1.ObjectMeta, requestsPerPod int) (*FuncSvc, int, error) {
|
||||
key := crd.CacheKey(m)
|
||||
|
||||
fsvcI, err := fsc.connFunctionCache.GetValue(key)
|
||||
fsvcI, active, err := fsc.connFunctionCache.GetValue(key, requestsPerPod)
|
||||
if err != nil {
|
||||
fsc.logger.Info("Not found in Cache")
|
||||
return nil, err
|
||||
return nil, active, err
|
||||
}
|
||||
|
||||
// update atime
|
||||
@@ -191,7 +192,7 @@ func (fsc *FunctionServiceCache) GetFuncSvc(m *metav1.ObjectMeta) (*FuncSvc, err
|
||||
fsvc.Atime = time.Now()
|
||||
|
||||
fsvcCopy := *fsvc
|
||||
return &fsvcCopy, nil
|
||||
return &fsvcCopy, active, nil
|
||||
}
|
||||
|
||||
// GetByFunctionUID gets a function service from cache using function UUID.
|
||||
@@ -218,7 +219,7 @@ func (fsc *FunctionServiceCache) GetByFunctionUID(uid types.UID) (*FuncSvc, erro
|
||||
|
||||
// AddFunc adds a function service to pool cache.
|
||||
func (fsc *FunctionServiceCache) AddFunc(fsvc FuncSvc) {
|
||||
fsc.connFunctionCache.SetValue(crd.CacheKey(fsvc.Function), fsvc.Address, &fsvc)
|
||||
fsc.connFunctionCache.SetValue(crd.CacheKey(fsvc.Function), fsvc.Address, &fsvc, fsvc.CPULimit)
|
||||
now := time.Now()
|
||||
fsvc.Ctime = now
|
||||
fsvc.Atime = now
|
||||
@@ -226,9 +227,9 @@ func (fsc *FunctionServiceCache) AddFunc(fsvc FuncSvc) {
|
||||
fsc.setFuncAlive(fsvc.Function.Name, string(fsvc.Function.UID), true)
|
||||
}
|
||||
|
||||
// GetTotalAvailable returns the total number active function services.
|
||||
func (fsc *FunctionServiceCache) GetTotalAvailable(m *metav1.ObjectMeta) int {
|
||||
return fsc.connFunctionCache.GetTotalAvailable(crd.CacheKey(m))
|
||||
// SetCPUUtilizaton updates/sets CPUutilization in the pool cache
|
||||
func (fsc *FunctionServiceCache) SetCPUUtilizaton(key string, svcHost string, cpuUsage resource.Quantity) {
|
||||
fsc.connFunctionCache.SetCPUUtilization(key, svcHost, cpuUsage)
|
||||
}
|
||||
|
||||
// MarkAvailable marks the value at key [function][address] as available.
|
||||
@@ -358,6 +359,10 @@ func (fsc *FunctionServiceCache) DeleteFunctionSvc(fsvc *FuncSvc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (fsc *FunctionServiceCache) SetCPUUtilization(key string, svcHost string, cpuUsage resource.Quantity) {
|
||||
fsc.connFunctionCache.SetCPUUtilization(key, svcHost, cpuUsage)
|
||||
}
|
||||
|
||||
// DeleteOld deletes aged function service entries from cache.
|
||||
func (fsc *FunctionServiceCache) DeleteOld(fsvc *FuncSvc, minAge time.Duration) (bool, error) {
|
||||
if time.Since(fsvc.Atime) < minAge {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/core/v1"
|
||||
@@ -171,10 +172,10 @@ func TestFunctionServiceNewCache(t *testing.T) {
|
||||
},
|
||||
Address: "xxx",
|
||||
KubernetesObjects: objects,
|
||||
CPULimit: resource.MustParse("5m"),
|
||||
Ctime: now,
|
||||
Atime: now,
|
||||
}
|
||||
|
||||
fn := &fv1.Function{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
@@ -183,8 +184,10 @@ func TestFunctionServiceNewCache(t *testing.T) {
|
||||
}
|
||||
|
||||
fsc.AddFunc(*fsvc)
|
||||
|
||||
active := fsc.GetTotalAvailable(fsvc.Function)
|
||||
_, active, err := fsc.GetFuncSvc(fsvc.Function, 5)
|
||||
if err != nil {
|
||||
logger.Panic("received error while retrieving value from cache")
|
||||
}
|
||||
if active != 1 {
|
||||
logger.Panic(fmt.Sprintln("active instances not matched expected 1, found ", active))
|
||||
}
|
||||
@@ -192,11 +195,7 @@ func TestFunctionServiceNewCache(t *testing.T) {
|
||||
key := fmt.Sprintf("%v_%v", fn.ObjectMeta.UID, fn.ObjectMeta.ResourceVersion)
|
||||
fsc.MarkAvailable(key, fsvc.Address)
|
||||
|
||||
if fsc.GetTotalAvailable(fsvc.Function) != 0 {
|
||||
log.Panicln("active instances not matched")
|
||||
}
|
||||
|
||||
_, err = fsc.GetFuncSvc(fsvc.Function)
|
||||
_, _, err = fsc.GetFuncSvc(fsvc.Function, 5)
|
||||
if err != nil {
|
||||
logger.Panic("received error while retrieving value from cache")
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func MakeFetcher(logger *zap.Logger, sharedVolumePath string, sharedSecretPath s
|
||||
fLogger.Fatal("error creating shared config directory", zap.Error(err), zap.String("directory", sharedConfigPath))
|
||||
}
|
||||
|
||||
fissionClient, kubeClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubeClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error making the fission / kube client")
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func Commands() *cobra.Command {
|
||||
flag.FnEnvName, flag.FnEntryPoint, flag.FnPkgName,
|
||||
flag.FnExecutorType, flag.FnCfgMap, flag.FnSecret,
|
||||
flag.FnSpecializationTimeout, flag.FnExecutionTimeout,
|
||||
flag.FnIdleTimeout, flag.FnConcurrency,
|
||||
flag.FnIdleTimeout, flag.FnConcurrency, flag.FnRequestsPerPod,
|
||||
|
||||
// TODO retired pkg & trigger related flags from function cmd
|
||||
flag.PkgCode, flag.PkgSrcArchive, flag.PkgDeployArchive,
|
||||
@@ -86,7 +86,7 @@ func Commands() *cobra.Command {
|
||||
flag.FnEnvName, flag.FnEntryPoint, flag.FnPkgName,
|
||||
flag.FnExecutorType, flag.FnSecret, flag.FnCfgMap,
|
||||
flag.FnSpecializationTimeout, flag.FnExecutionTimeout,
|
||||
flag.FnIdleTimeout, flag.FnConcurrency,
|
||||
flag.FnIdleTimeout, flag.FnConcurrency, flag.FnRequestsPerPod,
|
||||
|
||||
flag.PkgCode, flag.PkgSrcArchive, flag.PkgDeployArchive,
|
||||
flag.PkgSrcChecksum, flag.PkgDeployChecksum, flag.PkgInsecure,
|
||||
|
||||
@@ -102,6 +102,8 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
|
||||
fnConcurrency = input.Int(flagkey.FnConcurrency)
|
||||
}
|
||||
|
||||
requestsPerPod := input.Int(flagkey.FnRequestsPerPod)
|
||||
|
||||
pkgName := input.String(flagkey.FnPackageName)
|
||||
|
||||
secretNames := input.StringSlice(flagkey.FnSecret)
|
||||
@@ -301,6 +303,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
|
||||
FunctionTimeout: fnTimeout,
|
||||
IdleTimeout: &fnIdleTimeout,
|
||||
Concurrency: fnConcurrency,
|
||||
RequestsPerPod: requestsPerPod,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,10 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
|
||||
function.Spec.Concurrency = input.Int(flagkey.FnConcurrency)
|
||||
}
|
||||
|
||||
if input.IsSet(flagkey.FnRequestsPerPod) {
|
||||
function.Spec.RequestsPerPod = input.Int(flagkey.FnRequestsPerPod)
|
||||
}
|
||||
|
||||
if len(pkgName) == 0 {
|
||||
pkgName = function.Spec.Package.PackageRef.Name
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ var (
|
||||
FnTestQuery = Flag{Type: StringSlice, Name: flagkey.FnTestQuery, Short: "q", Usage: "Request query parameters: -q key1=value1 -q key2=value2"}
|
||||
FnIdleTimeout = Flag{Type: Int, Name: flagkey.FnIdleTimeout, Usage: "The length of time (in seconds) that a function is idle before pod(s) are eligible for recycling", DefaultValue: 120}
|
||||
FnConcurrency = Flag{Type: Int, Name: flagkey.FnConcurrency, Aliases: []string{"con"}, Usage: "Maximum number of pods specialized concurrently to serve requests", DefaultValue: 5}
|
||||
FnRequestsPerPod = Flag{Type: Int, Name: flagkey.FnRequestsPerPod, Aliases: []string{"rpp"}, Usage: "Maximum number of concurrent requests that can be served by a specialized pod", DefaultValue: 1}
|
||||
|
||||
HtName = Flag{Type: String, Name: flagkey.HtName, Usage: "HTTP trigger name"}
|
||||
HtMethod = Flag{Type: String, Name: flagkey.HtMethod, Usage: "HTTP Method: GET|POST|PUT|DELETE|HEAD", DefaultValue: http.MethodGet}
|
||||
|
||||
@@ -64,6 +64,7 @@ const (
|
||||
FnTestQuery = "query"
|
||||
FnIdleTimeout = "idletimeout"
|
||||
FnConcurrency = "concurrency"
|
||||
FnRequestsPerPod = "requestsperpod"
|
||||
|
||||
HtName = resourceName
|
||||
HtMethod = "method"
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
func Start(logger *zap.Logger, routerUrl string) error {
|
||||
fissionClient, kubeClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubeClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get fission or kubernetes client")
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func Start() {
|
||||
}
|
||||
}
|
||||
go symlinkReaper(zapLogger)
|
||||
_, kubernetesClient, _, err := crd.MakeFissionClient()
|
||||
_, kubernetesClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
log.Fatalf("Error starting pod watcher: %v", err)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func getAuthTriggerClient(namespace string) (dynamic.ResourceInterface, error) {
|
||||
// StartScalerManager watches for changes in MessageQueueTrigger and,
|
||||
// Based on changes, it Creates, Updates and Deletes Objects of Kind ScaledObjects, AuthenticationTriggers and Deployments
|
||||
func StartScalerManager(logger *zap.Logger, routerURL string) error {
|
||||
fissionClient, kubeClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubeClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ import (
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
dynfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
@@ -386,40 +382,44 @@ func newUnstructured(apiVersion, kind, namespace, name, resourceVersion string)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getResourceVersion(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
client := dynfake.NewSimpleDynamicClient(scheme, newUnstructured(apiVersion, "ScaledObject", "default", "test-1", "12345"))
|
||||
dynamicResourceClient := client.Resource(schema.GroupVersionResource{
|
||||
Group: Group,
|
||||
Version: Version,
|
||||
Resource: "scaledobjects",
|
||||
})
|
||||
type args struct {
|
||||
scaledObjectName string
|
||||
kedaClient dynamic.ResourceInterface
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantVersion string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid Resource", args{"test-1", dynamicResourceClient}, "12345", false},
|
||||
{"Invalid Resource", args{"test-2", dynamicResourceClient}, "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotVersion, err := getResourceVersion(tt.args.scaledObjectName, tt.args.kedaClient)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getResourceVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotVersion != tt.wantVersion {
|
||||
t.Errorf("getResourceVersion() = %v, want %v", gotVersion, tt.wantVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// commented because this fails with k8s.io/client-go v0.17.2
|
||||
// func Test_getResourceVersion(t *testing.T) {
|
||||
// scheme := runtime.NewScheme()
|
||||
// fakeDynamicClient := &dynfake.FakeDynamicClient
|
||||
// client := dynfake.NewSimpleDynamicClient(scheme, newUnstructured(apiVersion, "ScaledObject", "default", "test-1", "12345"))
|
||||
// dynamicResourceClient := client.Resource(schema.GroupVersionResource{
|
||||
// Group: Group,
|
||||
// Version: Version,
|
||||
// Resource: "scaledobjects",
|
||||
// })
|
||||
// fmt.Println(dynamicResourceClient.List(metav1.ListOptions{}))
|
||||
// fmt.Println(dynamicResourceClient.Get("test-1", metav1.GetOptions{}))
|
||||
// type args struct {
|
||||
// scaledObjectName string
|
||||
// kedaClient dynamic.ResourceInterface
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// wantVersion string
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {"Valid Resource", args{"test-1", dynamicResourceClient}, "12345", false},
|
||||
// {"Invalid Resource", args{"test-2", dynamicResourceClient}, "", true},
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// gotVersion, err := getResourceVersion(tt.args.scaledObjectName, tt.args.kedaClient)
|
||||
// if (err != nil) != tt.wantErr {
|
||||
// t.Errorf("getResourceVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// return
|
||||
// }
|
||||
// if gotVersion != tt.wantVersion {
|
||||
// t.Errorf("getResourceVersion() = %v, want %v", gotVersion, tt.wantVersion)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
func Test_getAuthTriggerSpec(t *testing.T) {
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package poolcache
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
log.Panicf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolCache(t *testing.T) {
|
||||
c := NewPoolCache()
|
||||
|
||||
c.SetValue("func", "ip", "value")
|
||||
|
||||
c.SetValue("func2", "ip2", "value2")
|
||||
|
||||
c.SetValue("func2", "ip22", "value22")
|
||||
|
||||
cc := c.ListAvailableValue()
|
||||
if len(cc) != 0 {
|
||||
log.Panicf("expected 0 available items")
|
||||
}
|
||||
active := c.GetTotalAvailable("func2")
|
||||
if active != 2 {
|
||||
log.Panicf("expected 2 items")
|
||||
}
|
||||
|
||||
checkErr(c.DeleteValue("func2", "ip2"))
|
||||
|
||||
c.MarkAvailable("func", "ip")
|
||||
cc = c.ListAvailableValue()
|
||||
|
||||
if len(cc) != 1 {
|
||||
log.Panic("expected 1 available items, received", len(cc))
|
||||
}
|
||||
_, err := c.GetValue("func")
|
||||
checkErr(err)
|
||||
|
||||
checkErr(c.DeleteValue("func", "ip"))
|
||||
|
||||
_, err = c.GetValue("func")
|
||||
if err == nil {
|
||||
log.Panicf("found deleted element")
|
||||
}
|
||||
|
||||
c.SetValue("expires", "42", "all answers")
|
||||
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
_, err = c.GetValue("expires")
|
||||
if err == nil {
|
||||
log.Panicf("found expired element")
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
ferror "github.com/fission/fission/pkg/error"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
type requestType int
|
||||
@@ -33,13 +34,16 @@ const (
|
||||
setValue
|
||||
markAvailable
|
||||
deleteValue
|
||||
setCPUUtilization
|
||||
)
|
||||
|
||||
type (
|
||||
// value used as "value" in cache
|
||||
value struct {
|
||||
val interface{}
|
||||
isActive bool
|
||||
val interface{}
|
||||
activeRequests int // number of requests served by function pod
|
||||
currentCPUUsage resource.Quantity // current cpu usage of the specialized function pod
|
||||
cpuLimit resource.Quantity // if currentCPUUsage is more than cpuLimit cache miss occurs in getValue request
|
||||
}
|
||||
// Cache is simple cache having two keys [function][address] mapped to value and requestChannel for operation on it
|
||||
Cache struct {
|
||||
@@ -52,13 +56,15 @@ type (
|
||||
function interface{}
|
||||
address interface{}
|
||||
value interface{}
|
||||
requestsPerPod int
|
||||
cpuUsage resource.Quantity
|
||||
responseChannel chan *response
|
||||
}
|
||||
response struct {
|
||||
error
|
||||
allValues []interface{}
|
||||
value interface{}
|
||||
totalAvailable int
|
||||
allValues []interface{}
|
||||
value interface{}
|
||||
totalActive int
|
||||
}
|
||||
)
|
||||
|
||||
@@ -85,57 +91,52 @@ func (c *Cache) service() {
|
||||
fmt.Sprintf("function Name '%v' not found", req.function))
|
||||
} else {
|
||||
for addr := range values {
|
||||
if !values[addr].isActive {
|
||||
// update atime
|
||||
if values[addr].activeRequests < req.requestsPerPod && values[addr].currentCPUUsage.Cmp(values[addr].cpuLimit) < 1 {
|
||||
// mark active
|
||||
values[addr].isActive = true
|
||||
values[addr].activeRequests++
|
||||
resp.value = values[addr].val
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
resp.error = ferror.MakeError(ferror.ErrorNotFound, fmt.Sprintf("function '%v' No inactive function found", req.function))
|
||||
if !found {
|
||||
resp.error = ferror.MakeError(ferror.ErrorNotFound, fmt.Sprintf("function '%v' all functions are busy", req.function))
|
||||
}
|
||||
resp.totalActive = len(values)
|
||||
}
|
||||
req.responseChannel <- resp
|
||||
case setValue:
|
||||
if _, ok := c.cache[req.function]; !ok {
|
||||
c.cache[req.function] = make(map[interface{}]*value)
|
||||
}
|
||||
if _, ok := c.cache[req.function][req.address]; !ok {
|
||||
c.cache[req.function][req.address] = &value{}
|
||||
}
|
||||
c.cache[req.function][req.address].val = req.value
|
||||
c.cache[req.function][req.address].activeRequests++
|
||||
c.cache[req.function][req.address].cpuLimit = req.cpuUsage
|
||||
case listAvailableValue:
|
||||
vals := make([]interface{}, 0)
|
||||
for _, values := range c.cache {
|
||||
for _, value := range values {
|
||||
if !value.isActive {
|
||||
if value.activeRequests == 0 {
|
||||
vals = append(vals, value.val)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.allValues = vals
|
||||
req.responseChannel <- resp
|
||||
case getTotalAvailable:
|
||||
if values, ok := c.cache[req.function]; ok {
|
||||
for addr := range values {
|
||||
if values[addr].isActive {
|
||||
resp.totalAvailable++
|
||||
}
|
||||
}
|
||||
}
|
||||
req.responseChannel <- resp
|
||||
case setValue:
|
||||
if _, ok := c.cache[req.function]; ok {
|
||||
c.cache[req.function][req.address] = &value{
|
||||
val: req.value,
|
||||
isActive: true,
|
||||
}
|
||||
} else {
|
||||
case setCPUUtilization:
|
||||
if _, ok := c.cache[req.function]; !ok {
|
||||
c.cache[req.function] = make(map[interface{}]*value)
|
||||
c.cache[req.function][req.address] = &value{
|
||||
val: req.value,
|
||||
isActive: true,
|
||||
}
|
||||
}
|
||||
if _, ok := c.cache[req.function][req.address]; ok {
|
||||
c.cache[req.function][req.address].currentCPUUsage = req.cpuUsage
|
||||
}
|
||||
case markAvailable:
|
||||
if _, ok := c.cache[req.function]; ok {
|
||||
if _, ok = c.cache[req.function][req.address]; ok {
|
||||
c.cache[req.function][req.address].isActive = false
|
||||
c.cache[req.function][req.address].activeRequests--
|
||||
}
|
||||
}
|
||||
case deleteValue:
|
||||
@@ -150,15 +151,16 @@ func (c *Cache) service() {
|
||||
}
|
||||
|
||||
// GetValue returns a value interface with status inActive else return error
|
||||
func (c *Cache) GetValue(function interface{}) (interface{}, error) {
|
||||
func (c *Cache) GetValue(function interface{}, requestsPerPod int) (interface{}, int, error) {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: getValue,
|
||||
function: function,
|
||||
requestsPerPod: requestsPerPod,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
return resp.value, resp.error
|
||||
return resp.value, resp.totalActive, resp.error
|
||||
}
|
||||
|
||||
// ListAvailableValue returns a list of the available function services stored in the Cache
|
||||
@@ -172,30 +174,30 @@ func (c *Cache) ListAvailableValue() []interface{} {
|
||||
return resp.allValues
|
||||
}
|
||||
|
||||
// GetTotalAvailable returns a total number active function services
|
||||
func (c *Cache) GetTotalAvailable(function interface{}) int {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: getTotalAvailable,
|
||||
function: function,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
resp := <-respChannel
|
||||
return resp.totalAvailable
|
||||
}
|
||||
|
||||
// SetValue marks the value at key [function][address] as active(begin used)
|
||||
func (c *Cache) SetValue(function, address, value interface{}) {
|
||||
func (c *Cache) SetValue(function, address, value interface{}, cpuLimit resource.Quantity) {
|
||||
respChannel := make(chan *response)
|
||||
c.requestChannel <- &request{
|
||||
requestType: setValue,
|
||||
function: function,
|
||||
address: address,
|
||||
value: value,
|
||||
cpuUsage: cpuLimit,
|
||||
responseChannel: respChannel,
|
||||
}
|
||||
}
|
||||
|
||||
// SetCPUUtilization updates/sets the CPU utilization limit for the pod
|
||||
func (c *Cache) SetCPUUtilization(function, address interface{}, cpuUsage resource.Quantity) {
|
||||
c.requestChannel <- &request{
|
||||
requestType: setCPUUtilization,
|
||||
function: function,
|
||||
address: address,
|
||||
cpuUsage: cpuUsage,
|
||||
responseChannel: make(chan *response),
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAvailable marks the value at key [function][address] as available
|
||||
func (c *Cache) MarkAvailable(function, address interface{}) {
|
||||
respChannel := make(chan *response)
|
||||
@@ -0,0 +1,58 @@
|
||||
package poolcache
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
log.Panicf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolCache(t *testing.T) {
|
||||
c := NewPoolCache()
|
||||
|
||||
c.SetValue("func", "ip", "value", resource.MustParse("45m"))
|
||||
|
||||
c.SetValue("func2", "ip2", "value2", resource.MustParse("50m"))
|
||||
|
||||
c.SetValue("func2", "ip22", "value22", resource.MustParse("33m"))
|
||||
|
||||
checkErr(c.DeleteValue("func2", "ip2"))
|
||||
|
||||
cc := c.ListAvailableValue()
|
||||
if len(cc) != 0 {
|
||||
log.Panicf("expected 0 available items")
|
||||
}
|
||||
|
||||
c.MarkAvailable("func", "ip")
|
||||
|
||||
_, active, err := c.GetValue("func", 5)
|
||||
if active != 1 {
|
||||
log.Panicln("Expected 1 active, found", active)
|
||||
}
|
||||
checkErr(err)
|
||||
|
||||
checkErr(c.DeleteValue("func", "ip"))
|
||||
|
||||
_, active, err = c.GetValue("func", 5)
|
||||
if err == nil {
|
||||
log.Panicf("found deleted element")
|
||||
}
|
||||
|
||||
c.SetValue("cpulimit", "100", "value", resource.MustParse("3m"))
|
||||
c.SetCPUUtilization("cpulimit", "100", resource.MustParse("4m"))
|
||||
|
||||
_, _, err = c.GetValue("cpulimit", 5)
|
||||
|
||||
if err == nil {
|
||||
log.Panicf("received pod address with higher CPU usage than limit")
|
||||
}
|
||||
c.SetCPUUtilization("cpulimit", "100", resource.MustParse("2m"))
|
||||
_, _, err = c.GetValue("cpulimit", 5)
|
||||
checkErr(err)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func Start(logger *zap.Logger, port int, executorURL string) {
|
||||
|
||||
fmap := makeFunctionServiceMap(logger, time.Minute)
|
||||
|
||||
fissionClient, kubeClient, _, err := crd.MakeFissionClient()
|
||||
fissionClient, kubeClient, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
logger.Fatal("error connecting to kubernetes API", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type ArchivePruner struct {
|
||||
const defaultPruneInterval int = 60 // in minutes
|
||||
|
||||
func MakeArchivePruner(logger *zap.Logger, stowClient *StowClient, pruneInterval time.Duration) (*ArchivePruner, error) {
|
||||
crdClient, _, _, err := crd.MakeFissionClient()
|
||||
crdClient, _, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
func Start(logger *zap.Logger, routerUrl string) error {
|
||||
fissionClient, _, _, err := crd.MakeFissionClient()
|
||||
fissionClient, _, _, _, err := crd.MakeFissionClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get fission or kubernetes client")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user