|
| 1 | +/* |
| 2 | +Copyright 2019 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 reconcilers |
| 18 | + |
| 19 | +import ( |
| 20 | + corev1 "k8s.io/api/core/v1" |
| 21 | + discovery "k8s.io/api/discovery/v1alpha1" |
| 22 | + "k8s.io/apimachinery/pkg/api/errors" |
| 23 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 24 | + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" |
| 25 | + discoveryclient "k8s.io/client-go/kubernetes/typed/discovery/v1alpha1" |
| 26 | +) |
| 27 | + |
| 28 | +const ( |
| 29 | + // serviceNameLabel is used to indicate the name of a Kubernetes service |
| 30 | + // associated with an EndpointSlice. |
| 31 | + serviceNameLabel = "kubernetes.io/service-name" |
| 32 | +) |
| 33 | + |
| 34 | +// EndpointsAdapter provides a simple interface for reading and writing both |
| 35 | +// Endpoints and Endpoint Slices. |
| 36 | +// NOTE: This is an incomplete adapter implementation that is only suitable for |
| 37 | +// use in this package. This takes advantage of the Endpoints used in this |
| 38 | +// package always having a consistent set of ports, a single subset, and a small |
| 39 | +// set of addresses. Any more complex Endpoints resource would likely translate |
| 40 | +// into multiple Endpoint Slices creating significantly more complexity instead |
| 41 | +// of the 1:1 mapping this allows. |
| 42 | +type EndpointsAdapter struct { |
| 43 | + endpointClient corev1client.EndpointsGetter |
| 44 | + endpointSliceClient discoveryclient.EndpointSlicesGetter |
| 45 | +} |
| 46 | + |
| 47 | +// NewEndpointsAdapter returns a new EndpointsAdapter. |
| 48 | +func NewEndpointsAdapter(endpointClient corev1client.EndpointsGetter, endpointSliceClient discoveryclient.EndpointSlicesGetter) EndpointsAdapter { |
| 49 | + return EndpointsAdapter{ |
| 50 | + endpointClient: endpointClient, |
| 51 | + endpointSliceClient: endpointSliceClient, |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +// Get takes the name and namespace of the Endpoints resource, and returns a |
| 56 | +// corresponding Endpoints object if it exists, and an error if there is any. |
| 57 | +func (adapter *EndpointsAdapter) Get(namespace, name string, getOpts metav1.GetOptions) (*corev1.Endpoints, error) { |
| 58 | + return adapter.endpointClient.Endpoints(namespace).Get(name, getOpts) |
| 59 | +} |
| 60 | + |
| 61 | +// Create accepts a namespace and Endpoints object and creates the Endpoints |
| 62 | +// object. If an endpointSliceClient exists, a matching EndpointSlice will also |
| 63 | +// be created or updated. The created Endpoints object or an error will be |
| 64 | +// returned. |
| 65 | +func (adapter *EndpointsAdapter) Create(namespace string, endpoints *corev1.Endpoints) (*corev1.Endpoints, error) { |
| 66 | + endpoints, err := adapter.endpointClient.Endpoints(namespace).Create(endpoints) |
| 67 | + if err == nil && adapter.endpointSliceClient != nil { |
| 68 | + _, err = adapter.ensureEndpointSliceFromEndpoints(namespace, endpoints) |
| 69 | + } |
| 70 | + return endpoints, err |
| 71 | +} |
| 72 | + |
| 73 | +// Update accepts a namespace and Endpoints object and updates it. If an |
| 74 | +// endpointSliceClient exists, a matching EndpointSlice will also be created or |
| 75 | +// updated. The updated Endpoints object or an error will be returned. |
| 76 | +func (adapter *EndpointsAdapter) Update(namespace string, endpoints *corev1.Endpoints) (*corev1.Endpoints, error) { |
| 77 | + endpoints, err := adapter.endpointClient.Endpoints(namespace).Update(endpoints) |
| 78 | + if err == nil && adapter.endpointSliceClient != nil { |
| 79 | + _, err = adapter.ensureEndpointSliceFromEndpoints(namespace, endpoints) |
| 80 | + } |
| 81 | + return endpoints, err |
| 82 | +} |
| 83 | + |
| 84 | +// ensureEndpointSliceFromEndpoints accepts a namespace and Endpoints resource |
| 85 | +// and creates or updates a corresponding EndpointSlice. The EndpointSlice |
| 86 | +// and/or an error will be returned. |
| 87 | +func (adapter *EndpointsAdapter) ensureEndpointSliceFromEndpoints(namespace string, endpoints *corev1.Endpoints) (*discovery.EndpointSlice, error) { |
| 88 | + endpointSlice := endpointSliceFromEndpoints(endpoints) |
| 89 | + _, err := adapter.endpointSliceClient.EndpointSlices(namespace).Get(endpointSlice.Name, metav1.GetOptions{}) |
| 90 | + |
| 91 | + if err != nil { |
| 92 | + if errors.IsNotFound(err) { |
| 93 | + return adapter.endpointSliceClient.EndpointSlices(namespace).Create(endpointSlice) |
| 94 | + } |
| 95 | + return nil, err |
| 96 | + } |
| 97 | + |
| 98 | + return adapter.endpointSliceClient.EndpointSlices(namespace).Update(endpointSlice) |
| 99 | +} |
| 100 | + |
| 101 | +// endpointSliceFromEndpoints generates an EndpointSlice from an Endpoints |
| 102 | +// resource. |
| 103 | +func endpointSliceFromEndpoints(endpoints *corev1.Endpoints) *discovery.EndpointSlice { |
| 104 | + endpointSlice := &discovery.EndpointSlice{} |
| 105 | + endpointSlice.Name = endpoints.Name |
| 106 | + endpointSlice.Labels = map[string]string{serviceNameLabel: endpoints.Name} |
| 107 | + endpointSlice.OwnerReferences = []metav1.OwnerReference{{Kind: "Service", Name: endpoints.Name}} |
| 108 | + |
| 109 | + ipAddressType := discovery.AddressTypeIP |
| 110 | + endpointSlice.AddressType = &ipAddressType |
| 111 | + |
| 112 | + if len(endpoints.Subsets) > 0 { |
| 113 | + subset := endpoints.Subsets[0] |
| 114 | + for i := range subset.Ports { |
| 115 | + endpointSlice.Ports = append(endpointSlice.Ports, discovery.EndpointPort{ |
| 116 | + Port: &subset.Ports[i].Port, |
| 117 | + Name: &subset.Ports[i].Name, |
| 118 | + Protocol: &subset.Ports[i].Protocol, |
| 119 | + }) |
| 120 | + } |
| 121 | + for _, address := range subset.Addresses { |
| 122 | + endpointSlice.Endpoints = append(endpointSlice.Endpoints, endpointFromAddress(address, true)) |
| 123 | + } |
| 124 | + for _, address := range subset.NotReadyAddresses { |
| 125 | + endpointSlice.Endpoints = append(endpointSlice.Endpoints, endpointFromAddress(address, false)) |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + return endpointSlice |
| 130 | +} |
| 131 | + |
| 132 | +// endpointFromAddress generates an Endpoint from an EndpointAddress resource. |
| 133 | +func endpointFromAddress(address corev1.EndpointAddress, ready bool) discovery.Endpoint { |
| 134 | + topology := map[string]string{} |
| 135 | + if address.NodeName != nil { |
| 136 | + topology["kubernetes.io/hostname"] = *address.NodeName |
| 137 | + } |
| 138 | + |
| 139 | + return discovery.Endpoint{ |
| 140 | + Addresses: []string{address.IP}, |
| 141 | + Conditions: discovery.EndpointConditions{Ready: &ready}, |
| 142 | + TargetRef: address.TargetRef, |
| 143 | + Topology: topology, |
| 144 | + } |
| 145 | +} |
0 commit comments