diff --git a/apis/go.mod b/apis/go.mod index dcfb67c4..9ad32304 100644 --- a/apis/go.mod +++ b/apis/go.mod @@ -4,8 +4,8 @@ go 1.24.4 require ( github.com/go-logr/logr v1.4.3 - github.com/onsi/gomega v1.39.1 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 + github.com/onsi/gomega v1.40.0 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260506154724-30a976ba8ef0 k8s.io/api v0.31.14 k8s.io/apiextensions-apiserver v0.33.2 k8s.io/apimachinery v0.31.14 @@ -40,7 +40,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo/v2 v2.28.2 // indirect github.com/openshift/api v3.9.0+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.22.0 // indirect diff --git a/apis/go.sum b/apis/go.sum index 0ebeeee0..5bed0a7a 100644 --- a/apis/go.sum +++ b/apis/go.sum @@ -76,14 +76,15 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= +github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 h1:v1viH0gmNb+AXMg/0GxDcj8VUTdjVLotfOIGrNyMxHk= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:I/VBXZLdjk8DUGsEbB+Ha72JBFYYntP7Pm2FpEto9K8= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260506154724-30a976ba8ef0 h1:vkFvn06Ns9qW4AbzFjFDu8ioosRmhkEZiDrO3DOQhLg= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260506154724-30a976ba8ef0/go.mod h1:aIuG6lx3aS0vnXweRNdR/Q0SlfOsLIo0OzrqKK7C6xs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -113,8 +114,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= diff --git a/apis/network/v1beta1/dnsmasq_webhook.go b/apis/network/v1beta1/dnsmasq_webhook.go index b6899d8e..47ade3d5 100644 --- a/apis/network/v1beta1/dnsmasq_webhook.go +++ b/apis/network/v1beta1/dnsmasq_webhook.go @@ -17,13 +17,16 @@ limitations under the License. package v1beta1 import ( + "fmt" + + annotations "github.com/openstack-k8s-operators/lib-common/modules/common/annotations" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" ) // DNSMasqDefaults - @@ -48,20 +51,38 @@ var _ webhook.Defaulter = &DNSMasq{} func (r *DNSMasq) Default() { dnsmasqlog.Info("default", "name", r.Name) - r.Spec.Default() + r.Spec.Default(r.Namespace) + + ann := r.GetAnnotations() + if _, exists := ann[annotations.ReconcileTriggerAnnotation]; exists { + delete(ann, annotations.ReconcileTriggerAnnotation) + r.SetAnnotations(ann) + } } // Default - set defaults for this DNSMasq spec -func (spec *DNSMasqSpec) Default() { +func (spec *DNSMasqSpec) Default(namespace string) { if spec.ContainerImage == "" { spec.ContainerImage = dnsMasqDefaults.ContainerImageURL } - spec.DNSMasqSpecCore.Default() + spec.DNSMasqSpecCore.Default(namespace) } // Default - common defaults go here (for the OpenStackControlplane which uses this one) -func (spec *DNSMasqSpecCore) Default() { - // nothing here +func (spec *DNSMasqSpecCore) Default(namespace string) { + hasLocal := false + for _, opt := range spec.Options { + if opt.Key == "local" { + hasLocal = true + break + } + } + if !hasLocal { + spec.Options = append(spec.Options, DNSMasqOption{ + Key: "local", + Values: []string{fmt.Sprintf("/%s.svc/", namespace)}, + }) + } } var _ webhook.Validator = &DNSMasq{} diff --git a/go.mod b/go.mod index e9d8af79..640ee3f6 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 github.com/metallb/frr-k8s v0.0.15 github.com/onsi/ginkgo/v2 v2.28.2 - github.com/onsi/gomega v1.39.1 + github.com/onsi/gomega v1.40.0 github.com/openshift/api v3.9.0+incompatible github.com/openstack-k8s-operators/infra-operator/apis v0.0.0-00010101000000-000000000000 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 - github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260417092244-81c71b39e981 - go.uber.org/zap v1.27.1 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260506154724-30a976ba8ef0 + github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260506154724-30a976ba8ef0 + go.uber.org/zap v1.28.0 golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 k8s.io/api v0.31.14 k8s.io/apiextensions-apiserver v0.33.2 diff --git a/go.sum b/go.sum index 6fc1b14f..60625b48 100644 --- a/go.sum +++ b/go.sum @@ -114,14 +114,14 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc= +github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 h1:v1viH0gmNb+AXMg/0GxDcj8VUTdjVLotfOIGrNyMxHk= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:I/VBXZLdjk8DUGsEbB+Ha72JBFYYntP7Pm2FpEto9K8= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260417092244-81c71b39e981 h1:KAQ8T+Ri3JWgsyK1D6QybScMh6fpkYUUA+0ntnOiAl4= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:dEjz8zHRIlP3vnMmWdHytlLeSZ6BHcIiSTPM7xTQxFg= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260506154724-30a976ba8ef0 h1:vkFvn06Ns9qW4AbzFjFDu8ioosRmhkEZiDrO3DOQhLg= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260506154724-30a976ba8ef0/go.mod h1:aIuG6lx3aS0vnXweRNdR/Q0SlfOsLIo0OzrqKK7C6xs= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260506154724-30a976ba8ef0 h1:mG3QhS/QWv9Y/AkZZ5OzO6hu6+l5oDXnI/Q5ZUbj6Zs= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260506154724-30a976ba8ef0/go.mod h1:ZYG9CQe7cOePOKQbenEZFA28kPdkUOe9QKbDRwGhEV0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -186,8 +186,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= diff --git a/internal/controller/network/dnsmasq_controller.go b/internal/controller/network/dnsmasq_controller.go index f4c2adfa..9630e752 100644 --- a/internal/controller/network/dnsmasq_controller.go +++ b/internal/controller/network/dnsmasq_controller.go @@ -49,6 +49,7 @@ import ( topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" dnsmasq "github.com/openstack-k8s-operators/infra-operator/internal/dnsmasq" common "github.com/openstack-k8s-operators/lib-common/modules/common" + annotations "github.com/openstack-k8s-operators/lib-common/modules/common/annotations" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" configmap "github.com/openstack-k8s-operators/lib-common/modules/common/configmap" deployment "github.com/openstack-k8s-operators/lib-common/modules/common/deployment" @@ -58,6 +59,7 @@ import ( common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" service "github.com/openstack-k8s-operators/lib-common/modules/common/service" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "github.com/openstack-k8s-operators/lib-common/modules/common/webhook" ) // DNSMasqReconciler reconciles a DNSMasq object @@ -375,6 +377,30 @@ func (r *DNSMasqReconciler) reconcileNormal(ctx context.Context, instance *netwo Log := r.GetLogger(ctx) Log.Info("Reconciling Service") + // Ensure existing CRs get the default local=/.svc/ option on + // upgrade by triggering the webhook via a reconcile-trigger annotation. + hasLocal := false + for _, opt := range instance.Spec.Options { + if opt.Key == "local" { + hasLocal = true + break + } + } + if !hasLocal { + result, err := webhook.EnsureWebhookTrigger( + ctx, + instance, + annotations.ReconcileTriggerAnnotation, + "DNSMasq local option defaulting", + Log, + 0, // use default 5 minute timeout + ) + if err != nil { + return ctrl.Result{}, err + } + return result, nil + } + serviceLabels := map[string]string{ common.AppSelector: dnsmasq.ServiceName, } diff --git a/test/functional/dnsmasq_controller_test.go b/test/functional/dnsmasq_controller_test.go index 2ca851b8..5d9f88af 100644 --- a/test/functional/dnsmasq_controller_test.go +++ b/test/functional/dnsmasq_controller_test.go @@ -25,6 +25,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + networkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/util" @@ -141,6 +142,8 @@ var _ = Describe("DNSMasq controller", func() { ContainSubstring("server=1.1.1.1")) Expect(configData.Data[dnsMasqName.Name]).Should( ContainSubstring("no-negcache\n")) + Expect(configData.Data[dnsMasqName.Name]).Should( + ContainSubstring(fmt.Sprintf("local=/%s.svc/", namespace))) Expect(configData.Labels["dnsmasq.openstack.org/name"]).To(Equal(dnsMasqName.Name)) }) @@ -273,6 +276,64 @@ var _ = Describe("DNSMasq controller", func() { }) }) + When("A DNSMasq is created with a custom local option", func() { + BeforeEach(func() { + spec := GetDefaultDNSMasqSpec() + spec["options"] = any([]networkv1.DNSMasqOption{ + { + Key: "server", + Values: []string{"1.1.1.1"}, + }, + { + Key: "no-negcache", + Values: []string{}, + }, + { + Key: "local", + Values: []string{"/custom.svc/"}, + }, + }) + instance := CreateDNSMasq(namespace, spec) + dnsMasqName = types.NamespacedName{ + Name: instance.GetName(), + Namespace: namespace, + } + + dnsDataCM = types.NamespacedName{ + Namespace: namespace, + Name: "some-dnsdata", + } + + th.CreateConfigMap(dnsDataCM, map[string]any{ + dnsDataCM.Name: "172.20.0.80 keystone-internal.openstack.svc", + }) + cm := th.GetConfigMap(dnsDataCM) + cm.Labels = util.MergeStringMaps(cm.Labels, map[string]string{ + "dnsmasqhosts": "dnsdata", + }) + Expect(th.K8sClient.Update(ctx, cm)).Should(Succeed()) + + DeferCleanup(th.DeleteConfigMap, dnsDataCM) + DeferCleanup(th.DeleteInstance, instance) + }) + + It("uses the custom local value and does not add the default", func() { + th.ExpectCondition( + dnsMasqName, + ConditionGetterFunc(DNSMasqConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + + configData := th.GetConfigMap(dnsMasqName) + Expect(configData).ShouldNot(BeNil()) + Expect(configData.Data[dnsMasqName.Name]).Should( + ContainSubstring("local=/custom.svc/")) + Expect(configData.Data[dnsMasqName.Name]).ShouldNot( + ContainSubstring(fmt.Sprintf("local=/%s.svc/", namespace))) + }) + }) + When("Deployment rollout is progressing", func() { BeforeEach(func() { instance := CreateDNSMasq(namespace, GetDefaultDNSMasqSpec())