github.com/kubeflow/training-operator@v1.7.0/pkg/controller.v1/control/service_control.go (about) 1 // Copyright 2019 The Kubeflow Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package control 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 22 log "github.com/sirupsen/logrus" 23 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/errors" 25 "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/types" 30 clientset "k8s.io/client-go/kubernetes" 31 "k8s.io/client-go/tools/record" 32 ) 33 34 const ( 35 // FailedCreateServiceReason is added in an event and in a job controller condition 36 // when a service for a job is failed to be created. 37 FailedCreateServiceReason = "FailedCreateService" 38 // SuccessfulCreateServiceReason is added in an event when a service for a job 39 // is successfully created. 40 SuccessfulCreateServiceReason = "SuccessfulCreateService" 41 // FailedDeleteServiceReason is added in an event and in a job condition 42 // when a service for a job is failed to be deleted. 43 FailedDeleteServiceReason = "FailedDeleteService" 44 // SuccessfulDeleteServiceReason is added in an event when a service for a job 45 // is successfully deleted. 46 SuccessfulDeleteServiceReason = "SuccessfulDeleteService" 47 ) 48 49 // ServiceControlInterface is an interface that knows how to add or delete Services 50 // created as an interface to allow testing. 51 type ServiceControlInterface interface { 52 // CreateServices creates new Services according to the spec. 53 CreateServices(namespace string, service *v1.Service, object runtime.Object) error 54 // CreateServicesWithControllerRef creates new services according to the spec, and sets object as the service's controller. 55 CreateServicesWithControllerRef(namespace string, service *v1.Service, object runtime.Object, controllerRef *metav1.OwnerReference) error 56 // PatchService patches the service. 57 PatchService(namespace, name string, data []byte) error 58 // DeleteService deletes the service identified by serviceID. 59 DeleteService(namespace, serviceID string, object runtime.Object) error 60 } 61 62 // RealServiceControl is the default implementation of ServiceControlInterface. 63 type RealServiceControl struct { 64 KubeClient clientset.Interface 65 Recorder record.EventRecorder 66 } 67 68 func (r RealServiceControl) PatchService(namespace, name string, data []byte) error { 69 _, err := r.KubeClient.CoreV1().Services(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, data, metav1.PatchOptions{}) 70 return err 71 } 72 73 func (r RealServiceControl) CreateServices(namespace string, service *v1.Service, object runtime.Object) error { 74 return r.createServices(namespace, service, object, nil) 75 } 76 77 func (r RealServiceControl) CreateServicesWithControllerRef(namespace string, service *v1.Service, controllerObject runtime.Object, controllerRef *metav1.OwnerReference) error { 78 if err := ValidateControllerRef(controllerRef); err != nil { 79 return err 80 } 81 return r.createServices(namespace, service, controllerObject, controllerRef) 82 } 83 84 func (r RealServiceControl) createServices(namespace string, service *v1.Service, object runtime.Object, controllerRef *metav1.OwnerReference) error { 85 if labels.Set(service.Labels).AsSelectorPreValidated().Empty() { 86 return fmt.Errorf("unable to create Services, no labels") 87 } 88 serviceWithOwner, err := GetServiceFromTemplate(service, object, controllerRef) 89 if err != nil { 90 r.Recorder.Eventf(object, v1.EventTypeWarning, FailedCreateServiceReason, "Error creating: %v", err) 91 return fmt.Errorf("unable to create services: %v", err) 92 } 93 94 newService, err := r.KubeClient.CoreV1().Services(namespace).Create(context.TODO(), serviceWithOwner, metav1.CreateOptions{}) 95 if err != nil { 96 r.Recorder.Eventf(object, v1.EventTypeWarning, FailedCreateServiceReason, "Error creating: %v", err) 97 return fmt.Errorf("unable to create services: %v", err) 98 } 99 100 accessor, err := meta.Accessor(object) 101 if err != nil { 102 log.Errorf("parentObject does not have ObjectMeta, %v", err) 103 return nil 104 } 105 log.Infof("Controller %v created service %v", accessor.GetName(), newService.Name) 106 r.Recorder.Eventf(object, v1.EventTypeNormal, SuccessfulCreateServiceReason, "Created service: %v", newService.Name) 107 108 return nil 109 } 110 111 // DeleteService deletes the service identified by serviceID. 112 func (r RealServiceControl) DeleteService(namespace, serviceID string, object runtime.Object) error { 113 accessor, err := meta.Accessor(object) 114 if err != nil { 115 return fmt.Errorf("object does not have ObjectMeta, %v", err) 116 } 117 service, err := r.KubeClient.CoreV1().Services(namespace).Get(context.TODO(), serviceID, metav1.GetOptions{}) 118 if err != nil { 119 if errors.IsNotFound(err) { 120 return nil 121 } 122 return err 123 } 124 if service.DeletionTimestamp != nil { 125 log.Infof("service %s/%s is terminating, skip deleting", service.Namespace, service.Name) 126 return nil 127 } 128 log.Infof("Controller %v deleting service %v/%v", accessor.GetName(), namespace, serviceID) 129 if err := r.KubeClient.CoreV1().Services(namespace).Delete(context.TODO(), serviceID, metav1.DeleteOptions{}); err != nil { 130 r.Recorder.Eventf(object, v1.EventTypeWarning, FailedDeleteServiceReason, "Error deleting: %v", err) 131 return fmt.Errorf("unable to delete service: %v", err) 132 } else { 133 r.Recorder.Eventf(object, v1.EventTypeNormal, SuccessfulDeleteServiceReason, "Deleted service: %v", serviceID) 134 } 135 return nil 136 } 137 138 type FakeServiceControl struct { 139 sync.Mutex 140 Templates []v1.Service 141 ControllerRefs []metav1.OwnerReference 142 DeleteServiceName []string 143 Patches [][]byte 144 Err error 145 CreateLimit int 146 CreateCallCount int 147 } 148 149 var _ ServiceControlInterface = &FakeServiceControl{} 150 151 func (f *FakeServiceControl) PatchService(namespace, name string, data []byte) error { 152 f.Lock() 153 defer f.Unlock() 154 f.Patches = append(f.Patches, data) 155 if f.Err != nil { 156 return f.Err 157 } 158 return nil 159 } 160 161 func (f *FakeServiceControl) CreateServices(namespace string, service *v1.Service, object runtime.Object) error { 162 f.Lock() 163 defer f.Unlock() 164 f.CreateCallCount++ 165 if f.CreateLimit != 0 && f.CreateCallCount > f.CreateLimit { 166 return fmt.Errorf("not creating service, limit %d already reached (create call %d)", f.CreateLimit, f.CreateCallCount) 167 } 168 f.Templates = append(f.Templates, *service) 169 if f.Err != nil { 170 return f.Err 171 } 172 return nil 173 } 174 175 func (f *FakeServiceControl) CreateServicesWithControllerRef(namespace string, service *v1.Service, object runtime.Object, controllerRef *metav1.OwnerReference) error { 176 f.Lock() 177 defer f.Unlock() 178 f.CreateCallCount++ 179 if f.CreateLimit != 0 && f.CreateCallCount > f.CreateLimit { 180 return fmt.Errorf("not creating service, limit %d already reached (create call %d)", f.CreateLimit, f.CreateCallCount) 181 } 182 f.Templates = append(f.Templates, *service) 183 f.ControllerRefs = append(f.ControllerRefs, *controllerRef) 184 if f.Err != nil { 185 return f.Err 186 } 187 return nil 188 } 189 190 func (f *FakeServiceControl) DeleteService(namespace string, serviceID string, object runtime.Object) error { 191 f.Lock() 192 defer f.Unlock() 193 f.DeleteServiceName = append(f.DeleteServiceName, serviceID) 194 if f.Err != nil { 195 return f.Err 196 } 197 return nil 198 } 199 200 func (f *FakeServiceControl) Clear() { 201 f.Lock() 202 defer f.Unlock() 203 f.DeleteServiceName = []string{} 204 f.Templates = []v1.Service{} 205 f.ControllerRefs = []metav1.OwnerReference{} 206 f.Patches = [][]byte{} 207 f.CreateLimit = 0 208 f.CreateCallCount = 0 209 }