diff --git a/hack/kind-config.yaml b/hack/kind-config.yaml index 700c163d..3c26e364 100644 --- a/hack/kind-config.yaml +++ b/hack/kind-config.yaml @@ -4,11 +4,6 @@ featureGates: MaxUnavailableStatefulSet: true nodes: - role: control-plane - extraPortMappings: - - containerPort: 30000 - hostPort: 30000 - listenAddress: "0.0.0.0" - protocol: tcp - role: worker - role: worker - role: worker diff --git a/pkg/controller/inference/playground_controller.go b/pkg/controller/inference/playground_controller.go index 3cc701d3..a332febd 100644 --- a/pkg/controller/inference/playground_controller.go +++ b/pkg/controller/inference/playground_controller.go @@ -83,9 +83,9 @@ func (r *PlaygroundReconciler) Reconcile(ctx context.Context, req ctrl.Request) var serviceApplyConfiguration *inferenceclientgo.ServiceApplyConfiguration + model := &coreapi.OpenModel{} if playground.Spec.ModelClaim != nil { modelName := playground.Spec.ModelClaim.ModelName - model := &coreapi.OpenModel{} if err := r.Get(ctx, types.NamespacedName{Name: string(modelName)}, model); err != nil { return ctrl.Result{}, err diff --git a/pkg/webhook/playground_webhook.go b/pkg/webhook/playground_webhook.go index e439ee32..fb52c9b6 100644 --- a/pkg/webhook/playground_webhook.go +++ b/pkg/webhook/playground_webhook.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + coreapi "github.com/inftyai/llmaz/api/core/v1alpha1" inferenceapi "github.com/inftyai/llmaz/api/inference/v1alpha1" ) @@ -46,6 +47,19 @@ var _ webhook.CustomDefaulter = &PlaygroundWebhook{} // Default implements webhook.Defaulter so a webhook will be registered for the type func (w *PlaygroundWebhook) Default(ctx context.Context, obj runtime.Object) error { + playground := obj.(*inferenceapi.Playground) + + var modelName string + if playground.Spec.ModelClaim != nil { + modelName = string(playground.Spec.ModelClaim.ModelName) + } + // TODO: handle MultiModelsClaims in the future. + + if playground.Labels == nil { + playground.Labels = map[string]string{} + } + playground.Labels[coreapi.ModelNameLabelKey] = modelName + return nil } diff --git a/test/e2e/playground_test.go b/test/e2e/playground_test.go index b54f579b..9aacb2c0 100644 --- a/test/e2e/playground_test.go +++ b/test/e2e/playground_test.go @@ -56,10 +56,12 @@ var _ = ginkgo.Describe("playground e2e tests", func() { playground := wrapper.MakePlayground("qwen2-0-5b-gguf", ns.Name).ModelClaim("qwen2-0-5b-gguf").Backend("llamacpp").Replicas(3).Obj() gomega.Expect(k8sClient.Create(ctx, playground)).To(gomega.Succeed()) + validation.ValidatePlayground(ctx, k8sClient, playground) validation.ValidatePlaygroundStatusEqualTo(ctx, k8sClient, playground, inferenceapi.PlaygroundAvailable, "PlaygroundReady", metav1.ConditionTrue) service := &inferenceapi.Service{} gomega.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: playground.Name, Namespace: playground.Namespace}, service)).To(gomega.Succeed()) + validation.ValidateService(ctx, k8sClient, service) validation.ValidateServiceStatusEqualTo(ctx, k8sClient, service, inferenceapi.ServiceAvailable, "ServiceReady", metav1.ConditionTrue) validation.ValidateServicePods(ctx, k8sClient, service) }) diff --git a/test/integration/controller/inference/playground_test.go b/test/integration/controller/inference/playground_test.go index 9a25e635..c823297e 100644 --- a/test/integration/controller/inference/playground_test.go +++ b/test/integration/controller/inference/playground_test.go @@ -121,7 +121,7 @@ var _ = ginkgo.Describe("playground controller test", func() { }), ginkgo.Entry("advance configured Playground with sglang", &testValidatingCase{ makePlayground: func() *inferenceapi.Playground { - return wrapper.MakePlayground("playground", ns.Name).ModelClaim(model.Name). + return wrapper.MakePlayground("playground", ns.Name).ModelClaim(model.Name).Label(coreapi.ModelNameLabelKey, model.Name). Backend("sglang").BackendVersion("main").BackendArgs([]string{"--foo", "bar"}).BackendEnv("FOO", "BAR").BackendRequest("cpu", "1").BackendLimit("cpu", "10"). Obj() }, @@ -139,7 +139,7 @@ var _ = ginkgo.Describe("playground controller test", func() { }), ginkgo.Entry("advance configured Playground with llamacpp", &testValidatingCase{ makePlayground: func() *inferenceapi.Playground { - return wrapper.MakePlayground("playground", ns.Name).ModelClaim(model.Name). + return wrapper.MakePlayground("playground", ns.Name).ModelClaim(model.Name).Label(coreapi.ModelNameLabelKey, model.Name). Backend("llamacpp").BackendVersion("main").BackendArgs([]string{"--foo", "bar"}).BackendEnv("FOO", "BAR").BackendRequest("cpu", "1").BackendLimit("cpu", "10"). Obj() }, diff --git a/test/integration/webhook/playground_test.go b/test/integration/webhook/playground_test.go index 5442ba16..1bcb1c25 100644 --- a/test/integration/webhook/playground_test.go +++ b/test/integration/webhook/playground_test.go @@ -17,11 +17,13 @@ limitations under the License. package webhook import ( + "github.com/google/go-cmp/cmp/cmpopts" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + coreapi "github.com/inftyai/llmaz/api/core/v1alpha1" inferenceapi "github.com/inftyai/llmaz/api/inference/v1alpha1" "github.com/inftyai/llmaz/test/util/wrapper" ) @@ -47,7 +49,6 @@ var _ = ginkgo.Describe("playground default and validation", func() { playground func() *inferenceapi.Playground failed bool } - // TODO: Add more testCases to cover updating. ginkgo.DescribeTable("test validating", func(tc *testValidatingCase) { if tc.failed { @@ -93,4 +94,26 @@ var _ = ginkgo.Describe("playground default and validation", func() { failed: true, }), ) + + type testDefaultingCase struct { + playground func() *inferenceapi.Playground + wantPlayground func() *inferenceapi.Playground + } + ginkgo.DescribeTable("test validating", + func(tc *testDefaultingCase) { + playground := tc.playground() + gomega.Expect(k8sClient.Create(ctx, playground)).To(gomega.Succeed()) + gomega.Expect(playground).To(gomega.BeComparableTo(tc.wantPlayground(), + cmpopts.IgnoreTypes(inferenceapi.PlaygroundStatus{}), + cmpopts.IgnoreFields(metav1.ObjectMeta{}, "UID", "ResourceVersion", "Generation", "CreationTimestamp", "ManagedFields"))) + }, + ginkgo.Entry("defaulting label with modelClaim", &testDefaultingCase{ + playground: func() *inferenceapi.Playground { + return wrapper.MakePlayground("playground", ns.Name).ModelClaim("llama3-8b").Replicas(1).Obj() + }, + wantPlayground: func() *inferenceapi.Playground { + return wrapper.MakePlayground("playground", ns.Name).ModelClaim("llama3-8b").Replicas(1).Label(coreapi.ModelNameLabelKey, "llama3-8b").Obj() + }, + }), + ) }) diff --git a/test/util/mock.go b/test/util/mock.go index b39faf97..d96c7391 100644 --- a/test/util/mock.go +++ b/test/util/mock.go @@ -16,7 +16,7 @@ limitations under the License. package util import ( - api "github.com/inftyai/llmaz/api/core/v1alpha1" + coreapi "github.com/inftyai/llmaz/api/core/v1alpha1" inferenceapi "github.com/inftyai/llmaz/api/inference/v1alpha1" "github.com/inftyai/llmaz/test/util/wrapper" ) @@ -25,12 +25,12 @@ const ( sampleModelName = "llama3-8b" ) -func MockASampleModel() *api.OpenModel { +func MockASampleModel() *coreapi.OpenModel { return wrapper.MakeModel(sampleModelName).FamilyName("llama3").ModelSourceWithModelHub("Huggingface").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "").Obj() } func MockASamplePlayground(ns string) *inferenceapi.Playground { - return wrapper.MakePlayground("playground-llama3-8b", ns).ModelClaim(sampleModelName).Obj() + return wrapper.MakePlayground("playground-llama3-8b", ns).ModelClaim(sampleModelName).Label(coreapi.ModelNameLabelKey, sampleModelName).Obj() } func MockASampleService(ns string) *inferenceapi.Service { diff --git a/test/util/validation/validate_playground.go b/test/util/validation/validate_playground.go index e92682a9..b10f744f 100644 --- a/test/util/validation/validate_playground.go +++ b/test/util/validation/validate_playground.go @@ -62,6 +62,10 @@ func ValidatePlayground(ctx context.Context, k8sClient client.Client, playground } } + if playground.Labels[coreapi.ModelNameLabelKey] != model.Name { + return fmt.Errorf("unexpected Playground label value, want %v, got %v", model.Name, playground.Labels[coreapi.ModelNameLabelKey]) + } + // TODO: MultiModelsClaim backendName := inferenceapi.DefaultBackend diff --git a/test/util/wrapper/playground.go b/test/util/wrapper/playground.go index ca009e25..81123625 100644 --- a/test/util/wrapper/playground.go +++ b/test/util/wrapper/playground.go @@ -43,6 +43,14 @@ func (w *PlaygroundWrapper) Obj() *inferenceapi.Playground { return &w.Playground } +func (w *PlaygroundWrapper) Label(k, v string) *PlaygroundWrapper { + if w.Labels == nil { + w.Labels = map[string]string{} + } + w.Labels[k] = v + return w +} + func (w *PlaygroundWrapper) Replicas(replicas int32) *PlaygroundWrapper { w.Spec.Replicas = &replicas return w @@ -53,10 +61,12 @@ func (w *PlaygroundWrapper) ModelClaim(modelName string, flavorNames ...string) for _, name := range flavorNames { names = append(names, coreapi.FlavorName(name)) } - w.Spec.ModelClaim = &coreapi.ModelClaim{ - ModelName: coreapi.ModelName(modelName), - InferenceFlavors: names, + ModelName: coreapi.ModelName(modelName), + } + + if len(names) > 0 { + w.Spec.ModelClaim.InferenceFlavors = names } return w }