@@ -10,6 +10,7 @@ import (
1010 "github.com/go-logr/logr"
1111 . "github.com/onsi/ginkgo"
1212 . "github.com/onsi/gomega"
13+ corev1 "k8s.io/api/core/v1"
1314 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1415 "k8s.io/client-go/util/retry"
1516 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -28,18 +29,20 @@ func testInstanceRestore() {
2829 interval = time .Millisecond * 15
2930 )
3031 var (
31- Namespace string
32- InstanceName string
33- BackupName string
34- BackupID string
35- ObjKey client.ObjectKey
32+ Namespace string
33+ CrossNamespacePrefix string
34+ InstanceName string
35+ BackupName string
36+ BackupID string
37+ ObjKey client.ObjectKey
3638 )
3739
3840 var fakeDatabaseClient * testhelpers.FakeDatabaseClient
3941 oldPreflightFunc := restorePhysicalPreflightCheck
4042
4143 BeforeEach (func () {
4244 Namespace = "default"
45+ CrossNamespacePrefix = "cross-namespace-"
4346 InstanceName = testhelpers .RandName ("test-instance-restore" )
4447 BackupName = testhelpers .RandName ("test-backup" )
4548 BackupID = testhelpers .RandName ("test-backup-id" )
@@ -67,6 +70,23 @@ func testInstanceRestore() {
6770 ctx := context .Background ()
6871 restoreRequestTime := metav1 .Now ()
6972
73+ createNamespace := func (ctx context.Context , k8sClient client.Client , name string , crossNSLabel bool ) {
74+ // 1. Define the Namespace object
75+ ns := & corev1.Namespace {
76+ ObjectMeta : metav1.ObjectMeta {
77+ Name : name ,
78+ },
79+ }
80+
81+ if crossNSLabel {
82+ ns .ObjectMeta .Labels = map [string ]string {
83+ crossNSBackupRefAccessLabel : "true" ,
84+ }
85+ }
86+
87+ testhelpers .K8sCreateWithRetry (k8sClient , ctx , ns )
88+ }
89+
7090 createInstanceAndStartRestore := func (mode testhelpers.FakeOperationStatus ) (* v1alpha1.Instance , * v1alpha1.Backup ) {
7191 instance := createSimpleInstance (ctx , InstanceName , Namespace , timeout , interval )
7292 backup := createSimpleRMANBackup (ctx , InstanceName , BackupName , BackupID , Namespace )
@@ -91,6 +111,36 @@ func testInstanceRestore() {
91111 return instance , backup
92112 }
93113
114+ createInstanceAndStartRestoreWithBackupRef := func (mode testhelpers.FakeOperationStatus , backupNS string ) (* v1alpha1.Instance , * v1alpha1.Backup ) {
115+ instance := createSimpleInstance (ctx , InstanceName , Namespace , timeout , interval )
116+ var backup * v1alpha1.Backup
117+ if backupNS != "" {
118+ backup = createSimpleRMANBackup (ctx , InstanceName , BackupName , BackupID , backupNS )
119+
120+ }
121+ By ("invoking RMAN restore for the Instance with backupRef" )
122+
123+ // configure fakeDatabaseClient to be in requested mode
124+ fakeDatabaseClient .SetNextGetOperationStatus (mode )
125+ Expect (retry .RetryOnConflict (retry .DefaultBackoff , func () error {
126+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
127+ return err
128+ }
129+ instance .Spec .Restore = & v1alpha1.RestoreSpec {
130+ BackupRef : & v1alpha1.BackupReference {
131+ Namespace : backupNS ,
132+ Name : BackupName ,
133+ },
134+ BackupType : "Physical" ,
135+ Force : true ,
136+ RequestTime : restoreRequestTime ,
137+ }
138+ return k8sClient .Update (ctx , instance )
139+ })).Should (Succeed ())
140+
141+ return instance , backup
142+ }
143+
94144 // extract happy path restore test part into a function for reuse
95145 // in multiple tests
96146 testCaseHappyPathLRORestore := func () (* v1alpha1.Instance , * v1alpha1.Backup ) {
@@ -405,6 +455,164 @@ func testInstanceRestore() {
405455 testhelpers .K8sDeleteWithRetry (k8sClient , ctx , ObjKey , instance )
406456 testhelpers .K8sDeleteWithRetry (k8sClient , ctx , client.ObjectKey {Namespace : Namespace , Name : BackupName }, backup )
407457 })
458+
459+ It ("it should restore successfully with backupRef of the same namespace" , func () {
460+ fakeDatabaseClient .SetAsyncPhysicalRestore (false )
461+ instance , backup := createInstanceAndStartRestoreWithBackupRef (testhelpers .StatusDone , Namespace )
462+
463+ By ("checking that instance status is Ready" )
464+ Eventually (func () (metav1.ConditionStatus , error ) {
465+ return getConditionStatus (ctx , ObjKey , k8s .Ready )
466+ }, timeout , interval ).Should (Equal (metav1 .ConditionTrue ))
467+
468+ Expect (fakeDatabaseClient .DeleteOperationCalledCnt ()).Should (Equal (0 ))
469+
470+ By ("checking that instance Restore section is deleted" )
471+ Eventually (func () error {
472+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
473+ return err
474+ }
475+ if instance .Spec .Restore != nil {
476+ return fmt .Errorf ("expected update has not yet happened" )
477+ }
478+ return nil
479+ }, timeout , interval ).Should (Succeed ())
480+
481+ By ("checking that instance Status.Description is updated" )
482+ Eventually (func () error {
483+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
484+ return err
485+ }
486+ if ! strings .HasPrefix (instance .Status .Description , "Restored on" ) {
487+ return fmt .Errorf ("%q does not have expected prefix" , instance .Status .Description )
488+ }
489+ return nil
490+ }, timeout , interval ).Should (Succeed ())
491+
492+ By ("checking that instance maintenance lock is released" )
493+ // Instance object should be fresh at this point, no need to retry
494+ Expect (instance .Status .LockedByController ).Should (Equal ("" ))
495+
496+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , ObjKey , instance )
497+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , client.ObjectKey {Namespace : Namespace , Name : BackupName }, backup )
498+ })
499+
500+ It ("it should restore fail with cross-namespace backupRef if the cross-namespace backupRef specifies a namespace that does not exist." , func () {
501+ fakeDatabaseClient .SetAsyncPhysicalRestore (false )
502+ instance , _ := createInstanceAndStartRestoreWithBackupRef (testhelpers .StatusDone , "" )
503+
504+ By ("checking that instance Restore section is deleted" )
505+ Eventually (func () error {
506+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
507+ return err
508+ }
509+ if instance .Spec .Restore != nil {
510+ return fmt .Errorf ("expected update has not yet happened" )
511+ }
512+ return nil
513+ }, timeout , interval ).Should (Succeed ())
514+
515+ By ("checking that instance Status.Description is updated" )
516+ Eventually (func () error {
517+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
518+ return err
519+ }
520+ cond := k8s .FindCondition (instance .Status .Conditions , k8s .Ready )
521+ if ! strings .HasPrefix (cond .Message , "Could not find a matching backup" ) {
522+ return fmt .Errorf ("%q does not have expected prefix in the condition" , cond .Message )
523+ }
524+ return nil
525+ }, timeout , interval ).Should (Succeed ())
526+
527+ By ("checking that instance maintenance lock is released" )
528+ // Instance object should be fresh at this point, no need to retry
529+ Expect (instance .Status .LockedByController ).Should (Equal ("" ))
530+
531+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , ObjKey , instance )
532+ })
533+
534+ It ("it should restore fail with cross-namespace backupRef without cross-namespace backupRef access label" , func () {
535+ fakeDatabaseClient .SetAsyncPhysicalRestore (false )
536+ // Create a different namespace
537+ crossNS := CrossNamespacePrefix + "1"
538+ createNamespace (ctx , k8sClient , crossNS , false )
539+
540+ instance , backup := createInstanceAndStartRestoreWithBackupRef (testhelpers .StatusDone , crossNS )
541+
542+ By ("checking that instance Restore section is deleted" )
543+ Eventually (func () error {
544+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
545+ return err
546+ }
547+ if instance .Spec .Restore != nil {
548+ return fmt .Errorf ("expected update has not yet happened" )
549+ }
550+ return nil
551+ }, timeout , interval ).Should (Succeed ())
552+
553+ By ("checking that instance Status.Description is updated" )
554+ Eventually (func () error {
555+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
556+ return err
557+ }
558+ cond := k8s .FindCondition (instance .Status .Conditions , k8s .Ready )
559+ if ! strings .HasPrefix (cond .Message , "Could not find a matching backup" ) {
560+ return fmt .Errorf ("%q does not have expected prefix in the condition" , cond .Message )
561+ }
562+ return nil
563+ }, timeout , interval ).Should (Succeed ())
564+
565+ By ("checking that instance maintenance lock is released" )
566+ // Instance object should be fresh at this point, no need to retry
567+ Expect (instance .Status .LockedByController ).Should (Equal ("" ))
568+
569+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , ObjKey , instance )
570+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , client.ObjectKey {Namespace : crossNS , Name : BackupName }, backup )
571+ })
572+
573+ It ("it should restore successfully with cross-namespace backupRef if the cross namespace is labeled with allow-cross-namespace-backup-reference" , func () {
574+ fakeDatabaseClient .SetAsyncPhysicalRestore (false )
575+ // Create a different namespace
576+ crossNS := CrossNamespacePrefix + "2"
577+ createNamespace (ctx , k8sClient , crossNS , true )
578+
579+ instance , backup := createInstanceAndStartRestoreWithBackupRef (testhelpers .StatusDone , crossNS )
580+ By ("checking that instance status is Ready" )
581+ Eventually (func () (metav1.ConditionStatus , error ) {
582+ return getConditionStatus (ctx , ObjKey , k8s .Ready )
583+ }, timeout , interval ).Should (Equal (metav1 .ConditionTrue ))
584+
585+ Expect (fakeDatabaseClient .DeleteOperationCalledCnt ()).Should (Equal (0 ))
586+
587+ By ("checking that instance Restore section is deleted" )
588+ Eventually (func () error {
589+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
590+ return err
591+ }
592+ if instance .Spec .Restore != nil {
593+ return fmt .Errorf ("expected update has not yet happened" )
594+ }
595+ return nil
596+ }, timeout , interval ).Should (Succeed ())
597+
598+ By ("checking that instance Status.Description is updated" )
599+ Eventually (func () error {
600+ if err := k8sClient .Get (ctx , ObjKey , instance ); err != nil {
601+ return err
602+ }
603+ if ! strings .HasPrefix (instance .Status .Description , "Restored on" ) {
604+ return fmt .Errorf ("%q does not have expected prefix" , instance .Status .Description )
605+ }
606+ return nil
607+ }, timeout , interval ).Should (Succeed ())
608+
609+ By ("checking that instance maintenance lock is released" )
610+ // Instance object should be fresh at this point, no need to retry
611+ Expect (instance .Status .LockedByController ).Should (Equal ("" ))
612+
613+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , ObjKey , instance )
614+ testhelpers .K8sDeleteWithRetry (k8sClient , ctx , client.ObjectKey {Namespace : crossNS , Name : BackupName }, backup )
615+ })
408616}
409617
410618func createSimpleRMANBackup (ctx context.Context , instanceName string , backupName string , backupID string , namespace string ) * v1alpha1.Backup {
0 commit comments