@@ -41,6 +41,8 @@ import (
4141 "k8s.io/apimachinery/pkg/api/resource"
4242 "k8s.io/apimachinery/pkg/types"
4343 "sigs.k8s.io/controller-runtime/pkg/client"
44+
45+ mlflowv1 "github.com/opendatahub-io/mlflow-operator/api/v1"
4446)
4547
4648const MlmdIsRequired = "MLMD explicitly disabled in DSPA, but is a required component for DSP"
@@ -101,6 +103,7 @@ type DSPAParams struct {
101103
102104 APIServerServiceDNSName string
103105 FIPSEnabled bool
106+ MLflow * dspa.MLflowConfig
104107
105108 // Proxy configuration for all DSPA components
106109 ProxyConfig * dspa.ProxyConfig
@@ -141,6 +144,29 @@ type ObjectStorageConnection struct {
141144 ExternalRouteURL string
142145}
143146
147+ type PluginConfig struct {
148+ Endpoint string `json:"endpoint,omitempty" mapstructure:"endpoint"`
149+ Timeout string `json:"timeout,omitempty" mapstructure:"timeout"`
150+ TLS TLSConfig `json:"tls,omitempty" mapstructure:"tls"`
151+ Settings MLflowPluginSettings `json:"settings,omitempty" mapstructure:"settings"`
152+ }
153+
154+ type TLSConfig struct {
155+ InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" mapstructure:"insecureSkipVerify"`
156+ CABundlePath string `json:"caBundlePath,omitempty" mapstructure:"caBundlePath"`
157+ }
158+
159+ // MLflowPluginSettings contains MLflow-specific settings to be marshalled into PluginConfig.Settings
160+ type MLflowPluginSettings struct {
161+ WorkspacesEnabled bool `json:"workspacesEnabled,omitempty"`
162+ ExperimentDescription string `json:"experimentDescription,omitempty"`
163+ //todo: add default experiment name?
164+ DefaultExperimentName string `json:"defaultExperimentName"`
165+ //todo: KFP base URL?
166+ KFPBaseURL string `json:"kfpBaseURL,omitempty"`
167+ InjectUserEnvVars bool `json:"injectUserEnvVars,omitempty"`
168+ }
169+
144170// UsingExternalDB will return true if an external Database is specified in the CR, otherwise false.
145171func (p * DSPAParams ) UsingExternalDB (dsp * dspa.DataSciencePipelinesApplication ) bool {
146172 if dsp .Spec .Database != nil && dsp .Spec .Database .ExternalDB != nil {
@@ -217,6 +243,25 @@ func (p *DSPAParams) RetrieveSecret(ctx context.Context, client client.Client, s
217243 return base64 .StdEncoding .EncodeToString (secret .Data [secretKey ]), nil
218244}
219245
246+ func (p * DSPAParams ) RetrieveMLflowEndpoint (ctx context.Context , client client.Client , log logr.Logger ) (string , error ) {
247+ mlflowName := "mlflow"
248+ mlflow := & mlflowv1.MLflow {}
249+ namespacedName := types.NamespacedName {
250+ Name : mlflowName ,
251+ Namespace : p .Namespace ,
252+ }
253+ err := client .Get (ctx , namespacedName , mlflow )
254+ if err != nil {
255+ log .V (1 ).Info (fmt .Sprintf ("Unable to retrieve mlflow resource [%s]." , mlflowName ))
256+ return "" , err
257+ }
258+ status := mlflow .Status
259+ if status .Address != nil && status .Address .URL != "" {
260+ return mlflow .Status .Address .URL , nil
261+ }
262+ return "" , errors .New ("MLflow resource missing Status.Address.URL field. Unable to resolve endpoint" )
263+ }
264+
220265func (p * DSPAParams ) RetrieveOrCreateSecret (ctx context.Context , client client.Client , secretName , secretKey string , generatedPasswordLength int , log logr.Logger ) (string , error ) {
221266 val , err := p .RetrieveSecret (ctx , client , secretName , secretKey , log )
222267 if err != nil && apierrs .IsNotFound (err ) {
@@ -703,6 +748,23 @@ func (p *DSPAParams) ExtractParams(ctx context.Context, dsp *dspa.DataSciencePip
703748 p .PodToPodTLS = * dsp .Spec .PodToPodTLS
704749 }
705750
751+ if dsp .Spec .MLflow != nil {
752+ mlflowCfg := dspa.MLflowConfig {
753+ IntegrationMode : dspa .AutoDetect ,
754+ InjectUserEnvVars : false ,
755+ }
756+
757+ // Override default settings if specified.
758+ if dsp .Spec .MLflow .IntegrationMode == dspa .Disabled {
759+ mlflowCfg .IntegrationMode = dspa .Disabled
760+ }
761+ if dsp .Spec .MLflow .InjectUserEnvVars == true {
762+ mlflowCfg .InjectUserEnvVars = true
763+ }
764+
765+ p .MLflow = & mlflowCfg
766+ }
767+
706768 p .ProxyConfig = dsp .Spec .Proxy
707769
708770 log := loggr .WithValues ("namespace" , p .Namespace ).WithValues ("dspa_name" , p .Name )
@@ -729,6 +791,27 @@ func (p *DSPAParams) ExtractParams(ctx context.Context, dsp *dspa.DataSciencePip
729791
730792 setResourcesDefault (config .APIServerResourceRequirements , & p .APIServer .Resources )
731793
794+ if p .MLflow != nil && p .MLflow .IntegrationMode == dspa .AutoDetect {
795+ // Retrieve internal MLflow service endpoint for use in API Server MLflow plugin config.
796+ mlflowEndpoint , err := p .RetrieveMLflowEndpoint (ctx , client , log )
797+ if err == nil {
798+ pluginCfg , err := BuildMLflowPluginConfigJson (mlflowEndpoint , p .CustomCABundleRootMountPath , p .MLflow .InjectUserEnvVars )
799+ if err != nil {
800+ //todo: do we want to return an error here? or just log an error.
801+ log .Info ("Failed to build MLflow plugin config. MLflow API server plugin will not be enabled." )
802+ return err
803+ }
804+ // Append MLflow Plugin config to API server configs.
805+ err = util .UpdateConfigMapByName (ctx , p .APIServer .CustomServerConfig .Name , p .Namespace , client , "config.json" , pluginCfg )
806+ if err != nil {
807+ log .Info ("Failed to update API server config with MLflow plugin. MLflow API server plugin will not be enabled." )
808+ return err
809+ }
810+ } else {
811+ log .Error (err , "failed to retrieve MLflow internal endpoint. MLflow API server plugin will not be enabled." )
812+ }
813+ }
814+
732815 if p .APIServer .CustomServerConfig == nil {
733816 p .APIServer .CustomServerConfig = & dspa.ScriptConfigMap {
734817 Name : config .CustomServerConfigMapNamePrefix + dsp .Name ,
@@ -977,3 +1060,29 @@ func (p *DSPAParams) ExtractParams(ctx context.Context, dsp *dspa.DataSciencePip
9771060
9781061 return nil
9791062}
1063+
1064+ func BuildMLflowPluginConfigJson (mlflowEndpoint string , caBundlePath string , injectUserEnvVars bool ) (string , error ) {
1065+ settings := MLflowPluginSettings {
1066+ WorkspacesEnabled : true ,
1067+ ExperimentDescription : "Created by AI Pipelines." ,
1068+ DefaultExperimentName : "pipelines-exp" ,
1069+ //todo: this should potentially be passed in.
1070+ KFPBaseURL : "" ,
1071+ InjectUserEnvVars : injectUserEnvVars ,
1072+ }
1073+
1074+ pluginCfgJson , err := json .Marshal (
1075+ PluginConfig {
1076+ Endpoint : mlflowEndpoint ,
1077+ Timeout : "30s" ,
1078+ TLS : TLSConfig {
1079+ InsecureSkipVerify : false ,
1080+ CABundlePath : caBundlePath ,
1081+ },
1082+ Settings : settings ,
1083+ })
1084+ if err != nil {
1085+ return "" , fmt .Errorf ("failed to marshal MLflow plugin config: %w" , err )
1086+ }
1087+ return string (pluginCfgJson ), nil
1088+ }
0 commit comments