Skip to content

Commit 076b509

Browse files
committed
Ensure that pods are replaced if we change the Pod IP family
1 parent 90b5c53 commit 076b509

File tree

8 files changed

+483
-60
lines changed

8 files changed

+483
-60
lines changed

api/v1beta2/foundationdb_labels.go

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ const (
4040
// gets its public IP from.
4141
PublicIPSourceAnnotation = "foundationdb.org/public-ip-source"
4242

43+
// IPFamilyAnnotation is an annotation key that specifies the IP family of the pod.
44+
IPFamilyAnnotation = "foundationdb.org/ip-family"
45+
4346
// PublicIPAnnotation is an annotation key that specifies the current public
4447
// IP for a pod.
4548
PublicIPAnnotation = "foundationdb.org/public-ip"

api/v1beta2/foundationdbcluster_types.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -2104,8 +2104,7 @@ type RoutingConfig struct {
21042104

21052105
// PodIPFamily tells the pod which family of IP addresses to use.
21062106
// You can use 4 to represent IPv4, and 6 to represent IPv6.
2107-
// This feature is only supported in FDB 7.0 or later, and requires
2108-
// dual-stack support in your Kubernetes environment.
2107+
// This feature requires dual-stack support in your Kubernetes environment.
21092108
PodIPFamily *int `json:"podIPFamily,omitempty"`
21102109

21112110
// UseDNSInClusterFile determines whether to use DNS names rather than IP
@@ -2907,6 +2906,13 @@ func (cluster *FoundationDBCluster) Validate() error {
29072906
}
29082907
}
29092908

2909+
if cluster.Spec.Routing.PodIPFamily != nil {
2910+
ipFamily := *cluster.Spec.Routing.PodIPFamily
2911+
if ipFamily != 4 && ipFamily != 6 {
2912+
validations = append(validations, fmt.Sprintf("Pod IP Family %d is not valid, only 4 or 6 are allowed.", ipFamily))
2913+
}
2914+
}
2915+
29102916
if len(validations) == 0 {
29112917
return nil
29122918
}
@@ -3052,9 +3058,16 @@ func (cluster *FoundationDBCluster) GetProcessGroupID(processClass ProcessClass,
30523058
return fmt.Sprintf("%s-%s-%d", cluster.Name, processClass.GetProcessClassForPodName(), idNum), processGroupID
30533059
}
30543060

3061+
var (
3062+
// PodIPFamilyIPv4 represents the desired IP family as IPv4.
3063+
PodIPFamilyIPv4 = 4
3064+
// PodIPFamilyIPv6 represents the desired IP family as IPv6.
3065+
PodIPFamilyIPv6 = 6
3066+
)
3067+
30553068
// IsPodIPFamily6 determines whether the podIPFamily setting in cluster is set to use the IPv6 family.
30563069
func (cluster *FoundationDBCluster) IsPodIPFamily6() bool {
3057-
return pointer.IntDeref(cluster.Spec.Routing.PodIPFamily, 4) == 6
3070+
return pointer.IntDeref(cluster.Spec.Routing.PodIPFamily, PodIPFamilyIPv4) == PodIPFamilyIPv6
30583071
}
30593072

30603073
// ProcessSharesDC returns true if the process's locality matches the cluster's Datacenter.

docs/cluster_spec.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ RoutingConfig allows configuring routing to our pods, and services that sit in f
468468
| ----- | ----------- | ------ | -------- |
469469
| headlessService | Headless determines whether we want to run a headless service for the cluster. | *bool | false |
470470
| publicIPSource | PublicIPSource specifies what source a process should use to get its public IPs. This supports the values `pod` and `service`. | *[PublicIPSource](#publicipsource) | false |
471-
| podIPFamily | PodIPFamily tells the pod which family of IP addresses to use. You can use 4 to represent IPv4, and 6 to represent IPv6. This feature is only supported in FDB 7.0 or later, and requires dual-stack support in your Kubernetes environment. | *int | false |
471+
| podIPFamily | PodIPFamily tells the pod which family of IP addresses to use. You can use 4 to represent IPv4, and 6 to represent IPv6. This feature requires dual-stack support in your Kubernetes environment. | *int | false |
472472
| useDNSInClusterFile | UseDNSInClusterFile determines whether to use DNS names rather than IP addresses to identify coordinators in the cluster file. This requires FoundationDB 7.0+. | *bool | false |
473473
| defineDNSLocalityFields | DefineDNSLocalityFields determines whether to define pod DNS names on pod specs and provide them in the locality arguments to fdbserver. This is ignored if UseDNSInCluster is true. | *bool | false |
474474
| dnsDomain | DNSDomain defines the cluster domain used in a DNS name generated for a service. The default is `cluster.local`. | *string | false |

e2e/test_operator/operator_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"fmt"
3737
"log"
3838
"math"
39+
"strconv"
3940
"strings"
4041
"time"
4142

@@ -2737,4 +2738,39 @@ var _ = Describe("Operator", Label("e2e", "pr"), func() {
27372738
})
27382739
})
27392740
})
2741+
2742+
When("the pod IP family is changed", func() {
2743+
var testStartTime time.Time
2744+
// TODO (johscheuer): Make the IP family configurable in the future.
2745+
var podIPFamily = fdbv1beta2.PodIPFamilyIPv4
2746+
2747+
BeforeEach(func() {
2748+
testStartTime = time.Now()
2749+
spec := fdbCluster.GetCluster().Spec.DeepCopy()
2750+
spec.Routing.PodIPFamily = pointer.Int(podIPFamily)
2751+
fdbCluster.UpdateClusterSpecWithSpec(spec)
2752+
Expect(fdbCluster.WaitForReconciliation()).To(Succeed())
2753+
})
2754+
2755+
AfterEach(func() {
2756+
spec := fdbCluster.GetCluster().Spec.DeepCopy()
2757+
spec.Routing.PodIPFamily = nil
2758+
fdbCluster.UpdateClusterSpecWithSpec(spec)
2759+
Expect(fdbCluster.WaitForReconciliation(fixtures.SoftReconcileOption(true))).To(Succeed())
2760+
})
2761+
2762+
It("should replace all pods and configure them properly", func() {
2763+
pods := fdbCluster.GetPods()
2764+
podIPFamilyString := strconv.Itoa(podIPFamily)
2765+
2766+
for _, pod := range pods.Items {
2767+
if !pod.DeletionTimestamp.IsZero() {
2768+
continue
2769+
}
2770+
2771+
Expect(pod.CreationTimestamp.After(testStartTime)).To(BeTrue())
2772+
Expect(pod.Annotations).To(HaveKeyWithValue(fdbv1beta2.IPFamilyAnnotation, podIPFamilyString))
2773+
}
2774+
})
2775+
})
27402776
})

