Skip to content

Commit

Permalink
Merge changes in 2.3.0_release (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
powerfooI authored Oct 17, 2024
1 parent 05aa122 commit 4c56b70
Show file tree
Hide file tree
Showing 49 changed files with 629 additions and 211 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: run tests

on:
pull_request:
branches:
- master
- "*_release"
paths:
- '**/*.go'
push:
branches:
- master
- "*_release"
paths:
- '**/*.go'

env:
GO_VERSION: "1.22"

jobs:
run-tests:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Go 1.x
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}

- name: go mod vendor
run: go mod vendor

- name: install tools
run: make tools

- name: test webhooks
run: make test-webhooks

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
include make/*

VERSION ?= 2.2.2
VERSION ?= 2.3.0
# Image URL to use all building/pushing image targets
IMG ?= quay.io/oceanbase/ob-operator:${VERSION}
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
Expand Down
8 changes: 4 additions & 4 deletions README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ ob-operator 是满足 Kubernetes Operator 扩展范式的自动化工具,可
ob-operator 依赖 [cert-manager](https://cert-manager.io/docs/), cert-manager 的安装可以参考对应的[安装文档](https://cert-manager.io/docs/installation/),如果您无法访问官方制品托管在 `quay.io` 镜像站的镜像,可通过下面的指令安装我们转托在 `docker.io` 中的制品:

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

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

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

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

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

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

一般初始化集群需要 2 分钟左右的时间,执行以下命令,查询集群状态,当集群状态变成 running 之后表示集群创建和初始化成功:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ob-operator relies on [cert-manager](https://cert-manager.io/docs/) for certific
If you have trouble accessing `quay.io` image registry, our mirrored cert-manager manifests can be applied through following command:

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

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).
Expand All @@ -30,7 +30,7 @@ You can deploy ob-operator in a Kubernetes cluster by executing the following co
- Stable

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

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

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

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

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:
Expand Down
4 changes: 3 additions & 1 deletion api/v1alpha1/obcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,7 @@ func init() {
}

func (c *OBCluster) SupportStaticIP() bool {
return c.Annotations[oceanbaseconst.AnnotationsSupportStaticIP] == "true"
return c.Annotations[oceanbaseconst.AnnotationsSupportStaticIP] == "true" ||
c.Annotations[oceanbaseconst.AnnotationsMode] == oceanbaseconst.ModeService ||
c.Annotations[oceanbaseconst.AnnotationsMode] == oceanbaseconst.ModeStandalone
}
11 changes: 7 additions & 4 deletions api/v1alpha1/obcluster_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ var _ = Describe("Test OBCluster Webhook", Label("webhook"), func() {

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

cluster3 := newOBCluster("test3", 1, 1)
cluster2.Spec.UserSecrets.Monitor = wrongKeySecret
Expect(k8sClient.Create(ctx, cluster)).ShouldNot(Succeed())
Expect(k8sClient.Create(ctx, cluster3)).ShouldNot(Succeed())

Expect(k8sClient.Delete(ctx, cluster)).Should(Succeed())
Expect(k8sClient.Delete(ctx, cluster2)).Should(Succeed())
})

It("Validate secrets creation and fetch them", func() {
By("Create normal cluster")
cluster := newOBCluster("test", 1, 1)
cluster := newOBCluster("test-create-secrets", 1, 1)
cluster.Spec.UserSecrets.Monitor = ""
cluster.Spec.UserSecrets.ProxyRO = ""
cluster.Spec.UserSecrets.Operator = ""
Expand All @@ -178,6 +180,7 @@ var _ = Describe("Test OBCluster Webhook", Label("webhook"), func() {
Expect(cluster.Spec.UserSecrets.Monitor).ShouldNot(BeEmpty())
Expect(cluster.Spec.UserSecrets.ProxyRO).ShouldNot(BeEmpty())
Expect(cluster.Spec.UserSecrets.Operator).ShouldNot(BeEmpty())
Expect(k8sClient.Delete(ctx, cluster)).Should(Succeed())
})

It("Validate single pvc with multiple storage classes", func() {
Expand Down
122 changes: 73 additions & 49 deletions api/v1alpha1/obtenant_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package v1alpha1

import (
"context"
"errors"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -291,55 +292,13 @@ func (r *OBTenant) validateMutation() error {

if res.ArchiveSource == nil && res.BakDataSource == nil && res.SourceUri == "" {
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"))
}

if res.ArchiveSource != nil && res.ArchiveSource.Type == constants.BackupDestTypeOSS {
if res.ArchiveSource.OSSAccessSecret == "" {
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"))
} else {
secret := &v1.Secret{}
err := tenantClt.Get(context.Background(), types.NamespacedName{
Namespace: r.GetNamespace(),
Name: res.ArchiveSource.OSSAccessSecret,
}, secret)
if err != nil {
if apierrors.IsNotFound(err) {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), res.ArchiveSource.OSSAccessSecret, "Given OSSAccessSecret not found"))
}
allErrs = append(allErrs, field.InternalError(field.NewPath("spec").Child("source").Child("restore").Child("archiveSource").Child("ossAccessSecret"), err))
} else {
if _, ok := secret.Data["accessId"]; !ok {
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"))
}
if _, ok := secret.Data["accessKey"]; !ok {
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"))
}
}
}
}

if res.BakDataSource != nil && res.BakDataSource.Type == constants.BackupDestTypeOSS {
if res.BakDataSource.OSSAccessSecret == "" {
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"))
} else {
secret := &v1.Secret{}
err := tenantClt.Get(context.Background(), types.NamespacedName{
Namespace: r.GetNamespace(),
Name: res.BakDataSource.OSSAccessSecret,
}, secret)
if err != nil {
if apierrors.IsNotFound(err) {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), res.BakDataSource.OSSAccessSecret, "Given OSSAccessSecret not found"))
}
allErrs = append(allErrs, field.InternalError(field.NewPath("spec").Child("source").Child("restore").Child("bakDataSource").Child("ossAccessSecret"), err))
} else {
if _, ok := secret.Data["accessId"]; !ok {
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"))
}
if _, ok := secret.Data["accessKey"]; !ok {
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"))
}
}
} else {
destErrs := errors.Join(
validateBackupDestination(cluster, res.ArchiveSource, "spec", "source", "restore", "archiveSource"),
validateBackupDestination(cluster, res.BakDataSource, "spec", "source", "restore", "bakDataSource"),
)
if destErrs != nil {
return destErrs
}
}
}
Expand All @@ -355,3 +314,68 @@ func (r *OBTenant) ValidateDelete() (admission.Warnings, error) {
// TODO(user): fill in your validation logic upon object deletion.
return nil, nil
}

func validateBackupDestination(cluster *OBCluster, dest *apitypes.BackupDestination, paths ...string) error {
var errorPath *field.Path
if len(paths) == 0 {
errorPath = field.NewPath("spec").Child("destination")
} else {
errorPath = field.NewPath("spec").Child(paths[0])
for _, p := range paths[1:] {
errorPath = errorPath.Child(p)
}
}
if dest.Type == constants.BackupDestTypeNFS && cluster.Spec.BackupVolume == nil {
return field.Invalid(errorPath, cluster.Spec.BackupVolume, "backupVolume of obcluster is required when backing up data to NFS")
}
pattern, ok := constants.DestPathPatternMapping[dest.Type]
if !ok {
return field.Invalid(errorPath.Child("destination").Child("type"), dest.Type, "invalid backup destination type")
}
if !pattern.MatchString(dest.Path) {
return field.Invalid(errorPath.Child("destination").Child("path"), dest.Path, "invalid backup destination path, the path format should be "+pattern.String())
}
if dest.Type != constants.BackupDestTypeNFS {
if dest.OSSAccessSecret == "" {
return field.Invalid(errorPath.Child("destination"), dest.OSSAccessSecret, "OSSAccessSecret is required when backing up data to OSS, COS or S3")
}
secret := &v1.Secret{}
err := bakClt.Get(context.Background(), types.NamespacedName{
Namespace: cluster.GetNamespace(),
Name: dest.OSSAccessSecret,
}, secret)
fieldPath := errorPath.Child("destination").Child("ossAccessSecret")
if err != nil {
if apierrors.IsNotFound(err) {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "Given OSSAccessSecret not found")
}
return field.InternalError(fieldPath, err)
}
// All the following types need accessId and accessKey
switch dest.Type {
case
constants.BackupDestTypeCOS,
constants.BackupDestTypeOSS,
constants.BackupDestTypeS3,
constants.BackupDestTypeS3Compatible:
if _, ok := secret.Data["accessId"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessId field not found in given OSSAccessSecret")
}
if _, ok := secret.Data["accessKey"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret")
}
}
// The following types need additional fields
switch dest.Type {
case constants.BackupDestTypeCOS:
if _, ok := secret.Data["appId"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "appId field not found in given OSSAccessSecret")
}
case constants.BackupDestTypeS3:
if _, ok := secret.Data["s3Region"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "s3Region field not found in given OSSAccessSecret")
}
}
}
return nil
}
72 changes: 37 additions & 35 deletions api/v1alpha1/obtenantbackuppolicy_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,44 +304,46 @@ func (r *OBTenantBackupPolicy) validateDestination(cluster *OBCluster, dest *api
if !pattern.MatchString(dest.Path) {
return field.Invalid(field.NewPath("spec").Child(fieldName).Child("destination"), dest.Path, "invalid backup destination path, the path format should be "+pattern.String())
}
if dest.Type != constants.BackupDestTypeNFS && dest.OSSAccessSecret == "" {
return field.Invalid(field.NewPath("spec").Child(fieldName).Child("destination"), dest.OSSAccessSecret, "OSSAccessSecret is required when backing up data to OSS, COS or S3")
}
secret := &v1.Secret{}
err := bakClt.Get(context.Background(), types.NamespacedName{
Namespace: r.GetNamespace(),
Name: dest.OSSAccessSecret,
}, secret)
fieldPath := field.NewPath("spec").Child(fieldName).Child("destination").Child("ossAccessSecret")
if err != nil {
if apierrors.IsNotFound(err) {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "Given OSSAccessSecret not found")
}
return field.InternalError(fieldPath, err)
}
// All the following types need accessId and accessKey
switch dest.Type {
case
constants.BackupDestTypeCOS,
constants.BackupDestTypeOSS,
constants.BackupDestTypeS3,
constants.BackupDestTypeS3Compatible:
if _, ok := secret.Data["accessId"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessId field not found in given OSSAccessSecret")
if dest.Type != constants.BackupDestTypeNFS {
if dest.OSSAccessSecret == "" {
return field.Invalid(field.NewPath("spec").Child(fieldName).Child("destination"), dest.OSSAccessSecret, "OSSAccessSecret is required when backing up data to OSS, COS or S3")
}
if _, ok := secret.Data["accessKey"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret")
secret := &v1.Secret{}
err := bakClt.Get(context.Background(), types.NamespacedName{
Namespace: r.GetNamespace(),
Name: dest.OSSAccessSecret,
}, secret)
fieldPath := field.NewPath("spec").Child(fieldName).Child("destination").Child("ossAccessSecret")
if err != nil {
if apierrors.IsNotFound(err) {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "Given OSSAccessSecret not found")
}
return field.InternalError(fieldPath, err)
}
}
// The following types need additional fields
switch dest.Type {
case constants.BackupDestTypeCOS:
if _, ok := secret.Data["appId"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "appId field not found in given OSSAccessSecret")
// All the following types need accessId and accessKey
switch dest.Type {
case
constants.BackupDestTypeCOS,
constants.BackupDestTypeOSS,
constants.BackupDestTypeS3,
constants.BackupDestTypeS3Compatible:
if _, ok := secret.Data["accessId"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessId field not found in given OSSAccessSecret")
}
if _, ok := secret.Data["accessKey"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "accessKey field not found in given OSSAccessSecret")
}
}
case constants.BackupDestTypeS3:
if _, ok := secret.Data["s3Region"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "s3Region field not found in given OSSAccessSecret")
// The following types need additional fields
switch dest.Type {
case constants.BackupDestTypeCOS:
if _, ok := secret.Data["appId"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "appId field not found in given OSSAccessSecret")
}
case constants.BackupDestTypeS3:
if _, ok := secret.Data["s3Region"]; !ok {
return field.Invalid(fieldPath, dest.OSSAccessSecret, "s3Region field not found in given OSSAccessSecret")
}
}
}
return nil
Expand Down
Loading

0 comments on commit 4c56b70

Please sign in to comment.