diff --git a/coredns/constants/constants.go b/coredns/constants/constants.go index 1f48db7b6..b1a7ceb77 100644 --- a/coredns/constants/constants.go +++ b/coredns/constants/constants.go @@ -19,9 +19,10 @@ limitations under the License. package constants const ( - KubernetesServiceName = "kubernetes.io/service-name" - LabelValueManagedBy = "lighthouse-agent.submariner.io" - LabelSourceNamespace = "lighthouse.submariner.io/sourceNamespace" - MCSLabelSourceCluster = "multicluster.kubernetes.io/source-cluster" - LabelIsHeadless = "lighthouse.submariner.io/is-headless" + KubernetesServiceName = "kubernetes.io/service-name" + LabelValueManagedBy = "lighthouse-agent.submariner.io" + LabelSourceNamespace = "lighthouse.submariner.io/sourceNamespace" + MCSLabelSourceCluster = "multicluster.kubernetes.io/source-cluster" + LabelIsHeadless = "lighthouse.submariner.io/is-headless" + PublishNotReadyAddresses = "lighthouse.submariner.io/publish-not-ready-addresses" ) diff --git a/coredns/resolver/endpoint_slice.go b/coredns/resolver/endpoint_slice.go index fc9d5a4a0..04a9fb82a 100644 --- a/coredns/resolver/endpoint_slice.go +++ b/coredns/resolver/endpoint_slice.go @@ -21,6 +21,7 @@ package resolver import ( "context" "fmt" + "strconv" "github.com/submariner-io/admiral/pkg/log" "github.com/submariner-io/lighthouse/coredns/constants" @@ -132,12 +133,15 @@ func (i *Interface) putHeadlessEndpointSlice(key, clusterID string, endpointSlic mcsPorts := mcsServicePortsFrom(endpointSlice.Ports) + publishNotReadyAddresses := endpointSlice.Annotations[constants.PublishNotReadyAddresses] == strconv.FormatBool(true) + for i := range endpointSlice.Endpoints { endpoint := &endpointSlice.Endpoints[i] - // Skip if not ready. Note: we're treating nil as ready to be on the safe side as the EndpointConditions doc - // states "In most cases consumers should interpret this unknown state (ie nil) as ready". - if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready { + // Skip if not ready and the user does not want to publish not-ready addresses. Note: we're treating nil as ready + // to be on the safe side as the EndpointConditions doc states "In most cases consumers should interpret this + // unknown state (ie nil) as ready". + if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready && !publishNotReadyAddresses { continue } diff --git a/coredns/resolver/headless_service_test.go b/coredns/resolver/headless_service_test.go index 61eedc85e..82e93b243 100644 --- a/coredns/resolver/headless_service_test.go +++ b/coredns/resolver/headless_service_test.go @@ -19,6 +19,8 @@ limitations under the License. package resolver_test import ( + "strconv" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/submariner-io/lighthouse/coredns/constants" @@ -31,10 +33,120 @@ import ( var _ = Describe("GetDNSRecords", func() { Describe("Headless Service", func() { + testHeadlessService() When("a service is present in multiple clusters", testHeadlessServiceInMultipleClusters) }) }) +func testHeadlessService() { + t := newTestDriver() + + BeforeEach(func() { + t.resolver.PutServiceImport(newHeadlessAggregatedServiceImport(namespace1, service1)) + }) + + When("a service has both ready and not-ready addresses", func() { + var annotations map[string]string + + BeforeEach(func() { + annotations = nil + }) + + JustBeforeEach(func() { + eps := newEndpointSlice(namespace1, service1, clusterID1, []mcsv1a1.ServicePort{port1}, + discovery.Endpoint{ + Addresses: []string{endpointIP1}, + Conditions: discovery.EndpointConditions{Ready: &ready}, + }, + discovery.Endpoint{ + Addresses: []string{endpointIP2}, + Conditions: discovery.EndpointConditions{Ready: ¬Ready}, + }, + discovery.Endpoint{ + Addresses: []string{endpointIP3}, + Conditions: discovery.EndpointConditions{Ready: &ready}, + }, + discovery.Endpoint{ + Addresses: []string{endpointIP4}, + Conditions: discovery.EndpointConditions{Ready: ¬Ready}, + }, + ) + eps.Annotations = annotations + + t.putEndpointSlice(eps) + }) + + Context("and the publish-not-ready-addresses annotation is not present", func() { + It("should return DNS records for only the ready addresses", func() { + t.assertDNSRecordsFound(namespace1, service1, "", "", true, + resolver.DNSRecord{ + IP: endpointIP1, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + resolver.DNSRecord{ + IP: endpointIP3, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + ) + }) + }) + + Context("and the publish-not-ready-addresses annotation is set to false", func() { + BeforeEach(func() { + annotations = map[string]string{constants.PublishNotReadyAddresses: strconv.FormatBool(false)} + }) + + It("should return DNS records for only the ready addresses", func() { + t.assertDNSRecordsFound(namespace1, service1, "", "", true, + resolver.DNSRecord{ + IP: endpointIP1, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + resolver.DNSRecord{ + IP: endpointIP3, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + ) + }) + }) + + Context("and the publish-not-ready-addresses annotation is set to true", func() { + BeforeEach(func() { + annotations = map[string]string{constants.PublishNotReadyAddresses: strconv.FormatBool(true)} + }) + + It("should return all the DNS records", func() { + t.assertDNSRecordsFound(namespace1, service1, "", "", true, + resolver.DNSRecord{ + IP: endpointIP1, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + resolver.DNSRecord{ + IP: endpointIP2, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + resolver.DNSRecord{ + IP: endpointIP3, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + resolver.DNSRecord{ + IP: endpointIP4, + Ports: []mcsv1a1.ServicePort{port1}, + ClusterName: clusterID1, + }, + ) + }) + }) + }) +} + func testHeadlessServiceInMultipleClusters() { t := newTestDriver() @@ -106,10 +218,7 @@ func testHeadlessServiceInMultipleClusters() { Hostname: &hostName2, NodeName: &nodeName3, }, - discovery.Endpoint{ - Addresses: []string{"1.2.3.4"}, - Conditions: discovery.EndpointConditions{Ready: ¬Ready}, - })) + )) }) Context("and no specific cluster is requested", func() {