-
Notifications
You must be signed in to change notification settings - Fork 80
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
Remote restore support for Medusa #454
Conversation
return ctrl.Result{RequeueAfter: r.DefaultDelay}, err | ||
} | ||
|
||
// If the CassandraBackup object was created by a sync operation, we mustn't trigger a backup. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue with this approach is that I don't think "backup" definition and "backupJob" should be the same. That would solve this mentioned issue. Also, if metadata per object is necessary, we could use annotation instead of polluting the CRD with extra fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that change is coming in the next PR 👍
if !task.Status.StartTime.IsZero() { | ||
// If there is anything in progress, simply requeue the request | ||
if len(task.Status.InProgress) > 0 { | ||
logger.Info("Tasks already in progress") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and next logger lines, without other info in the log statement the reader can't tell which restore process this is related to. So we need some KeyValues in the .Info()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it what line 59 does?
logger := log.FromContext(ctx).WithValues("MedusaTask", req.NamespacedName)
// Set the finish time | ||
// Note that the time here is not accurate, but that is ok. For now we are just | ||
// using it as a completion marker. | ||
patch := client.MergeFrom(task.DeepCopy()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
task is already DeepCopy of instance. Do we really need double copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm following the conventions we've used throughout the project when patching CRs.
Do you think we're wrong to do so?
I seem to remember that patches wouldn't be applied properly without this, but I could be wrong.
} | ||
|
||
// If the task is already finished, there is nothing to do. | ||
if taskFinished(task) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be before the previous if statement? StartTime is not zero for finished jobs either?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it would be better to move it before the previous if indeed 👍
I'll move it.
return nil, err | ||
} | ||
|
||
pods := make([]corev1.Pod, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
, len(podList.Items)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
|
||
// Invoke the purge operation on all pods, in the background | ||
go func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is the verification done if this go func is still alive or failed? What if the k8ssandra-operator crashes while this is running?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we lose track of the operation. For better or worse, this is still what we currently have for backups as well (I re-used the same code structure).
We could definitely improve the resiliency by making the calls non blocking with a way to check the state asynchronously, like we're about to do with backups.
The tasks we run here should be far more short lived and easy to rerun in case of failure though, so it's less worrisome than backups and could be dealt with in a future PR.
} | ||
|
||
// Invoke the purge operation on all pods, in the background | ||
go func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as next go func() question. Liveness check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same answer :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A partial review. More to follow tomorrow...
Failed []string `json:"failed,omitempty"` | ||
} | ||
|
||
type TaskResult struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all of the fields in this struct specific to a purge operation? If so maybe rename to PurgeTaskResult or PurgeResult.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not the case. The prepare_restore
will generate TaskResult
instances with just the PodName
filled, leaving purge specific variables empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of this. I think it obscures the code. From a maintenance perspective for example, how am I supposed to know that a particular field is just empty at a given point in time vs empty because it isn't used for the given task type. How about some comments to explain which fields are used for which task types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's fair, I'll add the comments.
task.Status.FinishTime = metav1.Now() | ||
if err := r.Status().Patch(ctx, task, patch); err != nil { | ||
logger.Error(err, "failed to patch status with finish time") | ||
return ctrl.Result{RequeueAfter: r.DefaultDelay}, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to set RequeueAfter
when returning a non-nil error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return ctrl.Result{}, err
will reschedule a reconcile later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, with rate limiting. Here is the relevant code from the controller:
result, err := c.Reconcile(ctx, req)
switch {
case err != nil:
c.Queue.AddRateLimited(req)
ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelError).Inc()
log.Error(err, "Reconciler error")
case result.RequeueAfter > 0:
// The result.RequeueAfter request will be lost, if it is returned
// along with a non-nil error. But this is intended as
// We need to drive to stable reconcile loops before queuing due
// to result.RequestAfter
c.Queue.Forget(obj)
c.Queue.AddAfter(req, result.RequeueAfter)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeueAfter).Inc()
case result.Requeue:
c.Queue.AddRateLimited(req)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeue).Inc()
default:
// Finally, if no error occurs we Forget this item so it does not
// get queued again until another change happens.
c.Queue.Forget(obj)
ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelSuccess).Inc()
}
}
Notice that when a non-nil error is returned the controller calls c.Queue.AddRateLimited(req)
. See here for the full source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, I'll do the changes 👍
podList := &corev1.PodList{} | ||
labels := client.MatchingLabels{cassdcapi.DatacenterLabel: cassdc.Name} | ||
if err := r.List(ctx, podList, labels); err != nil { | ||
logger.Error(err, "failed to get pods for cassandradatacenter", "CassandraDatacenter", cassdc.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw you wanted us to remove these loggings. The problem is that the error printed out if we don't put our own logging is usually more obscure and doesn't give details allowing to know precisely what failed. Technically we know what failed, but it's harder to know what we were trying to do when it failed.
I'm ok with some repetition in the logs if that allows more clarity during troubleshooting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error printed out should include your error message. What isn't included though is a stack trace which can be problematic when the same error message is used in multiple places.
Whatever we do or change (if anything) for logging is a separate concern from this PR, so I think we're good to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the Wrap
function from github.com/pkg/errors
to get a stacktrace in the output. Instead of the logger.Error
call, you could just do
return nil, errors.Wrap(err, fmt.Sprintf("failed to get pods for cassandradatacenter %s", cassdc.Name))
The output will include a stacktrace from the point where Wrap
is called.
if err := r.Status().Patch(ctx, task, patch); err != nil { | ||
logger.Error(err, "Failed to patch status") | ||
// We received a stale object, requeue for next processing | ||
return ctrl.Result{RequeueAfter: r.DefaultDelay}, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to set RequeueAfter
when returning a non-nil error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
wg.Wait() | ||
logger.Info("finished task operations") | ||
if err := r.Status().Patch(context.Background(), task, patch); err != nil { | ||
logger.Error(err, "failed to patch status", "MedusaTask", fmt.Sprintf("%s/%s", task.Spec.Operation, task.Namespace)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fmt.Sprintf("%s/%s", task.Spec.Operation, task.Namespace)
is used in multiple places. Maybe implement the Stringer interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good 👍
if err := r.Status().Patch(ctx, task, patch); err != nil { | ||
logger.Error(err, "Failed to patch status") | ||
// We received a stale object, requeue for next processing | ||
return ctrl.Result{RequeueAfter: r.DefaultDelay}, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to set RequeueAfter
when returning a non-nil error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
localBackups := &medusav1alpha1.CassandraBackupList{} | ||
if err = r.List(ctx, localBackups, client.InNamespace(task.Namespace)); err != nil { | ||
logger.Error(err, "failed to list backups") | ||
return ctrl.Result{RequeueAfter: r.DefaultDelay}, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to set RequeueAfter
when returning a non-nil error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
83ff351
to
56d3524
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume we want to support the scenario of restoring from one bucket and then storing subsequent backups in a different bucket. Do the API changes support that? It doesn't look like it.
Not yet, it's part of another upcoming ticket: #453 |
82ca807
to
ab94d8d
Compare
@@ -439,3 +439,7 @@ install-kuttl: | |||
mocks: | |||
mockery --dir=./pkg/cassandra --output=./pkg/mocks --name=ManagementApiFacade | |||
mockery --dir=./pkg/reaper --output=./pkg/mocks --name=Manager --filename=reaper_manager.go --structname=ReaperManager | |||
|
|||
PHONY: protobuf-code-gen | |||
protobuf-code-gen: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that the protobuf compiler is installed. It would be really nice if the Makefile automatically installed the binary into the bin
dir like it does with some other tools. That could make for a follow up enhancement. For now how about a comment pointing to some docs for installing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a comment on line 443
Failed []string `json:"failed,omitempty"` | ||
} | ||
|
||
type TaskResult struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of this. I think it obscures the code. From a maintenance perspective for example, how am I supposed to know that a particular field is just empty at a given point in time vs empty because it isn't used for the given task type. How about some comments to explain which fields are used for which task types?
@@ -18,20 +18,13 @@ package v1alpha1 | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we intend to replace this api with medusabackup_types, then I think we need to discuss how we want to handle breaking API changes. That is not a discussion that I want to have in the PR review 😄 Do you think though that some comments should be added to indicate that the API is deprecated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very much so. I had looked in the kubebuilder book if there was any annotation we could put to mark stuff as deprecated, with no luck.
But I just came across this: kubernetes-sigs/kubebuilder#2116
Turns out the annotation exists but isn't documented: //+kubebuilder:deprecatedversion:warning="example.com/v1alpha1 CronTab is deprecated"
I'll use this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect 🙂
d9da468
to
f81b6db
Compare
@jsanda, I've made the requested changes. Let me know what you think. Also, I'd like to point out I've removed the backup name in the spec of MedusaBackupJob as I feel it's redundant with the |
} | ||
|
||
func (r *MedusaBackupJobReconciler) createMedusaBackup(ctx context.Context, backup *medusav1alpha1.MedusaBackupJob, logger logr.Logger) error { | ||
// Create a prepare_restore medusa task to create the mapping files in each pod. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this comment is in the wrong place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch 👍
} | ||
} | ||
|
||
func (r *MedusaTaskReconciler) getCassandraDatacenterPods(ctx context.Context, cassdc *cassdcapi.CassandraDatacenter, logger logr.Logger) ([]corev1.Pod, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is identical to the getCassandraDatacenterPods
in medusabackupjob_controller.go. Can we refactor this into a common, shared method or func?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good
} | ||
|
||
func (r *MedusaTaskReconciler) purgeOperation(ctx context.Context, task *medusav1alpha1.MedusaTask, pods []corev1.Pod, logger logr.Logger) (reconcile.Result, error) { | ||
logger.Info("Starting purge operations") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to include the datacenter name in log statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
k, I need to move the logging further in the code so that we can access the info after reading the task object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about adding the key/value pair to the logger in the Reconcile method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea. Done in my upcoming commit 👍
} | ||
|
||
func (r *MedusaTaskReconciler) prepareRestoreOperation(ctx context.Context, task *medusav1alpha1.MedusaTask, pods []corev1.Pod, logger logr.Logger) (reconcile.Result, error) { | ||
logger.Info("Starting prepare restore operations") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to include the datacenter name in log statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
backupMutex := sync.Mutex{} | ||
patch := client.MergeFrom(task.DeepCopy()) | ||
|
||
for _, p := range pods { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The control flow in this loop in nearly identical to the control flow in purgeOperation
as well as in MedusaBackupJobReconciler.Reconcile
. The status updates appear to be the same as well. I realize that in one case we are updating a MedusaTaskStatus and in other case we are updating a MedusaBackupJobStatus. How about moving the common fields into shared, common struct? And maybe define an interface through which the status updates are made. Both MedusaTaskStatus and MedusaBackupJobStatus would implement the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry but I'm not entirely convinced of the benefits of doing so. Statuses between both objects are fairly different even if there are common fields.
Also, that would apply to MedusaBackupJob as well, which uses the same control flow as well.
I could see some value if we treated restore jobs and backup jobs as a tasks, and used a common reconcile loop for all, but that's not what your suggesting.
Am I missing your point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to include my main point 🤦♂️ The control logic is duplicated in several places. It would be good to look at deduplicating it. Doing so would handling both MedusaTaskStatus and MedusaBackupJobStatus, hence my suggestions for the common struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adejanovski I pushed some initial changes here to illustrate what I had in mind. I introduced a executePodOperations
which encapsulates all of the control flow and goroutines for making calls against individual pods. Some more work is needed to handle the additional status updates in purgeOperation
. With the status-related refactoring I mentioned in my earlier comment you should be able reuse this in the backup controller as well.
To be clear though, my main observation is that the control flow for executing operations against pod is duplicated in multiple places and should be refactored to eliminate the duplication. A lot of that deduplication however could be done without the status-related changes I suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I've refactored the code using your experiment code. Haven't pushed it as far as refactoring the status because I'd like to work on using common code between task and backup operations in a subsequent PR.
There's a lot being done here already IMO and we should scope this out.
I'll create a ticket for this if you agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delayed response. Follow up ticket works for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I've create the issue: #541
podList := &corev1.PodList{} | ||
labels := client.MatchingLabels{cassdcapi.DatacenterLabel: cassdc.Name} | ||
if err := r.List(ctx, podList, labels); err != nil { | ||
logger.Error(err, "failed to get pods for cassandradatacenter", "CassandraDatacenter", cassdc.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the Wrap
function from github.com/pkg/errors
to get a stacktrace in the output. Instead of the logger.Error
call, you could just do
return nil, errors.Wrap(err, fmt.Sprintf("failed to get pods for cassandradatacenter %s", cassdc.Name))
The output will include a stacktrace from the point where Wrap
is called.
} | ||
|
||
// Prepare the restore by placing a mapping file in the Cassandra data volume. | ||
if !request.RestoreJob.Status.RestorePrepared { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this for an in-place restore do we? I haven't tested but if I recall I want to say it is fine to do; however, that is a good bit of overhead that could otherwise be avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is that at this stage, we don't know yet if it's an in place restore or not. That distinction was removed from the MedusaRestoreJob CRD as it is computed by Medusa itself... during the prepare restore task.
In our future design, it's the operator that will compute it based on the host names (if they match between the source and target clusters, then it's in place).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In our future design, it's the operator that will compute it based on the host names
Will these changes be included in this PR or a follow up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up PR, in this PR Medusa computes the mappings, including the in place/remote nature.
err := f.Client.Create(ctx, kc) | ||
require.NoError(err, "failed to create K8ssandraCluster") | ||
|
||
reconcileReplicatedSecret(ctx, t, f, kc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
medusaClientFactory = NewMedusaClientFactory() | ||
|
||
err := testEnv.Start(ctx, t, func(controlPlaneMgr manager.Manager, clientCache *clientcache.ClientCache, clusters []cluster.Cluster) error { | ||
err := (&k8ssandractrl.K8ssandraClusterReconciler{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize we are already creating and deploying the K8ssandraCluster controller in main, but you shouldn't be. The integration tests are structured in a way to facilitate testing controllers in isolation. I created #538 to address this. It can be done a separate, follow up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that's a lack of knowledge on my side, sorry. I couldn't figure out how to isolate them properly in the tests.
medusaClientFactory = NewMedusaClientFactory() | ||
|
||
err := testEnv.Start(ctx, t, func(controlPlaneMgr manager.Manager, clientCache *clientcache.ClientCache, clusters []cluster.Cluster) error { | ||
err := (&k8ssandractrl.K8ssandraClusterReconciler{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per #538 there shouldn't be any need to deploy the K8ssandraCluster controller for backup tests. You should only deploy the backup controller.
} | ||
|
||
for _, env := range testEnv.GetDataPlaneEnvTests() { | ||
dataPlaneMgr, err := ctrl.NewManager(env.Config, ctrl.Options{Scheme: scheme.Scheme}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per #538 there shouldn't be any need to deploy any other controller besides the task controller for task integration tests. Considering that the tests are already setup this way in main, I'm fine with addressing in a follow up PR.
return false | ||
} | ||
|
||
return !updated.Status.FinishTime.IsZero() && updated.Status.Finished[0].NbBackupsPurged == 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also verify that Finished
has 3 elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
// Schedule a sync if the task is a purge | ||
if task.Spec.Operation == medusav1alpha1.OperationTypePurge { | ||
r.scheduleSyncForPurge(task) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to handle the error returned here.
Something else I thought about while thinking about this part of the code is cleanup. We need an automatic cleanup of completed tasks. This is done for the Cassandra tasks in cass-operator.
If the CassandraDacenter on which the operation was performed is deleted, should the corresponding tasks be deleted as well? I am inclined to say yes. If we are in agreement on that then we need either owner references or some new finalizer logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're very right. Also, it's not a sync anymore as I've changed this part into directly creating the MedusaBackup object instead.
I'll rework that part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confusing with some code from the backup job controller. This is still necessary and indeed I need to handle the errors.
b6166b6
to
5c9e762
Compare
@@ -36,6 +36,22 @@ func createSingleMedusa(t *testing.T, ctx context.Context, namespace string, f * | |||
verifyRestoreFinished(t, ctx, f, dcKey, backupKey) | |||
} | |||
|
|||
func createSingleMedusaJob(t *testing.T, ctx context.Context, namespace string, f *framework.E2eFramework) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize that the setup will be a bit more involved that existing tests, but we should have a e2e test that covers remote restores. Would you create a follow up ticket for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, &ctrl.Result{RequeueAfter: 10 * time.Second}, err | ||
} | ||
|
||
backup := &api.MedusaBackup{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part needs to be updated. I hit this error when I tried restoring to a new cluster. I had to manually create a sync task to avoid this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's the process actually. Did you expect the sync to be done automatically?
Backups won't get synced unless you run a sync
MedusaTask, then you'll have them created as MedusaBackup CR which can be restored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I misread the code last night and thought that the restore controller created the sync task as a preliminary step. It creates the prepare restore task. I'm good here.
// Create the sync task | ||
prepare = &medusav1alpha1.MedusaTask{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: request.RestoreJob.Status.RestoreKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change the task name to something more descriptive, maybe something like <cluster-name>-<dc-name>-prepare-restore
? Seeing task objects with UUIDs for their names isn't very helpful. Alternatively we could update the CRD to include the operation type and cassdc name in the output for kubectl get
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be fair, it'll be created in the namespace of the cluster that's being restored.
If multiple restores are done, we need a name that distinguish them, and using the restore key seemed like a good idea.
I'm afraid that we may create names that are too long if we stuff in things like the cluster name 😕
Alternatively we could update the CRD to include the operation type and cassdc name in the output for kubectl get.
Would you add that in the status of the CRD?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid that we may create names that are too long if we stuff in things like the cluster name
Definitely a valid concern.
Would you add that in the status of the CRD?
Not in the status. There is a kubebuilder:printcolumn:name
annotation. See https://github.com/k8ssandra/k8ssandra-operator/blob/main/apis/stargate/v1alpha1/stargate_types.go#L283 for examples. Would make for a nice follow up enhancement. I don't think it's needed for this PR.
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. | ||
|
||
// MedusaRestoreJobSpec defines the desired state of MedusaRestoreJob | ||
type MedusaRestoreJobSpec struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For remote restores shouldn't we allow the bucket from which we are restoring to be specified separately instead of just reusing what is specified for backups? I am thinking that I have some CI/CD pipeline where I provision clusters from a known, good backup I would probably want to keep their storage buckets separate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only thing we could do with the way Medusa is currently designed, is to specify a different prefix for the sync and the restore, which would then be specified at sync and restore time. It probably means that the MedusaBackup object should contain that prefix in the spec.
This is part of a follow up ticket: #453
b2fd6b5
to
a3d2bf8
Compare
@@ -66,6 +66,7 @@ jobs: | |||
- CreateMultiReaper | |||
- ClusterScoped/MultiDcMultiCluster | |||
- CreateMultiMedusa |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateMultiMedusa
is matching both CreateMultiMedusaOld
and CreateMultiMedusaJob
, so both tests are being run and CreateMultiMedusaJob
is being run twice. It should be changed to CreateMultiMedusaOld
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn, I was pretty sure I fixed this already 🤔
Good catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm gonna go ahead and approve so we don't have to wait until I am back online. I spent some time investigating the e2e test failure, and I think we are good to go with a small change. @adejanovski well done 👏
29f6967
to
8bcd162
Compare
… implement remote restore. The PrepareRestore grpc call will have Medusa compute the mapping between the backup and restore clusters, and store it in the local storage. This will allow the restore init container to know which mapping should be performed, without having Cassandra up. Disable ip address resolving in Medusa to allow restore mappings.
6b1f1b8
to
b14b4ac
Compare
What this PR does:
Allow restoring a backup from another cluster (including backups created with Medusa outside of K8ssandra).
Requires these medusa PR to be merged first, and a Medusa release so that we can update the tag for the medusa image: 448 and 449.
The design is fully documented in
docs/medusa/remote-restore-design.md
.Which issue(s) this PR fixes:
Fixes #450
Fixes #451
Fixes #452
Checklist