Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show a restore point info in the vdb status #942

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 11 additions & 1 deletion api/v1/verticadb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ limitations under the License.
package v1

import (
"github.com/vertica/vcluster/vclusterops"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -847,17 +848,26 @@ type VerticaDBStatus struct {
// +operator-sdk:csv:customresourcedefinitions:type=status
// +optional
// The details about the last created restore point
RestorePoint *RestorePointInfo `json:"restorePoint"`
RestorePoint *RestorePointInfo `json:"restorePoint,omitempty"`
}

type RestorePointInfo struct {
// +operator-sdk:csv:customresourcedefinitions:type=status
// Name of the archive that this restore point was created in.
Archive string `json:"archive"`
// +operator-sdk:csv:customresourcedefinitions:type=status
// The timestamp when the save restore point api call started. This is helpful
// to manually query the restore point after its creation in case the operator could
// not retrieve all of the restore point info.
StartTimestamp string `json:"startTimestamp"`
// +operator-sdk:csv:customresourcedefinitions:type=status
// The timestamp when the save restore point api call ended. This is helpful
// to manually query the restore point after its creation in case the operator could
// not retrieve all of the restore point info.
EndTimestamp string `json:"endTimestamp"`
// +operator-sdk:csv:customresourcedefinitions:type=status
// This contains the result of the restore points query.
Details *vclusterops.RestorePoint `json:"details"`
}

type SandboxUpgradeState struct {
Expand Down
8 changes: 7 additions & 1 deletion api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion api/v1beta1/verticadb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"regexp"

"github.com/vertica/vcluster/vclusterops"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -928,17 +929,26 @@ type VerticaDBStatus struct {
// +operator-sdk:csv:customresourcedefinitions:type=status
// +optional
// The details about the last created restore point
RestorePoint *RestorePointInfo `json:"restorePoint"`
RestorePoint *RestorePointInfo `json:"restorePoint,omitempty"`
}

type RestorePointInfo struct {
// +operator-sdk:csv:customresourcedefinitions:type=status
// Name of the archive that this restore point was created in.
Archive string `json:"archive"`
// +operator-sdk:csv:customresourcedefinitions:type=status
// The timestamp when the save restore point api call started. This is helpful
// to manually query the restore point after its creation in case the operator could
// not retrieve all of the restore point info.
StartTimestamp string `json:"startTimestamp"`
// +operator-sdk:csv:customresourcedefinitions:type=status
// The timestamp when the save restore point api call ended. This is helpful
// to manually query the restore point after its creation in case the operator could
// not retrieve all of the restore point info.
EndTimestamp string `json:"endTimestamp"`
// +operator-sdk:csv:customresourcedefinitions:type=status
// This contains the result of the restore points query.
Details *vclusterops.RestorePoint `json:"details"`
}

type SandboxUpgradeState struct {
Expand Down
18 changes: 8 additions & 10 deletions pkg/controllers/vdb/saverestorepoint_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,23 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// SaveRestorePointReconciler saves a restore point to an archive, and create the archive if it doesn't exist
type SaveRestorePointReconciler struct {
VRec *VerticaDBReconciler
Vdb *vapi.VerticaDB
Log logr.Logger
client.Client
Dispatcher vadmin.Dispatcher
PFacts *PodFacts
InitiatorIP string // The IP of the pod that we run vclusterOps from
VRec *VerticaDBReconciler
Vdb *vapi.VerticaDB
Log logr.Logger
Dispatcher vadmin.Dispatcher
PFacts *PodFacts
}

func MakeSaveRestorePointReconciler(r *VerticaDBReconciler, vdb *vapi.VerticaDB, log logr.Logger,
pfacts *PodFacts, dispatcher vadmin.Dispatcher, cli client.Client) controllers.ReconcileActor {
pfacts *PodFacts, dispatcher vadmin.Dispatcher) controllers.ReconcileActor {
return &SaveRestorePointReconciler{
VRec: r,
Log: log.WithName("SaveRestorePointReconciler"),
Vdb: vdb,
Client: cli,
Dispatcher: dispatcher,
PFacts: pfacts,
}
Expand Down Expand Up @@ -215,6 +211,8 @@ func (s *SaveRestorePointReconciler) updateVDB(ctx context.Context, start, end t
vdb.Status.RestorePoint.Archive = s.Vdb.Spec.RestorePoint.Archive
vdb.Status.RestorePoint.StartTimestamp = start.Format("2006-01-02 15:04:05.000000000")
vdb.Status.RestorePoint.EndTimestamp = end.Format("2006-01-02 15:04:05.000000000")
// A separate reconciler will handle this
vdb.Status.RestorePoint.Details = nil
return nil
}
// Clear the condition and add a status after restore point creation.
Expand Down
2 changes: 1 addition & 1 deletion pkg/controllers/vdb/saverestorepoint_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var _ = Describe("saverestorepoint_reconciler", func() {
Expect(k8sClient.Status().Update(ctx, vdb)).Should(Succeed())
fpr := &cmds.FakePodRunner{}
dispatcher := vdbRec.makeDispatcher(logger, vdb, fpr, TestPassword)
r := MakeSaveRestorePointReconciler(vdbRec, vdb, logger, &PodFacts{}, dispatcher, k8sClient)
r := MakeSaveRestorePointReconciler(vdbRec, vdb, logger, &PodFacts{}, dispatcher)
res, err := r.Reconcile(ctx, &ctrl.Request{})
Expect(err).Should(Succeed())
Expect(res.Requeue).Should(BeFalse())
Expand Down
155 changes: 155 additions & 0 deletions pkg/controllers/vdb/showrestorepoint_reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
(c) Copyright [2021-2024] Open Text.
Licensed under the Apache License, Version 2.0 (the "License");
You may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package vdb

import (
"context"
"time"

"github.com/go-logr/logr"
"github.com/vertica/vcluster/vclusterops"
vapi "github.com/vertica/vertica-kubernetes/api/v1"
"github.com/vertica/vertica-kubernetes/pkg/controllers"
"github.com/vertica/vertica-kubernetes/pkg/events"
"github.com/vertica/vertica-kubernetes/pkg/iter"
"github.com/vertica/vertica-kubernetes/pkg/vadmin"
"github.com/vertica/vertica-kubernetes/pkg/vadmin/opts/showrestorepoints"
config "github.com/vertica/vertica-kubernetes/pkg/vdbconfig"
"github.com/vertica/vertica-kubernetes/pkg/vdbstatus"
"github.com/vertica/vertica-kubernetes/pkg/vk8s"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
)

// ShowRestorePointReconciler will query a restore point and update the status
type ShowRestorePointReconciler struct {
VRec *VerticaDBReconciler
Vdb *vapi.VerticaDB
Log logr.Logger
Dispatcher vadmin.Dispatcher
PFacts *PodFacts
config.ConfigParamsGenerator
}

func MakeShowRestorePointReconciler(r *VerticaDBReconciler, vdb *vapi.VerticaDB, log logr.Logger,
pfacts *PodFacts, dispatcher vadmin.Dispatcher) controllers.ReconcileActor {
return &ShowRestorePointReconciler{
VRec: r,
Log: log.WithName("ShowRestorePointReconciler"),
Vdb: vdb,
Dispatcher: dispatcher,
PFacts: pfacts,
ConfigParamsGenerator: config.ConfigParamsGenerator{
VRec: r,
Vdb: vdb,
Log: log.WithName("ShowRestorePointReconciler"),
},
}
}

func (s *ShowRestorePointReconciler) Reconcile(ctx context.Context, _ *ctrl.Request) (ctrl.Result, error) {
if shouldEarlyExit(s.Vdb) {
return ctrl.Result{}, nil
}

restorePoints, requeue, err := s.runShowRestorePoints(ctx)
if err != nil || requeue {
return ctrl.Result{Requeue: requeue}, err
}
return ctrl.Result{}, s.saveRestorePointDetailsInVDB(ctx, restorePoints)
}

// runShowRestorePoints call the vclusterOps API to get the restore points
func (s *ShowRestorePointReconciler) runShowRestorePoints(ctx context.Context) ([]vclusterops.RestorePoint, bool, error) {
finder := iter.MakeSubclusterFinder(s.VRec.GetClient(), s.Vdb)
pods, err := finder.FindPods(ctx, iter.FindExisting, vapi.MainCluster)
if err != nil {
return nil, false, err
}

// find a pod to execute the vclusterops API
podIP := vk8s.FindRunningPodWithNMAContainer(pods)
if podIP == "" {
s.Log.Info("couldn't find any pod to run the query")
return nil, true, nil
}

// extract out the communal and config information to pass down to the vclusterops API.
opts := []showrestorepoints.Option{}
opts = append(opts,
showrestorepoints.WithInitiator(podIP),
showrestorepoints.WithCommunalPath(s.Vdb.GetCommunalPath()),
showrestorepoints.WithConfigurationParams(s.ConfigurationParams.GetMap()),
showrestorepoints.WithArchiveNameFilter(s.Vdb.Status.RestorePoint.Archive),
showrestorepoints.WithStartTimestampFilter(s.Vdb.Status.RestorePoint.StartTimestamp),
showrestorepoints.WithEndTimestampFilter(s.Vdb.Status.RestorePoint.EndTimestamp),
)

// call showRestorePoints vcluster API
s.VRec.Eventf(s.Vdb, corev1.EventTypeNormal, events.ShowRestorePointsStarted,
"Starting show restore points")
start := time.Now()
restorePoints, err := s.Dispatcher.ShowRestorePoints(ctx, opts...)
if err != nil {
return nil, false, err
}

s.VRec.Eventf(s.Vdb, corev1.EventTypeNormal, events.ShowRestorePointsSucceeded,
"Successfully queried restore points in %s", time.Since(start).Truncate(time.Second))
return restorePoints, false, nil
}

// saveRestorePointDetailsInVDB saves the last created restore point info in the status.
func (s *ShowRestorePointReconciler) saveRestorePointDetailsInVDB(ctx context.Context, restorePoints []vclusterops.RestorePoint) error {
// This is very unlikely to happen
if len(restorePoints) == 0 {
s.Log.Info("No restore point found.")
return nil
}
// We should normally only be getting the restore point that was just created
// so if there are more that one restore point we will randomly pick the first one
if len(restorePoints) > 1 {
s.Log.Info("Multiple restore points were found, only the first one will be saved in the status.")
}
restorePoint := restorePoints[0]
return s.updateRestorePointDetails(ctx, &restorePoint)
}

func (s *ShowRestorePointReconciler) updateRestorePointDetails(ctx context.Context, restorePoint *vclusterops.RestorePoint) error {
refreshStatusInPlace := func(vdb *vapi.VerticaDB) error {
if vdb.Status.RestorePoint == nil {
vdb.Status.RestorePoint = new(vapi.RestorePointInfo)
}
if vdb.Status.RestorePoint.Details == nil {
vdb.Status.RestorePoint.Details = new(vclusterops.RestorePoint)
}
vdb.Status.RestorePoint.Details.Archive = restorePoint.Archive
vdb.Status.RestorePoint.Details.Index = restorePoint.Index
vdb.Status.RestorePoint.Details.ID = restorePoint.ID
vdb.Status.RestorePoint.Details.Timestamp = restorePoint.Timestamp
vdb.Status.RestorePoint.Details.VerticaVersion = restorePoint.VerticaVersion
return nil
}
return vdbstatus.Update(ctx, s.VRec.Client, s.Vdb, refreshStatusInPlace)
}

func shouldEarlyExit(vdb *vapi.VerticaDB) bool {
return vdb.Status.RestorePoint == nil ||
vdb.Status.RestorePoint.Archive == "" ||
vdb.Status.RestorePoint.StartTimestamp == "" ||
vdb.Status.RestorePoint.EndTimestamp == "" ||
vdb.Status.RestorePoint.Details != nil
}
77 changes: 77 additions & 0 deletions pkg/controllers/vdb/showrestorepoint_reconciler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
(c) Copyright [2021-2024] Open Text.
Licensed under the Apache License, Version 2.0 (the "License");
You may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package vdb

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vertica/vcluster/vclusterops"
vapi "github.com/vertica/vertica-kubernetes/api/v1"
"github.com/vertica/vertica-kubernetes/pkg/test"
)

var _ = Describe("showrestorepoint_reconciler", func() {
ctx := context.Background()
const archive = "arch"

It("should exit early", func() {
vdb := vapi.MakeVDB()
vdb.Status = vapi.VerticaDBStatus{}
// should exit because status.restorePoint is nil
Expect(shouldEarlyExit(vdb)).Should(BeTrue())
vdb.Status.RestorePoint = &vapi.RestorePointInfo{
Archive: archive,
StartTimestamp: "start",
}
// should exit because at least one of archive, startTimeStamp,
// endTimeStamp is not set
Expect(shouldEarlyExit(vdb)).Should(BeTrue())
vdb.Status.RestorePoint.EndTimestamp = "end"
// should not exit
Expect(shouldEarlyExit(vdb)).Should(BeFalse())
vdb.Status.RestorePoint.Details = &vclusterops.RestorePoint{}
// should exit because status.restorePoint.details is already set
Expect(shouldEarlyExit(vdb)).Should(BeTrue())
})

It("should update status with restore point info", func() {
vdb := vapi.MakeVDB()
test.CreateVDB(ctx, k8sClient, vdb)
defer test.DeleteVDB(ctx, k8sClient, vdb)

s := &ShowRestorePointReconciler{
Vdb: vdb,
VRec: vdbRec,
Log: logger,
}
const id = "abcdef"
rpts := []vclusterops.RestorePoint{
{
Archive: archive,
ID: id,
Index: 1,
},
}
Expect(s.saveRestorePointDetailsInVDB(ctx, rpts)).Should(Succeed())
fetchedVdb := &vapi.VerticaDB{}
Expect(k8sClient.Get(ctx, vdb.ExtractNamespacedName(), fetchedVdb)).Should(Succeed())
Expect(fetchedVdb.Status.RestorePoint.Details.Archive).Should(Equal(archive))
Expect(fetchedVdb.Status.RestorePoint.Details.ID).Should(Equal(id))
Expect(fetchedVdb.Status.RestorePoint.Details.Index).Should(Equal(1))
})
})
4 changes: 3 additions & 1 deletion pkg/controllers/vdb/verticadb_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,9 @@ func (r *VerticaDBReconciler) constructActors(log logr.Logger, vdb *vapi.Vertica
// Add the label after update the sandbox subcluster status field
MakeObjReconciler(r, log, vdb, pfacts, ObjReconcileModeAll),
// Handle calls to create a restore point
MakeSaveRestorePointReconciler(r, vdb, log, pfacts, dispatcher, r.Client),
MakeSaveRestorePointReconciler(r, vdb, log, pfacts, dispatcher),
// Update the status with the restore point info
MakeShowRestorePointReconciler(r, vdb, log, pfacts, dispatcher),
// Resize any PVs if the local data size changed in the vdb
MakeResizePVReconciler(r, log, vdb, prunner, pfacts),
// This must be the last reconciler. It makes sure that all dependent
Expand Down
Loading
Loading