@@ -30,10 +30,13 @@ import (
3030 // to ensure that exec-entrypoint and run can make use of them.
3131 _ "k8s.io/client-go/plugin/pkg/client/auth"
3232
33+ "github.com/go-logr/logr"
3334 flag "github.com/spf13/pflag"
3435 "k8s.io/apimachinery/pkg/runtime"
3536 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
37+ "k8s.io/client-go/discovery"
3638 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
39+ "k8s.io/client-go/rest"
3740 ctrl "sigs.k8s.io/controller-runtime"
3841 "sigs.k8s.io/controller-runtime/pkg/cache"
3942 "sigs.k8s.io/controller-runtime/pkg/certwatcher"
@@ -48,6 +51,7 @@ import (
4851 "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/collector/source"
4952 "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/collector/source/prometheus"
5053 "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/config"
54+ "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/constants"
5155 "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/controller"
5256 "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/controller/indexers"
5357 "github.com/llm-d/llm-d-workload-variant-autoscaler/internal/datastore"
@@ -63,6 +67,7 @@ import (
6367 crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
6468 inferencePoolV1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
6569 inferencePoolV1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
70+ lwsv1 "sigs.k8s.io/lws/api/leaderworkerset/v1"
6671 //+kubebuilder:scaffold:imports
6772)
6873
@@ -76,9 +81,44 @@ func init() {
7681 utilruntime .Must (promoperator .AddToScheme (scheme ))
7782 utilruntime .Must (inferencePoolV1 .Install (scheme ))
7883 utilruntime .Must (inferencePoolV1alpha2 .Install (scheme ))
84+ // Note: LeaderWorkerSet scheme is added conditionally in main() after checking if CRD exists
7985 //+kubebuilder:scaffold:scheme
8086}
8187
88+ // checkLeaderWorkerSetCRD checks if the LeaderWorkerSet CRD is installed in the cluster
89+ // TODO: this is checked once at start up for now. We should handle LWS installed after controller starts.
90+ func checkLeaderWorkerSetCRD (restConfig * rest.Config , logger logr.Logger ) bool {
91+ discoveryClient , err := discovery .NewDiscoveryClientForConfig (restConfig )
92+ if err != nil {
93+ logger .Error (err , "failed to create discovery client for CRD detection - assuming LWS not installed" )
94+ return false
95+ }
96+
97+ // Check if leaderworkersets.leaderworkerset.x-k8s.io CRD exists
98+ _ , apiLists , err := discoveryClient .ServerGroupsAndResources ()
99+ if err != nil {
100+ // Partial errors are common (e.g., unavailable API services), so check if we got any results
101+ if apiLists == nil {
102+ logger .Error (err , "failed to discover API resources - assuming LWS not installed" )
103+ return false
104+ }
105+ // Log but continue with partial results
106+ logger .V (1 ).Info ("partial error discovering API resources (this is usually fine)" , "error" , err )
107+ }
108+
109+ for _ , apiList := range apiLists {
110+ if apiList .GroupVersion == constants .LeaderWorkerSetAPIVersion {
111+ for _ , resource := range apiList .APIResources {
112+ if resource .Kind == constants .LeaderWorkerSetKind {
113+ return true
114+ }
115+ }
116+ }
117+ }
118+
119+ return false
120+ }
121+
82122// nolint:gocyclo
83123func main () {
84124 // Command-line flags
@@ -153,6 +193,18 @@ func main() {
153193 }
154194 setupLog .Info ("Configuration loaded successfully" )
155195
196+ // Conditionally add LeaderWorkerSet scheme if CRD exists
197+ lwsEnabled := checkLeaderWorkerSetCRD (restConfig , setupLog )
198+ if lwsEnabled {
199+ if err := lwsv1 .AddToScheme (scheme ); err != nil {
200+ setupLog .Error (err , "failed to add LeaderWorkerSet scheme" )
201+ os .Exit (1 )
202+ }
203+ setupLog .Info ("LeaderWorkerSet CRD detected - support enabled" )
204+ } else {
205+ setupLog .Info ("LeaderWorkerSet CRD not found - support disabled (Deployment-only mode)" )
206+ }
207+
156208 // if the enable-http2 flag is false (the default), http/2 should be disabled
157209 // due to its vulnerabilities. More specifically, disabling http/2 will
158210 // prevent from being vulnerable to the HTTP/2 Stream Cancellation and
@@ -425,13 +477,14 @@ func main() {
425477 }
426478
427479 // Create the reconciler with unified Config and datastore
428- reconciler := & controller.VariantAutoscalingReconciler {
429- Client : mgr .GetClient (),
430- Scheme : mgr .GetScheme (),
431- Recorder : mgr .GetEventRecorderFor ("workload-variant-autoscaler-controller-manager" ),
432- Config : cfg , // Pass unified Config to reconciler
433- Datastore : ds , // Pass datastore for namespace tracking
434- }
480+ reconciler := controller .NewVariantAutoscalingReconciler (
481+ mgr .GetClient (),
482+ mgr .GetScheme (),
483+ mgr .GetEventRecorderFor ("workload-variant-autoscaler-controller-manager" ),
484+ cfg ,
485+ ds ,
486+ lwsEnabled ,
487+ )
435488
436489 // Setup the controller with the manager
437490 if err = reconciler .SetupWithManager (mgr ); err != nil {
0 commit comments