Skip to content

Commit 0569dfc

Browse files
Integrating Major PG Upgrades controller logic and testing into PGO.
[sc-16348] Co-authored-by: Tony Landreth <anthony.w.landreth@gmail.com>
1 parent b893a5a commit 0569dfc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2732
-11
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ check-kuttl:
220220
--config testing/kuttl/kuttl-test.yaml
221221

222222
.PHONY: generate-kuttl
223+
generate-kuttl: export KUTTL_PG_UPGRADE_FROM_VERSION ?= 13
223224
generate-kuttl: export KUTTL_PG_VERSION ?= 14
224225
generate-kuttl: export KUTTL_POSTGIS_VERSION ?= 3.1
225226
generate-kuttl: export KUTTL_PSQL_IMAGE ?= registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.6-2
@@ -234,7 +235,7 @@ generate-kuttl:
234235
12 ) export KUTTL_BITNAMI_IMAGE_TAG=12.12.0-debian-11-r40 ;; \
235236
11 ) export KUTTL_BITNAMI_IMAGE_TAG=11.17.0-debian-11-r39 ;; \
236237
esac; \
237-
render() { envsubst '"'"'$$KUTTL_PG_VERSION $$KUTTL_POSTGIS_VERSION $$KUTTL_PSQL_IMAGE $$KUTTL_BITNAMI_IMAGE_TAG'"'"'; }; \
238+
render() { envsubst '"'"'$$KUTTL_PG_UPGRADE_FROM_VERSION $$KUTTL_PG_VERSION $$KUTTL_POSTGIS_VERSION $$KUTTL_PSQL_IMAGE $$KUTTL_BITNAMI_IMAGE_TAG'"'"'; }; \
238239
while [ $$# -gt 0 ]; do \
239240
source="$${1}" target="$${1/e2e/e2e-generated}"; \
240241
mkdir -p "$${target%/*}"; render < "$${source}" > "$${target}"; \

cmd/postgres-operator/main.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import (
2020
"os"
2121
"strings"
2222

23+
"github.com/go-logr/logr"
2324
"go.opentelemetry.io/otel"
2425
"k8s.io/client-go/discovery"
2526
"k8s.io/client-go/rest"
2627
cruntime "sigs.k8s.io/controller-runtime"
2728
"sigs.k8s.io/controller-runtime/pkg/manager"
2829

2930
"github.com/crunchydata/postgres-operator/internal/bridge"
31+
"github.com/crunchydata/postgres-operator/internal/controller/pgupgrade"
3032
"github.com/crunchydata/postgres-operator/internal/controller/postgrescluster"
3133
"github.com/crunchydata/postgres-operator/internal/controller/runtime"
3234
"github.com/crunchydata/postgres-operator/internal/logging"
@@ -89,8 +91,7 @@ func main() {
8991
}
9092

9193
// add all PostgreSQL Operator controllers to the runtime manager
92-
err = addControllersToManager(mgr, openshift)
93-
assertNoError(err)
94+
addControllersToManager(mgr, openshift, log)
9495

9596
if util.DefaultMutableFeatureGate.Enabled(util.BridgeIdentifiers) {
9697
constructor := func() *bridge.Client {
@@ -121,15 +122,30 @@ func main() {
121122

122123
// addControllersToManager adds all PostgreSQL Operator controllers to the provided controller
123124
// runtime manager.
124-
func addControllersToManager(mgr manager.Manager, openshift bool) error {
125-
r := &postgrescluster.Reconciler{
125+
func addControllersToManager(mgr manager.Manager, openshift bool, log logr.Logger) {
126+
pgReconciler := &postgrescluster.Reconciler{
126127
Client: mgr.GetClient(),
127128
Owner: postgrescluster.ControllerName,
128129
Recorder: mgr.GetEventRecorderFor(postgrescluster.ControllerName),
129130
Tracer: otel.Tracer(postgrescluster.ControllerName),
130131
IsOpenShift: openshift,
131132
}
132-
return r.SetupWithManager(mgr)
133+
134+
if err := pgReconciler.SetupWithManager(mgr); err != nil {
135+
log.Error(err, "unable to create PostgresCluster controller")
136+
os.Exit(1)
137+
}
138+
139+
upgradeReconciler := &pgupgrade.PGUpgradeReconciler{
140+
Client: mgr.GetClient(),
141+
Owner: "pgupgrade-controller",
142+
Scheme: mgr.GetScheme(),
143+
}
144+
145+
if err := upgradeReconciler.SetupWithManager(mgr); err != nil {
146+
log.Error(err, "unable to create PGUpgrade controller")
147+
os.Exit(1)
148+
}
133149
}
134150

135151
func isOpenshift(cfg *rest.Config) bool {

config/crd/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ kind: Kustomization
33

44
resources:
55
- bases/postgres-operator.crunchydata.com_postgresclusters.yaml
6+
- bases/postgres-operator.crunchydata.com_pgupgrades.yaml

config/manager/manager.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ spec:
4444
value: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.17-5"
4545
- name: RELATED_IMAGE_PGEXPORTER
4646
value: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-5.3.0-0"
47+
- name: RELATED_IMAGE_PGUPGRADE
48+
value: "registry.developers.crunchydata.com/crunchydata/crunchy-upgrade:ubi8-5.3.0-0"
4749
securityContext:
4850
allowPrivilegeEscalation: false
4951
capabilities: { drop: [ALL] }

config/rbac/cluster/role.yaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,24 +102,34 @@ rules:
102102
- apiGroups:
103103
- postgres-operator.crunchydata.com
104104
resources:
105-
- postgresclusters
105+
- pgupgrades
106106
verbs:
107107
- get
108108
- list
109-
- patch
110109
- watch
111110
- apiGroups:
112111
- postgres-operator.crunchydata.com
113112
resources:
113+
- pgupgrades/finalizers
114114
- postgresclusters/finalizers
115115
verbs:
116116
- update
117117
- apiGroups:
118118
- postgres-operator.crunchydata.com
119119
resources:
120+
- pgupgrades/status
120121
- postgresclusters/status
121122
verbs:
122123
- patch
124+
- apiGroups:
125+
- postgres-operator.crunchydata.com
126+
resources:
127+
- postgresclusters
128+
verbs:
129+
- get
130+
- list
131+
- patch
132+
- watch
123133
- apiGroups:
124134
- rbac.authorization.k8s.io
125135
resources:

config/rbac/namespace/role.yaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,24 +102,34 @@ rules:
102102
- apiGroups:
103103
- postgres-operator.crunchydata.com
104104
resources:
105-
- postgresclusters
105+
- pgupgrades
106106
verbs:
107107
- get
108108
- list
109-
- patch
110109
- watch
111110
- apiGroups:
112111
- postgres-operator.crunchydata.com
113112
resources:
113+
- pgupgrades/finalizers
114114
- postgresclusters/finalizers
115115
verbs:
116116
- update
117117
- apiGroups:
118118
- postgres-operator.crunchydata.com
119119
resources:
120+
- pgupgrades/status
120121
- postgresclusters/status
121122
verbs:
122123
- patch
124+
- apiGroups:
125+
- postgres-operator.crunchydata.com
126+
resources:
127+
- postgresclusters
128+
verbs:
129+
- get
130+
- list
131+
- patch
132+
- watch
123133
- apiGroups:
124134
- rbac.authorization.k8s.io
125135
resources:
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright 2021 - 2022 Crunchy Data Solutions, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pgupgrade
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"reflect"
21+
"strings"
22+
23+
"k8s.io/apimachinery/pkg/types"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
)
26+
27+
// JSON6902 represents a JSON Patch according to RFC 6902; the same as
28+
// k8s.io/apimachinery/pkg/types.JSONPatchType.
29+
type JSON6902 []interface{}
30+
31+
// NewJSONPatch creates a new JSON Patch according to RFC 6902; the same as
32+
// k8s.io/apimachinery/pkg/types.JSONPatchType.
33+
func NewJSONPatch() *JSON6902 { return &JSON6902{} }
34+
35+
// escapeJSONPointer encodes '~' and '/' according to RFC 6901.
36+
var escapeJSONPointer = strings.NewReplacer(
37+
"~", "~0",
38+
"/", "~1",
39+
).Replace
40+
41+
func (*JSON6902) pointer(tokens ...string) string {
42+
var b strings.Builder
43+
44+
for _, t := range tokens {
45+
_ = b.WriteByte('/')
46+
_, _ = b.WriteString(escapeJSONPointer(t))
47+
}
48+
49+
return b.String()
50+
}
51+
52+
// Add appends an "add" operation to patch.
53+
//
54+
// > The "add" operation performs one of the following functions,
55+
// > depending upon what the target location references:
56+
// >
57+
// > o If the target location specifies an array index, a new value is
58+
// > inserted into the array at the specified index.
59+
// >
60+
// > o If the target location specifies an object member that does not
61+
// > already exist, a new member is added to the object.
62+
// >
63+
// > o If the target location specifies an object member that does exist,
64+
// > that member's value is replaced.
65+
//
66+
67+
func (patch *JSON6902) Add(path ...string) func(value interface{}) *JSON6902 {
68+
i := len(*patch)
69+
f := func(value interface{}) *JSON6902 {
70+
(*patch)[i] = map[string]interface{}{
71+
"op": "add",
72+
"path": patch.pointer(path...),
73+
"value": value,
74+
}
75+
return patch
76+
}
77+
78+
*patch = append(*patch, f)
79+
80+
return f
81+
}
82+
83+
// Remove appends a "remove" operation to patch.
84+
//
85+
// > The "remove" operation removes the value at the target location.
86+
// >
87+
// > The target location MUST exist for the operation to be successful.
88+
//
89+
90+
func (patch *JSON6902) Remove(path ...string) *JSON6902 {
91+
*patch = append(*patch, map[string]interface{}{
92+
"op": "remove",
93+
"path": patch.pointer(path...),
94+
})
95+
96+
return patch
97+
}
98+
99+
// Replace appends a "replace" operation to patch.
100+
//
101+
// > The "replace" operation replaces the value at the target location
102+
// > with a new value.
103+
// >
104+
// > The target location MUST exist for the operation to be successful.
105+
//
106+
107+
func (patch *JSON6902) Replace(path ...string) func(value interface{}) *JSON6902 {
108+
i := len(*patch)
109+
f := func(value interface{}) *JSON6902 {
110+
(*patch)[i] = map[string]interface{}{
111+
"op": "replace",
112+
"path": patch.pointer(path...),
113+
"value": value,
114+
}
115+
return patch
116+
}
117+
118+
*patch = append(*patch, f)
119+
120+
return f
121+
}
122+
123+
// Bytes returns the JSON representation of patch.
124+
func (patch JSON6902) Bytes() ([]byte, error) { return patch.Data(nil) }
125+
126+
// Data returns the JSON representation of patch.
127+
func (patch JSON6902) Data(client.Object) ([]byte, error) { return json.Marshal(patch) }
128+
129+
// IsEmpty returns true when patch has no operations.
130+
func (patch JSON6902) IsEmpty() bool { return len(patch) == 0 }
131+
132+
// Type returns k8s.io/apimachinery/pkg/types.JSONPatchType.
133+
func (patch JSON6902) Type() types.PatchType { return types.JSONPatchType }
134+
135+
// patch sends patch to object's endpoint in the Kubernetes API and updates
136+
// object with any returned content. The fieldManager is set to r.Owner, but
137+
// can be overridden in options.
138+
// - https://docs.k8s.io/reference/using-api/server-side-apply/#managers
139+
func (r *PGUpgradeReconciler) patch(
140+
ctx context.Context, object client.Object,
141+
patch client.Patch, options ...client.PatchOption,
142+
) error {
143+
options = append([]client.PatchOption{r.Owner}, options...)
144+
return r.Client.Patch(ctx, object, patch, options...)
145+
}
146+
147+
// apply sends an apply patch to object's endpoint in the Kubernetes API and
148+
// updates object with any returned content. The fieldManager is set to
149+
// r.Owner and the force parameter is true.
150+
// - https://docs.k8s.io/reference/using-api/server-side-apply/#managers
151+
// - https://docs.k8s.io/reference/using-api/server-side-apply/#conflicts
152+
func (r *PGUpgradeReconciler) apply(ctx context.Context, object client.Object) error {
153+
// Generate an apply-patch by comparing the object to its zero value.
154+
zero := reflect.New(reflect.TypeOf(object).Elem()).Interface()
155+
data, err := client.MergeFrom(zero.(client.Object)).Data(object)
156+
apply := client.RawPatch(client.Apply.Type(), data)
157+
158+
// Send the apply-patch with force=true.
159+
if err == nil {
160+
err = r.patch(ctx, object, apply, client.ForceOwnership)
161+
}
162+
163+
return err
164+
}

0 commit comments

Comments
 (0)