sigs.k8s.io/kueue@v0.6.2/pkg/controller/jobframework/integrationmanager.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package jobframework
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"sort"
    24  
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/client-go/tools/record"
    28  	ctrl "sigs.k8s.io/controller-runtime"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    31  )
    32  
    33  var (
    34  	errDuplicateFrameworkName = errors.New("duplicate framework name")
    35  	errMissingMandatoryField  = errors.New("mandatory field missing")
    36  )
    37  
    38  type JobReconcilerInterface interface {
    39  	reconcile.Reconciler
    40  	SetupWithManager(mgr ctrl.Manager) error
    41  }
    42  
    43  type ReconcilerFactory func(client client.Client, record record.EventRecorder, opts ...Option) JobReconcilerInterface
    44  
    45  // IntegrationCallbacks groups a set of callbacks used to integrate a new framework.
    46  type IntegrationCallbacks struct {
    47  	// NewReconciler creates a new reconciler
    48  	NewReconciler ReconcilerFactory
    49  	// SetupWebhook sets up the framework's webhook with the controllers manager
    50  	SetupWebhook func(mgr ctrl.Manager, opts ...Option) error
    51  	// JobType holds an object of the type managed by the integration's webhook
    52  	JobType runtime.Object
    53  	// SetupIndexes registers any additional indexes with the controllers manager
    54  	// (this callback is optional)
    55  	SetupIndexes func(ctx context.Context, indexer client.FieldIndexer) error
    56  	// AddToScheme adds any additional types to the controllers manager's scheme
    57  	// (this callback is optional)
    58  	AddToScheme func(s *runtime.Scheme) error
    59  	// Returns true if the provided owner reference identifies an object
    60  	// managed by this integration
    61  	// (this callback is optional)
    62  	IsManagingObjectsOwner func(ref *metav1.OwnerReference) bool
    63  	// CanSupportIntegration returns true if the integration meets any additional condition
    64  	// like the Kubernetes version.
    65  	CanSupportIntegration func(opts ...Option) (bool, error)
    66  }
    67  
    68  type integrationManager struct {
    69  	names        []string
    70  	integrations map[string]IntegrationCallbacks
    71  }
    72  
    73  var manager integrationManager
    74  
    75  func (m *integrationManager) register(name string, cb IntegrationCallbacks) error {
    76  	if m.integrations == nil {
    77  		m.integrations = make(map[string]IntegrationCallbacks)
    78  	}
    79  	if _, exists := m.integrations[name]; exists {
    80  		return fmt.Errorf("%w %q", errDuplicateFrameworkName, name)
    81  	}
    82  
    83  	if cb.NewReconciler == nil {
    84  		return fmt.Errorf("%w \"NewReconciler\" for %q", errMissingMandatoryField, name)
    85  	}
    86  
    87  	if cb.SetupWebhook == nil {
    88  		return fmt.Errorf("%w \"SetupWebhook\" for %q", errMissingMandatoryField, name)
    89  	}
    90  
    91  	if cb.JobType == nil {
    92  		return fmt.Errorf("%w \"WebhookType\" for %q", errMissingMandatoryField, name)
    93  	}
    94  
    95  	m.integrations[name] = cb
    96  	m.names = append(m.names, name)
    97  
    98  	return nil
    99  }
   100  
   101  func (m *integrationManager) forEach(f func(name string, cb IntegrationCallbacks) error) error {
   102  	for _, name := range m.names {
   103  		if err := f(name, m.integrations[name]); err != nil {
   104  			return err
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  func (m *integrationManager) get(name string) (IntegrationCallbacks, bool) {
   111  	cb, f := m.integrations[name]
   112  	return cb, f
   113  }
   114  
   115  func (m *integrationManager) getList() []string {
   116  	ret := make([]string, len(m.names))
   117  	copy(ret, m.names)
   118  	sort.Strings(ret)
   119  	return ret
   120  }
   121  
   122  func (m *integrationManager) getCallbacksForOwner(ownerRef *metav1.OwnerReference) *IntegrationCallbacks {
   123  	for _, name := range m.names {
   124  		cbs := m.integrations[name]
   125  		if cbs.IsManagingObjectsOwner != nil && cbs.IsManagingObjectsOwner(ownerRef) {
   126  			return &cbs
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  // RegisterIntegration registers a new framework, returns an error when
   133  // attempting to register multiple frameworks with the same name of if a
   134  // mandatory callback is missing.
   135  func RegisterIntegration(name string, cb IntegrationCallbacks) error {
   136  	return manager.register(name, cb)
   137  }
   138  
   139  // ForEachIntegration loops through the registered list of frameworks calling f,
   140  // if at any point f returns an error the loop is stopped and that error is returned.
   141  func ForEachIntegration(f func(name string, cb IntegrationCallbacks) error) error {
   142  	return manager.forEach(f)
   143  }
   144  
   145  // GetIntegration looks-up the framework identified by name in the currently registered
   146  // list of frameworks returning its callbacks and true if found.
   147  func GetIntegration(name string) (IntegrationCallbacks, bool) {
   148  	return manager.get(name)
   149  }
   150  
   151  // GetIntegrationsList returns the list of currently registered frameworks.
   152  func GetIntegrationsList() []string {
   153  	return manager.getList()
   154  }
   155  
   156  // IsOwnerManagedByKueue returns true if the provided owner can be managed by
   157  // kueue.
   158  func IsOwnerManagedByKueue(owner *metav1.OwnerReference) bool {
   159  	return manager.getCallbacksForOwner(owner) != nil
   160  }
   161  
   162  // GetEmptyOwnerObject returns an empty object of the owner's type,
   163  // returns nil if the owner is not manageable by kueue.
   164  func GetEmptyOwnerObject(owner *metav1.OwnerReference) client.Object {
   165  	if cbs := manager.getCallbacksForOwner(owner); cbs != nil {
   166  		return cbs.JobType.DeepCopyObject().(client.Object)
   167  	}
   168  	return nil
   169  }