diff --git a/pkg/obc/obc_test.go b/pkg/obc/obc_test.go index 20fe961c2..d320de9f6 100644 --- a/pkg/obc/obc_test.go +++ b/pkg/obc/obc_test.go @@ -159,3 +159,93 @@ var _ = Describe("OBC referenced BucketClass", func() { }) }) }) + +var _ = Describe("getExternalDNSDetails", func() { + noobaaWithExternalDNS := func(s3URLs, vectorsURLs []string) *nbv1.NooBaa { + return &nbv1.NooBaa{ + Status: nbv1.NooBaaStatus{ + Services: &nbv1.ServicesStatus{ + ServiceS3: nbv1.ServiceStatus{ExternalDNS: s3URLs}, + ServiceVectors: nbv1.ServiceStatus{ExternalDNS: vectorsURLs}, + }, + }, + } + } + + It("returns host and port from the first S3 ExternalDNS entry", func() { + host, port, err := getExternalDNSDetails( + noobaaWithExternalDNS([]string{"https://s3.route.example.com:443"}, nil), + externalDNSServiceS3, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(host).To(Equal("s3.route.example.com")) + Expect(port).To(Equal(443)) + }) + + It("returns host and port from the first Vectors ExternalDNS entry", func() { + host, port, err := getExternalDNSDetails( + noobaaWithExternalDNS(nil, []string{"https://vectors.apps.example.com:8443"}), + externalDNSServiceVectors, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(host).To(Equal("vectors.apps.example.com")) + Expect(port).To(Equal(8443)) + }) + + It("uses the first URL when S3 ExternalDNS lists multiple entries", func() { + host, port, err := getExternalDNSDetails( + noobaaWithExternalDNS([]string{"https://first.route.test:443", "https://second.route.test:443"}, nil), + externalDNSServiceS3, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(host).To(Equal("first.route.test")) + Expect(port).To(Equal(443)) + }) + + It("returns an error when the selected service has no ExternalDNS", func() { + _, _, err := getExternalDNSDetails( + noobaaWithExternalDNS(nil, []string{"https://vectors-only:443"}), + externalDNSServiceS3, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no external")) + Expect(err.Error()).To(ContainSubstring("s3")) + }) + + It("returns an error for an unknown externalDNSService value", func() { + _, _, err := getExternalDNSDetails( + noobaaWithExternalDNS([]string{"https://x:443"}, nil), + externalDNSService("other"), + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unknown external DNS service")) + }) + + It("returns a parse error when the primary URL is invalid", func() { + _, _, err := getExternalDNSDetails( + noobaaWithExternalDNS([]string{"not-a-valid-request-uri"}, nil), + externalDNSServiceS3, + ) + Expect(err).To(HaveOccurred()) + }) + + It("returns an error when the URL omits a numeric port", func() { + _, _, err := getExternalDNSDetails( + noobaaWithExternalDNS([]string{"https://s3.example.com"}, nil), + externalDNSServiceS3, + ) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to parse external DNS")) + }) + + It("getExternalDNSDetails returns an error when NooBaa Status.Services is nil", func() { + for _, nb := range []*nbv1.NooBaa{ + {}, + {Status: nbv1.NooBaaStatus{Services: nil}}, + } { + _, _, err := getExternalDNSDetails(nb, externalDNSServiceS3) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no services found")) + } + }) +}) diff --git a/pkg/obc/provisioner.go b/pkg/obc/provisioner.go index 39a4e9778..d5e2b3cdb 100644 --- a/pkg/obc/provisioner.go +++ b/pkg/obc/provisioner.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strconv" "time" @@ -31,8 +32,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +type externalDNSService string + const ( - allNamespaces = "" + allNamespaces = "" + externalDNSServiceS3 externalDNSService = "s3" + externalDNSServiceVectors externalDNSService = "vectors" ) var excludeBucketTaggingLabelKeysSet = map[string]struct{}{ @@ -404,6 +409,20 @@ func NewBucketRequest( endpointHostname = vectorsHostname endpointPort = vectorsPort } + if util.IsRemoteObcAnnotation(r.OBC.Annotations) { + extSvc := externalDNSServiceS3 + if r.BucketClass.Spec.VectorPolicy != nil { + extSvc = externalDNSServiceVectors + } + extHost, extPort, externalErr := getExternalDNSDetails(sysClient.NooBaa, extSvc) + if externalErr != nil { + p.recorder.Event(r.OBC, "Warning", "RemoteOBCExternalDNSUnavailable", + fmt.Sprintf("using internal DNS details: %v", externalErr)) + } else { + endpointHostname = extHost + endpointPort = extPort + } + } additionalConfig := r.OBC.Spec.AdditionalConfig if additionalConfig == nil { @@ -936,3 +955,37 @@ func getBucketClass( return bucketClass, false } + +// getExternalDNSDetails returns hostname and port for the ObjectBucket ConfigMap from NooBaa status ExternalDNS +func getExternalDNSDetails(nb *nbv1.NooBaa, svc externalDNSService) (string, int, error) { + if nb == nil || nb.Status.Services == nil { + return "", 0, fmt.Errorf("no services found in status") + } + var externalDNS []string + switch svc { + case externalDNSServiceS3: + externalDNS = nb.Status.Services.ServiceS3.ExternalDNS + case externalDNSServiceVectors: + externalDNS = nb.Status.Services.ServiceVectors.ExternalDNS + default: + return "", 0, fmt.Errorf("unknown external DNS service %q", svc) + } + if len(externalDNS) == 0 { + return "", 0, fmt.Errorf("no external %q service endpoint in status (Route or LoadBalancer)", svc) + } + // ExternalDNS order is defined in CheckServiceStatus: + // append Route URL when route.Spec.Host is set, then append each LoadBalancer ingress hostname. + // For status produced by that code, when both exist index 0 is always the Route URL; + primaryExternalDNS := externalDNS[0] + uri, err := url.ParseRequestURI(primaryExternalDNS) + if err != nil { + return "", 0, err + } + hostname := uri.Hostname() + portStr := uri.Port() + port, err := strconv.Atoi(portStr) + if err != nil { + return "", 0, fmt.Errorf("failed to parse external DNS in %q service: port %q. got error: %v", svc, portStr, err) + } + return hostname, port, nil +}