Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apis/dataprotection/v1alpha1/restore_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ type BackupRef struct {
// +kubebuilder:validation:Required
Name string `json:"name"`

// Specifies the backup namespace.
// Specifies the backup namespace. When this differs from the Restore namespace,
// a Gateway API ReferenceGrant in the backup namespace must allow Restores from
// the Restore namespace to reference the Backup. Cross-namespace VolumeSnapshot
// restores are not supported.
//
// +kubebuilder:validation:Required
Namespace string `json:"namespace"`
Expand Down
12 changes: 11 additions & 1 deletion apis/operations/v1alpha1/opsrequest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,11 +513,21 @@ type FromBackup struct {
// +kubebuilder:validation:Required
Name string `json:"name"`

// Specifies the namespace of the Backup namespace.
// Specifies the namespace of the Backup.
// If not specified, the namespace of the OpsRequest will be used.
// Cross-namespace references require a Gateway API ReferenceGrant in the Backup
// namespace that allows Restores from the OpsRequest namespace to reference the
// Backup. Cross-namespace VolumeSnapshot restores are not supported.
// +optional
Namespace string `json:"namespace,omitempty"`

// Specifies the backup source target name to restore from.
// This field is required when the referenced Backup has multiple source targets.
// It is propagated to Restore.spec.backup.sourceTargetName.
//
// +optional
SourceTargetName string `json:"sourceTargetName,omitempty"`

// Defines container environment variables for the restore process.
// merged with the ones specified in the Backup and ActionSet resources.
//
Expand Down
2 changes: 2 additions & 0 deletions cmd/dataprotection/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

// +kubebuilder:scaffold:imports

Expand Down Expand Up @@ -98,6 +99,7 @@ func init() {
utilruntime.Must(dpv1alpha1.AddToScheme(scheme))
utilruntime.Must(snapshotv1.AddToScheme(scheme))
utilruntime.Must(snapshotv1beta1.AddToScheme(scheme))
utilruntime.Must(gatewayv1beta1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme

viper.SetConfigName("config") // name of config file (without extension)
Expand Down
2 changes: 2 additions & 0 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

// +kubebuilder:scaffold:imports

Expand Down Expand Up @@ -122,6 +123,7 @@ func init() {
utilruntime.Must(dpv1alpha1.AddToScheme(scheme))
utilruntime.Must(snapshotv1.AddToScheme(scheme))
utilruntime.Must(snapshotv1beta1.AddToScheme(scheme))
utilruntime.Must(gatewayv1beta1.AddToScheme(scheme))
utilruntime.Must(extensionsv1alpha1.AddToScheme(scheme))
utilruntime.Must(workloadsv1.AddToScheme(scheme))
utilruntime.Must(experimentalv1alpha1.AddToScheme(scheme))
Expand Down
6 changes: 5 additions & 1 deletion config/crd/bases/dataprotection.kubeblocks.io_restores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ spec:
description: Specifies the backup name.
type: string
namespace:
description: Specifies the backup namespace.
description: |-
Specifies the backup namespace. When this differs from the Restore namespace,
a Gateway API ReferenceGrant in the backup namespace must allow Restores from
the Restore namespace to reference the Backup. Cross-namespace VolumeSnapshot
restores are not supported.
type: string
sourceTargetName:
description: Specifies the source target for restoration, identified
Expand Down
11 changes: 10 additions & 1 deletion config/crd/bases/operations.kubeblocks.io_opsrequests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -619,8 +619,11 @@ spec:
type: string
namespace:
description: |-
Specifies the namespace of the Backup namespace.
Specifies the namespace of the Backup.
If not specified, the namespace of the OpsRequest will be used.
Cross-namespace references require a Gateway API ReferenceGrant in the Backup
namespace that allows Restores from the OpsRequest namespace to reference the
Backup. Cross-namespace VolumeSnapshot restores are not supported.
type: string
restoreEnv:
description: |-
Expand Down Expand Up @@ -757,6 +760,12 @@ spec:
- RFC3339 format, e.g. "2023-11-25T18:52:53Z"
- A human-readable date-time format, e.g. "Jul 25,2023 18:52:53 UTC+0800"
type: string
sourceTargetName:
description: |-
Specifies the backup source target name to restore from.
This field is required when the referenced Backup has multiple source targets.
It is propagated to Restore.spec.backup.sourceTargetName.
type: string
required:
- name
type: object
Expand Down
8 changes: 8 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@ rules:
- get
- patch
- update
- apiGroups:
- gateway.networking.k8s.io
resources:
- referencegrants
verbs:
- get
- list
- watch
- apiGroups:
- operations.kubeblocks.io
resources:
Expand Down
5 changes: 5 additions & 0 deletions controllers/dataprotection/backuprepo_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,11 @@ new-item=new-value
backup := createBackupSpec(func(backup *dpv1alpha1.Backup) {
backup.Namespace = testCtx.DefaultNamespace
})
grant := newBackupReferenceGrant("allow-backup-repo-restore", backup.Namespace, namespace2, backup.Name)
Expect(k8sClient.Create(ctx, grant)).Should(Succeed())
DeferCleanup(func() {
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, grant))).Should(Succeed())
})
By("creating a Restore object in the namespace")
createRestoreSpec(func(restore *dpv1alpha1.Restore) {
restore.Namespace = namespace2
Expand Down
4 changes: 4 additions & 0 deletions controllers/dataprotection/restore_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type RestoreReconciler struct {
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants,verbs=get;list;watch

func (r *RestoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
reqCtx := intctrlutil.RequestCtx{
Expand Down Expand Up @@ -159,6 +160,9 @@ func (r *RestoreReconciler) deleteExternalResources(reqCtx intctrlutil.RequestCt
}

func CheckBackupRepoForRestore(reqCtx intctrlutil.RequestCtx, cli client.Client, restore *dpv1alpha1.Restore) (string, error) {
if err := dprestore.ValidateBackupReferenceGrant(reqCtx, cli, restore); err != nil {
return "", err
}
backupName := restore.Spec.Backup.Name
backupNamespace := restore.Spec.Backup.Namespace
backup := &dpv1alpha1.Backup{}
Expand Down
5 changes: 5 additions & 0 deletions controllers/dataprotection/restore_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,11 @@ var _ = Describe("Restore Controller test", func() {

Context("test cross namespace", func() {
It("should wait for preparation of backup repo", func() {
grant := newBackupReferenceGrant("allow-cross-namespace-restore", testCtx.DefaultNamespace, namespace2, "")
Expect(k8sClient.Create(ctx, grant)).Should(Succeed())
DeferCleanup(func() {
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, grant))).Should(Succeed())
})
By("creating a restore in a different namespace from backup")
initResourcesAndWaitRestore(true, false, true, "", dpv1alpha1.RestorePhaseRunning,
func(f *testdp.MockRestoreFactory) {
Expand Down
82 changes: 82 additions & 0 deletions controllers/dataprotection/restore_referencegrant_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
Copyright (C) 2022-2026 ApeCloud Co., Ltd

This file is part of KubeBlocks project

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package dataprotection

import (
"context"
"strings"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
)

func newBackupReferenceGrant(name, backupNamespace, restoreNamespace, backupName string) *gatewayv1beta1.ReferenceGrant {
var grantedBackupName *gatewayv1beta1.ObjectName
if backupName != "" {
name := gatewayv1beta1.ObjectName(backupName)
grantedBackupName = &name
}
return &gatewayv1beta1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: backupNamespace},
Spec: gatewayv1beta1.ReferenceGrantSpec{
From: []gatewayv1beta1.ReferenceGrantFrom{{
Group: gatewayv1beta1.Group(dpv1alpha1.GroupVersion.Group),
Kind: gatewayv1beta1.Kind("Restore"),
Namespace: gatewayv1beta1.Namespace(restoreNamespace),
}},
To: []gatewayv1beta1.ReferenceGrantTo{{
Group: gatewayv1beta1.Group(dpv1alpha1.GroupVersion.Group),
Kind: gatewayv1beta1.Kind("Backup"),
Name: grantedBackupName,
}},
},
}
}

func TestCheckBackupRepoForRestoreAuthorizesBeforeBackupRead(t *testing.T) {
scheme := runtime.NewScheme()
if err := dpv1alpha1.AddToScheme(scheme); err != nil {
t.Fatal(err)
}
if err := gatewayv1beta1.AddToScheme(scheme); err != nil {
t.Fatal(err)
}
cli := fake.NewClientBuilder().WithScheme(scheme).Build()
restore := &dpv1alpha1.Restore{
ObjectMeta: metav1.ObjectMeta{Name: "restore", Namespace: "target"},
Spec: dpv1alpha1.RestoreSpec{
Backup: dpv1alpha1.BackupRef{Name: "backup", Namespace: "source"},
},
}

_, err := CheckBackupRepoForRestore(intctrlutil.RequestCtx{Ctx: context.Background()}, cli, restore)
if err == nil || !strings.Contains(err.Error(), "ReferenceGrant") {
t.Fatalf("expected ReferenceGrant error before Backup lookup, got %v", err)
}
if strings.Contains(err.Error(), "backups.dataprotection.kubeblocks.io") {
t.Fatalf("Backup was read before authorization: %v", err)
}
}
6 changes: 6 additions & 0 deletions controllers/dataprotection/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
Expand Down Expand Up @@ -105,6 +106,8 @@ var _ = BeforeSuite(func() {
// resolved by ref: https://github.com/operator-framework/operator-sdk/issues/4434#issuecomment-786794418
filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "kubernetes-csi/external-snapshotter/",
"client/v6@v6.2.0", "config", "crd"),
filepath.Join(build.Default.GOPATH, "pkg", "mod", "sigs.k8s.io", "gateway-api@v1.0.0",
"config", "crd", "standard"),
},
ErrorIfCRDPathMissing: true,
}
Expand All @@ -129,6 +132,9 @@ var _ = BeforeSuite(func() {
err = dpv1alpha1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())

err = gatewayv1beta1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())

// +kubebuilder:scaffold:scheme

k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,7 @@ func TestPopulateCreatesExecutionRestoreAndPolls(t *testing.T) {
Recorder: record.NewFakeRecorder(10),
}
backup := newBackupForRestoreDecision([]string{"data"}, nil)
backup.Status.Target.Name = "source"
backup.Status.Target.PodSelector = &dpv1alpha1.PodSelector{Strategy: dpv1alpha1.PodSelectionStrategyAny}
actionSet := &dpv1alpha1.ActionSet{
Spec: dpv1alpha1.ActionSetSpec{
Expand Down
8 changes: 8 additions & 0 deletions deploy/helm/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@ rules:
- get
- patch
- update
- apiGroups:
- gateway.networking.k8s.io
resources:
- referencegrants
verbs:
- get
- list
- watch
- apiGroups:
- operations.kubeblocks.io
resources:
Expand Down
6 changes: 5 additions & 1 deletion deploy/helm/crds/dataprotection.kubeblocks.io_restores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ spec:
description: Specifies the backup name.
type: string
namespace:
description: Specifies the backup namespace.
description: |-
Specifies the backup namespace. When this differs from the Restore namespace,
a Gateway API ReferenceGrant in the backup namespace must allow Restores from
the Restore namespace to reference the Backup. Cross-namespace VolumeSnapshot
restores are not supported.
type: string
sourceTargetName:
description: Specifies the source target for restoration, identified
Expand Down
11 changes: 10 additions & 1 deletion deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -619,8 +619,11 @@ spec:
type: string
namespace:
description: |-
Specifies the namespace of the Backup namespace.
Specifies the namespace of the Backup.
If not specified, the namespace of the OpsRequest will be used.
Cross-namespace references require a Gateway API ReferenceGrant in the Backup
namespace that allows Restores from the OpsRequest namespace to reference the
Backup. Cross-namespace VolumeSnapshot restores are not supported.
type: string
restoreEnv:
description: |-
Expand Down Expand Up @@ -757,6 +760,12 @@ spec:
- RFC3339 format, e.g. "2023-11-25T18:52:53Z"
- A human-readable date-time format, e.g. "Jul 25,2023 18:52:53 UTC+0800"
type: string
sourceTargetName:
description: |-
Specifies the backup source target name to restore from.
This field is required when the referenced Backup has multiple source targets.
It is propagated to Restore.spec.backup.sourceTargetName.
type: string
required:
- name
type: object
Expand Down
5 changes: 4 additions & 1 deletion docs/developer_docs/api-reference/dataprotection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2841,7 +2841,10 @@ string
</em>
</td>
<td>
<p>Specifies the backup namespace.</p>
<p>Specifies the backup namespace. When this differs from the Restore namespace,
a Gateway API ReferenceGrant in the backup namespace must allow Restores from
the Restore namespace to reference the Backup. Cross-namespace VolumeSnapshot
restores are not supported.</p>
</td>
</tr>
<tr>
Expand Down
21 changes: 19 additions & 2 deletions docs/developer_docs/api-reference/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1087,8 +1087,25 @@ string
</td>
<td>
<em>(Optional)</em>
<p>Specifies the namespace of the Backup namespace.
If not specified, the namespace of the OpsRequest will be used.</p>
<p>Specifies the namespace of the Backup.
If not specified, the namespace of the OpsRequest will be used.
Cross-namespace references require a Gateway API ReferenceGrant in the Backup
namespace that allows Restores from the OpsRequest namespace to reference the
Backup. Cross-namespace VolumeSnapshot restores are not supported.</p>
</td>
</tr>
<tr>
<td>
<code>sourceTargetName</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Specifies the backup source target name to restore from.
This field is required when the referenced Backup has multiple source targets.
It is propagated to Restore.spec.backup.sourceTargetName.</p>
</td>
</tr>
<tr>
Expand Down
Loading
Loading