Skip to content
Merged
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
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ the actual replication itself.
- **Automated Lifecycle**: Automatically creates `VolumeReplication` objects for PVCs with the appropriate annotation.
- **Inheritance**: Can inherit the `VolumeReplicationClass` from the PVC or from the PVC's namespace (if not specified on the PVC).
- **Exclusion by Name**: Supports excluding PVCs from replication using a global regular expression.
- **Pause**: Supports pausing replication on a per-PVC or per-namespace basis via an annotation, freezing `VolumeReplication` objects in place.
- **VRC Selector**: Supports selecting a `VolumeReplicationClass` using a selector, allowing for more dynamic configuration based on `StorageClass` groups.
- **Cleanup**: Automatically deletes `VolumeReplication` resources when their parent PVC is deleted or when the replication annotation is removed.
- **Leader Election**: Supports high availability with leader election to ensure only one instance is active at a time.
Expand Down Expand Up @@ -148,6 +149,39 @@ If the annotation is modified, the `VolumeReplication` is updated accordingly wi

If the annotation is deleted on both the PVC and the namespace, the VolumeReplication is deleted.

### Pausing replication

Replication can be paused for a specific PVC or for an entire namespace using the `replication.superphenix.net/pause: "true"` annotation. In both cases, the controller skips creating or updating `VolumeReplication` objects, but it **still deletes** the `VolumeReplication` when the PVC itself is deleted.

Any other value (including `"false"` or the absence of the annotation) means replication is not paused.

The annotation on the PVC takes precedence over the annotation on the namespace, so a PVC can opt back into normal reconciliation by setting `replication.superphenix.net/pause: "false"` even if its namespace is paused.

Example — pausing an entire namespace:

```yaml
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
annotations:
replication.superphenix.net/pause: "true"
```

Example — pausing a single PVC:

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
annotations:
replication.superphenix.net/pause: "true"
```

> [!NOTE]
> While paused, existing `VolumeReplication` objects are frozen in their current state. Unpausing resumes normal reconciliation.

### Excluding PVCs from replication

It is possible to exclude some PVCs from being replicated, even if they have the correct annotations (or their namespace has them).
Expand Down Expand Up @@ -177,7 +211,7 @@ Standard `klog` flags are also supported for logging configuration.

### Prerequisites

- Go 1.22+
- Go 1.25+
- Docker (optional, for containerized builds)

### Building the binary
Expand Down
4 changes: 2 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"os"
"os/signal"

"github.com/skalanetworks/volume-replicator/internal/k8s"
"github.com/skalanetworks/volume-replicator/internal/replicator"
"github.com/super-phenix/volume-replicator/internal/k8s"
"github.com/super-phenix/volume-replicator/internal/replicator"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/klog/v2"
)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/skalanetworks/volume-replicator
module github.com/super-phenix/volume-replicator

go 1.25.3

Expand Down
1 change: 1 addition & 0 deletions internal/constants/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const (
LockName = "spx-volume-replicator-leader-election"
VrcValueAnnotation = "replication.superphenix.net/class"
VrcSelectorAnnotation = "replication.superphenix.net/classSelector"
PauseAnnotation = "replication.superphenix.net/pause"
ParentLabel = "replication.superphenix.net/parent"
StorageClassGroup = "replication.superphenix.net/storageClassGroup"
StorageProvisionerAnnotation = "volume.kubernetes.io/storage-provisioner"
Expand Down
7 changes: 4 additions & 3 deletions internal/k8s/lease.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package k8s

import (
"context"
"github.com/skalanetworks/volume-replicator/internal/constants"
"os"
"time"

"github.com/super-phenix/volume-replicator/internal/constants"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"k8s.io/klog/v2"
"os"
"time"
)

// GetLease returns a Kubernetes lease object
Expand Down
2 changes: 1 addition & 1 deletion internal/replicator/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package replicator
import (
"reflect"

"github.com/skalanetworks/volume-replicator/internal/constants"
"github.com/super-phenix/volume-replicator/internal/constants"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
Expand Down
4 changes: 2 additions & 2 deletions internal/replicator/informers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"time"

"github.com/skalanetworks/volume-replicator/internal/k8s"
"github.com/super-phenix/volume-replicator/internal/k8s"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -71,7 +71,7 @@ func (c *Controller) createPvcInformer(factory informers.SharedInformerFactory)
UpdateFunc: func(_, newObj any) {
c.pvcUpdate(newObj.(*corev1.PersistentVolumeClaim))
},
DeleteFunc: func(obj interface{}) {
DeleteFunc: func(obj any) {
c.pvcUpdate(obj.(*corev1.PersistentVolumeClaim))
},
})
Expand Down
11 changes: 10 additions & 1 deletion internal/replicator/replicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (c *Controller) Run(ctx context.Context, workers int) {
defer c.pvcQueue.ShutDown()
klog.Info("Starting replication controller")

for i := 0; i < workers; i++ {
for range workers {
go wait.UntilWithContext(ctx, c.runWorker, time.Second)
}

Expand Down Expand Up @@ -98,6 +98,12 @@ func reconcileVolumeReplication(key string) {
return
}

// Both PVC-level and namespace-level pause skip create/update
if isPvcPaused(pvc, namespace) {
klog.Infof("PVC %s is paused, skipping reconciliation", key)
return
}

// Retrieve the VRC that should apply to this PVC
replicationClass := getVolumeReplicationClass(pvc)
if replicationClass != "" {
Expand All @@ -115,6 +121,9 @@ func reconcileVolumeReplication(key string) {

if !vrcExists || !vrCorrect {
klog.Infof("deleting VolumeReplication %s as it doesn't conform anymore, vrcExists(%t), vrCorrect(%t)", key, vrcExists, vrCorrect)

// If we're meant to update the VolumeReplication (!vrCorrect), we delete it here, and it will trigger an
// event that will bring us back in this function to re-create it with the correct definition
cleanupVolumeReplication(name, namespace)
return
}
Expand Down
Loading
Loading