internal/pod_helper.go

+97-45
Original file line numberDiff line numberDiff line change
@@ -25,72 +25,57 @@ import (
2525
"encoding/hex"
2626
"encoding/json"
2727
"fmt"
28+
monitorapi "github.com/apple/foundationdb/fdbkubernetesmonitor/api"
2829
"net"
2930
"strconv"
3031

31-
"github.com/go-logr/logr"
32-
33-
"k8s.io/utils/pointer"
34-
3532
fdbv1beta2 "github.com/FoundationDB/fdb-kubernetes-operator/v2/api/v1beta2"
33+
"github.com/go-logr/logr"
3634
corev1 "k8s.io/api/core/v1"
3735
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36+
"k8s.io/utils/pointer"
3837
"sigs.k8s.io/controller-runtime/pkg/client"
3938
)
4039

4140
// GetPublicIPsForPod returns the public IPs for a Pod
4241
func GetPublicIPsForPod(pod *corev1.Pod, log logr.Logger) []string {
43-
var podIPFamily *int
44-
4542
if pod == nil {
4643
return []string{}
4744
}
4845

49-
for _, container := range pod.Spec.Containers {
50-
if container.Name != fdbv1beta2.SidecarContainerName {
51-
continue
52-
}
53-
for indexOfArgument, argument := range container.Args {
54-
if argument == "--public-ip-family" && indexOfArgument < len(container.Args)-1 {
55-
familyString := container.Args[indexOfArgument+1]
56-
family, err := strconv.Atoi(familyString)
57-
if err != nil {
58-
log.Error(err, "Error parsing public IP family", "family", familyString)
59-
return nil
60-
}
61-
podIPFamily = &family
62-
break
63-
}
64-
}
46+
podIPFamily, err := GetIPFamilyFromPod(pod)
47+
if err != nil {
48+
log.Error(err, "Could not parse IP family")
49+
return []string{pod.Status.PodIP}
6550
}
6651

67-
if podIPFamily != nil {
68-
podIPs := pod.Status.PodIPs
69-
matchingIPs := make([]string, 0, len(podIPs))
52+
if podIPFamily == nil {
53+
return []string{pod.Status.PodIP}
54+
}
7055

71-
for _, podIP := range podIPs {
72-
ip := net.ParseIP(podIP.IP)
73-
if ip == nil {
74-
log.Error(nil, "Failed to parse IP from pod", "ip", podIP)
75-
continue
76-
}
77-
matches := false
78-
switch *podIPFamily {
79-
case 4:
80-
matches = ip.To4() != nil
81-
case 6:
82-
matches = ip.To4() == nil
83-
default:
84-
log.Error(nil, "Could not match IP address against IP family", "family", *podIPFamily)
85-
}
86-
if matches {
87-
matchingIPs = append(matchingIPs, podIP.IP)
88-
}
56+
podIPs := pod.Status.PodIPs
57+
matchingIPs := make([]string, 0, len(podIPs))
58+
for _, podIP := range podIPs {
59+
ip := net.ParseIP(podIP.IP)
60+
if ip == nil {
61+
log.Error(nil, "Failed to parse IP from pod", "ip", podIP)
62+
continue
63+
}
64+
matches := false
65+
switch *podIPFamily {
66+
case fdbv1beta2.PodIPFamilyIPv4:
67+
matches = ip.To4() != nil
68+
case fdbv1beta2.PodIPFamilyIPv6:
69+
matches = ip.To4() == nil
70+
default:
71+
log.Error(nil, "Could not match IP address against IP family", "family", *podIPFamily)
72+
}
73+
if matches {
74+
matchingIPs = append(matchingIPs, podIP.IP)
8975
}
90-
return matchingIPs
9176
}
9277

93-
return []string{pod.Status.PodIP}
78+
return matchingIPs
9479
}
9580

9681
// GetProcessGroupIDFromMeta fetches the process group ID from an object's metadata.
@@ -237,6 +222,73 @@ func GetPublicIPSource(pod *corev1.Pod) (fdbv1beta2.PublicIPSource, error) {
237222
return fdbv1beta2.PublicIPSource(source), nil
238223
}
239224

225+
// GetIPFamilyFromPod returns the IP family from the pod configuration.
226+
func GetIPFamilyFromPod(pod *corev1.Pod) (*int, error) {
227+
if GetImageType(pod) == fdbv1beta2.ImageTypeUnified {
228+
currentData, present := pod.Annotations[monitorapi.CurrentConfigurationAnnotation]
229+
if !present {
230+
return nil, fmt.Errorf("could not read current launcher configruations")
231+
}
232+
233+
currentConfiguration := monitorapi.ProcessConfiguration{}
234+
err := json.Unmarshal([]byte(currentData), &currentConfiguration)
235+
if err != nil {
236+
return nil, fmt.Errorf("could not parse current process configuration: %w", err)
237+
}
238+
239+
for _, argument := range currentConfiguration.Arguments {
240+
if argument.ArgumentType != monitorapi.IPListArgumentType {
241+
continue
242+
}
243+
244+
return pointer.Int(argument.IPFamily), nil
245+
}
246+
247+
// No IP List setting is defined.
248+
return nil, nil
249+
}
250+
251+
for _, container := range pod.Spec.Containers {
252+
if container.Name != fdbv1beta2.SidecarContainerName {
253+
continue
254+
}
255+
256+
for indexOfArgument, argument := range container.Args {
257+
if argument == "--public-ip-family" && indexOfArgument < len(container.Args)-1 {
258+
familyString := container.Args[indexOfArgument+1]
259+
family, err := strconv.Atoi(familyString)
260+
if err == nil {
261+
return &family, nil
262+
}
263+
break
264+
}
265+
}
266+
}
267+
268+
return nil, nil
269+
}
270+
271+
// GetIPFamily determines the IP family based on the annotation.
272+
// TODO (johscheuer): Make use of this method once we did the next release 2.2.0. This will ensure that the operator
273+
// can add the fdbv1beta2.IPFamilyAnnotation to all pods.
274+
//func GetIPFamily(pod *corev1.Pod) (*int, error) {
275+
// if pod == nil {
276+
// return nil, fmt.Errorf("failed to fetch IP family from nil Pod")
277+
// }
278+
//
279+
// ipFamilyValue := pod.ObjectMeta.Annotations[fdbv1beta2.IPFamilyAnnotation]
280+
// if ipFamilyValue != "" {
281+
// ipFamily, err := strconv.Atoi(ipFamilyValue)
282+
// if err != nil {
283+
// return nil, err
284+
// }
285+
//
286+
// return pointer.Int(ipFamily), nil
287+
// }
288+
//
289+
// return nil, nil
290+
//}
291+
240292
// PodHasSidecarTLS determines whether a pod currently has TLS enabled for the sidecar process.
241293
// This method should only be used for split images.
242294
func PodHasSidecarTLS(pod *corev1.Pod) bool {

internal/pod_models.go

+3
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,9 @@ func GetPodMetadata(cluster *fdbv1beta2.FoundationDBCluster, processClass fdbv1b
10391039
metadata.Annotations[fdbv1beta2.LastSpecKey] = specHash
10401040
metadata.Annotations[fdbv1beta2.PublicIPSourceAnnotation] = string(cluster.GetPublicIPSource())
10411041
metadata.Annotations[fdbv1beta2.ImageTypeAnnotation] = string(cluster.DesiredImageType())
1042+
if cluster.Spec.Routing.PodIPFamily != nil {
1043+
metadata.Annotations[fdbv1beta2.IPFamilyAnnotation] = strconv.Itoa(*cluster.Spec.Routing.PodIPFamily)
1044+
}
10421045

10431046
return metadata
10441047
}

internal/replacements/replacements.go

+15
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,21 @@ func processGroupNeedsRemovalForPod(cluster *fdbv1beta2.FoundationDBCluster, pod
234234
return true, nil
235235
}
236236

237+
podIPFamily, err := internal.GetIPFamilyFromPod(pod)
238+
if err != nil {
239+
return false, err
240+
}
241+
242+
if !equality.Semantic.DeepEqual(cluster.Spec.Routing.PodIPFamily, podIPFamily) {
243+
logger.Info("Replace process group",
244+
"reason", "pod IP family has changed",
245+
"currentPodIPFamily", podIPFamily,
246+
"desiredPodIPFamily", cluster.Spec.Routing.PodIPFamily,
247+
)
248+
249+
return true, nil
250+
}
251+
237252
if cluster.NeedsReplacement(processGroup) {
238253
jsonSpec, err := json.Marshal(spec)
239254
if err != nil {

0 commit comments

Comments
 (0)