Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions controllers/designatebackendbind9_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ func (r *DesignateBackendbind9Reconciler) SetupWithManager(ctx context.Context,
For(&designatev1beta1.DesignateBackendbind9{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
Owns(&corev1.Pod{}).
// watch the config CMs we don't own
Watches(&corev1.ConfigMap{},
handler.EnqueueRequestsFromMapFunc(configMapFn)).
Expand Down Expand Up @@ -661,6 +662,18 @@ func (r *DesignateBackendbind9Reconciler) reconcileNormal(ctx context.Context, i
}
// create StatefulSet - end

// Handle pod labeling for predictable IPs
config := designate.PodLabelingConfig{
ConfigMapName: designate.BindPredIPConfigMap,
IPKeyPrefix: "bind_address_",
ServiceName: "designate-backendbind9",
}
err = designate.HandlePodLabeling(ctx, helper, instance.Name, instance.Namespace, config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We call this function every reconcile loop, and if I understand correctly, we list all of the pods of the deployment and then filter for the designate ones. We might want to implement a mechanism to call it only when the configmap changes, e.g. comparing hashes as we do with the pools.yaml generation. But we can implement that in a future patch if we see that it is necessary.

if err != nil {
Log.Error(err, "Failed to handle pod labeling")
// Don't return error as this is not critical for the main reconcile loop
}

// We reached the end of the Reconcile, update the Ready condition based on
// the sub conditions
if instance.Status.Conditions.AllSubConditionIsTrue() {
Expand Down
13 changes: 13 additions & 0 deletions controllers/designatemdns_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ func (r *DesignateMdnsReconciler) SetupWithManager(ctx context.Context, mgr ctrl
For(&designatev1beta1.DesignateMdns{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
Owns(&corev1.Pod{}).
// watch the secrets we don't own
Watches(&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(svcSecretFn)).
Expand Down Expand Up @@ -768,6 +769,18 @@ func (r *DesignateMdnsReconciler) reconcileNormal(ctx context.Context, instance
}
// create StatefulSet - end

// Handle pod labeling for predictable IPs
config := designate.PodLabelingConfig{
ConfigMapName: designate.MdnsPredIPConfigMap,
IPKeyPrefix: "mdns_address_",
ServiceName: "designate-mdns",
}
err = designate.HandlePodLabeling(ctx, helper, instance.Name, instance.Namespace, config)
if err != nil {
Log.Error(err, "Failed to handle pod labeling")
// Don't return error as this is not critical for the main reconcile loop
}

// We reached the end of the Reconcile, update the Ready condition based on
// the sub conditions
if instance.Status.Conditions.AllSubConditionIsTrue() {
Expand Down
81 changes: 81 additions & 0 deletions pkg/designate/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@ limitations under the License.
package designate

import (
"context"
"errors"
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/openstack-k8s-operators/lib-common/modules/common"
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
)

Expand Down Expand Up @@ -90,3 +99,75 @@ type MessageBus struct {
SecretName string
Status MessageBusStatus
}

// PodLabelingConfig holds configuration for pod labeling
type PodLabelingConfig struct {
ConfigMapName string
IPKeyPrefix string
ServiceName string
}

// HandlePodLabeling handles adding predictableip labels to pods
func HandlePodLabeling(ctx context.Context, h *helper.Helper, instanceName, namespace string, config PodLabelingConfig) error {
// List all pods owned by this instance
podList := &corev1.PodList{}
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{
common.AppSelector: instanceName,
},
}

err := h.GetClient().List(ctx, podList, listOpts...)
if err != nil {
return fmt.Errorf("failed to list pods: %w", err)
}

// Get the IP configmap once for all pods
configMap := &corev1.ConfigMap{}
err = h.GetClient().Get(ctx, types.NamespacedName{Name: config.ConfigMapName, Namespace: namespace}, configMap)
if err != nil {
if k8s_errors.IsNotFound(err) {
return nil // Configmap not found, skip labeling
}
return err
}

// Process each pod
for _, pod := range podList.Items {
// Check if the pod already has the predictableip label
if pod.Labels != nil && pod.Labels["predictableip"] != "" {
continue
}

// Extract pod index from pod name (e.g., "designate-backendbind9-0" -> "0")
podName := pod.Name
nameParts := strings.Split(podName, "-")
if len(nameParts) == 0 {
continue // Skip invalid pod name format
}
podIndex := nameParts[len(nameParts)-1]

// Get the IP for this pod index
ipKey := fmt.Sprintf("%s%s", config.IPKeyPrefix, podIndex)
predictableIP, exists := configMap.Data[ipKey]
if !exists {
continue // No IP found for this pod index, skip labeling
}

// Add the predictableip label to the pod
if pod.Labels == nil {
pod.Labels = make(map[string]string)
}
pod.Labels["predictableip"] = predictableIP

// Update the pod
err = h.GetClient().Update(ctx, &pod)
if err != nil {
// Log error but continue processing other pods
continue
}
}

return nil
}
23 changes: 15 additions & 8 deletions tests/functional/designate_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,14 +605,18 @@ var _ = Describe("Designate controller", func() {
Name: spec["designateNetworkAttachment"].(string),
Namespace: namespace,
}))

DeferCleanup(th.DeleteInstance, CreateDesignate(designateName, spec))
th.SimulateJobSuccess(designateDBSyncName)

createAndSimulateBind9(designateBind9Name)
createAndSimulateMdns(designateMdnsName)
})

It("should have Unknown and false Conditions initialized for Designate services conditions initially", func() {
It("should have Unknown or False Conditions initialized for Designate services conditions initially", func() {
// With actual controllers running, conditions may transition from Unknown to False
// quickly if dependencies aren't ready yet. Both Unknown and False are acceptable
// initial states.
for _, cond := range []condition.Type{
designatev1.DesignateMdnsReadyCondition,
designatev1.DesignateUnboundReadyCondition,
Expand All @@ -622,12 +626,14 @@ var _ = Describe("Designate controller", func() {
designatev1.DesignateProducerReadyCondition,
designatev1.DesignateBackendbind9ReadyCondition,
} {
th.ExpectCondition(
designateName,
ConditionGetterFunc(DesignateConditionGetter),
cond,
corev1.ConditionUnknown,
)
Eventually(func(g Gomega) {
instance := GetDesignate(designateName)
c := instance.Status.Conditions.Get(cond)
g.Expect(c).NotTo(BeNil(), "condition %s should exist", cond)
// Condition should be Unknown or False initially (not True)
g.Expect(c.Status).To(Or(Equal(corev1.ConditionUnknown), Equal(corev1.ConditionFalse)),
"condition %s should be Unknown or False initially, got: %s", cond, c.Status)
}, timeout, interval).Should(Succeed())
}
})

Expand Down Expand Up @@ -889,7 +895,8 @@ var _ = Describe("Designate controller", func() {
Name: expectedTopology.Name,
Namespace: expectedTopology.Namespace,
})
g.Expect(tp.GetFinalizers()).To(HaveLen(4))
// API, Central, Producer, Worker, Mdns + Unbound = 6 finalizers
g.Expect(tp.GetFinalizers()).To(HaveLen(6))
finalizers := tp.GetFinalizers()

designateAPI := GetDesignateAPI(designateAPIName)
Expand Down
66 changes: 66 additions & 0 deletions tests/functional/designatebackendbind9_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ import (
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports
. "github.com/onsi/gomega" //revive:disable:dot-imports
"github.com/openstack-k8s-operators/lib-common/modules/common"
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

// revive:disable-next-line:dot-imports
designatev1 "github.com/openstack-k8s-operators/designate-operator/api/v1beta1"
"github.com/openstack-k8s-operators/designate-operator/pkg/designate"
. "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
Expand Down Expand Up @@ -131,6 +135,12 @@ var _ = Describe("DesignateBackendbind9 controller", func() {
Name: spec["designateNetworkAttachment"].(string),
Namespace: namespace,
}))

// Create control network attachment definition (default name is "designate")
DeferCleanup(k8sClient.Delete, ctx, CreateNAD(types.NamespacedName{
Name: "designate",
Namespace: namespace,
}))
})

It("should be in state of having the input ready", func() {
Expand All @@ -141,5 +151,61 @@ var _ = Describe("DesignateBackendbind9 controller", func() {
corev1.ConditionTrue,
)
})

It("should add predictableip labels to pods", func() {
// Create predictable IP configmap
configData := map[string]any{
"bind_address_0": "172.28.0.31",
"bind_address_1": "172.28.0.32",
}
DeferCleanup(k8sClient.Delete, ctx, CreateBindIPMap(namespace, configData))

// Create a pod with the expected labels
podName := types.NamespacedName{
Name: fmt.Sprintf("%s-0", designateBackendbind9Name.Name),
Namespace: namespace,
}
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName.Name,
Namespace: podName.Namespace,
Labels: map[string]string{
common.AppSelector: designateBackendbind9Name.Name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "test",
Image: "test",
}},
},
}
Expect(k8sClient.Create(ctx, pod)).Should(Succeed())
DeferCleanup(k8sClient.Delete, ctx, pod)

// Test the HandlePodLabeling function directly
config := designate.PodLabelingConfig{
ConfigMapName: designate.BindPredIPConfigMap,
IPKeyPrefix: "bind_address_",
ServiceName: "designate-backendbind9",
}
h, err := helper.NewHelper(
&designatev1.DesignateBackendbind9{},
k8sClient,
nil,
nil,
logger,
)
Expect(err).ShouldNot(HaveOccurred())
err = designate.HandlePodLabeling(ctx, h, designateBackendbind9Name.Name, namespace, config)
Expect(err).ShouldNot(HaveOccurred())

// Verify the pod has the predictableip label
Eventually(func(g Gomega) {
updatedPod := &corev1.Pod{}
g.Expect(k8sClient.Get(ctx, podName, updatedPod)).Should(Succeed())
g.Expect(updatedPod.Labels).Should(HaveKeyWithValue("predictableip", "172.28.0.31"))
}, timeout, interval).Should(Succeed())
})
})
})
Loading