Envtest based integration tests for Fission (#2858)

* skeleton for envtest fission
* Refactor code and add CLI test
* hack
* Update server test
* remove skip-ci for lint tests
* Pass client go storagesvc
* Add clientGen interface across code
* Fix storagesvc test
* Fix cmd client
* add retry in server test
* Fix concurrenct access to pool deployment
* Remove old executor test
* get rid of ginkgo/gomega
* disable flaky test
* flaky test
* revert ci change
* handle err from ParseBool

---------

Signed-off-by: Sanket Sudake <sanketsudake@gmail.com>
Co-authored-by: Pranoy Kundu <pranoy1998k@gmail.com>
This commit is contained in:
Sanket Sudake
2023-10-26 12:09:11 +05:30
committed by GitHub
co-authored by Pranoy Kundu
parent 15e16fcc82
commit 8a17d391c5
44 changed files with 534 additions and 524 deletions
+7
View File
@@ -127,3 +127,10 @@ release:
@./hack/release.sh $(VERSION) @./hack/release.sh $(VERSION)
@./hack/release-tag.sh $(VERSION) @./hack/release-tag.sh $(VERSION)
@./hack/changelog.sh @./hack/changelog.sh
## Envtest
install-envtest:
go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
setup-envtest:
setup-envtest -p path use 1.23.x
+3 -2
View File
@@ -28,6 +28,7 @@ import (
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/fetcher" "github.com/fission/fission/pkg/fetcher"
"github.com/fission/fission/pkg/utils/httpserver" "github.com/fission/fission/pkg/utils/httpserver"
otelUtils "github.com/fission/fission/pkg/utils/otel" otelUtils "github.com/fission/fission/pkg/utils/otel"
@@ -37,7 +38,7 @@ var (
readyToServe uint32 readyToServe uint32
) )
func Run(ctx context.Context, logger *zap.Logger) { func Run(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger) {
flag.Usage = fetcherUsage flag.Usage = fetcherUsage
specializeOnStart := flag.Bool("specialize-on-startup", false, "Flag to activate specialize process at pod startup") specializeOnStart := flag.Bool("specialize-on-startup", false, "Flag to activate specialize process at pod startup")
specializePayload := flag.String("specialize-request", "", "JSON payload for specialize request") specializePayload := flag.String("specialize-request", "", "JSON payload for specialize request")
@@ -72,7 +73,7 @@ func Run(ctx context.Context, logger *zap.Logger) {
ctx, span := tracer.Start(ctx, "fetcher/Run") ctx, span := tracer.Start(ctx, "fetcher/Run")
defer span.End() defer span.End()
f, err := fetcher.MakeFetcher(logger, dir, *secretDir, *configDir) f, err := fetcher.MakeFetcher(logger, clientGen, dir, *secretDir, *configDir)
if err != nil { if err != nil {
logger.Fatal("error making fetcher", zap.Error(err)) logger.Fatal("error making fetcher", zap.Error(err))
} }
+2 -1
View File
@@ -20,6 +20,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager/signals" "sigs.k8s.io/controller-runtime/pkg/manager/signals"
"github.com/fission/fission/cmd/fetcher/app" "github.com/fission/fission/cmd/fetcher/app"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/utils/loggerfactory" "github.com/fission/fission/pkg/utils/loggerfactory"
"github.com/fission/fission/pkg/utils/profile" "github.com/fission/fission/pkg/utils/profile"
) )
@@ -31,5 +32,5 @@ func main() {
ctx := signals.SetupSignalHandler() ctx := signals.SetupSignalHandler()
profile.ProfileIfEnabled(ctx, logger) profile.ProfileIfEnabled(ctx, logger)
app.Run(ctx, logger) app.Run(ctx, crd.NewClientGenerator(), logger)
} }
+21 -21
View File
@@ -50,8 +50,8 @@ func runWebhook(ctx context.Context, logger *zap.Logger, port int) error {
return webhook.Start(ctx, logger, port) return webhook.Start(ctx, logger, port)
} }
func runCanaryConfigServer(ctx context.Context, logger *zap.Logger) error { func runCanaryConfigServer(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger) error {
return canaryconfigmgr.StartCanaryServer(ctx, logger, false) return canaryconfigmgr.StartCanaryServer(ctx, clientGen, logger, false)
} }
func runRouter(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, port int, executorUrl string) error { func runRouter(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, port int, executorUrl string) error {
@@ -62,33 +62,33 @@ func runExecutor(ctx context.Context, clientGen crd.ClientGeneratorInterface, lo
return executor.StartExecutor(ctx, clientGen, logger, port) return executor.StartExecutor(ctx, clientGen, logger, port)
} }
func runKubeWatcher(ctx context.Context, logger *zap.Logger, routerUrl string) error { func runKubeWatcher(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerUrl string) error {
return kubewatcher.Start(ctx, logger, routerUrl) return kubewatcher.Start(ctx, clientGen, logger, routerUrl)
} }
func runTimer(ctx context.Context, logger *zap.Logger, routerUrl string) error { func runTimer(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerUrl string) error {
return timer.Start(ctx, logger, routerUrl) return timer.Start(ctx, clientGen, logger, routerUrl)
} }
func runMessageQueueMgr(ctx context.Context, logger *zap.Logger, routerUrl string) error { func runMessageQueueMgr(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerUrl string) error {
return mqtrigger.Start(ctx, logger, routerUrl) return mqtrigger.Start(ctx, clientGen, logger, routerUrl)
} }
// KEDA based MessageQueue Trigger Manager // KEDA based MessageQueue Trigger Manager
func runMQManager(ctx context.Context, logger *zap.Logger, routerURL string) error { func runMQManager(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerURL string) error {
return mqt.StartScalerManager(ctx, logger, routerURL) return mqt.StartScalerManager(ctx, clientGen, logger, routerURL)
} }
func runStorageSvc(ctx context.Context, logger *zap.Logger, port int, storage storagesvc.Storage) error { func runStorageSvc(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, port int, storage storagesvc.Storage) error {
return storagesvc.Start(ctx, logger, storage, port) return storagesvc.Start(ctx, clientGen, logger, storage, port)
} }
func runBuilderMgr(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, storageSvcUrl string) error { func runBuilderMgr(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, storageSvcUrl string) error {
return buildermgr.Start(ctx, clientGen, logger, storageSvcUrl) return buildermgr.Start(ctx, clientGen, logger, storageSvcUrl)
} }
func runLogger(ctx context.Context, logger *zap.Logger) error { func runLogger(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger) error {
return functionLogger.Start(ctx, logger) return functionLogger.Start(ctx, clientGen, logger)
} }
func getPort(logger *zap.Logger, portArg interface{}) int { func getPort(logger *zap.Logger, portArg interface{}) int {
@@ -236,7 +236,7 @@ Options:
} }
if arguments["--canaryConfig"] == true { if arguments["--canaryConfig"] == true {
err := runCanaryConfigServer(ctx, logger) err := runCanaryConfigServer(ctx, clientGen, logger)
if err != nil { if err != nil {
logger.Error("canary config server exited with error: ", zap.Error(err)) logger.Error("canary config server exited with error: ", zap.Error(err))
return return
@@ -262,7 +262,7 @@ Options:
} }
if arguments["--kubewatcher"] == true { if arguments["--kubewatcher"] == true {
err = runKubeWatcher(ctx, logger, routerUrl) err = runKubeWatcher(ctx, clientGen, logger, routerUrl)
if err != nil { if err != nil {
logger.Error("kubewatcher exited", zap.Error(err)) logger.Error("kubewatcher exited", zap.Error(err))
return return
@@ -270,7 +270,7 @@ Options:
} }
if arguments["--timer"] == true { if arguments["--timer"] == true {
err = runTimer(ctx, logger, routerUrl) err = runTimer(ctx, clientGen, logger, routerUrl)
if err != nil { if err != nil {
logger.Error("timer exited", zap.Error(err)) logger.Error("timer exited", zap.Error(err))
return return
@@ -278,7 +278,7 @@ Options:
} }
if arguments["--mqt"] == true { if arguments["--mqt"] == true {
err = runMessageQueueMgr(ctx, logger, routerUrl) err = runMessageQueueMgr(ctx, clientGen, logger, routerUrl)
if err != nil { if err != nil {
logger.Error("message queue manager exited", zap.Error(err)) logger.Error("message queue manager exited", zap.Error(err))
return return
@@ -286,7 +286,7 @@ Options:
} }
if arguments["--mqt_keda"] == true { if arguments["--mqt_keda"] == true {
err = runMQManager(ctx, logger, routerUrl) err = runMQManager(ctx, clientGen, logger, routerUrl)
if err != nil { if err != nil {
logger.Error("mqt scaler manager exited", zap.Error(err)) logger.Error("mqt scaler manager exited", zap.Error(err))
return return
@@ -302,7 +302,7 @@ Options:
} }
if arguments["--logger"] == true { if arguments["--logger"] == true {
err = runLogger(ctx, logger) err = runLogger(ctx, clientGen, logger)
if err != nil { if err != nil {
logger.Error("logger exited", zap.Error(err)) logger.Error("logger exited", zap.Error(err))
} }
@@ -319,7 +319,7 @@ Options:
} else if arguments["--storageType"] == string(storagesvc.StorageTypeLocal) { } else if arguments["--storageType"] == string(storagesvc.StorageTypeLocal) {
storage = storagesvc.NewLocalStorage("/fission") storage = storagesvc.NewLocalStorage("/fission")
} }
err := runStorageSvc(ctx, logger, port, storage) err := runStorageSvc(ctx, clientGen, logger, port, storage)
if err != nil { if err != nil {
logger.Error("storage service exited", zap.Error(err)) logger.Error("storage service exited", zap.Error(err))
return return
+1 -2
View File
@@ -34,8 +34,7 @@ import (
_ "github.com/fission/fission/pkg/mqtrigger/messageQueue/kafka" _ "github.com/fission/fission/pkg/mqtrigger/messageQueue/kafka"
) )
func Start(ctx context.Context, logger *zap.Logger, routerUrl string) error { func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerUrl string) error {
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get fission client") return errors.Wrap(err, "failed to get fission client")
+2 -4
View File
@@ -49,7 +49,7 @@ const (
` `
) )
func App() *cobra.Command { func App(clientOptions cmd.ClientOptions) *cobra.Command {
cobra.EnableCommandSorting = false cobra.EnableCommandSorting = false
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
@@ -59,9 +59,7 @@ func App() *cobra.Command {
PersistentPreRunE: wrapper.Wrapper( PersistentPreRunE: wrapper.Wrapper(
func(input cli.Input) error { func(input cli.Input) error {
console.Verbosity = input.Int(flagkey.Verbosity) console.Verbosity = input.Int(flagkey.Verbosity)
clientOptions := cmd.ClientOptions{ clientOptions.KubeContext = input.String(flagkey.KubeContext)
KubeContext: input.String(flagkey.KubeContext),
}
// TODO: use fake rest client for offline spec generation // TODO: use fake rest client for offline spec generation
// if input.IsSet(flagkey.ClientOnly) || input.IsSet(flagkey.PreCheckOnly) { // if input.IsSet(flagkey.ClientOnly) || input.IsSet(flagkey.PreCheckOnly) {
// } // }
+2 -1
View File
@@ -20,11 +20,12 @@ import (
"os" "os"
"github.com/fission/fission/cmd/fission-cli/app" "github.com/fission/fission/cmd/fission-cli/app"
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/pkg/fission-cli/console" "github.com/fission/fission/pkg/fission-cli/console"
) )
func main() { func main() {
cmd := app.App() cmd := app.App(cmd.ClientOptions{})
cmd.SilenceErrors = true // use our own error message printer cmd.SilenceErrors = true // use our own error message printer
err := cmd.Execute() err := cmd.Execute()
+1 -2
View File
@@ -51,8 +51,7 @@ const (
MqtCRD = "messagequeuetriggers.fission.io" MqtCRD = "messagequeuetriggers.fission.io"
) )
func makePreUpgradeTaskClient(logger *zap.Logger) (*PreUpgradeTaskClient, error) { func makePreUpgradeTaskClient(clientGen crd.ClientGeneratorInterface, logger *zap.Logger) (*PreUpgradeTaskClient, error) {
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to get fission client") return nil, errors.Wrap(err, "failed to get fission client")
+2 -1
View File
@@ -20,6 +20,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"sigs.k8s.io/controller-runtime/pkg/manager/signals" "sigs.k8s.io/controller-runtime/pkg/manager/signals"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/utils/loggerfactory" "github.com/fission/fission/pkg/utils/loggerfactory"
) )
@@ -27,7 +28,7 @@ func main() {
logger := loggerfactory.GetLogger() logger := loggerfactory.GetLogger()
defer logger.Sync() defer logger.Sync()
crdBackedClient, err := makePreUpgradeTaskClient(logger) crdBackedClient, err := makePreUpgradeTaskClient(crd.NewClientGenerator(), logger)
if err != nil { if err != nil {
logger.Fatal("error creating a crd client, please retry helm upgrade", logger.Fatal("error creating a crd client, please retry helm upgrade",
zap.Error(err)) zap.Error(err))
+6 -1
View File
@@ -28,9 +28,14 @@ set +x
# for codecov # for codecov
echo "" > coverage.txt echo "" > coverage.txt
make install-envtest
KUBEBUILDER_ASSETS=$(setup-envtest -p path use 1.23.x)
export KUBEBUILDER_ASSETS
# The executor 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 except some partial tests. # now. So disable it for our travis ci tests except some partial tests.
for d in $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go' | grep -v executor | grep -v 'benchmark') github.com/fission/fission/pkg/executor/util; do for d in $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go' | grep -v 'benchmark'); do
echo "Running tests in $d"
go test -race -v -coverprofile=profile.out -covermode=atomic $d go test -race -v -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then if [ -f profile.out ]; then
cat profile.out >> coverage.txt cat profile.out >> coverage.txt
+1 -1
View File
@@ -309,7 +309,7 @@ func (pkgw *packageWatcher) packageInformerHandler(ctx context.Context) k8sCache
} }
func (pkgw *packageWatcher) Run(ctx context.Context) error { func (pkgw *packageWatcher) Run(ctx context.Context) error {
go metrics.ServeMetrics(ctx, pkgw.logger) go metrics.ServeMetrics(ctx, "buildermgr", pkgw.logger)
for _, podInformer := range pkgw.podInformer { for _, podInformer := range pkgw.podInformer {
go podInformer.Run(ctx.Done()) go podInformer.Run(ctx.Done())
} }
+1 -2
View File
@@ -558,10 +558,9 @@ func getEnvValue(envVar string) string {
return envVarSplit[1] return envVarSplit[1]
} }
func StartCanaryServer(ctx context.Context, logger *zap.Logger, unitTestFlag bool) error { func StartCanaryServer(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, unitTestFlag bool) error {
cLogger := logger.Named("CanaryServer") cLogger := logger.Named("CanaryServer")
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return fmt.Errorf("failed to get fission client: %w", err) return fmt.Errorf("failed to get fission client: %w", err)
+1 -1
View File
@@ -390,7 +390,7 @@ func StartExecutor(ctx context.Context, clientGen crd.ClientGeneratorInterface,
utils.CreateMissingPermissionForSA(ctx, kubernetesClient, logger) utils.CreateMissingPermissionForSA(ctx, kubernetesClient, logger)
go metrics.ServeMetrics(ctx, logger) go metrics.ServeMetrics(ctx, "executor", logger)
go api.Serve(ctx, port) go api.Serve(ctx, port)
return nil return nil
-295
View File
@@ -1,295 +0,0 @@
//
// This test depends on several env vars:
//
// KUBECONFIG has to point at a kube config with a cluster. The test
// will use the default context from that config. Be careful,
// don't point this at your production environment. The test is
// skipped if KUBECONFIG is undefined.
//
// TEST_SPECIALIZE_URL
// TEST_FETCHER_URL
// These need to point at <node ip>:30001 and <node ip>:30002,
// where <node ip> is the address of any node in the test
// cluster.
//
// FETCHER_IMAGE
// Optional. Set this to a fetcher image; otherwise uses the
// default.
//
// 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 .
package executor
import (
"context"
"fmt"
"log"
"math/rand"
"os"
"testing"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
fv1 "github.com/fission/fission/pkg/apis/core/v1"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/executor/client"
)
func panicIf(err error) {
if err != nil {
log.Panicf("Error: %v", err)
}
}
// return the number of pods in the given namespace matching the given labels
func countPods(ctx context.Context, kubeClient kubernetes.Interface, ns string, labelz map[string]string) int {
pods, err := kubeClient.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
LabelSelector: labels.Set(labelz).AsSelector().String(),
})
if err != nil {
log.Panicf("Failed to list pods: %v", err)
}
return len(pods.Items)
}
func createTestNamespace(ctx context.Context, kubeClient kubernetes.Interface, ns string) {
_, err := kubeClient.CoreV1().Namespaces().Create(ctx, &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
},
}, metav1.CreateOptions{})
if err != nil {
log.Panicf("failed to create ns %v: %v", ns, err)
}
log.Printf("Created namespace %v", ns)
}
// create a nodeport service
func createSvc(ctx context.Context, kubeClient kubernetes.Interface, ns string, name string, targetPort int, nodePort int32, labels map[string]string) *apiv1.Service {
svc, err := kubeClient.CoreV1().Services(ns).Create(ctx, &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: apiv1.ServiceSpec{
Type: apiv1.ServiceTypeNodePort,
Ports: []apiv1.ServicePort{
{
Protocol: apiv1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(targetPort),
NodePort: nodePort,
},
},
Selector: labels,
},
}, metav1.CreateOptions{})
if err != nil {
log.Panicf("Failed to create svc: %v", err)
}
return svc
}
func TestExecutor(t *testing.T) {
// run in a random namespace so we can have concurrent tests
// on a given cluster
testID := rand.Intn(999)
fissionNs := fmt.Sprintf("test-%v", testID)
functionNs := fmt.Sprintf("test-function-%v", testID)
// skip test if no cluster available for testing
kubeconfig := os.Getenv("KUBECONFIG")
if len(kubeconfig) == 0 {
t.Skip("Skipping test, no kubernetes cluster")
return
}
// connect to k8s
// and get CRD client
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient()
if err != nil {
log.Panicf("failed to connect: %s", err)
}
kubeClient, err := clientGen.GetKubernetesClient()
if err != nil {
log.Panicf("failed to connect: %s", err)
}
apiExtClient, err := clientGen.GetApiExtensionsClient()
if err != nil {
log.Panicf("failed to connect: %s", err)
}
ctx := context.Background()
// create the test's namespaces
createTestNamespace(ctx, kubeClient, fissionNs)
defer func() {
err := kubeClient.CoreV1().Namespaces().Delete(ctx, fissionNs, metav1.DeleteOptions{})
if err != nil {
log.Fatalf("failed to delete namespace: %v", err)
}
}()
createTestNamespace(ctx, kubeClient, functionNs)
defer func() {
err := kubeClient.CoreV1().Namespaces().Delete(ctx, functionNs, metav1.DeleteOptions{})
if err != nil {
log.Fatalf("failed to delete namespace: %v", err)
}
}()
config := zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := config.Build()
panicIf(err)
// make sure CRD types exist on cluster
err = crd.EnsureFissionCRDs(ctx, logger, apiExtClient)
if err != nil {
log.Panicf("failed to ensure crds: %v", err)
}
err = crd.WaitForCRDs(ctx, logger, fissionClient)
if err != nil {
log.Panicf("failed to wait crds: %v", err)
}
// create an env on the cluster
env, err := fissionClient.CoreV1().Environments(fissionNs).Create(ctx, &fv1.Environment{
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs",
Namespace: fissionNs,
},
Spec: fv1.EnvironmentSpec{
Version: 1,
Runtime: fv1.Runtime{
Image: "fission/node-env",
},
Builder: fv1.Builder{},
},
}, metav1.CreateOptions{})
if err != nil {
log.Panicf("failed to create env: %v", err)
}
// create poolmgr
port := 9999
err = StartExecutor(ctx, crd.NewClientGenerator(), logger, port)
if err != nil {
log.Panicf("failed to start poolmgr: %v", err)
}
// connect poolmgr client
poolmgrClient := client.MakeClient(logger, fmt.Sprintf("http://localhost:%v", port))
// Wait for pool to be created (we don't actually need to do
// this, since the API should do the right thing in any case).
// waitForPool(functionNs, "nodejs")
time.Sleep(6 * time.Second)
envRef := fv1.EnvironmentReference{
Namespace: env.ObjectMeta.Namespace,
Name: env.ObjectMeta.Name,
}
deployment := fv1.Archive{
Type: fv1.ArchiveTypeLiteral,
Literal: []byte(`module.exports = async function(context) { return { status: 200, body: "Hello, world!\n" }; }`),
}
// create a package
p := &fv1.Package{
ObjectMeta: metav1.ObjectMeta{
Name: "hello",
Namespace: fissionNs,
},
Spec: fv1.PackageSpec{
Environment: envRef,
Deployment: deployment,
},
}
p, err = fissionClient.CoreV1().Packages(fissionNs).Create(ctx, p, metav1.CreateOptions{})
if err != nil {
log.Panicf("failed to create package: %v", err)
}
// create a function
f := &fv1.Function{
ObjectMeta: metav1.ObjectMeta{
Name: "hello",
Namespace: fissionNs,
},
Spec: fv1.FunctionSpec{
Environment: envRef,
Package: fv1.FunctionPackageRef{
PackageRef: fv1.PackageRef{
Namespace: p.ObjectMeta.Namespace,
Name: p.ObjectMeta.Name,
ResourceVersion: p.ObjectMeta.ResourceVersion,
},
},
},
}
_, err = fissionClient.CoreV1().Functions(fissionNs).Create(ctx, f, metav1.CreateOptions{})
if err != nil {
log.Panicf("failed to create function: %v", err)
}
// create a service to call fetcher and the env container
labels := map[string]string{"functionName": f.ObjectMeta.Name}
var fetcherPort int32 = 30001
fetcherSvc := createSvc(ctx, kubeClient, functionNs, fmt.Sprintf("%v-%v", f.ObjectMeta.Name, "fetcher"), 8000, fetcherPort, labels)
defer func() {
err := kubeClient.CoreV1().Services(functionNs).Delete(ctx, fetcherSvc.ObjectMeta.Name, metav1.DeleteOptions{})
if err != nil {
log.Fatalf("failed to delete service: %v", err)
}
}()
var funcSvcPort int32 = 30002
functionSvc := createSvc(ctx, kubeClient, functionNs, f.ObjectMeta.Name, 8888, funcSvcPort, labels)
defer func() {
err := kubeClient.CoreV1().Services(functionNs).Delete(ctx, functionSvc.ObjectMeta.Name, metav1.DeleteOptions{})
if err != nil {
log.Fatalf("failed to delete service: %v", err)
}
}()
// the main test: get a service for a given function
t1 := time.Now()
svc, err := poolmgrClient.GetServiceForFunction(ctx, f)
if err != nil {
log.Panicf("failed to get func svc: %v", err)
}
log.Printf("svc for function created at: %v (in %v)", svc, time.Since(t1))
// ensure that a pod with the label functionName=f.ObjectMeta.Name exists
podCount := countPods(ctx, kubeClient, functionNs, map[string]string{"functionName": f.ObjectMeta.Name})
if podCount != 1 {
log.Panicf("expected 1 function pod, found %v", podCount)
}
// call the service to ensure it works
// wait for a bit
// tap service to simulate calling it again
// make sure the same pod is still there
// wait for idleTimeout to ensure the pod is removed
// remove env
// wait for pool to be destroyed
// that's it
}
+4
View File
@@ -59,6 +59,7 @@ type (
// GenericPool represents a generic environment pool // GenericPool represents a generic environment pool
GenericPool struct { GenericPool struct {
logger *zap.Logger logger *zap.Logger
lock sync.Mutex
env *fv1.Environment env *fv1.Environment
deployment *appsv1.Deployment // kubernetes deployment deployment *appsv1.Deployment // kubernetes deployment
fnNamespace string // namespace to keep our resources fnNamespace string // namespace to keep our resources
@@ -130,6 +131,7 @@ func MakeGenericPool(
instanceID: instanceID, instanceID: instanceID,
podFSVCMap: sync.Map{}, podFSVCMap: sync.Map{},
podSpecPatch: podSpecPatch, podSpecPatch: podSpecPatch,
lock: sync.Mutex{},
} }
gp.runtimeImagePullPolicy = utils.GetImagePullPolicy(os.Getenv("RUNTIME_IMAGE_PULL_POLICY")) gp.runtimeImagePullPolicy = utils.GetImagePullPolicy(os.Getenv("RUNTIME_IMAGE_PULL_POLICY"))
@@ -643,6 +645,8 @@ func (gp *GenericPool) getPercent(cpuUsage resource.Quantity, percentage float64
// destroys the pool -- the deployment, replicaset and pods // destroys the pool -- the deployment, replicaset and pods
func (gp *GenericPool) destroy(ctx context.Context) error { func (gp *GenericPool) destroy(ctx context.Context) error {
gp.lock.Lock()
defer gp.lock.Unlock()
close(gp.stopReadyPodControllerCh) close(gp.stopReadyPodControllerCh)
deletePropagation := metav1.DeletePropagationBackground deletePropagation := metav1.DeletePropagationBackground
@@ -196,6 +196,10 @@ func (gp *GenericPool) genDeploymentSpec(env *fv1.Environment) (*appsv1.Deployme
// A pool is a deployment of generic containers for an env. This // A pool is a deployment of generic containers for an env. This
// creates the pool but doesn't wait for any pods to be ready. // creates the pool but doesn't wait for any pods to be ready.
func (gp *GenericPool) createPoolDeployment(ctx context.Context, env *fv1.Environment) error { func (gp *GenericPool) createPoolDeployment(ctx context.Context, env *fv1.Environment) error {
// avoid create/update/delete pool deployment at the same time
gp.lock.Lock()
defer gp.lock.Unlock()
deploymentMeta := gp.genDeploymentMeta(env) deploymentMeta := gp.genDeploymentMeta(env)
deploymentSpec, err := gp.genDeploymentSpec(env) deploymentSpec, err := gp.genDeploymentSpec(env)
if err != nil { if err != nil {
@@ -233,6 +237,9 @@ func (gp *GenericPool) createPoolDeployment(ctx context.Context, env *fv1.Enviro
} }
func (gp *GenericPool) updatePoolDeployment(ctx context.Context, env *fv1.Environment) error { func (gp *GenericPool) updatePoolDeployment(ctx context.Context, env *fv1.Environment) error {
// avoid create/update/delete pool deployment at the same time
gp.lock.Lock()
defer gp.lock.Unlock()
logger := gp.logger.With(zap.String("env", env.Name), zap.String("namespace", env.Namespace)) logger := gp.logger.With(zap.String("env", env.Name), zap.String("namespace", env.Namespace))
if gp.env.ObjectMeta.ResourceVersion == env.ObjectMeta.ResourceVersion { if gp.env.ObjectMeta.ResourceVersion == env.ObjectMeta.ResourceVersion {
logger.Debug("env resource version matching with pool env") logger.Debug("env resource version matching with pool env")
+8 -6
View File
@@ -535,12 +535,14 @@ func (gpm *GenericPoolManager) service() {
return return
} }
delete(gpm.pools, key) delete(gpm.pools, key)
err := pool.destroy(req.ctx) if pool != nil {
if err != nil { err := pool.destroy(req.ctx)
gpm.logger.Error("failed to destroy pool", if err != nil {
zap.String("environment", env.ObjectMeta.Name), gpm.logger.Error("failed to destroy pool",
zap.String("namespace", env.ObjectMeta.Namespace), zap.String("environment", env.ObjectMeta.Name),
zap.Error(err)) zap.String("namespace", env.ObjectMeta.Namespace),
zap.Error(err))
}
} }
// no response, caller doesn't wait // no response, caller doesn't wait
} }
@@ -398,7 +398,13 @@ func (p *PoolPodController) envDeleteQueueProcessFunc(ctx context.Context) bool
p.gpm.cleanupPool(ctx, env) p.gpm.cleanupPool(ctx, env)
specializePodLables := getSpecializedPodLabels(env) specializePodLables := getSpecializedPodLabels(env)
ns := p.nsResolver.ResolveNamespace(p.nsResolver.FunctionNamespace) ns := p.nsResolver.ResolveNamespace(p.nsResolver.FunctionNamespace)
specializedPods, err := p.podLister[ns].Pods(ns).List(labels.SelectorFromSet(specializePodLables)) podLister, ok := p.podLister[ns]
if !ok {
p.logger.Error("no pod lister found for namespace", zap.String("namespace", ns))
p.envDeleteQueue.Forget(obj)
return false
}
specializedPods, err := podLister.Pods(ns).List(labels.SelectorFromSet(specializePodLables))
if err != nil { if err != nil {
p.logger.Error("failed to list specialized pods", zap.Error(err)) p.logger.Error("failed to list specialized pods", zap.Error(err))
p.envDeleteQueue.Forget(obj) p.envDeleteQueue.Forget(obj)
@@ -30,6 +30,9 @@ func (gp *GenericPool) readyPodEventHandlers() k8sCache.ResourceEventHandlerFunc
} }
func (gp *GenericPool) setupReadyPodController() error { func (gp *GenericPool) setupReadyPodController() error {
// avoid concurrent access to gp.deployment
gp.lock.Lock()
defer gp.lock.Unlock()
gp.readyPodQueue = workqueue.NewDelayingQueue() gp.readyPodQueue = workqueue.NewDelayingQueue()
informerFactory, err := utils.GetInformerFactoryByReadyPod(gp.kubernetesClient, gp.fnNamespace, gp.deployment.Spec.Selector) informerFactory, err := utils.GetInformerFactoryByReadyPod(gp.kubernetesClient, gp.fnNamespace, gp.deployment.Spec.Selector)
if err != nil { if err != nil {
@@ -89,9 +89,10 @@ func TestFunctionServiceCache(t *testing.T) {
err = fsc.TouchByAddress(fsvc.Address) err = fsc.TouchByAddress(fsvc.Address)
require.NoError(t, err) require.NoError(t, err)
deleted, err := fsc.DeleteOld(fsvc, 0) // TODO: fix flaky test
require.NoError(t, err) // deleted, err := fsc.DeleteOld(fsvc, 0)
require.False(t, deleted) // require.NoError(t, err)
// require.False(t, deleted)
_, err = fsc.GetByFunction(fsvc.Function) _, err = fsc.GetByFunction(fsvc.Function)
require.NoError(t, err) require.NoError(t, err)
+28 -27
View File
@@ -67,38 +67,39 @@ func TestQueuePushWithConcurrentRequest(t *testing.T) {
} }
} }
func TestQueuePopWithConcurrentRequest(t *testing.T) { // TODO: Fix flaky test
q := NewQueue() // func TestQueuePopWithConcurrentRequest(t *testing.T) {
noOfPush := 20 // q := NewQueue()
noOfPop := 15 // noOfPush := 20
// noOfPop := 15
var wg sync.WaitGroup // var wg sync.WaitGroup
wg.Add(noOfPush + noOfPop) // wg.Add(noOfPush + noOfPop)
for i := 0; i < noOfPush; i++ { // for i := 0; i < noOfPush; i++ {
go func() { // go func() {
defer wg.Done() // defer wg.Done()
item := &svcWait{ // item := &svcWait{
svcChannel: make(chan *FuncSvc), // svcChannel: make(chan *FuncSvc),
ctx: nil, // ctx: nil,
} // }
q.Push(item) // q.Push(item)
}() // }()
} // }
for i := 0; i < noOfPop; i++ { // for i := 0; i < noOfPop; i++ {
go func() { // go func() {
defer wg.Done() // defer wg.Done()
q.Pop() // q.Pop()
}() // }()
} // }
wg.Wait() // wg.Wait()
if q.Len() != 5 { // if q.Len() != 5 {
t.Errorf("Expected queue length to be 5, got %d", q.Len()) // t.Errorf("Expected queue length to be 5, got %d", q.Len())
} // }
} // }
func TestQueueLen(t *testing.T) { func TestQueueLen(t *testing.T) {
q := NewQueue() q := NewQueue()
+1 -2
View File
@@ -75,7 +75,7 @@ func makeVolumeDir(dirPath string) error {
return os.MkdirAll(dirPath, os.ModeDir|0750) return os.MkdirAll(dirPath, os.ModeDir|0750)
} }
func MakeFetcher(logger *zap.Logger, sharedVolumePath string, sharedSecretPath string, sharedConfigPath string) (*Fetcher, error) { func MakeFetcher(logger *zap.Logger, clientGen crd.ClientGeneratorInterface, sharedVolumePath string, sharedSecretPath string, sharedConfigPath string) (*Fetcher, error) {
fLogger := logger.Named("fetcher") fLogger := logger.Named("fetcher")
err := makeVolumeDir(sharedVolumePath) err := makeVolumeDir(sharedVolumePath)
if err != nil { if err != nil {
@@ -90,7 +90,6 @@ func MakeFetcher(logger *zap.Logger, sharedVolumePath string, sharedSecretPath s
fLogger.Fatal("error creating shared config directory", zap.Error(err), zap.String("directory", sharedConfigPath)) fLogger.Fatal("error creating shared config directory", zap.Error(err), zap.String("directory", sharedConfigPath))
} }
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error making the fission client") return nil, errors.Wrap(err, "error making the fission client")
+27 -20
View File
@@ -35,10 +35,10 @@ import (
type ( type (
ClientOptions struct { ClientOptions struct {
KubeContext string KubeContext string
Namespace string
RestConfig *rest.Config
} }
Client struct { Client struct {
Options ClientOptions
ClientConfig clientcmd.ClientConfig
RestConfig *rest.Config RestConfig *rest.Config
FissionClientSet versioned.Interface FissionClientSet versioned.Interface
KubernetesClient kubernetes.Interface KubernetesClient kubernetes.Interface
@@ -98,29 +98,36 @@ func GetClientConfig(kubeContext string) (clientcmd.ClientConfig, error) {
} }
func NewClient(opts ClientOptions) (*Client, error) { func NewClient(opts ClientOptions) (*Client, error) {
client := &Client{ client := &Client{}
Options: opts, var err error
var cmdConfig clientcmd.ClientConfig
if len(opts.Namespace) > 0 {
client.Namespace = opts.Namespace
} else {
cmdConfig, err = GetClientConfig(opts.KubeContext)
if err != nil {
return nil, err
}
namespace, _, err := cmdConfig.Namespace()
if err != nil {
return nil, err
}
client.Namespace = namespace
} }
cmdConfig, err := GetClientConfig(opts.KubeContext)
if err != nil {
return nil, err
}
client.ClientConfig = cmdConfig
namespace, _, err := cmdConfig.Namespace() console.Verbose(2, "Kubeconfig default namespace %q", client.Namespace)
if err != nil {
return nil, err
}
client.Namespace = namespace
console.Verbose(2, "Kubeconfig default namespace %q", namespace)
restConfig, err := cmdConfig.ClientConfig() if opts.RestConfig != nil {
if err != nil { client.RestConfig = opts.RestConfig
return nil, err } else {
restConfig, err := cmdConfig.ClientConfig()
if err != nil {
return nil, err
}
client.RestConfig = restConfig
} }
client.RestConfig = restConfig
clientGen := crd.NewClientGeneratorWithRestConfig(restConfig) clientGen := crd.NewClientGeneratorWithRestConfig(client.RestConfig)
clientset, err := clientGen.GetKubernetesClient() clientset, err := clientGen.GetKubernetesClient()
if err != nil { if err != nil {
return nil, err return nil, err
+2 -17
View File
@@ -48,10 +48,11 @@ func SetupPortForward(ctx context.Context, client cmd.Client, namespace, labelSe
console.Verbose(2, "Setting up port forward to %s in namespace %s", console.Verbose(2, "Setting up port forward to %s in namespace %s",
labelSelector, namespace) labelSelector, namespace)
localPort, err := findFreePort() lcPort, err := utils.FindFreePort()
if err != nil { if err != nil {
return "", errors.Wrap(err, "error finding unused port") return "", errors.Wrap(err, "error finding unused port")
} }
localPort := strconv.Itoa(lcPort)
var waitDuration time.Duration = 50 var waitDuration time.Duration = 50
@@ -103,22 +104,6 @@ func SetupPortForward(ctx context.Context, client cmd.Client, namespace, labelSe
return localPort, nil return localPort, nil
} }
func findFreePort() (string, error) {
listener, err := net.Listen("tcp", ":0")
if err != nil {
return "", err
}
port := strconv.Itoa(listener.Addr().(*net.TCPAddr).Port)
err = listener.Close()
if err != nil {
return "", err
}
return port, nil
}
// runPortForward creates a local port forward to the specified pod // runPortForward creates a local port forward to the specified pod
func runPortForward(ctx context.Context, client cmd.Client, labelSelector string, localPort string, ns string) (chan struct{}, chan struct{}, error) { func runPortForward(ctx context.Context, client cmd.Client, labelSelector string, localPort string, ns string) (chan struct{}, chan struct{}, error) {
+1 -2
View File
@@ -26,8 +26,7 @@ import (
"github.com/fission/fission/pkg/publisher" "github.com/fission/fission/pkg/publisher"
) )
func Start(ctx context.Context, logger *zap.Logger, routerUrl string) error { func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerUrl string) error {
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get fission client") return errors.Wrap(err, "failed to get fission client")
+1 -2
View File
@@ -156,7 +156,7 @@ func symlinkReaper(zapLogger *zap.Logger) {
} }
} }
func Start(ctx context.Context, logger *zap.Logger) error { func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger) error {
if _, err := os.Stat(fissionSymlinkPath); os.IsNotExist(err) { if _, err := os.Stat(fissionSymlinkPath); os.IsNotExist(err) {
logger.Info("symlink path not exist, create it", logger.Info("symlink path not exist, create it",
zap.String("fissionSymlinkPath", fissionSymlinkPath)) zap.String("fissionSymlinkPath", fissionSymlinkPath))
@@ -167,7 +167,6 @@ func Start(ctx context.Context, logger *zap.Logger) error {
} }
go symlinkReaper(logger) go symlinkReaper(logger)
clientGen := crd.NewClientGenerator()
kubernetesClient, err := clientGen.GetKubernetesClient() kubernetesClient, err := clientGen.GetKubernetesClient()
if err != nil { if err != nil {
return err return err
+1 -1
View File
@@ -90,7 +90,7 @@ func (mqt *MessageQueueTriggerManager) Run(ctx context.Context) error {
mqt.logger.Fatal("failed to wait for caches to sync") mqt.logger.Fatal("failed to wait for caches to sync")
} }
} }
go metrics.ServeMetrics(ctx, mqt.logger) go metrics.ServeMetrics(ctx, "mqtrigger", mqt.logger)
return nil return nil
} }
+29 -51
View File
@@ -49,25 +49,15 @@ var (
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
) )
func getScaledObjectClient(namespace string) (dynamic.ResourceInterface, error) { func getScaledObjectClient(client dynamic.Interface, namespace string) dynamic.ResourceInterface {
clientGen := crd.NewClientGenerator() return client.Resource(scaledObjectGVR).Namespace(namespace)
dynamicClient, err := clientGen.GetDynamicClient()
if err != nil {
return nil, err
}
return dynamicClient.Resource(scaledObjectGVR).Namespace(namespace), nil
} }
func getAuthTriggerClient(namespace string) (dynamic.ResourceInterface, error) { func getAuthTriggerClient(client dynamic.Interface, namespace string) dynamic.ResourceInterface {
clientGen := crd.NewClientGenerator() return client.Resource(authTriggerGVR).Namespace(namespace)
dynamicClient, err := clientGen.GetDynamicClient()
if err != nil {
return nil, err
}
return dynamicClient.Resource(authTriggerGVR).Namespace(namespace), nil
} }
func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient kubernetes.Interface, routerURL string) k8sCache.ResourceEventHandlerFuncs { func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient kubernetes.Interface, dynamicClient dynamic.Interface, routerURL string) k8sCache.ResourceEventHandlerFuncs {
return k8sCache.ResourceEventHandlerFuncs{ return k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { AddFunc: func(obj interface{}) {
go func() { go func() {
@@ -80,7 +70,7 @@ func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient
authenticationRef := "" authenticationRef := ""
if len(mqt.Spec.Secret) > 0 { if len(mqt.Spec.Secret) > 0 {
authenticationRef = fmt.Sprintf("%s-auth-trigger", mqt.ObjectMeta.Name) authenticationRef = fmt.Sprintf("%s-auth-trigger", mqt.ObjectMeta.Name)
err := createAuthTrigger(ctx, mqt, authenticationRef, kubeClient) err := createAuthTrigger(ctx, dynamicClient, mqt, authenticationRef, kubeClient)
if err != nil { if err != nil {
logger.Error("Failed to create Authentication Trigger", zap.Error(err)) logger.Error("Failed to create Authentication Trigger", zap.Error(err))
return return
@@ -90,7 +80,7 @@ func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient
if err := createDeployment(ctx, mqt, routerURL, kubeClient); err != nil { if err := createDeployment(ctx, mqt, routerURL, kubeClient); err != nil {
logger.Error("Failed to create Deployment", zap.Error(err)) logger.Error("Failed to create Deployment", zap.Error(err))
if len(authenticationRef) > 0 { if len(authenticationRef) > 0 {
err = deleteAuthTrigger(ctx, authenticationRef, mqt.ObjectMeta.Namespace) err = deleteAuthTrigger(ctx, dynamicClient, authenticationRef, mqt.ObjectMeta.Namespace)
if err != nil { if err != nil {
logger.Error("Failed to delete Authentication Trigger", zap.Error(err)) logger.Error("Failed to delete Authentication Trigger", zap.Error(err))
} }
@@ -98,10 +88,10 @@ func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient
return return
} }
if err := createScaledObject(ctx, mqt, authenticationRef); err != nil { if err := createScaledObject(ctx, dynamicClient, mqt, authenticationRef); err != nil {
logger.Error("Failed to create ScaledObject", zap.Error(err)) logger.Error("Failed to create ScaledObject", zap.Error(err))
if len(authenticationRef) > 0 { if len(authenticationRef) > 0 {
if err = deleteAuthTrigger(ctx, authenticationRef, mqt.ObjectMeta.Namespace); err != nil { if err = deleteAuthTrigger(ctx, dynamicClient, authenticationRef, mqt.ObjectMeta.Namespace); err != nil {
logger.Error("Failed to delete Authentication Trigger", zap.Error(err)) logger.Error("Failed to delete Authentication Trigger", zap.Error(err))
} }
} }
@@ -127,7 +117,7 @@ func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient
authenticationRef := "" authenticationRef := ""
if len(newMqt.Spec.Secret) > 0 && newMqt.Spec.Secret != mqt.Spec.Secret { if len(newMqt.Spec.Secret) > 0 && newMqt.Spec.Secret != mqt.Spec.Secret {
authenticationRef = fmt.Sprintf("%s-auth-trigger", mqt.ObjectMeta.Name) authenticationRef = fmt.Sprintf("%s-auth-trigger", mqt.ObjectMeta.Name)
if err := updateAuthTrigger(ctx, mqt, authenticationRef, kubeClient); err != nil { if err := updateAuthTrigger(ctx, dynamicClient, mqt, authenticationRef, kubeClient); err != nil {
logger.Error("Failed to update Authentication Trigger", zap.Error(err)) logger.Error("Failed to update Authentication Trigger", zap.Error(err))
return return
} }
@@ -138,7 +128,7 @@ func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient
return return
} }
if err := updateScaledObject(ctx, mqt, authenticationRef); err != nil { if err := updateScaledObject(ctx, dynamicClient, mqt, authenticationRef); err != nil {
logger.Error("Failed to Update ScaledObject", zap.Error(err)) logger.Error("Failed to Update ScaledObject", zap.Error(err))
return return
} }
@@ -150,8 +140,7 @@ func mqTriggerEventHandlers(ctx context.Context, logger *zap.Logger, kubeClient
// StartScalerManager watches for changes in MessageQueueTrigger and, // StartScalerManager watches for changes in MessageQueueTrigger and,
// Based on changes, it Creates, Updates and Deletes Objects of Kind ScaledObjects, AuthenticationTriggers and Deployments // Based on changes, it Creates, Updates and Deletes Objects of Kind ScaledObjects, AuthenticationTriggers and Deployments
func StartScalerManager(ctx context.Context, logger *zap.Logger, routerURL string) error { func StartScalerManager(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerURL string) error {
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get fission client") return errors.Wrap(err, "failed to get fission client")
@@ -160,6 +149,10 @@ func StartScalerManager(ctx context.Context, logger *zap.Logger, routerURL strin
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get kubernetes client") return errors.Wrap(err, "failed to get kubernetes client")
} }
dynamicClient, err := clientGen.GetDynamicClient()
if err != nil {
return errors.Wrap(err, "failed to get dynamic client")
}
err = crd.WaitForCRDs(ctx, logger, fissionClient) err = crd.WaitForCRDs(ctx, logger, fissionClient)
if err != nil { if err != nil {
@@ -167,7 +160,7 @@ func StartScalerManager(ctx context.Context, logger *zap.Logger, routerURL strin
} }
for _, informer := range utils.GetInformersForNamespaces(fissionClient, time.Minute*30, fv1.MessageQueueResource) { for _, informer := range utils.GetInformersForNamespaces(fissionClient, time.Minute*30, fv1.MessageQueueResource) {
_, err := informer.AddEventHandler(mqTriggerEventHandlers(ctx, logger, kubeClient, routerURL)) _, err := informer.AddEventHandler(mqTriggerEventHandlers(ctx, logger, kubeClient, dynamicClient, routerURL))
if err != nil { if err != nil {
return err return err
} }
@@ -352,15 +345,12 @@ func getAuthTriggerSpec(ctx context.Context, mqt *fv1.MessageQueueTrigger, authe
return authTriggerObj, nil return authTriggerObj, nil
} }
func createAuthTrigger(ctx context.Context, mqt *fv1.MessageQueueTrigger, authenticationRef string, kubeClient kubernetes.Interface) error { func createAuthTrigger(ctx context.Context, client dynamic.Interface, mqt *fv1.MessageQueueTrigger, authenticationRef string, kubeClient kubernetes.Interface) error {
authTriggerObj, err := getAuthTriggerSpec(ctx, mqt, authenticationRef, kubeClient) authTriggerObj, err := getAuthTriggerSpec(ctx, mqt, authenticationRef, kubeClient)
if err != nil { if err != nil {
return err return err
} }
authTriggerClient, err := getAuthTriggerClient(mqt.ObjectMeta.Namespace) authTriggerClient := getAuthTriggerClient(client, mqt.ObjectMeta.Namespace)
if err != nil {
return err
}
_, err = authTriggerClient.Create(ctx, authTriggerObj, metav1.CreateOptions{}) _, err = authTriggerClient.Create(ctx, authTriggerObj, metav1.CreateOptions{})
if err != nil { if err != nil {
return err return err
@@ -368,11 +358,8 @@ func createAuthTrigger(ctx context.Context, mqt *fv1.MessageQueueTrigger, authen
return nil return nil
} }
func updateAuthTrigger(ctx context.Context, mqt *fv1.MessageQueueTrigger, authenticationRef string, kubeClient kubernetes.Interface) error { func updateAuthTrigger(ctx context.Context, client dynamic.Interface, mqt *fv1.MessageQueueTrigger, authenticationRef string, kubeClient kubernetes.Interface) error {
authTriggerClient, err := getAuthTriggerClient(mqt.ObjectMeta.Namespace) authTriggerClient := getAuthTriggerClient(client, mqt.ObjectMeta.Namespace)
if err != nil {
return err
}
oldAuthTriggerObj, err := authTriggerClient.Get(ctx, authenticationRef, metav1.GetOptions{}) oldAuthTriggerObj, err := authTriggerClient.Get(ctx, authenticationRef, metav1.GetOptions{})
if err != nil { if err != nil {
return err return err
@@ -391,12 +378,9 @@ func updateAuthTrigger(ctx context.Context, mqt *fv1.MessageQueueTrigger, authen
return nil return nil
} }
func deleteAuthTrigger(ctx context.Context, name, namespace string) error { func deleteAuthTrigger(ctx context.Context, client dynamic.Interface, name, namespace string) error {
authTriggerClient, err := getAuthTriggerClient(namespace) authTriggerClient := getAuthTriggerClient(client, namespace)
if err != nil { err := authTriggerClient.Delete(ctx, name, metav1.DeleteOptions{})
return err
}
err = authTriggerClient.Delete(ctx, name, metav1.DeleteOptions{})
if err != nil { if err != nil {
return err return err
} }
@@ -536,24 +520,18 @@ func getScaledObject(mqt *fv1.MessageQueueTrigger, authenticationRef string) *un
} }
} }
func createScaledObject(ctx context.Context, mqt *fv1.MessageQueueTrigger, authenticationRef string) error { func createScaledObject(ctx context.Context, client dynamic.Interface, mqt *fv1.MessageQueueTrigger, authenticationRef string) error {
scaledObject := getScaledObject(mqt, authenticationRef) scaledObject := getScaledObject(mqt, authenticationRef)
kedaClient, err := getScaledObjectClient(mqt.ObjectMeta.Namespace) kedaClient := getScaledObjectClient(client, mqt.ObjectMeta.Namespace)
if err != nil { _, err := kedaClient.Create(ctx, scaledObject, metav1.CreateOptions{})
return err
}
_, err = kedaClient.Create(ctx, scaledObject, metav1.CreateOptions{})
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func updateScaledObject(ctx context.Context, mqt *fv1.MessageQueueTrigger, authenticationRef string) error { func updateScaledObject(ctx context.Context, client dynamic.Interface, mqt *fv1.MessageQueueTrigger, authenticationRef string) error {
kedaClient, err := getScaledObjectClient(mqt.ObjectMeta.Namespace) kedaClient := getScaledObjectClient(client, mqt.ObjectMeta.Namespace)
if err != nil {
return err
}
oldScaledObject, err := kedaClient.Get(ctx, mqt.ObjectMeta.Name, metav1.GetOptions{}) oldScaledObject, err := kedaClient.Get(ctx, mqt.ObjectMeta.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return err return err
+12 -12
View File
@@ -18,16 +18,16 @@ import (
) )
var ( var (
malformedToken = errors.New("Unauthorized: malformed token") errMalformedToken = errors.New("unauthorized: malformed token")
expiredToken = errors.New("Unauthorized: token is either expired or not active yet") errExpiredToken = errors.New("unauthorized: token is either expired or not active yet")
invalidCreds = errors.New("Unauthorized: invalid username or password") errInvalidCreds = errors.New("unauthorized: invalid username or password")
) )
func checkAuthToken(r *http.Request) error { func checkAuthToken(r *http.Request) error {
authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ") authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ")
if len(authHeader) != 2 || len(authHeader[1]) == 0 { if len(authHeader) != 2 || len(authHeader[1]) == 0 {
// malformed token // malformed token
return malformedToken return errMalformedToken
} }
jwtToken := authHeader[1] jwtToken := authHeader[1]
@@ -43,17 +43,17 @@ func checkAuthToken(r *http.Request) error {
if ve, ok := err.(*jwt.ValidationError); ok { if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 { if ve.Errors&jwt.ValidationErrorMalformed != 0 {
// malformed token // malformed token
err = malformedToken err = errMalformedToken
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// token is either expired or not active yet // token is either expired or not active yet
err = expiredToken err = errExpiredToken
} else { } else {
err = fmt.Errorf("Unauthorized: %w", err) err = fmt.Errorf("unauthorized: %w", err)
} }
} }
if err == nil { if err == nil {
err = errors.New("Unauthorized: invalid token") err = errors.New("unauthorized: invalid token")
} }
return err return err
@@ -83,17 +83,17 @@ type AuthConf struct {
func parseAuthConf(auth *AuthConf) error { func parseAuthConf(auth *AuthConf) error {
username, ok := os.LookupEnv("AUTH_USERNAME") username, ok := os.LookupEnv("AUTH_USERNAME")
if !ok || len(username) == 0 { if !ok || len(username) == 0 {
return fmt.Errorf("Username not configured or invalid") return fmt.Errorf("username not configured or invalid")
} }
password, ok := os.LookupEnv("AUTH_PASSWORD") password, ok := os.LookupEnv("AUTH_PASSWORD")
if !ok || len(password) == 0 { if !ok || len(password) == 0 {
return fmt.Errorf("Password not configured or invalid") return fmt.Errorf("password not configured or invalid")
} }
signingKey, ok := os.LookupEnv("JWT_SIGNING_KEY") signingKey, ok := os.LookupEnv("JWT_SIGNING_KEY")
if !ok || len(signingKey) == 0 { if !ok || len(signingKey) == 0 {
return fmt.Errorf("Signing key not configured or invalid") return fmt.Errorf("signing key not configured or invalid")
} }
auth.username = username auth.username = username
@@ -138,7 +138,7 @@ func authLoginHandler(featureConfig *config.FeatureConfig) func(w http.ResponseW
rat := &fv1.RouterAuthToken{} rat := &fv1.RouterAuthToken{}
if t.Username != auth.username || t.Password != auth.password { if t.Username != auth.username || t.Password != auth.password {
http.Error(w, invalidCreds.Error(), http.StatusUnauthorized) http.Error(w, errInvalidCreds.Error(), http.StatusUnauthorized)
return return
} }
+1 -1
View File
@@ -101,7 +101,7 @@ func TestRouterAuth(t *testing.T) {
{ {
URL: "http://localhost:8990/test", URL: "http://localhost:8990/test",
StatusCode: http.StatusUnauthorized, StatusCode: http.StatusUnauthorized,
Body: "Unauthorized: malformed token\n", Body: "unauthorized: malformed token\n",
AuthReq: false, AuthReq: false,
}, },
} }
+20 -7
View File
@@ -93,21 +93,26 @@ func makeHTTPTriggerSet(logger *zap.Logger, fmap *functionServiceMap, fissionCli
return httpTriggerSet, nil return httpTriggerSet, nil
} }
func (ts *HTTPTriggerSet) subscribeRouter(ctx context.Context, mr *mutableRouter) { func (ts *HTTPTriggerSet) subscribeRouter(ctx context.Context, mr *mutableRouter) error {
resolver := makeFunctionReferenceResolver(ts.logger, ts.funcInformer) resolver := makeFunctionReferenceResolver(ts.logger, ts.funcInformer)
ts.resolver = resolver ts.resolver = resolver
ts.mutableRouter = mr ts.mutableRouter = mr
if ts.fissionClient == nil { if ts.fissionClient == nil {
// Used in tests only. // Used in tests only.
mr.updateRouter(ts.getRouter(nil)) router, err := ts.getRouter(nil)
if err != nil {
return err
}
mr.updateRouter(router)
ts.logger.Info("skipping continuous trigger updates") ts.logger.Info("skipping continuous trigger updates")
return return nil
} }
go ts.updateRouter() go ts.updateRouter()
go ts.syncTriggers() go ts.syncTriggers()
go ts.runInformer(ctx, ts.funcInformer) go ts.runInformer(ctx, ts.funcInformer)
go ts.runInformer(ctx, ts.triggerInformer) go ts.runInformer(ctx, ts.triggerInformer)
return nil
} }
func defaultHomeHandler(w http.ResponseWriter, r *http.Request) { func defaultHomeHandler(w http.ResponseWriter, r *http.Request) {
@@ -133,9 +138,12 @@ func versionHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router { func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) (*mux.Router, error) {
featureConfig, _ := config.GetFeatureConfig() featureConfig, err := config.GetFeatureConfig()
if err != nil {
return nil, err
}
muxRouter := mux.NewRouter() muxRouter := mux.NewRouter()
muxRouter.Use(metrics.HTTPMetricMiddleware) muxRouter.Use(metrics.HTTPMetricMiddleware)
@@ -289,7 +297,7 @@ func (ts *HTTPTriggerSet) getRouter(fnTimeoutMap map[types.UID]int) *mux.Router
// version of application. // version of application.
muxRouter.HandleFunc("/_version", versionHandler).Methods("GET") muxRouter.HandleFunc("/_version", versionHandler).Methods("GET")
return muxRouter return muxRouter, nil
} }
func (ts *HTTPTriggerSet) updateTriggerStatusFailed(ht *fv1.HTTPTrigger, err error) { func (ts *HTTPTriggerSet) updateTriggerStatusFailed(ht *fv1.HTTPTrigger, err error) {
@@ -408,6 +416,11 @@ func (ts *HTTPTriggerSet) updateRouter() {
ts.functions = allfunctions ts.functions = allfunctions
// make a new router and use it // make a new router and use it
ts.mutableRouter.updateRouter(ts.getRouter(functionTimeout)) router, err := ts.getRouter(functionTimeout)
if err != nil {
ts.logger.Error("error updating router", zap.Error(err))
continue
}
ts.mutableRouter.updateRouter(router)
} }
} }
+19 -10
View File
@@ -63,28 +63,38 @@ import (
// request url ---[trigger]---> Function(name, deployment) ----[deployment]----> Function(name, uid) ----[pool mgr]---> k8s service url // request url ---[trigger]---> Function(name, deployment) ----[deployment]----> Function(name, uid) ----[pool mgr]---> k8s service url
func router(ctx context.Context, logger *zap.Logger, httpTriggerSet *HTTPTriggerSet) *mutableRouter { func router(ctx context.Context, logger *zap.Logger, httpTriggerSet *HTTPTriggerSet) (*mutableRouter, error) {
var mr *mutableRouter var mr *mutableRouter
mux := mux.NewRouter() mux := mux.NewRouter()
mux.Use(metrics.HTTPMetricMiddleware) mux.Use(metrics.HTTPMetricMiddleware)
// see issue https://github.com/fission/fission/issues/1317 // see issue https://github.com/fission/fission/issues/1317
useEncodedPath, _ := strconv.ParseBool(os.Getenv("USE_ENCODED_PATH")) useEncodedPath, err := strconv.ParseBool(os.Getenv("USE_ENCODED_PATH"))
if err != nil {
return nil, err
}
if useEncodedPath { if useEncodedPath {
mr = newMutableRouter(logger, mux.UseEncodedPath()) mr = newMutableRouter(logger, mux.UseEncodedPath())
} else { } else {
mr = newMutableRouter(logger, mux) mr = newMutableRouter(logger, mux)
} }
httpTriggerSet.subscribeRouter(ctx, mr) err = httpTriggerSet.subscribeRouter(ctx, mr)
return mr if err != nil {
return nil, err
}
return mr, nil
} }
func serve(ctx context.Context, logger *zap.Logger, port int, func serve(ctx context.Context, logger *zap.Logger, port int,
httpTriggerSet *HTTPTriggerSet, displayAccessLog bool) { httpTriggerSet *HTTPTriggerSet, displayAccessLog bool) error {
mr := router(ctx, logger, httpTriggerSet) mr, err := router(ctx, logger, httpTriggerSet)
if err != nil {
return errors.Wrap(err, "error making router")
}
handler := otelUtils.GetHandlerWithOTEL(mr, "fission-router", otelUtils.UrlsToIgnore("/router-healthz")) handler := otelUtils.GetHandlerWithOTEL(mr, "fission-router", otelUtils.UrlsToIgnore("/router-healthz"))
httpserver.StartServer(ctx, logger, "router", fmt.Sprintf("%d", port), handler) go httpserver.StartServer(ctx, logger, "router", fmt.Sprintf("%d", port), handler)
return nil
} }
// Start starts a router // Start starts a router
@@ -198,7 +208,7 @@ func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *
if err != nil { if err != nil {
return errors.Wrap(err, "error making HTTP trigger set") return errors.Wrap(err, "error making HTTP trigger set")
} }
go metrics.ServeMetrics(ctx, logger) go metrics.ServeMetrics(ctx, "router", logger)
logger.Info("starting router", zap.Int("port", port)) logger.Info("starting router", zap.Int("port", port))
@@ -206,6 +216,5 @@ func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *
ctx, span := tracer.Start(ctx, "router/Start") ctx, span := tracer.Start(ctx, "router/Start")
defer span.End() defer span.End()
go serve(ctx, logger, port, triggers, displayAccessLog) return serve(ctx, logger, port, triggers, displayAccessLog)
return nil
} }
+1 -2
View File
@@ -39,8 +39,7 @@ type ArchivePruner struct {
const defaultPruneInterval int = 60 // in minutes const defaultPruneInterval int = 60 // in minutes
func MakeArchivePruner(logger *zap.Logger, stowClient *StowClient, pruneInterval time.Duration) (*ArchivePruner, error) { func MakeArchivePruner(logger *zap.Logger, clientGen crd.ClientGeneratorInterface, stowClient *StowClient, pruneInterval time.Duration) (*ArchivePruner, error) {
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to get fission client") return nil, errors.Wrap(err, "failed to get fission client")
+3 -2
View File
@@ -33,6 +33,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/storagesvc" "github.com/fission/fission/pkg/storagesvc"
) )
@@ -138,7 +139,7 @@ func TestS3StorageService(t *testing.T) {
storage := storagesvc.NewS3Storage() storage := storagesvc.NewS3Storage()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
_ = storagesvc.Start(ctx, logger, storage, port) _ = storagesvc.Start(ctx, crd.NewClientGenerator(), logger, storage, port)
time.Sleep(time.Second) time.Sleep(time.Second)
client := MakeClient(fmt.Sprintf("http://localhost:%v/", 8081)) client := MakeClient(fmt.Sprintf("http://localhost:%v/", 8081))
@@ -216,7 +217,7 @@ func TestLocalStorageService(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
os.Setenv("METRICS_ADDR", "8083") os.Setenv("METRICS_ADDR", "8083")
_ = storagesvc.Start(ctx, logger, storage, port) _ = storagesvc.Start(ctx, crd.NewClientGenerator(), logger, storage, port)
time.Sleep(time.Second) time.Sleep(time.Second)
client := MakeClient(fmt.Sprintf("http://localhost:%v/", port)) client := MakeClient(fmt.Sprintf("http://localhost:%v/", port))
+4 -3
View File
@@ -30,6 +30,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/utils/httpserver" "github.com/fission/fission/pkg/utils/httpserver"
"github.com/fission/fission/pkg/utils/metrics" "github.com/fission/fission/pkg/utils/metrics"
otelUtils "github.com/fission/fission/pkg/utils/otel" otelUtils "github.com/fission/fission/pkg/utils/otel"
@@ -281,7 +282,7 @@ func (ss *StorageService) Start(ctx context.Context, port int) {
} }
// Start runs storage service // Start runs storage service
func Start(ctx context.Context, logger *zap.Logger, storage Storage, port int) error { func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, storage Storage, port int) error {
enablePruner, err := strconv.ParseBool(os.Getenv("PRUNE_ENABLED")) enablePruner, err := strconv.ParseBool(os.Getenv("PRUNE_ENABLED"))
if err != nil { if err != nil {
logger.Warn("PRUNE_ENABLED value not set. Enabling archive pruner by default.", zap.Error(err)) logger.Warn("PRUNE_ENABLED value not set. Enabling archive pruner by default.", zap.Error(err))
@@ -295,7 +296,7 @@ func Start(ctx context.Context, logger *zap.Logger, storage Storage, port int) e
// create http handlers // create http handlers
storageService := MakeStorageService(logger, storageClient, port) storageService := MakeStorageService(logger, storageClient, port)
go metrics.ServeMetrics(ctx, logger) go metrics.ServeMetrics(ctx, "storagesvc", logger)
go storageService.Start(ctx, port) go storageService.Start(ctx, port)
// enablePruner prevents storagesvc unit test from needing to talk to kubernetes // enablePruner prevents storagesvc unit test from needing to talk to kubernetes
@@ -305,7 +306,7 @@ func Start(ctx context.Context, logger *zap.Logger, storage Storage, port int) e
if err != nil { if err != nil {
pruneInterval = defaultPruneInterval pruneInterval = defaultPruneInterval
} }
pruner, err := MakeArchivePruner(logger, storageClient, time.Duration(pruneInterval)) pruner, err := MakeArchivePruner(logger, clientGen, storageClient, time.Duration(pruneInterval))
if err != nil { if err != nil {
return errors.Wrap(err, "Error creating archivePruner") return errors.Wrap(err, "Error creating archivePruner")
} }
+1 -2
View File
@@ -26,8 +26,7 @@ import (
"github.com/fission/fission/pkg/publisher" "github.com/fission/fission/pkg/publisher"
) )
func Start(ctx context.Context, logger *zap.Logger, routerUrl string) error { func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *zap.Logger, routerUrl string) error {
clientGen := crd.NewClientGenerator()
fissionClient, err := clientGen.GetFissionClient() fissionClient, err := clientGen.GetFissionClient()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get fission client") return errors.Wrap(err, "failed to get fission client")
+15 -15
View File
@@ -7,6 +7,8 @@ import (
"testing" "testing"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/hashicorp/go-retryablehttp"
"github.com/stretchr/testify/require"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/fission/fission/pkg/utils/loggerfactory" "github.com/fission/fission/pkg/utils/loggerfactory"
@@ -27,36 +29,34 @@ func TestStartServer(t *testing.T) {
go StartServer(ctx, logger, "test", "8999", m) go StartServer(ctx, logger, "test", "8999", m)
tests := []struct { tests := []struct {
Name string
URL string URL string
StatusCode int StatusCode int
Body string Body string
}{ }{
{ {
Name: "test handler",
URL: "http://localhost:8999", URL: "http://localhost:8999",
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Body: "test handler", Body: "test handler",
}, },
{ {
Name: "not found",
URL: "http://localhost:8999/notfound", URL: "http://localhost:8999/notfound",
StatusCode: http.StatusNotFound, StatusCode: http.StatusNotFound,
Body: "404 page not found\n", Body: "404 page not found\n",
}, },
} }
client := retryablehttp.NewClient()
for _, test := range tests { for _, test := range tests {
resp, err := http.Get(test.URL) t.Run(test.Name, func(t *testing.T) {
if err != nil { resp, err := client.Get(test.URL)
t.Errorf("failed to make get request %v: %v", test.URL, err) require.NoError(t, err, "failed to make get request %s", test.URL)
} defer resp.Body.Close()
defer resp.Body.Close() require.Equal(t, test.StatusCode, resp.StatusCode)
if resp.StatusCode != test.StatusCode { body, err := io.ReadAll(resp.Body)
t.Errorf("expected status code %v, got %v", test.StatusCode, resp.StatusCode) require.NoError(t, err)
} require.Equal(t, string(body), test.Body)
body, err := io.ReadAll(resp.Body) })
if err != nil {
t.Errorf("failed to read response body: %v", err)
}
if string(body) != test.Body {
t.Errorf("expected body \"%v\", got \"%v\"", test.Body, string(body))
}
} }
} }
+2 -2
View File
@@ -28,7 +28,7 @@ import (
"github.com/fission/fission/pkg/utils/httpserver" "github.com/fission/fission/pkg/utils/httpserver"
) )
func ServeMetrics(ctx context.Context, logger *zap.Logger) { func ServeMetrics(ctx context.Context, parent string, logger *zap.Logger) {
metricsAddr := os.Getenv("METRICS_ADDR") metricsAddr := os.Getenv("METRICS_ADDR")
if metricsAddr == "" { if metricsAddr == "" {
metricsAddr = "8080" metricsAddr = "8080"
@@ -45,5 +45,5 @@ func ServeMetrics(ctx context.Context, logger *zap.Logger) {
EnableOpenMetrics: true, EnableOpenMetrics: true,
}, },
)) ))
httpserver.StartServer(ctx, logger, "metrics", metricsAddr, mux) httpserver.StartServer(ctx, logger, parent+"/metrics", metricsAddr, mux)
} }
+16
View File
@@ -231,3 +231,19 @@ func GetUIntValueFromEnv(envVar string) (uint, error) {
} }
return uint(value), nil return uint(value), nil
} }
func FindFreePort() (int, error) {
listener, err := net.Listen("tcp", ":0")
if err != nil {
return 0, err
}
port := listener.Addr().(*net.TCPAddr).Port
err = listener.Close()
if err != nil {
return 0, err
}
return port, nil
}
+59
View File
@@ -0,0 +1,59 @@
package cli_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission/test/e2e/framework"
"github.com/fission/fission/test/e2e/framework/cli"
"github.com/fission/fission/test/e2e/framework/services"
)
func TestFissionCLI(t *testing.T) {
f := framework.NewFramework()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := f.Start(ctx)
require.NoError(t, err)
err = services.StartServices(ctx, f)
require.NoError(t, err)
fissionClient, err := f.ClientGen().GetFissionClient()
require.NoError(t, err)
t.Run("environment", func(t *testing.T) {
t.Run("create", func(t *testing.T) {
_, err := cli.ExecCommand(f, ctx, "env", "create", "--name", "test-env", "--image", "fission/python-env")
require.NoError(t, err)
env, err := fissionClient.CoreV1().Environments(metav1.NamespaceDefault).Get(ctx, "test-env", metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, env)
require.Equal(t, "test-env", env.Name)
require.Equal(t, "fission/python-env", env.Spec.Runtime.Image)
})
t.Run("update", func(t *testing.T) {
_, err := cli.ExecCommand(f, ctx, "env", "update", "--name", "test-env", "--image", "fission/python-env:v2")
require.NoError(t, err)
env, err := fissionClient.CoreV1().Environments(metav1.NamespaceDefault).Get(ctx, "test-env", metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, env)
require.Equal(t, "test-env", env.Name)
require.Equal(t, "fission/python-env:v2", env.Spec.Runtime.Image)
})
t.Run("delete", func(t *testing.T) {
_, err := cli.ExecCommand(f, ctx, "env", "delete", "--name", "test-env")
require.NoError(t, err)
_, err = fissionClient.CoreV1().Environments(metav1.NamespaceDefault).Get(ctx, "test-env", metav1.GetOptions{})
require.Error(t, err)
})
})
}
+27
View File
@@ -0,0 +1,27 @@
package cli
import (
"bytes"
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission/cmd/fission-cli/app"
"github.com/fission/fission/pkg/fission-cli/cmd"
"github.com/fission/fission/test/e2e/framework"
)
func ExecCommand(f *framework.Framework, ctx context.Context, args ...string) (string, error) {
cmd := app.App(cmd.ClientOptions{
RestConfig: f.RestConfig(),
Namespace: metav1.NamespaceDefault,
})
cmd.SilenceErrors = true // use our own error message printer
cmd.SetArgs(args)
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
err := cmd.ExecuteContext(ctx)
return buf.String(), err
}
+86
View File
@@ -0,0 +1,86 @@
package framework
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"go.uber.org/zap"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/loggerfactory"
)
const (
EXECUTOR_URL = "http://executor.fission"
STORAGESVC_URL = "http://storagesvc.fission"
)
type ServiceInfo struct {
Port int
}
type Framework struct {
env *envtest.Environment
config *rest.Config
logger *zap.Logger
ServiceInfo map[string]ServiceInfo
}
func NewFramework() *Framework {
return &Framework{
logger: loggerfactory.GetLogger(),
env: &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("../../..", "crds", "v1")},
ErrorIfCRDPathMissing: true,
CRDInstallOptions: envtest.CRDInstallOptions{
MaxTime: 60 * time.Second,
},
BinaryAssetsDirectory: os.Getenv("KUBEBUILDER_ASSETS"),
},
ServiceInfo: make(map[string]ServiceInfo),
}
}
func (f *Framework) Start(ctx context.Context) error {
var err error
f.config, err = f.env.Start()
if err != nil {
return fmt.Errorf("error starting test env: %v", err)
}
return nil
}
func (f *Framework) ToggleMetricAddr() error {
port, err := utils.FindFreePort()
if err != nil {
return fmt.Errorf("error finding unused port: %v", err)
}
os.Setenv("METRICS_ADDR", fmt.Sprint(port))
return nil
}
func (f *Framework) RestConfig() *rest.Config {
return f.config
}
func (f *Framework) Logger() *zap.Logger {
return f.logger
}
func (f *Framework) ClientGen() *crd.ClientGenerator {
return crd.NewClientGeneratorWithRestConfig(f.config)
}
func (f *Framework) Stop() error {
err := f.env.Stop()
if err != nil {
return fmt.Errorf("error stopping test env: %v", err)
}
return nil
}
+92
View File
@@ -0,0 +1,92 @@
package services
import (
"context"
"fmt"
"os"
"github.com/fission/fission/pkg/buildermgr"
"github.com/fission/fission/pkg/executor"
"github.com/fission/fission/pkg/router"
"github.com/fission/fission/pkg/storagesvc"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/test/e2e/framework"
)
func StartServices(ctx context.Context, f *framework.Framework) error {
executorPort, err := utils.FindFreePort()
if err != nil {
return fmt.Errorf("error finding unused port: %v", err)
}
err = f.ToggleMetricAddr()
if err != nil {
return fmt.Errorf("error toggling metric address: %v", err)
}
err = executor.StartExecutor(ctx, f.ClientGen(), f.Logger(), executorPort)
if err != nil {
return fmt.Errorf("error starting executor: %v", err)
}
f.ServiceInfo["executor"] = framework.ServiceInfo{
Port: executorPort,
}
os.Setenv("PRUNE_ENABLED", "true")
os.Setenv("PRUNE_INTERVAL", "60")
storageDir, err := os.MkdirTemp("/tmp", "storagesvc")
if err != nil {
return fmt.Errorf("error creating temp directory: %v", err)
}
storageSvcPort, err := utils.FindFreePort()
if err != nil {
return fmt.Errorf("error finding unused port: %v", err)
}
err = f.ToggleMetricAddr()
if err != nil {
return fmt.Errorf("error toggling metric address: %v", err)
}
err = storagesvc.Start(ctx, f.ClientGen(), f.Logger(), storagesvc.NewLocalStorage(storageDir), storageSvcPort)
if err != nil {
return fmt.Errorf("error starting storage service: %v", err)
}
f.ServiceInfo["storagesvc"] = framework.ServiceInfo{
Port: storageSvcPort,
}
err = f.ToggleMetricAddr()
if err != nil {
return fmt.Errorf("error toggling metric address: %v", err)
}
err = buildermgr.Start(ctx, f.ClientGen(), f.Logger(), fmt.Sprintf("http://localhost:%d", storageSvcPort))
if err != nil {
return fmt.Errorf("error starting builder manager: %v", err)
}
f.ServiceInfo["buildermgr"] = framework.ServiceInfo{}
os.Setenv("ROUTER_ROUND_TRIP_TIMEOUT", "50ms")
os.Setenv("ROUTER_ROUNDTRIP_TIMEOUT_EXPONENT", "2")
os.Setenv("ROUTER_ROUND_TRIP_KEEP_ALIVE_TIME", "30s")
os.Setenv("ROUTER_ROUND_TRIP_DISABLE_KEEP_ALIVE", "true")
os.Setenv("ROUTER_ROUND_TRIP_MAX_RETRIES", "10")
os.Setenv("ROUTER_SVC_ADDRESS_MAX_RETRIES", "5")
os.Setenv("ROUTER_SVC_ADDRESS_UPDATE_TIMEOUT", "30s")
os.Setenv("ROUTER_UNTAP_SERVICE_TIMEOUT", "3600s")
os.Setenv("USE_ENCODED_PATH", "false")
os.Setenv("DISPLAY_ACCESS_LOG", "false")
os.Setenv("DEBUG_ENV", "false")
routerPort, err := utils.FindFreePort()
if err != nil {
return fmt.Errorf("error finding unused port: %v", err)
}
err = f.ToggleMetricAddr()
if err != nil {
return fmt.Errorf("error toggling metric address: %v", err)
}
err = router.Start(ctx, f.ClientGen(), f.Logger(), routerPort, fmt.Sprintf("http://localhost:%d", executorPort))
if err != nil {
return fmt.Errorf("error starting router: %v", err)
}
f.ServiceInfo["router"] = framework.ServiceInfo{
Port: routerPort,
}
return nil
}
+3 -2
View File
@@ -11,6 +11,7 @@ import (
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
"github.com/fission/fission/cmd/fission-cli/app" "github.com/fission/fission/cmd/fission-cli/app"
"github.com/fission/fission/pkg/fission-cli/cmd"
) )
const fmTemplate = `--- const fmTemplate = `---
@@ -40,9 +41,9 @@ func main() {
Use: "fission-cli-docs", Use: "fission-cli-docs",
Short: "Generate docs for fission-cli", Short: "Generate docs for fission-cli",
Long: "Generate docs for fission-cli", Long: "Generate docs for fission-cli",
Run: func(cmd *cobra.Command, args []string) { Run: func(command *cobra.Command, args []string) {
log.Printf("Generating docs in directory %s", outdir) log.Printf("Generating docs in directory %s", outdir)
fissionApp := app.App() fissionApp := app.App(cmd.ClientOptions{})
fissionApp.DisableAutoGenTag = true fissionApp.DisableAutoGenTag = true
fissionApp.Short = "Serverless framework for Kubernetes" fissionApp.Short = "Serverless framework for Kubernetes"
err := doc.GenMarkdownTreeCustom(fissionApp, outdir, filePrepender, linkHandler) err := doc.GenMarkdownTreeCustom(fissionApp, outdir, filePrepender, linkHandler)