Drop unreleased features (record & replay) (#1406)

1. The records are stored in redis which is not migratable to another cluster for the testing purposes.
2. Some of the requests fields are not recorded.
3. People should consider using https://github.com/buger/goreplay which is an existing mature and well-tested solution for testing purposes.
This commit is contained in:
Ta-Ching Chen
2019-11-13 15:10:32 +08:00
committed by GitHub
parent 1cda7e051b
commit cf2d35291e
51 changed files with 10 additions and 3544 deletions
-43
View File
@@ -40,7 +40,6 @@ import (
ferror "github.com/fission/fission/pkg/error"
"github.com/fission/fission/pkg/error/network"
executorClient "github.com/fission/fission/pkg/executor/client"
"github.com/fission/fission/pkg/redis"
"github.com/fission/fission/pkg/throttler"
"github.com/fission/fission/pkg/types"
)
@@ -54,15 +53,12 @@ type (
functionHandler struct {
logger *zap.Logger
fmap *functionServiceMap
frmap *functionRecorderMap
trmap *triggerRecorderMap
executor *executorClient.Client
function *metav1.ObjectMeta
httpTrigger *fv1.HTTPTrigger
functionMetadataMap map[string]*metav1.ObjectMeta
fnWeightDistributionList []FunctionWeightDistribution
tsRoundTripperParams *tsRoundTripperParams
recorderName string
isDebugEnv bool
svcAddrUpdateThrottler *throttler.Throttler
functionTimeoutMap map[k8stypes.UID]int
@@ -153,26 +149,6 @@ func (roundTripper RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Res
// Set forwarded host header if not exists
roundTripper.addForwardedHostHeader(req)
// TODO: Keep? --> Needed for queries encoded in URL before they're stripped by the proxy
originalUrl := *req.URL
// Iff this request needs to be recorded, we save the body
var postedBody string
if len(roundTripper.funcHandler.recorderName) > 0 {
if req.ContentLength > 0 {
p := make([]byte, req.ContentLength)
buf, _ := ioutil.ReadAll(req.Body)
// We need two io readers because a single reader will drain the buffer, hence we keep a replacement copy
rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf))
rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf))
rdr1.Read(p)
postedBody = string(p)
roundTripper.logger.Debug("roundtripper posted body", zap.String("body", postedBody))
req.Body = rdr2
}
}
fnMeta := roundTripper.funcHandler.function
// Metrics stuff
@@ -315,22 +291,6 @@ func (roundTripper RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Res
functionCallCompleted(funcMetricLabels, httpMetricLabels,
overhead, time.Since(startTime), resp.ContentLength)
if len(roundTripper.funcHandler.recorderName) > 0 {
if roundTripper.funcHandler.httpTrigger != nil {
trigger := roundTripper.funcHandler.httpTrigger.Metadata.Name
redis.Record(
roundTripper.logger,
trigger,
roundTripper.funcHandler.recorderName,
req.Header.Get("X-Fission-ReqUID"), req, originalUrl, postedBody, resp, fnMeta.Namespace,
time.Now().UnixNano(),
)
} else {
roundTripper.logger.Error("no http trigger attached for recorder",
zap.String("recorder", roundTripper.funcHandler.recorderName))
}
}
// return response back to user
return resp, nil
} else if i >= roundTripper.funcHandler.tsRoundTripperParams.maxRetries-1 {
@@ -435,9 +395,6 @@ func (fh functionHandler) handler(responseWriter http.ResponseWriter, request *h
fh.logger.Debug("chosen function backend's metadata", zap.Any("metadata", fh.function))
}
// set record id
setRecordRequestIDHeader(fh.recorderName, request)
// url path
setPathInfoToHeader(request)
-66
View File
@@ -1,66 +0,0 @@
/*
Copyright 2018 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"time"
"go.uber.org/zap"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
"github.com/fission/fission/pkg/cache"
ferror "github.com/fission/fission/pkg/error"
)
type (
functionRecorderMap struct {
logger *zap.Logger
cache *cache.Cache // map[string]*fv1.Recorder
}
)
// Why do we need an expiry?
func makeFunctionRecorderMap(logger *zap.Logger, expiry time.Duration) *functionRecorderMap {
return &functionRecorderMap{
logger: logger.Named("function_recorder_map"),
cache: cache.MakeCache(expiry, 0),
}
}
func (frmap *functionRecorderMap) lookup(function string) (*fv1.Recorder, error) {
item, err := frmap.cache.Get(function)
if err != nil {
return nil, err
}
u := item.(*fv1.Recorder)
return u, nil
}
func (frmap *functionRecorderMap) assign(function string, recorder *fv1.Recorder) {
_, err := frmap.cache.Set(function, recorder)
if err != nil {
if e, ok := err.(ferror.Error); ok && e.Code == ferror.ErrorNameExists {
return
}
frmap.logger.Error("error caching recorder for function name with a different value", zap.Error(err))
}
}
func (frmap *functionRecorderMap) remove(function string) error {
return frmap.cache.Delete(function)
}
+3 -63
View File
@@ -53,14 +53,13 @@ type HTTPTriggerSet struct {
functions []fv1.Function
funcStore k8sCache.Store
funcController k8sCache.Controller
recorderSet *RecorderSet
updateRouterRequestChannel chan struct{}
tsRoundTripperParams *tsRoundTripperParams
isDebugEnv bool
svcAddrUpdateThrottler *throttler.Throttler
}
func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, frmap *functionRecorderMap, trmap *triggerRecorderMap, fissionClient *crd.FissionClient,
func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, fissionClient *crd.FissionClient,
kubeClient *kubernetes.Clientset, executor *executorClient.Client, crdClient *rest.RESTClient, params *tsRoundTripperParams, isDebugEnv bool, actionThrottler *throttler.Throttler) (*HTTPTriggerSet, k8sCache.Store, k8sCache.Store) {
httpTriggerSet := &HTTPTriggerSet{
@@ -76,9 +75,9 @@ func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, frmap *fun
isDebugEnv: isDebugEnv,
svcAddrUpdateThrottler: actionThrottler,
}
var tStore, fnStore, rStore k8sCache.Store
var tStore, fnStore k8sCache.Store
var tController, fnController k8sCache.Controller
var recorderSet *RecorderSet
if httpTriggerSet.crdClient != nil {
tStore, tController = httpTriggerSet.initTriggerController()
httpTriggerSet.triggerStore = tStore
@@ -87,8 +86,6 @@ func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, frmap *fun
httpTriggerSet.funcStore = fnStore
httpTriggerSet.funcController = fnController
}
recorderSet = MakeRecorderSet(logger, httpTriggerSet, crdClient, rStore, frmap, trmap)
httpTriggerSet.recorderSet = recorderSet
return httpTriggerSet, tStore, fnStore
}
@@ -106,11 +103,6 @@ func (ts *HTTPTriggerSet) subscribeRouter(ctx context.Context, mr *mutableRouter
go ts.syncTriggers()
go ts.runWatcher(ctx, ts.funcController)
go ts.runWatcher(ctx, ts.triggerController)
if ts.recorderSet.recController != nil {
go ts.runWatcher(ctx, ts.recorderSet.recController)
} else {
ts.logger.Fatal("failed to run recorder controller")
}
}
func defaultHomeHandler(w http.ResponseWriter, r *http.Request) {
@@ -140,12 +132,6 @@ func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router
continue
}
var recorderName string
recorder, err := ts.recorderSet.triggerRecorderMap.lookup(trigger.Metadata.Name)
if err == nil && recorder != nil {
recorderName = recorder.Spec.Name
}
if rr.resolveResultType != resolveResultSingleFunction && rr.resolveResultType != resolveResultMultipleFunctions {
// not implemented yet
ts.logger.Panic("resolve result type not implemented", zap.Any("type", rr.resolveResultType))
@@ -154,14 +140,11 @@ func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router
fh := &functionHandler{
logger: ts.logger.Named(trigger.Metadata.Name),
fmap: ts.functionServiceMap,
frmap: ts.recorderSet.functionRecorderMap,
trmap: ts.recorderSet.triggerRecorderMap,
executor: ts.executor,
httpTrigger: &trigger,
functionMetadataMap: rr.functionMetadataMap,
fnWeightDistributionList: rr.functionWtDistributionList,
tsRoundTripperParams: ts.tsRoundTripperParams,
recorderName: recorderName,
isDebugEnv: ts.isDebugEnv,
svcAddrUpdateThrottler: ts.svcAddrUpdateThrottler,
functionTimeoutMap: fnTimeoutMap,
@@ -205,21 +188,12 @@ func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router
for _, function := range ts.functions {
m := function.Metadata
var recorderName string
recorder, err := ts.recorderSet.functionRecorderMap.lookup(m.Name)
if err == nil && recorder != nil {
recorderName = recorder.Spec.Name
}
fh := &functionHandler{
logger: ts.logger.Named(m.Name),
fmap: ts.functionServiceMap,
frmap: ts.recorderSet.functionRecorderMap,
trmap: ts.recorderSet.triggerRecorderMap,
function: &m,
executor: ts.executor,
tsRoundTripperParams: ts.tsRoundTripperParams,
recorderName: recorderName,
isDebugEnv: ts.isDebugEnv,
svcAddrUpdateThrottler: ts.svcAddrUpdateThrottler,
functionTimeoutMap: fnTimeoutMap,
@@ -246,20 +220,11 @@ func (ts *HTTPTriggerSet) initTriggerController() (k8sCache.Store, k8sCache.Cont
trigger := obj.(*fv1.HTTPTrigger)
go createIngress(ts.logger, trigger, ts.kubeClient)
ts.syncTriggers()
// Check if this trigger's function needs to be recorded
fnRef := trigger.Spec.FunctionReference.Name
recorder, err := ts.recorderSet.functionRecorderMap.lookup(fnRef)
if err == nil {
if len(recorder.Spec.Triggers) == 0 {
ts.recorderSet.triggerRecorderMap.assign(trigger.Metadata.Name, recorder)
}
}
},
DeleteFunc: func(obj interface{}) {
ts.syncTriggers()
trigger := obj.(*fv1.HTTPTrigger)
go deleteIngress(ts.logger, trigger, ts.kubeClient)
go ts.recorderSet.DeleteTriggerFromRecorderMap(trigger)
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldTrigger := oldObj.(*fv1.HTTPTrigger)
@@ -285,9 +250,7 @@ func (ts *HTTPTriggerSet) initFunctionController() (k8sCache.Store, k8sCache.Con
ts.syncTriggers()
},
DeleteFunc: func(obj interface{}) {
function := obj.(*fv1.Function)
ts.syncTriggers()
go ts.recorderSet.DeleteFunctionFromRecorderMap(function)
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldFn := oldObj.(*fv1.Function)
@@ -317,29 +280,6 @@ func (ts *HTTPTriggerSet) initFunctionController() (k8sCache.Store, k8sCache.Con
return store, controller
}
func (ts *HTTPTriggerSet) initRecorderController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(ts.crdClient, "recorders", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &fv1.Recorder{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
recorder := obj.(*fv1.Recorder)
ts.recorderSet.newRecorder(recorder)
},
DeleteFunc: func(obj interface{}) {
recorder := obj.(*fv1.Recorder)
ts.recorderSet.disableRecorder(recorder)
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldRecorder := oldObj.(*fv1.Recorder)
newRecorder := newObj.(*fv1.Recorder)
ts.recorderSet.updateRecorder(oldRecorder, newRecorder)
},
},
)
return store, controller
}
func (ts *HTTPTriggerSet) runWatcher(ctx context.Context, controller k8sCache.Controller) {
go func() {
controller.Run(ctx.Done())
-135
View File
@@ -1,135 +0,0 @@
package router
import (
"go.uber.org/zap"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
)
type RecorderSet struct {
logger *zap.Logger
httpTriggerSet *HTTPTriggerSet
crdClient *rest.RESTClient
recStore k8sCache.Store
recController k8sCache.Controller
functionRecorderMap *functionRecorderMap
triggerRecorderMap *triggerRecorderMap
}
func MakeRecorderSet(logger *zap.Logger, httpTriggerSet *HTTPTriggerSet, crdClient *rest.RESTClient, rStore k8sCache.Store, frmap *functionRecorderMap, trmap *triggerRecorderMap) *RecorderSet {
recorderSet := &RecorderSet{
logger: logger.Named("recorder_set"),
httpTriggerSet: httpTriggerSet,
crdClient: crdClient,
recStore: rStore,
functionRecorderMap: frmap,
triggerRecorderMap: trmap,
}
recorderSet.recStore, recorderSet.recController = httpTriggerSet.initRecorderController()
return recorderSet
}
// All new recorders are by default enabled
func (rs *RecorderSet) newRecorder(r *fv1.Recorder) {
function := r.Spec.Function
triggers := r.Spec.Triggers
// If triggers are not explicitly specified during the creation of this recorder,
// keep track of those associated with the function specified [implicitly added triggers]
needTrackByFunction := len(triggers) == 0
rs.functionRecorderMap.assign(function, r)
if needTrackByFunction {
for _, t := range rs.httpTriggerSet.triggerStore.List() {
trigger := *t.(*fv1.HTTPTrigger)
if trigger.Spec.FunctionReference.Name == function {
rs.triggerRecorderMap.assign(trigger.Metadata.Name, r)
}
}
} else {
for _, trigger := range triggers {
rs.triggerRecorderMap.assign(trigger, r)
}
}
rs.httpTriggerSet.syncTriggers()
}
// TODO: Delete or disable?
func (rs *RecorderSet) disableRecorder(r *fv1.Recorder) {
function := r.Spec.Function
triggers := r.Spec.Triggers
rs.logger.Info("disabling recorder",
zap.String("recorder", r.Metadata.Name),
zap.String("function", function))
// Account for function
err := rs.functionRecorderMap.remove(function)
if err != nil {
rs.logger.Error("error disabling recorder (failed to remove function from functionRecorderMap)",
zap.Error(err),
zap.String("recorder", r.Metadata.Name),
zap.String("function", function))
}
// Account for explicitly added triggers
if len(triggers) != 0 {
for _, trigger := range triggers {
err := rs.triggerRecorderMap.remove(trigger)
if err != nil {
rs.logger.Error("error disabling recorder (failed to remove triggers from triggerRecorderMap)",
zap.Error(err),
zap.String("recorder", r.Metadata.Name),
zap.String("function", function),
zap.String("trigger", trigger))
}
}
} else {
// Account for implicitly added triggers
for _, t := range rs.httpTriggerSet.triggerStore.List() {
trigger := *t.(*fv1.HTTPTrigger)
if trigger.Spec.FunctionReference.Name == function {
err := rs.triggerRecorderMap.remove(trigger.Metadata.Name)
if err != nil {
rs.logger.Error("failed to remove trigger from triggerRecorderMap",
zap.Error(err),
zap.String("recorder", r.Metadata.Name),
zap.String("function", function),
zap.String("trigger", trigger.Metadata.Name))
}
}
}
}
rs.httpTriggerSet.syncTriggers()
}
func (rs *RecorderSet) updateRecorder(old *fv1.Recorder, newer *fv1.Recorder) {
if newer.Spec.Enabled {
rs.newRecorder(newer) // TODO: Test this
} else {
rs.disableRecorder(old)
}
}
func (rs *RecorderSet) DeleteTriggerFromRecorderMap(trigger *fv1.HTTPTrigger) {
err := rs.triggerRecorderMap.remove(trigger.Metadata.Name)
if err != nil {
rs.logger.Error("failed to remove trigger from triggerRecorderMap", zap.Error(err))
}
}
func (rs *RecorderSet) DeleteFunctionFromRecorderMap(function *fv1.Function) {
err := rs.functionRecorderMap.remove(function.Metadata.Name)
if err != nil {
rs.logger.Error("failed to remove function from functionRecorderMap", zap.Error(err))
}
}
-10
View File
@@ -19,10 +19,8 @@ package router
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
uuid "github.com/satori/go.uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -48,11 +46,3 @@ func setPathInfoToHeader(request *http.Request) {
}
request.Header.Set("X-Fission-Full-Url", request.URL.String())
}
// setRecordRequestIDHeader set record ID to request header
func setRecordRequestIDHeader(recorderName string, request *http.Request) {
if len(recorderName) > 0 {
reqUID := "REQ" + strings.ToLower(uuid.NewV4().String())
request.Header.Set("X-Fission-ReqUID", reqUID)
}
}
+1 -5
View File
@@ -116,10 +116,6 @@ func Start(logger *zap.Logger, port int, executorUrl string) {
fmap := makeFunctionServiceMap(logger, time.Minute)
frmap := makeFunctionRecorderMap(logger, time.Minute)
trmap := makeTriggerRecorderMap(logger, time.Minute)
fissionClient, kubeClient, _, err := crd.MakeFissionClient()
if err != nil {
logger.Fatal("error connecting to kubernetes API", zap.Error(err))
@@ -226,7 +222,7 @@ func Start(logger *zap.Logger, port int, executorUrl string) {
zap.Bool("default", displayAccessLog))
}
triggers, _, fnStore := makeHTTPTriggerSet(logger.Named("triggerset"), fmap, frmap, trmap, fissionClient, kubeClient, executor, restClient, &tsRoundTripperParams{
triggers, _, fnStore := makeHTTPTriggerSet(logger.Named("triggerset"), fmap, fissionClient, kubeClient, executor, restClient, &tsRoundTripperParams{
timeout: timeout,
timeoutExponent: timeoutExponent,
disableKeepAlive: disableKeepAlive,
+1 -5
View File
@@ -51,12 +51,8 @@ func TestRouter(t *testing.T) {
fmap := makeFunctionServiceMap(logger, 0)
fmap.assign(fn, testServiceUrl)
frmap := makeFunctionRecorderMap(logger, time.Minute)
trmap := makeTriggerRecorderMap(logger, time.Minute)
// HTTP trigger set with a trigger for this function
triggers, _, _ := makeHTTPTriggerSet(logger, fmap, frmap, trmap, nil, nil, nil, nil,
triggers, _, _ := makeHTTPTriggerSet(logger, fmap, nil, nil, nil, nil,
&tsRoundTripperParams{
timeout: 50 * time.Millisecond,
timeoutExponent: 2,
-65
View File
@@ -1,65 +0,0 @@
/*
Copyright 2018 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package router
import (
"time"
"go.uber.org/zap"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
"github.com/fission/fission/pkg/cache"
ferror "github.com/fission/fission/pkg/error"
)
type (
triggerRecorderMap struct {
logger *zap.Logger
cache *cache.Cache // map[string]*fv1.Recorder
}
)
func makeTriggerRecorderMap(logger *zap.Logger, expiry time.Duration) *triggerRecorderMap {
return &triggerRecorderMap{
logger: logger.Named("trigger_recorder_map"),
cache: cache.MakeCache(expiry, 0),
}
}
func (trmap *triggerRecorderMap) lookup(trigger string) (*fv1.Recorder, error) {
item, err := trmap.cache.Get(trigger)
if err != nil {
return nil, err
}
u := item.(*fv1.Recorder)
return u, nil
}
func (trmap *triggerRecorderMap) assign(trigger string, recorder *fv1.Recorder) {
_, err := trmap.cache.Set(trigger, recorder)
if err != nil {
if e, ok := err.(ferror.Error); ok && e.Code == ferror.ErrorNameExists {
return
}
trmap.logger.Error("error caching recorder for function name with a different value", zap.Error(err))
}
}
func (trmap *triggerRecorderMap) remove(trigger string) error {
return trmap.cache.Delete(trigger)
}