Skip to content

Commit 4c56b70

Browse files
authored
Merge changes in 2.3.0_release (#585)
1 parent 05aa122 commit 4c56b70

File tree

49 files changed

+629
-211
lines changed

Some content is hidden

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

49 files changed

+629
-211
lines changed

.github/workflows/ci-tests.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: run tests
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
- "*_release"
8+
paths:
9+
- '**/*.go'
10+
push:
11+
branches:
12+
- master
13+
- "*_release"
14+
paths:
15+
- '**/*.go'
16+
17+
env:
18+
GO_VERSION: "1.22"
19+
20+
jobs:
21+
run-tests:
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@v3
27+
28+
- name: Set up Go 1.x
29+
uses: actions/setup-go@v3
30+
with:
31+
go-version: ${{ env.GO_VERSION }}
32+
33+
- name: go mod vendor
34+
run: go mod vendor
35+
36+
- name: install tools
37+
run: make tools
38+
39+
- name: test webhooks
40+
run: make test-webhooks
41+

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
include make/*
22

3-
VERSION ?= 2.2.2
3+
VERSION ?= 2.3.0
44
# Image URL to use all building/pushing image targets
55
IMG ?= quay.io/oceanbase/ob-operator:${VERSION}
66
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.

README-CN.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ ob-operator 是满足 Kubernetes Operator 扩展范式的自动化工具,可
1515
ob-operator 依赖 [cert-manager](https://cert-manager.io/docs/), cert-manager 的安装可以参考对应的[安装文档](https://cert-manager.io/docs/installation/),如果您无法访问官方制品托管在 `quay.io` 镜像站的镜像,可通过下面的指令安装我们转托在 `docker.io` 中的制品:
1616

1717
```shell
18-
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_release/deploy/cert-manager.yaml
18+
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.3.0_release/deploy/cert-manager.yaml
1919
```
2020

2121
本例子中的 OceanBase 集群存储依赖 [local-path-provisioner](https://github.com/rancher/local-path-provisioner) 提供, 需要提前进行安装并确保其存储目的地有足够大的磁盘空间。如果您计划在生产环境部署,推荐使用其他的存储解决方案。我们在[存储兼容性](#存储兼容性)一节提供了我们测试过的存储兼容性结果。
@@ -29,7 +29,7 @@ kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_r
2929
- 稳定版本
3030

3131
```shell
32-
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_release/deploy/operator.yaml
32+
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.3.0_release/deploy/operator.yaml
3333
```
3434

3535
- 开发版本
@@ -45,7 +45,7 @@ Helm Chart 将 ob-operator 部署的命名空间进行了参数化,可在安
4545
```shell
4646
helm repo add ob-operator https://oceanbase.github.io/ob-operator/
4747
helm repo update
48-
helm install ob-operator ob-operator/ob-operator --namespace=oceanbase-system --create-namespace --version=2.2.2
48+
helm install ob-operator ob-operator/ob-operator --namespace=oceanbase-system --create-namespace --version=2.3.0
4949
```
5050

5151
#### 使用 terraform
@@ -97,7 +97,7 @@ kubectl create secret generic root-password --from-literal=password='root_passwo
9797
通过以下命令即可在 K8s 集群中部署 OceanBase:
9898

9999
```shell
100-
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_release/example/quickstart/obcluster.yaml
100+
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.3.0_release/example/quickstart/obcluster.yaml
101101
```
102102

103103
一般初始化集群需要 2 分钟左右的时间,执行以下命令,查询集群状态,当集群状态变成 running 之后表示集群创建和初始化成功:

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ob-operator relies on [cert-manager](https://cert-manager.io/docs/) for certific
1616
If you have trouble accessing `quay.io` image registry, our mirrored cert-manager manifests can be applied through following command:
1717

1818
```shell
19-
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_release/deploy/cert-manager.yaml
19+
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.3.0_release/deploy/cert-manager.yaml
2020
```
2121

2222
Storage of OceanBase cluster in this example relies on [local-path-provisioner](https://github.com/rancher/local-path-provisioner), which should be installed beforehand. You should confirm that there is enough disk space in storage destination of local-path-provisioner. If you decide to deploy OceanBase cluster in production environment, it is recommended to use other storage solutions. We have provided a compatible table for storage solutions that we tested in section [Storage Compatibility](#storage-compatibility).
@@ -30,7 +30,7 @@ You can deploy ob-operator in a Kubernetes cluster by executing the following co
3030
- Stable
3131

3232
```shell
33-
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_release/deploy/operator.yaml
33+
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.3.0_release/deploy/operator.yaml
3434
```
3535

3636
- Development
@@ -46,7 +46,7 @@ Helm Chart parameterizes the namespace in which ob-operator is deployed, allowin
4646
```shell
4747
helm repo add ob-operator https://oceanbase.github.io/ob-operator/
4848
helm repo update
49-
helm install ob-operator ob-operator/ob-operator --namespace=oceanbase-system --create-namespace --version=2.2.2
49+
helm install ob-operator ob-operator/ob-operator --namespace=oceanbase-system --create-namespace --version=2.3.0
5050
```
5151

5252
#### Using terraform
@@ -98,7 +98,7 @@ kubectl create secret generic root-password --from-literal=password='root_passwo
9898
You can deploy OceanBase in a Kubernetes cluster by executing the following command:
9999

100100
```shell
101-
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.2.2_release/example/quickstart/obcluster.yaml
101+
kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.3.0_release/example/quickstart/obcluster.yaml
102102
```
103103

104104
It generally takes around 2 minutes to bootstrap a cluster. Execute the following command to check the status of the cluster. Once the cluster status changes to "running," it indicates that the cluster has been successfully created and bootstrapped:

api/v1alpha1/obcluster_types.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,7 @@ func init() {
8888
}
8989

9090
func (c *OBCluster) SupportStaticIP() bool {
91-
return c.Annotations[oceanbaseconst.AnnotationsSupportStaticIP] == "true"
91+
return c.Annotations[oceanbaseconst.AnnotationsSupportStaticIP] == "true" ||
92+
c.Annotations[oceanbaseconst.AnnotationsMode] == oceanbaseconst.ModeService ||
93+
c.Annotations[oceanbaseconst.AnnotationsMode] == oceanbaseconst.ModeStandalone
9294
}

api/v1alpha1/obcluster_webhook_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ var _ = Describe("Test OBCluster Webhook", Label("webhook"), func() {
148148

149149
It("Validate existence of secrets", func() {
150150
By("Create normal cluster")
151-
cluster := newOBCluster("test", 1, 1)
151+
cluster := newOBCluster("test3", 1, 1)
152152
cluster.Spec.UserSecrets.Monitor = ""
153153
cluster.Spec.UserSecrets.ProxyRO = ""
154154
cluster.Spec.UserSecrets.Operator = ""
@@ -158,17 +158,19 @@ var _ = Describe("Test OBCluster Webhook", Label("webhook"), func() {
158158
cluster2.Spec.UserSecrets.Monitor = "secret-that-does-not-exist"
159159
cluster2.Spec.UserSecrets.ProxyRO = ""
160160
cluster2.Spec.UserSecrets.Operator = ""
161-
Expect(k8sClient.Create(ctx, cluster)).ShouldNot(Succeed())
161+
Expect(k8sClient.Create(ctx, cluster2)).Should(Succeed())
162162

163+
cluster3 := newOBCluster("test3", 1, 1)
163164
cluster2.Spec.UserSecrets.Monitor = wrongKeySecret
164-
Expect(k8sClient.Create(ctx, cluster)).ShouldNot(Succeed())
165+
Expect(k8sClient.Create(ctx, cluster3)).ShouldNot(Succeed())
165166

166167
Expect(k8sClient.Delete(ctx, cluster)).Should(Succeed())
168+
Expect(k8sClient.Delete(ctx, cluster2)).Should(Succeed())
167169
})
168170

169171
It("Validate secrets creation and fetch them", func() {
170172
By("Create normal cluster")
171-
cluster := newOBCluster("test", 1, 1)
173+
cluster := newOBCluster("test-create-secrets", 1, 1)
172174
cluster.Spec.UserSecrets.Monitor = ""
173175
cluster.Spec.UserSecrets.ProxyRO = ""
174176
cluster.Spec.UserSecrets.Operator = ""
@@ -178,6 +180,7 @@ var _ = Describe("Test OBCluster Webhook", Label("webhook"), func() {
178180
Expect(cluster.Spec.UserSecrets.Monitor).ShouldNot(BeEmpty())
179181
Expect(cluster.Spec.UserSecrets.ProxyRO).ShouldNot(BeEmpty())
180182
Expect(cluster.Spec.UserSecrets.Operator).ShouldNot(BeEmpty())
183+
Expect(k8sClient.Delete(ctx, cluster)).Should(Succeed())
181184
})
182185

183186
It("Validate single pvc with multiple storage classes", func() {

api/v1alpha1/obtenant_webhook.go

Lines changed: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v1alpha1
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"regexp"
2324
"strings"
@@ -291,55 +292,13 @@ func (r *OBTenant) validateMutation() error {
291292

292293
if res.ArchiveSource == nil && res.BakDataSource == nil && res.SourceUri == "" {
293294
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore"), res, "Restore must have a source option, but both archiveSource, bakDataSource and sourceUri are nil now"))
294-
}
295-
296-
if res.ArchiveSource != nil && res.ArchiveSource.Type == constants.BackupDestTypeOSS {
297-
if res.ArchiveSource.OSSAccessSecret == "" {
298-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), res.ArchiveSource.OSSAccessSecret, "Tenant restoring from OSS type backup data must have a OSSAccessSecret"))
299-
} else {
300-
secret := &v1.Secret{}
301-
err := tenantClt.Get(context.Background(), types.NamespacedName{
302-
Namespace: r.GetNamespace(),
303-
Name: res.ArchiveSource.OSSAccessSecret,
304-
}, secret)
305-
if err != nil {
306-
if apierrors.IsNotFound(err) {
307-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), res.ArchiveSource.OSSAccessSecret, "Given OSSAccessSecret not found"))
308-
}
309-
allErrs = append(allErrs, field.InternalError(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), err))
310-
} else {
311-
if _, ok := secret.Data["accessId"]; !ok {
312-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), res.ArchiveSource.OSSAccessSecret, "accessId field not found in given OSSAccessSecret"))
313-
}
314-
if _, ok := secret.Data["accessKey"]; !ok {
315-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), res.ArchiveSource.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret"))
316-
}
317-
}
318-
}
319-
}
320-
321-
if res.BakDataSource != nil && res.BakDataSource.Type == constants.BackupDestTypeOSS {
322-
if res.BakDataSource.OSSAccessSecret == "" {
323-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), res.BakDataSource.OSSAccessSecret, "Tenant restoring from OSS type backup data must have a OSSAccessSecret"))
324-
} else {
325-
secret := &v1.Secret{}
326-
err := tenantClt.Get(context.Background(), types.NamespacedName{
327-
Namespace: r.GetNamespace(),
328-
Name: res.BakDataSource.OSSAccessSecret,
329-
}, secret)
330-
if err != nil {
331-
if apierrors.IsNotFound(err) {
332-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), res.BakDataSource.OSSAccessSecret, "Given OSSAccessSecret not found"))
333-
}
334-
allErrs = append(allErrs, field.InternalError(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), err))
335-
} else {
336-
if _, ok := secret.Data["accessId"]; !ok {
337-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), res.BakDataSource.OSSAccessSecret, "accessId field not found in given OSSAccessSecret"))
338-
}
339-
if _, ok := secret.Data["accessKey"]; !ok {
340-
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), res.BakDataSource.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret"))
341-
}
342-
}
295+
} else {
296+
destErrs := errors.Join(
297+
validateBackupDestination(cluster, res.ArchiveSource, "spec", "source", "restore", "archiveSource"),
298+
validateBackupDestination(cluster, res.BakDataSource, "spec", "source", "restore", "bakDataSource"),
299+
)
300+
if destErrs != nil {
301+
return destErrs
343302
}
344303
}
345304
}
@@ -355,3 +314,68 @@ func (r *OBTenant) ValidateDelete() (admission.Warnings, error) {
355314
// TODO(user): fill in your validation logic upon object deletion.
356315
return nil, nil
357316
}
317+
318+
func validateBackupDestination(cluster *OBCluster, dest *apitypes.BackupDestination, paths ...string) error {
319+
var errorPath *field.Path
320+
if len(paths) == 0 {
321+
errorPath = field.NewPath("spec").Child("destination")
322+
} else {
323+
errorPath = field.NewPath("spec").Child(paths[0])
324+
for _, p := range paths[1:] {
325+
errorPath = errorPath.Child(p)
326+
}
327+
}
328+
if dest.Type == constants.BackupDestTypeNFS && cluster.Spec.BackupVolume == nil {
329+
return field.Invalid(errorPath, cluster.Spec.BackupVolume, "backupVolume of obcluster is required when backing up data to NFS")
330+
}
331+
pattern, ok := constants.DestPathPatternMapping[dest.Type]
332+
if !ok {
333+
return field.Invalid(errorPath.Child("destination").Child("type"), dest.Type, "invalid backup destination type")
334+
}
335+
if !pattern.MatchString(dest.Path) {
336+
return field.Invalid(errorPath.Child("destination").Child("path"), dest.Path, "invalid backup destination path, the path format should be "+pattern.String())
337+
}
338+
if dest.Type != constants.BackupDestTypeNFS {
339+
if dest.OSSAccessSecret == "" {
340+
return field.Invalid(errorPath.Child("destination"), dest.OSSAccessSecret, "OSSAccessSecret is required when backing up data to OSS, COS or S3")
341+
}
342+
secret := &v1.Secret{}
343+
err := bakClt.Get(context.Background(), types.NamespacedName{
344+
Namespace: cluster.GetNamespace(),
345+
Name: dest.OSSAccessSecret,
346+
}, secret)
347+
fieldPath := errorPath.Child("destination").Child("ossAccessSecret")
348+
if err != nil {
349+
if apierrors.IsNotFound(err) {
350+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "Given OSSAccessSecret not found")
351+
}
352+
return field.InternalError(fieldPath, err)
353+
}
354+
// All the following types need accessId and accessKey
355+
switch dest.Type {
356+
case
357+
constants.BackupDestTypeCOS,
358+
constants.BackupDestTypeOSS,
359+
constants.BackupDestTypeS3,
360+
constants.BackupDestTypeS3Compatible:
361+
if _, ok := secret.Data["accessId"]; !ok {
362+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessId field not found in given OSSAccessSecret")
363+
}
364+
if _, ok := secret.Data["accessKey"]; !ok {
365+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret")
366+
}
367+
}
368+
// The following types need additional fields
369+
switch dest.Type {
370+
case constants.BackupDestTypeCOS:
371+
if _, ok := secret.Data["appId"]; !ok {
372+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "appId field not found in given OSSAccessSecret")
373+
}
374+
case constants.BackupDestTypeS3:
375+
if _, ok := secret.Data["s3Region"]; !ok {
376+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "s3Region field not found in given OSSAccessSecret")
377+
}
378+
}
379+
}
380+
return nil
381+
}

api/v1alpha1/obtenantbackuppolicy_webhook.go

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -304,44 +304,46 @@ func (r *OBTenantBackupPolicy) validateDestination(cluster *OBCluster, dest *api
304304
if !pattern.MatchString(dest.Path) {
305305
return field.Invalid(field.NewPath("spec").Child(fieldName).Child("destination"), dest.Path, "invalid backup destination path, the path format should be "+pattern.String())
306306
}
307-
if dest.Type != constants.BackupDestTypeNFS && dest.OSSAccessSecret == "" {
308-
return field.Invalid(field.NewPath("spec").Child(fieldName).Child("destination"), dest.OSSAccessSecret, "OSSAccessSecret is required when backing up data to OSS, COS or S3")
309-
}
310-
secret := &v1.Secret{}
311-
err := bakClt.Get(context.Background(), types.NamespacedName{
312-
Namespace: r.GetNamespace(),
313-
Name: dest.OSSAccessSecret,
314-
}, secret)
315-
fieldPath := field.NewPath("spec").Child(fieldName).Child("destination").Child("ossAccessSecret")
316-
if err != nil {
317-
if apierrors.IsNotFound(err) {
318-
return field.Invalid(fieldPath, dest.OSSAccessSecret, "Given OSSAccessSecret not found")
319-
}
320-
return field.InternalError(fieldPath, err)
321-
}
322-
// All the following types need accessId and accessKey
323-
switch dest.Type {
324-
case
325-
constants.BackupDestTypeCOS,
326-
constants.BackupDestTypeOSS,
327-
constants.BackupDestTypeS3,
328-
constants.BackupDestTypeS3Compatible:
329-
if _, ok := secret.Data["accessId"]; !ok {
330-
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessId field not found in given OSSAccessSecret")
307+
if dest.Type != constants.BackupDestTypeNFS {
308+
if dest.OSSAccessSecret == "" {
309+
return field.Invalid(field.NewPath("spec").Child(fieldName).Child("destination"), dest.OSSAccessSecret, "OSSAccessSecret is required when backing up data to OSS, COS or S3")
331310
}
332-
if _, ok := secret.Data["accessKey"]; !ok {
333-
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret")
311+
secret := &v1.Secret{}
312+
err := bakClt.Get(context.Background(), types.NamespacedName{
313+
Namespace: r.GetNamespace(),
314+
Name: dest.OSSAccessSecret,
315+
}, secret)
316+
fieldPath := field.NewPath("spec").Child(fieldName).Child("destination").Child("ossAccessSecret")
317+
if err != nil {
318+
if apierrors.IsNotFound(err) {
319+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "Given OSSAccessSecret not found")
320+
}
321+
return field.InternalError(fieldPath, err)
334322
}
335-
}
336-
// The following types need additional fields
337-
switch dest.Type {
338-
case constants.BackupDestTypeCOS:
339-
if _, ok := secret.Data["appId"]; !ok {
340-
return field.Invalid(fieldPath, dest.OSSAccessSecret, "appId field not found in given OSSAccessSecret")
323+
// All the following types need accessId and accessKey
324+
switch dest.Type {
325+
case
326+
constants.BackupDestTypeCOS,
327+
constants.BackupDestTypeOSS,
328+
constants.BackupDestTypeS3,
329+
constants.BackupDestTypeS3Compatible:
330+
if _, ok := secret.Data["accessId"]; !ok {
331+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessId field not found in given OSSAccessSecret")
332+
}
333+
if _, ok := secret.Data["accessKey"]; !ok {
334+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret")
335+
}
341336
}
342-
case constants.BackupDestTypeS3:
343-
if _, ok := secret.Data["s3Region"]; !ok {
344-
return field.Invalid(fieldPath, dest.OSSAccessSecret, "s3Region field not found in given OSSAccessSecret")
337+
// The following types need additional fields
338+
switch dest.Type {
339+
case constants.BackupDestTypeCOS:
340+
if _, ok := secret.Data["appId"]; !ok {
341+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "appId field not found in given OSSAccessSecret")
342+
}
343+
case constants.BackupDestTypeS3:
344+
if _, ok := secret.Data["s3Region"]; !ok {
345+
return field.Invalid(fieldPath, dest.OSSAccessSecret, "s3Region field not found in given OSSAccessSecret")
346+
}
345347
}
346348
}
347349
return nil

0 commit comments

Comments
 (0)