sync all resources to cluster fields (#2713)

* sync all resources to cluster fields (CronJob, Streams, Patroni resources)
* separated sync and delete logic for Patroni resources
* align delete streams and secrets logic with other resources
* rename gatherApplicationIds to getDistinctApplicationIds
* improve slot check before syncing streams CRD
* add ownerReferences and annotations diff to Patroni objects
* add extra sync code for config service so it does not get too ugly
* some bugfixes when comparing annotations and return err on found
* sync Patroni resources on update event and extended unit tests
* add config service/endpoint owner references check to e2e tes
This commit is contained in:
Felix Kunde
2024-08-13 10:06:46 +02:00
committed by GitHub
parent 31f92a1aa0
commit 25ccc87317
11 changed files with 668 additions and 304 deletions
+167 -8
View File
@@ -15,6 +15,7 @@ import (
"github.com/zalando/postgres-operator/pkg/util"
"github.com/zalando/postgres-operator/pkg/util/constants"
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
@@ -80,6 +81,10 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
return err
}
if err = c.syncPatroniResources(); err != nil {
c.logger.Errorf("could not sync Patroni resources: %v", err)
}
// sync volume may already transition volumes to gp3, if iops/throughput or type is specified
if err = c.syncVolumes(); err != nil {
return err
@@ -173,6 +178,163 @@ func (c *Cluster) syncFinalizer() error {
return nil
}
func (c *Cluster) syncPatroniResources() error {
errors := make([]string, 0)
if err := c.syncPatroniService(); err != nil {
errors = append(errors, fmt.Sprintf("could not sync %s service: %v", Patroni, err))
}
for _, suffix := range patroniObjectSuffixes {
if c.patroniKubernetesUseConfigMaps() {
if err := c.syncPatroniConfigMap(suffix); err != nil {
errors = append(errors, fmt.Sprintf("could not sync %s Patroni config map: %v", suffix, err))
}
} else {
if err := c.syncPatroniEndpoint(suffix); err != nil {
errors = append(errors, fmt.Sprintf("could not sync %s Patroni endpoint: %v", suffix, err))
}
}
}
if len(errors) > 0 {
return fmt.Errorf("%v", strings.Join(errors, `', '`))
}
return nil
}
func (c *Cluster) syncPatroniConfigMap(suffix string) error {
var (
cm *v1.ConfigMap
err error
)
configMapName := fmt.Sprintf("%s-%s", c.Name, suffix)
c.logger.Debugf("syncing %s config map", configMapName)
c.setProcessName("syncing %s config map", configMapName)
if cm, err = c.KubeClient.ConfigMaps(c.Namespace).Get(context.TODO(), configMapName, metav1.GetOptions{}); err == nil {
c.PatroniConfigMaps[suffix] = cm
desiredOwnerRefs := c.ownerReferences()
if !reflect.DeepEqual(cm.ObjectMeta.OwnerReferences, desiredOwnerRefs) {
c.logger.Infof("new %s config map's owner references do not match the current ones", configMapName)
cm.ObjectMeta.OwnerReferences = desiredOwnerRefs
c.setProcessName("updating %s config map", configMapName)
cm, err = c.KubeClient.ConfigMaps(c.Namespace).Update(context.TODO(), cm, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("could not update %s config map: %v", configMapName, err)
}
c.PatroniConfigMaps[suffix] = cm
}
annotations := make(map[string]string)
maps.Copy(annotations, cm.Annotations)
desiredAnnotations := c.annotationsSet(cm.Annotations)
if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed {
patchData, err := metaAnnotationsPatch(desiredAnnotations)
if err != nil {
return fmt.Errorf("could not form patch for %s config map: %v", configMapName, err)
}
cm, err = c.KubeClient.ConfigMaps(c.Namespace).Patch(context.TODO(), configMapName, types.MergePatchType, []byte(patchData), metav1.PatchOptions{})
if err != nil {
return fmt.Errorf("could not patch annotations of %s config map: %v", configMapName, err)
}
c.PatroniConfigMaps[suffix] = cm
}
} else if !k8sutil.ResourceNotFound(err) {
// if config map does not exist yet, Patroni should create it
return fmt.Errorf("could not get %s config map: %v", configMapName, err)
}
return nil
}
func (c *Cluster) syncPatroniEndpoint(suffix string) error {
var (
ep *v1.Endpoints
err error
)
endpointName := fmt.Sprintf("%s-%s", c.Name, suffix)
c.logger.Debugf("syncing %s endpoint", endpointName)
c.setProcessName("syncing %s endpoint", endpointName)
if ep, err = c.KubeClient.Endpoints(c.Namespace).Get(context.TODO(), endpointName, metav1.GetOptions{}); err == nil {
c.PatroniEndpoints[suffix] = ep
desiredOwnerRefs := c.ownerReferences()
if !reflect.DeepEqual(ep.ObjectMeta.OwnerReferences, desiredOwnerRefs) {
c.logger.Infof("new %s endpoints's owner references do not match the current ones", endpointName)
ep.ObjectMeta.OwnerReferences = desiredOwnerRefs
c.setProcessName("updating %s endpoint", endpointName)
ep, err = c.KubeClient.Endpoints(c.Namespace).Update(context.TODO(), ep, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("could not update %s endpoint: %v", endpointName, err)
}
c.PatroniEndpoints[suffix] = ep
}
annotations := make(map[string]string)
maps.Copy(annotations, ep.Annotations)
desiredAnnotations := c.annotationsSet(ep.Annotations)
if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed {
patchData, err := metaAnnotationsPatch(desiredAnnotations)
if err != nil {
return fmt.Errorf("could not form patch for %s endpoint: %v", endpointName, err)
}
ep, err = c.KubeClient.Endpoints(c.Namespace).Patch(context.TODO(), endpointName, types.MergePatchType, []byte(patchData), metav1.PatchOptions{})
if err != nil {
return fmt.Errorf("could not patch annotations of %s endpoint: %v", endpointName, err)
}
c.PatroniEndpoints[suffix] = ep
}
} else if !k8sutil.ResourceNotFound(err) {
// if endpoint does not exist yet, Patroni should create it
return fmt.Errorf("could not get %s endpoint: %v", endpointName, err)
}
return nil
}
func (c *Cluster) syncPatroniService() error {
var (
svc *v1.Service
err error
)
serviceName := fmt.Sprintf("%s-%s", c.Name, Patroni)
c.setProcessName("syncing %s service", serviceName)
if svc, err = c.KubeClient.Services(c.Namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}); err == nil {
c.Services[Patroni] = svc
desiredOwnerRefs := c.ownerReferences()
if !reflect.DeepEqual(svc.ObjectMeta.OwnerReferences, desiredOwnerRefs) {
c.logger.Infof("new %s service's owner references do not match the current ones", serviceName)
svc.ObjectMeta.OwnerReferences = desiredOwnerRefs
c.setProcessName("updating %v service", serviceName)
svc, err = c.KubeClient.Services(c.Namespace).Update(context.TODO(), svc, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("could not update %s endpoint: %v", serviceName, err)
}
c.Services[Patroni] = svc
}
annotations := make(map[string]string)
maps.Copy(annotations, svc.Annotations)
desiredAnnotations := c.annotationsSet(svc.Annotations)
if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed {
patchData, err := metaAnnotationsPatch(desiredAnnotations)
if err != nil {
return fmt.Errorf("could not form patch for %s service: %v", serviceName, err)
}
svc, err = c.KubeClient.Services(c.Namespace).Patch(context.TODO(), serviceName, types.MergePatchType, []byte(patchData), metav1.PatchOptions{})
if err != nil {
return fmt.Errorf("could not patch annotations of %s service: %v", serviceName, err)
}
c.Services[Patroni] = svc
}
} else if !k8sutil.ResourceNotFound(err) {
// if config service does not exist yet, Patroni should create it
return fmt.Errorf("could not get %s service: %v", serviceName, err)
}
return nil
}
func (c *Cluster) syncServices() error {
for _, role := range []PostgresRole{Master, Replica} {
c.logger.Debugf("syncing %s service", role)
@@ -211,7 +373,6 @@ func (c *Cluster) syncService(role PostgresRole) error {
return fmt.Errorf("could not get %s service: %v", role, err)
}
// no existing service, create new one
c.Services[role] = nil
c.logger.Infof("could not find the cluster's %s service", role)
if svc, err = c.createService(role); err == nil {
@@ -236,7 +397,7 @@ func (c *Cluster) syncEndpoint(role PostgresRole) error {
)
c.setProcessName("syncing %s endpoint", role)
if ep, err = c.KubeClient.Endpoints(c.Namespace).Get(context.TODO(), c.endpointName(role), metav1.GetOptions{}); err == nil {
if ep, err = c.KubeClient.Endpoints(c.Namespace).Get(context.TODO(), c.serviceName(role), metav1.GetOptions{}); err == nil {
desiredEp := c.generateEndpoint(role, ep.Subsets)
// if owner references differ we update which would also change annotations
if !reflect.DeepEqual(ep.ObjectMeta.OwnerReferences, desiredEp.ObjectMeta.OwnerReferences) {
@@ -252,7 +413,7 @@ func (c *Cluster) syncEndpoint(role PostgresRole) error {
if err != nil {
return fmt.Errorf("could not form patch for %s endpoint: %v", role, err)
}
ep, err = c.KubeClient.Endpoints(c.Namespace).Patch(context.TODO(), c.endpointName(role), types.MergePatchType, []byte(patchData), metav1.PatchOptions{})
ep, err = c.KubeClient.Endpoints(c.Namespace).Patch(context.TODO(), c.serviceName(role), types.MergePatchType, []byte(patchData), metav1.PatchOptions{})
if err != nil {
return fmt.Errorf("could not patch annotations of %s endpoint: %v", role, err)
}
@@ -265,7 +426,6 @@ func (c *Cluster) syncEndpoint(role PostgresRole) error {
return fmt.Errorf("could not get %s endpoint: %v", role, err)
}
// no existing endpoint, create new one
c.Endpoints[role] = nil
c.logger.Infof("could not find the cluster's %s endpoint", role)
if ep, err = c.createEndpoint(role); err == nil {
@@ -275,7 +435,7 @@ func (c *Cluster) syncEndpoint(role PostgresRole) error {
return fmt.Errorf("could not create missing %s endpoint: %v", role, err)
}
c.logger.Infof("%s endpoint %q already exists", role, util.NameFromMeta(ep.ObjectMeta))
if ep, err = c.KubeClient.Endpoints(c.Namespace).Get(context.TODO(), c.endpointName(role), metav1.GetOptions{}); err != nil {
if ep, err = c.KubeClient.Endpoints(c.Namespace).Get(context.TODO(), c.serviceName(role), metav1.GetOptions{}); err != nil {
return fmt.Errorf("could not fetch existing %s endpoint: %v", role, err)
}
}
@@ -307,7 +467,6 @@ func (c *Cluster) syncPodDisruptionBudget(isUpdate bool) error {
return fmt.Errorf("could not get pod disruption budget: %v", err)
}
// no existing pod disruption budget, create new one
c.PodDisruptionBudget = nil
c.logger.Infof("could not find the cluster's pod disruption budget")
if pdb, err = c.createPodDisruptionBudget(); err != nil {
@@ -349,7 +508,6 @@ func (c *Cluster) syncStatefulSet() error {
if err != nil {
// statefulset does not exist, try to re-create it
c.Statefulset = nil
c.logger.Infof("cluster's statefulset does not exist")
sset, err = c.createStatefulSet()
@@ -714,7 +872,7 @@ func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration(pod *v1.Pod, effectiv
// check if specified slots exist in config and if they differ
for slotName, desiredSlot := range desiredPatroniConfig.Slots {
// only add slots specified in manifest to c.replicationSlots
for manifestSlotName, _ := range c.Spec.Patroni.Slots {
for manifestSlotName := range c.Spec.Patroni.Slots {
if manifestSlotName == slotName {
c.replicationSlots[slotName] = desiredSlot
}
@@ -1447,6 +1605,7 @@ func (c *Cluster) syncLogicalBackupJob() error {
return fmt.Errorf("could not patch annotations of the logical backup job %q: %v", jobName, err)
}
}
c.LogicalBackupJob = desiredJob
return nil
}
if !k8sutil.ResourceNotFound(err) {