github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/services.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider 5 6 import ( 7 "context" 8 "fmt" 9 10 "github.com/juju/errors" 11 core "k8s.io/api/core/v1" 12 k8serrors "k8s.io/apimachinery/pkg/api/errors" 13 meta "k8s.io/apimachinery/pkg/apis/meta/v1" 14 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 15 16 "github.com/juju/juju/caas/kubernetes/provider/application" 17 "github.com/juju/juju/caas/kubernetes/provider/constants" 18 k8sspecs "github.com/juju/juju/caas/kubernetes/provider/specs" 19 "github.com/juju/juju/caas/kubernetes/provider/utils" 20 k8sannotations "github.com/juju/juju/core/annotations" 21 ) 22 23 func getServiceLabels(appName string, legacy bool) map[string]string { 24 return utils.LabelsForApp(appName, legacy) 25 } 26 27 func (k *kubernetesClient) ensureServicesForApp(appName, deploymentName string, annotations k8sannotations.Annotation, services []k8sspecs.K8sService) (cleanUps []func(), err error) { 28 for _, v := range services { 29 if v.Name == deploymentName { 30 return cleanUps, errors.NewNotValid(nil, fmt.Sprintf("%q is a reserved service name", deploymentName)) 31 } 32 spec := &core.Service{ 33 ObjectMeta: meta.ObjectMeta{ 34 Name: v.Name, 35 Namespace: k.namespace, 36 Labels: utils.LabelsMerge(v.Labels, getServiceLabels(appName, k.IsLegacyLabels())), 37 Annotations: annotations.Copy().Merge(v.Annotations), 38 }, 39 Spec: v.Spec, 40 } 41 serviceCleanup, err := k.ensureK8sService(spec) 42 cleanUps = append(cleanUps, serviceCleanup) 43 if err != nil { 44 return cleanUps, errors.Trace(err) 45 } 46 } 47 return cleanUps, nil 48 } 49 50 // ensureK8sService ensures a k8s service resource. 51 func (k *kubernetesClient) ensureK8sService(spec *core.Service) (func(), error) { 52 cleanUp := func() {} 53 if k.namespace == "" { 54 return cleanUp, errNoNamespace 55 } 56 57 api := k.client().CoreV1().Services(k.namespace) 58 // Set any immutable fields if the service already exists. 59 existing, err := api.Get(context.TODO(), spec.Name, meta.GetOptions{}) 60 if err == nil { 61 spec.Spec.ClusterIP = existing.Spec.ClusterIP 62 spec.ObjectMeta.ResourceVersion = existing.ObjectMeta.ResourceVersion 63 } 64 _, err = api.Update(context.TODO(), spec, meta.UpdateOptions{}) 65 if k8serrors.IsNotFound(err) { 66 var svcCreated *core.Service 67 svcCreated, err = api.Create(context.TODO(), spec, meta.CreateOptions{}) 68 if err == nil { 69 cleanUp = func() { _ = k.deleteService(svcCreated.GetName()) } 70 } 71 } 72 return cleanUp, errors.Trace(err) 73 } 74 75 // deleteService deletes a service resource. 76 func (k *kubernetesClient) deleteService(serviceName string) error { 77 if k.namespace == "" { 78 return errNoNamespace 79 } 80 services := k.client().CoreV1().Services(k.namespace) 81 err := services.Delete(context.TODO(), serviceName, meta.DeleteOptions{ 82 PropagationPolicy: constants.DefaultPropagationPolicy(), 83 }) 84 if k8serrors.IsNotFound(err) { 85 return nil 86 } 87 return errors.Trace(err) 88 } 89 90 func (k *kubernetesClient) deleteServices(appName string) error { 91 if k.namespace == "" { 92 return errNoNamespace 93 } 94 // Service API does not have `DeleteCollection` implemented, so we have to do it like this. 95 api := k.client().CoreV1().Services(k.namespace) 96 services, err := api.List(context.TODO(), 97 meta.ListOptions{ 98 LabelSelector: utils.LabelsToSelector( 99 getServiceLabels(appName, k.IsLegacyLabels())).String(), 100 }, 101 ) 102 if err != nil { 103 return errors.Trace(err) 104 } 105 for _, svc := range services.Items { 106 if err := k.deleteService(svc.GetName()); err != nil { 107 if errors.IsNotFound(err) { 108 continue 109 } 110 return errors.Trace(err) 111 } 112 } 113 return nil 114 } 115 116 func findServiceForApplication( 117 ctx context.Context, 118 serviceI corev1.ServiceInterface, 119 appName string, 120 legacyLabels bool, 121 ) (*core.Service, error) { 122 labels := utils.LabelsForApp(appName, legacyLabels) 123 servicesList, err := serviceI.List(context.TODO(), meta.ListOptions{ 124 LabelSelector: utils.LabelsToSelector(labels).String(), 125 }) 126 127 if err != nil { 128 return nil, errors.Annotatef(err, "finding service for application %s", appName) 129 } 130 131 if len(servicesList.Items) == 0 { 132 return nil, errors.NotFoundf("finding service for application %s", appName) 133 } 134 135 services := []core.Service{} 136 endpointSvcName := application.HeadlessServiceName(appName) 137 // We want to filter out the endpoints services made by juju as they should 138 // not be considered. 139 for _, svc := range servicesList.Items { 140 if svc.Name != endpointSvcName { 141 services = append(services, svc) 142 } 143 } 144 145 if len(services) != 1 { 146 return nil, errors.NotValidf("unable to handle mutiple services %d for application %s", len(servicesList.Items), appName) 147 } 148 149 return &services[0], nil 150 }