diff --git a/src/compute-plane-services/nvca/internal/miniservice/BUILD.bazel b/src/compute-plane-services/nvca/internal/miniservice/BUILD.bazel index c1b28ec60..b903ca03b 100644 --- a/src/compute-plane-services/nvca/internal/miniservice/BUILD.bazel +++ b/src/compute-plane-services/nvca/internal/miniservice/BUILD.bazel @@ -161,6 +161,7 @@ go_test( "//src/compute-plane-services/nvca/internal/metrics", "//src/compute-plane-services/nvca/internal/miniservice/chartcache", "//src/compute-plane-services/nvca/internal/otel", + "//src/compute-plane-services/nvca/internal/transporttls", "//src/compute-plane-services/nvca/internal/util/k8sutil", "//src/compute-plane-services/nvca/internal/util/k8sutil/mock", "//src/compute-plane-services/nvca/pkg/apis/nvca/v1:nvca", @@ -219,6 +220,7 @@ go_test( "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/client", "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake", "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/client/interceptor", + "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/config", "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/log", "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/manager", "//src/compute-plane-services/nvca/vendor/sigs.k8s.io/controller-runtime/pkg/reconcile", diff --git a/src/compute-plane-services/nvca/internal/miniservice/controller_test.go b/src/compute-plane-services/nvca/internal/miniservice/controller_test.go index 7e7133544..45827a846 100644 --- a/src/compute-plane-services/nvca/internal/miniservice/controller_test.go +++ b/src/compute-plane-services/nvca/internal/miniservice/controller_test.go @@ -49,11 +49,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/cache/informertest" "sigs.k8s.io/controller-runtime/pkg/client" clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake" + ctrlconfig "sigs.k8s.io/controller-runtime/pkg/config" "sigs.k8s.io/controller-runtime/pkg/manager" nvcaenvtest "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/internal/envtest" "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/internal/icms" "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/internal/metrics" + "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/internal/transporttls" "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/internal/util/k8sutil" k8smock "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/internal/util/k8sutil/mock" "github.com/NVIDIA/nvcf/src/compute-plane-services/nvca/pkg/apis/nvca/v1alpha1" @@ -72,7 +74,38 @@ func init() { utilruntime.Must(SchemeBuilder.AddToScheme(mgrScheme)) } +type controllerTestCase struct { + functionType string + configure func(*nvcaconfig.Config, *featureflagmock.Fetcher) + additionalEnvs []corev1.EnvVar + assertUtilsPod func(*testing.T, context.Context, client.Client, *v1alpha1.MiniService, *corev1.Pod) +} + func TestController(t *testing.T) { + testController(t, controllerTestCase{functionType: "DEFAULT"}) +} + +func TestControllerHelmLLMTransportTLS(t *testing.T) { + testController(t, controllerTestCase{ + functionType: function.FunctionTypeLLM, + configure: func(cfg *nvcaconfig.Config, fff *featureflagmock.Fetcher) { + fff.EnabledFFs = append(fff.EnabledFFs, featureflag.EnforceHelmFunctionResourceLimits) + cfg.Workload.TransportTLS = &nvcaconfig.TransportTLSConfig{ + TrustMode: nvcaconfig.TrustModeBundle, + TrustBundleConfigMapName: "nvcf-transport-trust-bundle", + TrustBundleKey: "nvcf-ca-bundle.pem", + TrustBundleFingerprint: testTransportTLSRootFingerprint, + TrustBundlePEM: testTransportTLSRootCertPEM, + } + }, + additionalEnvs: []corev1.EnvVar{ + {Name: "LLM_REQUEST_ROUTER_ADDRESS", Value: "llm-router.example.test:443"}, + }, + assertUtilsPod: assertHelmLLMTransportTLS, + }) +} + +func testController(t *testing.T, tc controllerTestCase) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -80,12 +113,16 @@ func TestController(t *testing.T) { require.NoError(t, err) t.Cleanup(cleanup) + skipControllerNameValidation := true mgr, err := ctrl.NewManager(restConfig, manager.Options{ Scheme: mgrScheme, GracefulShutdownTimeout: new(time.Duration), BaseContext: func() context.Context { return ctx }, WebhookServer: nvcaenvtest.NewFakeWebhookServer(), Metrics: nvcaenvtest.NewFakeMetricsOptions(), + Controller: ctrlconfig.Controller{ + SkipNameValidation: &skipControllerNameValidation, + }, }) require.NoError(t, err) @@ -191,8 +228,14 @@ func TestController(t *testing.T) { FeatureFlagFetcher: fff, ClusterName: "local", ClusterRegion: "us-west-1", - Metrics: metrics.NewDefaultMetrics("nca-cluster", "cluster-foo", "cluster-group-foo", "1.2.3"), - cacheDir: t.TempDir(), + Metrics: metrics.NewDefaultMetrics( + "nca-cluster", + "cluster-foo", + "cluster-group-foo", + "1.2.3", + metrics.WithRegisterer(prometheus.NewRegistry()), + ), + cacheDir: t.TempDir(), } cfg := nvcaconfig.Config{ @@ -210,6 +253,9 @@ func TestController(t *testing.T) { }, }, } + if tc.configure != nil { + tc.configure(&cfg, fff) + } err = k8sutil.SetConfigDefaultResources(&cfg) require.NoError(t, err) @@ -250,6 +296,7 @@ func TestController(t *testing.T) { {Name: "TRACING_ACCESS_TOKEN", Value: "trace-tok-1"}, {Name: "UTILS_CONTAINER", Value: "registry.example.test/nvcf-core/nvcf_worker_utils:2.21.4"}, } + envs = append(envs, tc.additionalEnvs...) sr := &nvcav2beta1.ICMSRequest{} sr.Name = "sr-7788caf9-cac0-42a4-820d-36bde3ced020" @@ -258,7 +305,7 @@ func TestController(t *testing.T) { FunctionDetails: function.Details{ FunctionID: "funcid-1", FunctionVersionID: "funcverid-1", - FunctionType: "DEFAULT", + FunctionType: tc.functionType, }, Action: common.FunctionCreationAction, NCAId: "ncaid-1", @@ -397,6 +444,10 @@ rules: assert.NoError(collect, err) }, 2*time.Second, 100*time.Millisecond) + if tc.assertUtilsPod != nil { + tc.assertUtilsPod(t, ctx, crclient, ms, utilsPod) + } + utilsPod.Status.Phase = corev1.PodRunning utilsPod.Status.Conditions = []corev1.PodCondition{ { @@ -444,6 +495,34 @@ rules: <-mgrErrCh } +func assertHelmLLMTransportTLS( + t *testing.T, + ctx context.Context, + crclient client.Client, + ms *v1alpha1.MiniService, + utilsPod *corev1.Pod, +) { + llmWorker := findWorkloadContainer(utilsPod.Spec, function.LLMWorkerContainerName) + require.NotNil(t, llmWorker) + installContainer := findWorkloadInitContainer(utilsPod.Spec, transporttls.InstallContainerName) + require.NotNil(t, installContainer) + assert.Equal(t, llmWorker.Resources, installContainer.Resources) + assert.NotNil(t, findWorkloadVolume(utilsPod.Spec, transporttls.TrustBundleVolumeName)) + assert.NotNil(t, findWorkloadVolume(utilsPod.Spec, transporttls.MergedCertsVolumeName)) + assert.Equal(t, transporttls.SystemCertFile, + findWorkloadEnvValue(llmWorker, transporttls.CertPathEnv)) + assert.NotNil(t, findWorkloadVolumeMount(llmWorker, transporttls.MergedCertsVolumeName)) + + trustConfigMap := &corev1.ConfigMap{} + require.NoError(t, crclient.Get(ctx, client.ObjectKey{ + Name: "nvcf-transport-trust-bundle", + Namespace: ms.Spec.Namespace, + }, trustConfigMap)) + assert.Equal(t, testTransportTLSRootCertPEM, trustConfigMap.Data["nvcf-ca-bundle.pem"]) + assert.Equal(t, testTransportTLSRootFingerprint, + trustConfigMap.Data[transporttls.TrustBundleFingerprintKey]) +} + func TestNVLinkOptMetricsRunnable(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) t.Cleanup(cancel) diff --git a/src/compute-plane-services/nvca/internal/miniservice/reconcile.go b/src/compute-plane-services/nvca/internal/miniservice/reconcile.go index df5796538..1d9001db4 100644 --- a/src/compute-plane-services/nvca/internal/miniservice/reconcile.go +++ b/src/compute-plane-services/nvca/internal/miniservice/reconcile.go @@ -663,7 +663,8 @@ func (r *Reconciler) doInstall(ctx context.Context, return reconcile.Result{}, err } - if err := r.prepareTransportTLSForWorkloads(ctx, ms, workloadObjs); err != nil { + transportTLSObjs := append([]client.Object{utilsPod}, workloadObjs...) + if err := r.prepareTransportTLSForWorkloads(ctx, ms, transportTLSObjs); err != nil { return reconcile.Result{}, err } diff --git a/src/compute-plane-services/nvca/internal/transporttls/BUILD.bazel b/src/compute-plane-services/nvca/internal/transporttls/BUILD.bazel index d6449e2ba..72cacba47 100644 --- a/src/compute-plane-services/nvca/internal/transporttls/BUILD.bazel +++ b/src/compute-plane-services/nvca/internal/transporttls/BUILD.bazel @@ -36,6 +36,7 @@ go_test( "//src/compute-plane-services/nvca/vendor/github.com/stretchr/testify/assert", "//src/compute-plane-services/nvca/vendor/github.com/stretchr/testify/require", "//src/compute-plane-services/nvca/vendor/k8s.io/api/core/v1:core", + "//src/compute-plane-services/nvca/vendor/k8s.io/apimachinery/pkg/api/resource", "//src/compute-plane-services/nvca/vendor/k8s.io/apimachinery/pkg/util/strategicpatch", ], ) diff --git a/src/compute-plane-services/nvca/internal/transporttls/transport_tls.go b/src/compute-plane-services/nvca/internal/transporttls/transport_tls.go index 8027ccdaa..2379f2830 100644 --- a/src/compute-plane-services/nvca/internal/transporttls/transport_tls.go +++ b/src/compute-plane-services/nvca/internal/transporttls/transport_tls.go @@ -156,8 +156,9 @@ func InjectIntoPodSpec(podSpec *corev1.PodSpec, cfg nvcaconfig.TransportTLSConfi if err := validateInstalledBundleMountConflict(&podSpec.Containers[llmWorkerIdx], cfg.InstalledBundleMountPath); err != nil { return err } + llmWorkerResources := *podSpec.Containers[llmWorkerIdx].Resources.DeepCopy() upsertVolumes(podSpec, cfg) - upsertInstallContainer(podSpec, installImage, installImagePullPolicy, cfg) + upsertInstallContainer(podSpec, installImage, installImagePullPolicy, llmWorkerResources, cfg) llmWorker := &podSpec.Containers[llmWorkerIdx] upsertVolumeMount(&llmWorker.VolumeMounts, corev1.VolumeMount{ @@ -234,12 +235,14 @@ func upsertInstallContainer( podSpec *corev1.PodSpec, image string, imagePullPolicy corev1.PullPolicy, + resources corev1.ResourceRequirements, cfg nvcaconfig.TransportTLSConfig, ) { upsertContainer(&podSpec.InitContainers, corev1.Container{ Name: InstallContainerName, Image: image, ImagePullPolicy: imagePullPolicy, + Resources: resources, Command: []string{InstallCommandPath}, Args: []string{ "--system-bundle", SystemCertFile, diff --git a/src/compute-plane-services/nvca/internal/transporttls/transport_tls_test.go b/src/compute-plane-services/nvca/internal/transporttls/transport_tls_test.go index d4ffa22a7..902c54bfd 100644 --- a/src/compute-plane-services/nvca/internal/transporttls/transport_tls_test.go +++ b/src/compute-plane-services/nvca/internal/transporttls/transport_tls_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/strategicpatch" ) @@ -181,10 +182,24 @@ func TestValidateConfigRejectsInvalidInstalledBundleMountPaths(t *testing.T) { } func TestInjectIntoPodSpecOnlyMutatesLLMWorker(t *testing.T) { + llmWorkerResources := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + } podSpec := &corev1.PodSpec{ InitContainers: []corev1.Container{testWorkerInitContainer()}, Containers: []corev1.Container{ - {Name: function.LLMWorkerContainerName, Image: "nvcr.io/nvcf/llm-worker:test"}, + { + Name: function.LLMWorkerContainerName, + Image: "nvcr.io/nvcf/llm-worker:test", + Resources: llmWorkerResources, + }, {Name: "inference", Image: "nvcr.io/customer/inference:test"}, {Name: "smb-server", Image: "nvcr.io/nvcf/smb-server:test"}, }, @@ -203,7 +218,9 @@ func TestInjectIntoPodSpecOnlyMutatesLLMWorker(t *testing.T) { require.NotNil(t, trustBundleVolume.ConfigMap.Optional) assert.False(t, *trustBundleVolume.ConfigMap.Optional) assert.NotNil(t, findTestVolume(podSpec, MergedCertsVolumeName)) - assert.NotNil(t, findTestInitContainer(podSpec, InstallContainerName)) + installContainer := findTestInitContainer(podSpec, InstallContainerName) + require.NotNil(t, installContainer) + assert.Equal(t, llmWorkerResources, installContainer.Resources) llmWorker := findTestContainer(podSpec, function.LLMWorkerContainerName) require.NotNil(t, llmWorker)