oracle/controllers/backupcontroller/oracle_backup.go (240 lines of code) (raw):
package backupcontroller
import (
"context"
"errors"
"fmt"
"time"
"github.com/go-logr/logr"
snapv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
commonv1alpha1 "github.com/GoogleCloudPlatform/elcarro-oracle-operator/common/api/v1alpha1"
"github.com/GoogleCloudPlatform/elcarro-oracle-operator/common/pkg/utils"
"github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/api/v1alpha1"
"github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/controllers"
)
type oracleBackupFactory interface {
newOracleBackup(r *BackupReconciler, backup *v1alpha1.Backup, inst *v1alpha1.Instance, log logr.Logger) oracleBackup
}
type RealOracleBackupFactory struct{}
func (f *RealOracleBackupFactory) newOracleBackup(r *BackupReconciler, backup *v1alpha1.Backup, inst *v1alpha1.Instance, log logr.Logger) oracleBackup {
var b oracleBackup
if backup.Spec.Type == commonv1alpha1.BackupTypeSnapshot {
b = oracleBackup(&snapshotBackup{
r: r,
backup: backup,
inst: inst,
log: log,
})
} else {
b = oracleBackup(&physicalBackup{
r: r,
backup: backup,
log: log,
})
}
return b
}
type oracleBackup interface {
create(ctx context.Context) error
delete(ctx context.Context) error
status(ctx context.Context) (done bool, err error)
metadata(ctx context.Context) (metadata *oracleBackupMetadata, err error)
generateID() string
}
type oracleBackupMetadata struct {
timestamp time.Time
incarnation string
parentIncarnation string
scn string
databaseImage string
}
type snapshotBackup struct {
r *BackupReconciler
backup *v1alpha1.Backup
inst *v1alpha1.Instance
log logr.Logger
}
func (b *snapshotBackup) create(ctx context.Context) error {
// Load default preferences (aka "config") if provided by a customer.
config, err := b.r.BackupCtrl.LoadConfig(b.backup.Namespace)
if err != nil {
return err
}
var configSpec *commonv1alpha1.ConfigSpec
if config != nil {
configSpec = &config.Spec.ConfigSpec
b.log.Info("customer config loaded", "config", config)
} else {
b.log.Info("no customer specific config found, assuming all defaults")
}
vsc, err := utils.FindVolumeSnapshotClassName(b.backup.Spec.VolumeSnapshotClass, configSpec, utils.PlatformGCP, utils.EngineOracle)
if err != nil || vsc == "" {
return fmt.Errorf("failed to identify a volumeSnapshotClassName for instance: %q", b.backup.Spec.Instance)
}
b.log.Info("VolumeSnapshotClass", "volumeSnapshotClass", vsc)
getPvcNames := func(spec commonv1alpha1.DiskSpec) (string, string, string) {
var shortPVCName, mount string
if controllers.IsReservedDiskName(spec.Name) {
shortPVCName, mount = controllers.GetPVCNameAndMount(b.inst.Name, spec.Name)
} else {
shortPVCName, mount = controllers.GetCustomPVCNameAndMount(b.inst, spec.Name)
}
fullPVCName := fmt.Sprintf("%s-%s-0", shortPVCName, fmt.Sprintf(controllers.StsName, b.inst.Name))
snapshotName := fmt.Sprintf("%s-%s", b.backup.Status.BackupID, mount)
return fullPVCName, snapshotName, vsc
}
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("backup-controller")}
return utils.SnapshotDisks(ctx, controllers.DiskSpecs(b.inst, config), b.backup, b.r.Client, b.r.Scheme, getPvcNames, applyOpts)
}
func (b *snapshotBackup) delete(ctx context.Context) error {
// snapshot backup deletion is handled by k8s garbage collection.
return nil
}
func (b *snapshotBackup) status(ctx context.Context) (done bool, err error) {
b.log.Info("found a backup request in-progress")
ns := b.backup.Namespace
sel := labels.NewSelector()
vsLabels := []string{b.backup.Status.BackupID + "-u02", b.backup.Status.BackupID + "-u03"}
req1, err := labels.NewRequirement("name", selection.In, vsLabels)
if err != nil {
return false, err
}
sel.Add(*req1)
req2, err := labels.NewRequirement("namespace", selection.Equals, []string{ns})
if err != nil {
return false, err
}
sel.Add(*req2)
listOpts := []client.ListOption{
client.InNamespace(ns),
client.MatchingLabelsSelector{Selector: sel},
}
var volSnaps snapv1.VolumeSnapshotList
if err := b.r.List(ctx, &volSnaps, listOpts...); err != nil {
b.log.Error(err, "failed to get a volume snapshot")
return false, err
}
b.log.Info("list of found volume snapshots", "volSnaps", volSnaps)
if len(volSnaps.Items) < 1 {
b.log.Info("no volume snapshots found for a backup request marked as in-progress.", "backup.Status", b.backup.Status)
return false, errors.New("no volume snapshots found")
}
b.log.Info("found a volume snapshot(s) for a backup request in-progress")
vsStatus := make(map[string]bool)
for i, vs := range volSnaps.Items {
b.log.Info("iterating over volume snapshots", "VolumeSnapshot#", i, "name", vs.Name)
vsStatus[vs.Name] = false
if vs.Status == nil {
b.log.Info("not yet ready: Status missing for Volume Snapshot", "namespace", vs.Namespace, "volumeSnapshotName", vs.Name, "volumeSnapshotStatus", vs.Status)
return false, nil
}
if vs.Status.Error != nil {
b.log.Error(errors.New("the volumeSnapshot is failed"), "volumeSnapshot failed", "namespace", vs.Namespace, "volumeSnapshotName", vs.Name, "volumeSnapshotStatus", vs.Status, "VolumeSnapshotError", vs.Status.Error)
return true, fmt.Errorf("volumeSnapshot %s/%s failed with: %s", vs.Namespace, vs.Name, *vs.Status.Error.Message)
}
if !*vs.Status.ReadyToUse {
b.log.Info("not yet ready: Status found, but it's not flipped to DONE yet for VolumeSnapshot", "namespace", vs.Namespace, "volumeSnapshotName", vs.Name, "volumeSnapshotStatus", vs.Status)
return false, nil
}
b.log.Info("ready to use status", "VolumeSnapshot#", i, "name", vs, "status", *vs.Status.ReadyToUse)
vsStatus[vs.Name] = true
}
b.log.Info("summary of VolumeSnapshot statuses", "vsStatus", vsStatus)
return true, nil
}
func (b *snapshotBackup) generateID() string {
return fmt.Sprintf(backupName, b.backup.Spec.Instance, timeNow().Format("20060102"), "snap", timeNow().Nanosecond())
}
func (b *snapshotBackup) metadata(ctx context.Context) (metadata *oracleBackupMetadata, err error) {
b.log.Info("metadata() is not yet implemented for snapshot backup.")
return nil, nil
}
type physicalBackup struct {
r *BackupReconciler
backup *v1alpha1.Backup
log logr.Logger
}
func (b *physicalBackup) create(ctx context.Context) error {
timeLimitMinutes := controllers.PhysBackupTimeLimitDefault
if b.backup.Spec.TimeLimitMinutes != 0 {
timeLimitMinutes = time.Duration(b.backup.Spec.TimeLimitMinutes) * time.Minute
}
dop := int32(1)
if b.backup.Spec.Dop != 0 {
dop = b.backup.Spec.Dop
}
// the default is backupset true, not image copy
backupset := pointer.Bool(true)
if b.backup.Spec.Backupset != nil {
backupset = b.backup.Spec.Backupset
}
ctxBackup, cancel := context.WithTimeout(ctx, timeLimitMinutes)
defer cancel()
req := &controllers.PhysicalBackupRequest{
BackupSubType: backupSubType(b.backup.Spec.Subtype),
BackupItems: b.backup.Spec.BackupItems,
Backupset: *backupset,
CheckLogical: b.backup.Spec.CheckLogical,
Compressed: b.backup.Spec.Compressed,
Dop: dop,
Level: b.backup.Spec.Level,
Filesperset: b.backup.Spec.Filesperset,
SectionSize: b.backup.SectionSize(),
LocalPath: b.backup.Spec.LocalPath,
BackupTag: b.backup.Status.BackupTime,
GcsPath: b.backup.Spec.GcsPath,
LroInput: &controllers.LROInput{OperationId: lroOperationID(b.backup)},
}
if _, err := controllers.PhysicalBackup(ctxBackup, b.r, b.r.DatabaseClientFactory, b.backup.Namespace, b.backup.Spec.Instance, *req); err != nil &&
!controllers.IsAlreadyExistsError(err) {
return fmt.Errorf("failed on PhysicalBackup gRPC call: %v", err)
}
return nil
}
func (b *physicalBackup) delete(ctx context.Context) error {
if err := controllers.PhysicalBackupDelete(ctx, b.r, b.r.DatabaseClientFactory, b.backup.Namespace, b.backup.Spec.Instance, controllers.PhysicalBackupDeleteRequest{
LocalPath: b.backup.Spec.LocalPath,
GcsPath: controllers.GetBackupGcsPath(b.backup),
BackupTag: b.backup.Status.BackupTime,
}); err != nil {
return fmt.Errorf("failed on PhysicalBackupDelete call: %v", err)
}
return nil
}
func (b *physicalBackup) status(ctx context.Context) (done bool, err error) {
id := lroOperationID(b.backup)
operation, err := controllers.GetLROOperation(ctx, b.r.DatabaseClientFactory, b.r.Client, id, b.backup.GetNamespace(), b.backup.Spec.Instance)
if err != nil {
b.log.Error(err, "GetLROOperation error")
return false, err
}
if operation.Done {
b.log.Info("LRO is DONE", "id", id)
if operation.GetError() != nil {
err = errors.New(operation.GetError().GetMessage())
}
if err := controllers.DeleteLROOperation(ctx, b.r.DatabaseClientFactory, b.r.Client, id, b.backup.Namespace, b.backup.Spec.Instance); err != nil {
b.log.Error(err, "failed to delete a LRO ")
}
return true, err
}
b.log.Info("LRO is in progress", "id", id)
return false, nil
}
func (b *physicalBackup) generateID() string {
return fmt.Sprintf(backupName, b.backup.Spec.Instance, time.Now().Format("20060102"), "phys", time.Now().Nanosecond())
}
func (b *physicalBackup) metadata(ctx context.Context) (metadata *oracleBackupMetadata, err error) {
resp, err := controllers.PhysicalBackupMetadata(ctx, b.r, b.r.DatabaseClientFactory, b.backup.Namespace, b.backup.Spec.Instance, controllers.PhysicalBackupMetadataRequest{BackupTag: b.backup.Status.BackupTime})
if err != nil {
return nil, fmt.Errorf("failed to fetch physical backup metadata: %v", err)
}
if resp == nil {
return nil, nil
}
return &oracleBackupMetadata{
timestamp: resp.BackupTimestamp.AsTime(),
incarnation: resp.BackupIncarnation,
scn: resp.BackupScn,
}, nil
}