mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-09 05:25:55 +02:00
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:
+167
-8
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user