@@ -13,6 +13,7 @@ import (
13
13
"sync"
14
14
"time"
15
15
16
+ jsonpatch "github.com/evanphx/json-patch"
16
17
"github.com/sirupsen/logrus"
17
18
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
18
19
44
45
databaseNameRegexp = regexp .MustCompile ("^[a-zA-Z_][a-zA-Z0-9_]*$" )
45
46
userRegexp = regexp .MustCompile (`^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$` )
46
47
patroniObjectSuffixes = []string {"leader" , "config" , "sync" , "failover" }
48
+ finalizerName = "postgres-operator.acid.zalan.do"
47
49
)
48
50
49
51
// 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) {
260
262
}()
261
263
262
264
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
+ }
263
271
c .eventRecorder .Event (c .GetReference (), v1 .EventTypeNormal , "Create" , "Started creation of new cluster resources" )
264
272
265
273
for _ , role := range []PostgresRole {Master , Replica } {
@@ -763,6 +771,98 @@ func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) {
763
771
return true , ""
764
772
}
765
773
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
+
766
866
// Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object
767
867
// (i.e. service) is treated as an error
768
868
// 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 {
1005
1105
// DCS, reuses the master's endpoint to store the leader related metadata. If we remove the endpoint
1006
1106
// before the pods, it will be re-created by the current master pod and will remain, obstructing the
1007
1107
// 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 {
1009
1109
c .mu .Lock ()
1010
1110
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" )
1012
1112
1013
1113
if err := c .deleteStreams (); err != nil {
1014
1114
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 )
1015
1116
}
1117
+ var anyErrors = false
1016
1118
1017
1119
// delete the backup job before the stateful set of the cluster to prevent connections to non-existing pods
1018
1120
// deleting the cron job also removes pods and batch jobs it created
1019
1121
if err := c .deleteLogicalBackupJob (); err != nil {
1122
+ anyErrors = true
1020
1123
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 )
1021
1125
}
1022
1126
1023
1127
if err := c .deleteStatefulSet (); err != nil {
1128
+ anyErrors = true
1024
1129
c .logger .Warningf ("could not delete statefulset: %v" , err )
1130
+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete statefulset: %v" , err )
1025
1131
}
1026
1132
1027
1133
if err := c .deleteSecrets (); err != nil {
1134
+ anyErrors = true
1028
1135
c .logger .Warningf ("could not delete secrets: %v" , err )
1136
+ c .eventRecorder .Eventf (c .GetReference (), v1 .EventTypeWarning , "Delete" , "Could not delete secrets: %v" , err )
1029
1137
}
1030
1138
1031
1139
if err := c .deletePodDisruptionBudget (); err != nil {
1140
+ anyErrors = true
1032
1141
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 )
1033
1143
}
1034
1144
1035
1145
for _ , role := range []PostgresRole {Master , Replica } {
1036
1146
1037
1147
if ! c .patroniKubernetesUseConfigMaps () {
1038
1148
if err := c .deleteEndpoint (role ); err != nil {
1149
+ anyErrors = true
1039
1150
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 )
1040
1152
}
1041
1153
}
1042
1154
1043
1155
if err := c .deleteService (role ); err != nil {
1156
+ anyErrors = true
1044
1157
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 )
1045
1159
}
1046
1160
}
1047
1161
1048
1162
if err := c .deletePatroniClusterObjects (); err != nil {
1163
+ anyErrors = true
1049
1164
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 )
1050
1166
}
1051
1167
1052
1168
// Delete connection pooler objects anyway, even if it's not mentioned in the
1053
1169
// manifest, just to not keep orphaned components in case if something went
1054
1170
// wrong
1055
1171
for _ , role := range [2 ]PostgresRole {Master , Replica } {
1056
1172
if err := c .deleteConnectionPooler (role ); err != nil {
1173
+ anyErrors = true
1057
1174
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 )
1058
1176
}
1059
1177
}
1060
1178
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
1061
1190
}
1062
1191
1063
1192
// NeedsRepair returns true if the cluster should be included in the repair scan (based on its in-memory status).
0 commit comments