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 }