Files
2026-07-13 12:24:33 +08:00

174 lines
5.7 KiB
Go

/*
Copyright 2026.
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 webhook
import (
"context"
"crypto/tls"
"fmt"
"net"
"os"
"path/filepath"
"testing"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
lmcachev1alpha1 "github.com/LMCache/LMCache/api/v1alpha1"
)
// These package-level handles back the envtest-based integration spec
// (pod_injector_envtest_test.go). The fake-client unit tests in
// pod_injector_test.go do not use them. envtest is started once for the suite;
// if the binaries are missing the suite fails fast (run `make setup-envtest`).
var (
envtestCtx context.Context
envtestCancel context.CancelFunc
mgrCancel context.CancelFunc
testEnv *envtest.Environment
cfg *rest.Config
k8sClient client.Client
envtestScheme *runtime.Scheme
)
// TestWebhook runs the Ginkgo suite for the CacheBlend mutating webhook package.
func TestWebhook(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "CacheBlend Webhook Suite")
}
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
envtestCtx, envtestCancel = context.WithCancel(context.TODO())
envtestScheme = runtime.NewScheme()
Expect(clientgoscheme.AddToScheme(envtestScheme)).To(Succeed())
Expect(lmcachev1alpha1.AddToScheme(envtestScheme)).To(Succeed())
By("bootstrapping the test environment with the mutating webhook installed")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
ErrorIfCRDPathMissing: true,
// Point at the generated manifest FILE, not the config/webhook dir: the
// dir also holds the kustomize selectors patch (a partial
// MutatingWebhookConfiguration with the same name), which envtest's
// path loader would try to install and reject as invalid. The base
// manifest has no objectSelector, so the webhook matches every pod —
// fine here, since the specs control which pods exist.
WebhookInstallOptions: envtest.WebhookInstallOptions{
Paths: []string{filepath.Join("..", "..", "config", "webhook", "manifests.yaml")},
},
}
if dir := firstFoundEnvtestBinaryDir(); dir != "" {
testEnv.BinaryAssetsDirectory = dir
}
var err error
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
k8sClient, err = client.New(cfg, client.Options{Scheme: envtestScheme})
Expect(err).NotTo(HaveOccurred())
By("starting a manager that serves the CacheBlendPodInjector webhook")
wio := &testEnv.WebhookInstallOptions
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: envtestScheme,
Metrics: metricsserver.Options{BindAddress: "0"},
WebhookServer: webhook.NewServer(webhook.Options{
Host: wio.LocalServingHost,
Port: wio.LocalServingPort,
CertDir: wio.LocalServingCertDir,
}),
LeaderElection: false,
})
Expect(err).NotTo(HaveOccurred())
// The handler uses a direct (uncached) client so reads of the
// CacheBlendEngine and its connection ConfigMap succeed without waiting on
// informer cache sync — the production wiring uses mgr.GetClient().
directClient, err := client.New(cfg, client.Options{Scheme: envtestScheme})
Expect(err).NotTo(HaveOccurred())
mgr.GetWebhookServer().Register("/mutate--v1-pod", &webhook.Admission{Handler: &CacheBlendPodInjector{
Client: directClient,
Decoder: admission.NewDecoder(mgr.GetScheme()),
}})
var mgrCtx context.Context
mgrCtx, mgrCancel = context.WithCancel(envtestCtx)
go func() {
defer GinkgoRecover()
Expect(mgr.Start(mgrCtx)).To(Succeed())
}()
By("waiting for the webhook server's TLS port to accept connections")
addr := fmt.Sprintf("%s:%d", wio.LocalServingHost, wio.LocalServingPort)
Eventually(func() error {
conn, derr := tls.DialWithDialer(&net.Dialer{Timeout: time.Second}, "tcp", addr,
&tls.Config{InsecureSkipVerify: true}) //nolint:gosec // test-only readiness probe
if derr != nil {
return derr
}
return conn.Close()
}, 30*time.Second, 200*time.Millisecond).Should(Succeed())
})
var _ = AfterSuite(func() {
if mgrCancel != nil {
mgrCancel()
}
if envtestCancel != nil {
envtestCancel()
}
By("tearing down the test environment")
if testEnv != nil {
Eventually(func() error { return testEnv.Stop() }, time.Minute, time.Second).Should(Succeed())
}
})
// firstFoundEnvtestBinaryDir locates the envtest binary directory under bin/k8s
// so the suite runs from an IDE without KUBEBUILDER_ASSETS set, mirroring the
// controller suite's helper.
func firstFoundEnvtestBinaryDir() string {
basePath := filepath.Join("..", "..", "bin", "k8s")
entries, err := os.ReadDir(basePath)
if err != nil {
return ""
}
for _, entry := range entries {
if entry.IsDir() {
return filepath.Join(basePath, entry.Name())
}
}
return ""
}