@@ -13,6 +13,7 @@ import (
1313 "sync"
1414 "time"
1515
16+ jsonpatch "github.com/evanphx/json-patch"
1617 "github.com/sirupsen/logrus"
1718 acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
1819
4445 databaseNameRegexp = regexp .MustCompile ("^[a-zA-Z_][a-zA-Z0-9_]*$" )
4546 userRegexp = regexp .MustCompile (`^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$` )
4647 patroniObjectSuffixes = []string {"leader" , "config" , "sync" , "failover" }
48+ finalizerName = "postgres-operator.acid.zalan.do"
4749)
4850
4951// Config contains operator-wide clients and configuration used from a cluster. TODO: remove struct duplication.
@@ -260,6 +262,12 @@ func (c *Cluster) Create() (err error) {
260262 }()
261263
262264 c .KubeClient .SetPostgresCRDStatus (c .clusterName (), acidv1 .ClusterStatusCreating )
265+ if c .OpConfig .EnableFinalizers != nil && * c .OpConfig .EnableFinalizers {
266+ c .logger .Info ("Adding finalizer." )
267+ if err = c .AddFinalizer (); err != nil {
268+ return fmt .Errorf ("could not add Finalizer: %v" , err )
269+ }
270+ }
263271 c .eventRecorder .Event (c .GetReference (), v1 .EventTypeNormal , "Create" , "Started creation of new cluster resources" )
264272
265273 for _ , role := range []PostgresRole {Master , Replica } {
@@ -763,6 +771,98 @@ func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) {
763771 return true , ""
764772}
765773
774+ // AddFinalizer patches the postgresql CR to add our finalizer.
775+ func (c * Cluster ) AddFinalizer () error {
776+ if c .HasFinalizer () {
777+ c .logger .Debugf ("Finalizer %s already exists." , finalizerName )
778+ return nil
779+ }
780+
781+ currentSpec := c .DeepCopy ()
782+ newSpec := c .DeepCopy ()
783+ newSpec .ObjectMeta .SetFinalizers (append (newSpec .ObjectMeta .Finalizers , finalizerName ))
784+ patchBytes , err := getPatchBytes (currentSpec , newSpec )
785+ if err != nil {
786+ return fmt .Errorf ("Unable to produce patch to add finalizer: %v" , err )
787+ }
788+
789+ updatedSpec , err := c .KubeClient .AcidV1ClientSet .AcidV1 ().Postgresqls (c .clusterNamespace ()).Patch (
790+ context .TODO (), c .Name , types .MergePatchType , patchBytes , metav1.PatchOptions {})
791+ if err != nil {
792+ return fmt .Errorf ("Could not add finalizer: %v" , err )
793+ }
794+
795+ // update the spec, maintaining the new resourceVersion.
796+ c .setSpec (updatedSpec )
797+ return nil
798+ }
799+
800+ // RemoveFinalizer patches postgresql CR to remove finalizer.
801+ func (c * Cluster ) RemoveFinalizer () error {
802+ if ! c .HasFinalizer () {
803+ c .logger .Debugf ("No finalizer %s exists to remove." , finalizerName )
804+ return nil
805+ }
806+ currentSpec := c .DeepCopy ()
807+ newSpec := c .DeepCopy ()
808+ newSpec .ObjectMeta .SetFinalizers (removeString (newSpec .ObjectMeta .Finalizers , finalizerName ))
809+ patchBytes , err := getPatchBytes (currentSpec , newSpec )
810+ if err != nil {
811+ return fmt .Errorf ("Unable to produce patch to remove finalizer: %v" , err )
812+ }
813+
814+ updatedSpec , err := c .KubeClient .AcidV1ClientSet .AcidV1 ().Postgresqls (c .clusterNamespace ()).Patch (
815+ context .TODO (), c .Name , types .MergePatchType , patchBytes , metav1.PatchOptions {})
816+ if err != nil {
817+ return fmt .Errorf ("Could not remove finalizer: %v" , err )
818+ }
819+
820+ // update the spec, maintaining the new resourceVersion.
821+ c .setSpec (updatedSpec )
822+
823+ return nil
824+ }
825+
826+ // HasFinalizer checks if our finalizer is currently set or not
827+ func (c * Cluster ) HasFinalizer () bool {
828+ for _ , finalizer := range c .ObjectMeta .Finalizers {
829+ if finalizer == finalizerName {
830+ return true
831+ }
832+ }
833+ return false
834+ }
835+
836+ // Iterate through slice and remove certain string, then return cleaned slice
837+ func removeString (slice []string , s string ) (result []string ) {
838+ for _ , item := range slice {
839+ if item == s {
840+ continue
841+ }
842+ result = append (result , item )
843+ }
844+ return result
845+ }
846+
847+ // getPatchBytes will produce a JSONpatch between the two parameters of type acidv1.Postgresql
848+ func getPatchBytes (oldSpec , newSpec * acidv1.Postgresql ) ([]byte , error ) {
849+ oldData , err := json .Marshal (oldSpec )
850+ if err != nil {
851+ return nil , fmt .Errorf ("failed to Marshal oldSpec for postgresql %s/%s: %v" , oldSpec .Namespace , oldSpec .Name , err )
852+ }
853+
854+ newData , err := json .Marshal (newSpec )
855+ if err != nil {
856+ return nil , fmt .Errorf ("failed to Marshal newSpec for postgresql %s/%s: %v" , newSpec .Namespace , newSpec .Name , err )
857+ }
858+
859+ patchBytes , err := jsonpatch .CreateMergePatch (oldData , newData )
860+ if err != nil {
861+ return nil , fmt .Errorf ("failed to CreateMergePatch for postgresl %s/%s: %v" , oldSpec .Namespace , oldSpec .Name , err )
862+ }
863+ return patchBytes , nil
864+ }
865+
766866// Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object
767867// (i.e. service) is treated as an error
768868// logical backup cron jobs are an exception: a user-initiated Update can enable a logical backup job
@@ -1005,59 +1105,88 @@ func syncResources(a, b *v1.ResourceRequirements) bool {
10051105// DCS, reuses the master's endpoint to store the leader related metadata. If we remove the endpoint
10061106// before the pods, it will be re-created by the current master pod and will remain, obstructing the
10071107// creation of the new cluster with the same name. Therefore, the endpoints should be deleted last.
1008- func (c * Cluster ) Delete () {
1108+ func (c * Cluster ) Delete () error {
10091109 c .mu .Lock ()
10101110 defer c .mu .Unlock ()
1011- c .eventRecorder .Event (c .GetReference (), v1 .EventTypeNormal , "Delete" , "Started deletion of new cluster resources" )
1111+ c .eventRecorder .Event (c .GetReference (), v1 .EventTypeNormal , "Delete" , "Started deletion of cluster resources" )
10121112
10131113 if err := c .deleteStreams (); err != nil {
10141114 c .logger .Warningf ("could not delete event streams: %v" , err )
1115+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete event streams: %v" , err )
10151116 }
1117+ var anyErrors = false
10161118
10171119 // delete the backup job before the stateful set of the cluster to prevent connections to non-existing pods
10181120 // deleting the cron job also removes pods and batch jobs it created
10191121 if err := c .deleteLogicalBackupJob (); err != nil {
1122+ anyErrors = true
10201123 c .logger .Warningf ("could not remove the logical backup k8s cron job; %v" , err )
1124+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not remove the logical backup k8s cron job; %v" , err )
10211125 }
10221126
10231127 if err := c .deleteStatefulSet (); err != nil {
1128+ anyErrors = true
10241129 c .logger .Warningf ("could not delete statefulset: %v" , err )
1130+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete statefulset: %v" , err )
10251131 }
10261132
10271133 if err := c .deleteSecrets (); err != nil {
1134+ anyErrors = true
10281135 c .logger .Warningf ("could not delete secrets: %v" , err )
1136+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete secrets: %v" , err )
10291137 }
10301138
10311139 if err := c .deletePodDisruptionBudget (); err != nil {
1140+ anyErrors = true
10321141 c .logger .Warningf ("could not delete pod disruption budget: %v" , err )
1142+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete pod disruption budget: %v" , err )
10331143 }
10341144
10351145 for _ , role := range []PostgresRole {Master , Replica } {
10361146
10371147 if ! c .patroniKubernetesUseConfigMaps () {
10381148 if err := c .deleteEndpoint (role ); err != nil {
1149+ anyErrors = true
10391150 c .logger .Warningf ("could not delete %s endpoint: %v" , role , err )
1151+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete %s endpoint: %v" , role , err )
10401152 }
10411153 }
10421154
10431155 if err := c .deleteService (role ); err != nil {
1156+ anyErrors = true
10441157 c .logger .Warningf ("could not delete %s service: %v" , role , err )
1158+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete %s service: %v" , role , err )
10451159 }
10461160 }
10471161
10481162 if err := c .deletePatroniClusterObjects (); err != nil {
1163+ anyErrors = true
10491164 c .logger .Warningf ("could not remove leftover patroni objects; %v" , err )
1165+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not remove leftover patroni objects; %v" , err )
10501166 }
10511167
10521168 // Delete connection pooler objects anyway, even if it's not mentioned in the
10531169 // manifest, just to not keep orphaned components in case if something went
10541170 // wrong
10551171 for _ , role := range [2 ]PostgresRole {Master , Replica } {
10561172 if err := c .deleteConnectionPooler (role ); err != nil {
1173+ anyErrors = true
10571174 c .logger .Warningf ("could not remove connection pooler: %v" , err )
1175+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not remove connection pooler: %v" , err )
10581176 }
10591177 }
10601178
1179+ // If we are done deleting our various resources we remove the finalizer to let K8S finally delete the Postgres CR
1180+ if anyErrors {
1181+ c .eventRecorder .Event (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Some resources could be successfully deleted yet" )
1182+ return fmt .Errorf ("some error(s) occured when deleting resources, NOT removing finalizer yet" )
1183+ }
1184+ if err := c .RemoveFinalizer (); err != nil {
1185+ return fmt .Errorf ("Done cleaning up, but error when trying to remove our finalizer: %v" , err )
1186+ }
1187+
1188+ c .logger .Info ("Done cleaning up our resources, removed finalizer." )
1189+ return nil
10611190}
10621191
10631192// NeedsRepair returns true if the cluster should be included in the repair scan (based on its in-memory status).
0 commit comments