feat(linstor): bump linstor-csi to v1.10.6 with Protocol-C dual-attach fix
Bump linstor-csi to v1.10.6 and add an out-of-tree patch that overrides DrbdOptions/Net/protocol to C on the resource-definition whenever a volume is attached to a second node with allow-two-primaries (the live-migration flow), and reverts it on detach. Without the patch, any KubeVirt live migration of a VM whose volume sits on a Protocol-A/B resource (for example a "replicated-async" StorageClass) ends up in a permanent evacuation loop: drbdadm adjust on the satellite rejects allow-two-primaries with "Protocol C required" (errno 139), the migration fails, and KubeVirt retries until manual intervention. The override is set at the resource-definition level so it covers every connection, including diskless TieBreaker peers, and is tagged with an Aux marker so Detach reverts only the override we installed. The 001 relocate-after-clone patch was regenerated against v1.10.6 (context shift only, no logic change). Verified end-to-end on dev5: live migration of a KubeVirt VM whose PVC sits on a Protocol-A resource-group now succeeds in a single Attach. Patch is upstreamed as draft PR piraeusdatastore/linstor-csi#435. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
This commit is contained in:
parent
52edffbb9a
commit
073fb1630d
4 changed files with 140 additions and 58 deletions
|
|
@ -5,7 +5,7 @@ include ../../../hack/common-envs.mk
|
|||
include ../../../hack/package.mk
|
||||
|
||||
LINSTOR_VERSION ?= 1.33.2
|
||||
LINSTOR_CSI_VERSION ?= v1.10.5
|
||||
LINSTOR_CSI_VERSION ?= v1.10.6
|
||||
|
||||
image: image-piraeus-server image-linstor-csi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
FROM golang:1.25 AS builder
|
||||
|
||||
ARG VERSION=v1.10.5
|
||||
ARG VERSION=v1.10.6
|
||||
ARG LINSTOR_WAIT_UNTIL_VERSION=v0.3.1
|
||||
ARG TARGETARCH
|
||||
ARG TARGETOS
|
||||
|
|
|
|||
|
|
@ -1,25 +1,7 @@
|
|||
diff --git a/pkg/client/linstor.go b/pkg/client/linstor.go
|
||||
index f544493..98e7fde 100644
|
||||
index ac4651d..46d565d 100644
|
||||
--- a/pkg/client/linstor.go
|
||||
+++ b/pkg/client/linstor.go
|
||||
@@ -181,7 +181,7 @@ func LogLevel(s string) func(*Linstor) error {
|
||||
func (s *Linstor) ListAllWithStatus(ctx context.Context) ([]volume.VolumeStatus, error) {
|
||||
var vols []volume.VolumeStatus
|
||||
|
||||
- resourcesByName := make(map[string][]lapi.Resource)
|
||||
+ resourcesByName := make(map[string][]lapi.ResourceWithVolumes)
|
||||
|
||||
resDefs, err := s.client.ResourceDefinitions.GetAll(ctx, lapi.RDGetAllRequest{WithVolumeDefinitions: true})
|
||||
if err != nil {
|
||||
@@ -194,7 +194,7 @@ func (s *Linstor) ListAllWithStatus(ctx context.Context) ([]volume.VolumeStatus,
|
||||
}
|
||||
|
||||
for i := range allResources {
|
||||
- resourcesByName[allResources[i].Name] = append(resourcesByName[allResources[i].Name], allResources[i].Resource)
|
||||
+ resourcesByName[allResources[i].Name] = append(resourcesByName[allResources[i].Name], allResources[i])
|
||||
}
|
||||
|
||||
for _, rd := range resDefs {
|
||||
@@ -462,6 +462,14 @@ func (s *Linstor) Clone(ctx context.Context, vol, src *volume.Info, params *volu
|
||||
return err
|
||||
}
|
||||
|
|
@ -35,7 +17,7 @@ index f544493..98e7fde 100644
|
|||
logger.Debug("reconcile extra properties")
|
||||
|
||||
err = s.client.ResourceDefinitions.Modify(ctx, vol.ID, lapi.GenericPropsModify{OverrideProps: vol.Properties})
|
||||
@@ -1280,6 +1288,14 @@ func (s *Linstor) VolFromSnap(ctx context.Context, snap *volume.Snapshot, vol *v
|
||||
@@ -1297,6 +1305,14 @@ func (s *Linstor) VolFromSnap(ctx context.Context, snap *volume.Snapshot, vol *v
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +32,7 @@ index f544493..98e7fde 100644
|
|||
logger.Debug("reconcile extra properties")
|
||||
|
||||
err = s.client.ResourceDefinitions.Modify(ctx, vol.ID, lapi.GenericPropsModify{OverrideProps: vol.Properties})
|
||||
@@ -1470,9 +1486,8 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
|
||||
@@ -1487,9 +1503,8 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
|
||||
return fmt.Errorf("snapshot '%s' not deployed on any node", snap.Name)
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +44,7 @@ index f544493..98e7fde 100644
|
|||
for _, snapNode := range snap.Nodes {
|
||||
if err := s.NodeAvailable(ctx, snapNode); err != nil {
|
||||
logger.WithField("selected node candidate", snapNode).WithError(err).Debug("node is not available")
|
||||
@@ -1480,13 +1495,23 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
|
||||
@@ -1497,13 +1512,23 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
|
||||
}
|
||||
|
||||
if slices.Contains(preferredNodes, snapNode) {
|
||||
|
|
@ -92,7 +74,7 @@ index f544493..98e7fde 100644
|
|||
}
|
||||
|
||||
if selectedNode == "" {
|
||||
@@ -1679,6 +1704,114 @@ func (s *Linstor) reconcileResourcePlacement(ctx context.Context, vol *volume.In
|
||||
@@ -1696,6 +1721,114 @@ func (s *Linstor) reconcileResourcePlacement(ctx context.Context, vol *volume.In
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -207,40 +189,8 @@ index f544493..98e7fde 100644
|
|||
// FindSnapsByID searches the snapshot in the backend
|
||||
func (s *Linstor) FindSnapsByID(ctx context.Context, id string) ([]*volume.Snapshot, error) {
|
||||
snapshotId, err := volume.ParseSnapshotId(id)
|
||||
@@ -2173,7 +2306,7 @@ func (s *Linstor) Status(ctx context.Context, volId string) ([]string, *csi.Volu
|
||||
"volume": volId,
|
||||
}).Debug("getting assignments")
|
||||
|
||||
- ress, err := s.client.Resources.GetAll(ctx, volId)
|
||||
+ ress, err := s.client.Resources.GetResourceView(ctx, &lapi.ListOpts{Resource: []string{volId}})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to list resources for '%s': %w", volId, err)
|
||||
}
|
||||
@@ -2261,13 +2394,20 @@ func GetSnapshotRemoteAndReadiness(snap *lapi.Snapshot) (string, bool, error) {
|
||||
return "", slices.Contains(snap.Flags, lapiconsts.FlagSuccessful), nil
|
||||
}
|
||||
|
||||
-func NodesAndConditionFromResources(ress []lapi.Resource) ([]string, *csi.VolumeCondition) {
|
||||
+func NodesAndConditionFromResources(ress []lapi.ResourceWithVolumes) ([]string, *csi.VolumeCondition) {
|
||||
var allNodes, abnormalNodes []string
|
||||
|
||||
for i := range ress {
|
||||
res := &ress[i]
|
||||
|
||||
- allNodes = append(allNodes, res.NodeName)
|
||||
+ // A resource is a CSI publish target if any of its volumes were created
|
||||
+ // by ControllerPublishVolume, identified by the temporary-diskless-attach property.
|
||||
+ if slices.ContainsFunc(res.Volumes, func(v lapi.Volume) bool {
|
||||
+ createdFor, ok := v.Props[linstor.PropertyCreatedFor]
|
||||
+ return ok && createdFor == linstor.CreatedForTemporaryDisklessAttach
|
||||
+ }) {
|
||||
+ allNodes = append(allNodes, res.NodeName)
|
||||
+ }
|
||||
|
||||
if res.State == nil {
|
||||
abnormalNodes = append(abnormalNodes, res.NodeName)
|
||||
diff --git a/pkg/volume/parameter.go b/pkg/volume/parameter.go
|
||||
index 39acd95..aed18ab 100644
|
||||
index 39acd95..54d1dfb 100644
|
||||
--- a/pkg/volume/parameter.go
|
||||
+++ b/pkg/volume/parameter.go
|
||||
@@ -50,6 +50,7 @@ const (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
diff --git a/pkg/client/linstor.go b/pkg/client/linstor.go
|
||||
index ac4651d..9d75c79 100644
|
||||
--- a/pkg/client/linstor.go
|
||||
+++ b/pkg/client/linstor.go
|
||||
@@ -653,11 +653,35 @@ func (s *Linstor) Attach(ctx context.Context, volId, node string, rwxBlock bool)
|
||||
}
|
||||
|
||||
if otherResInUse > 0 && rwxBlock {
|
||||
- rdPropsModify := lapi.GenericPropsModify{OverrideProps: map[string]string{
|
||||
+ rdProps := map[string]string{
|
||||
linstor.PropertyAllowTwoPrimaries: "yes",
|
||||
- }}
|
||||
+ }
|
||||
|
||||
- err = s.client.ResourceDefinitions.Modify(ctx, volId, rdPropsModify)
|
||||
+ // DRBD requires Protocol C whenever allow-two-primaries is enabled.
|
||||
+ // If the resource is configured with Protocol A or B (typically
|
||||
+ // through its resource-group), drbdadm adjust on the satellites
|
||||
+ // fails with "Protocol C required" (errno 139) and live migration
|
||||
+ // cannot proceed. Override Protocol to C on the resource-definition
|
||||
+ // itself so it applies to every connection (including diskless
|
||||
+ // peers, e.g. a TieBreaker). The marker lets Detach revert exactly
|
||||
+ // the override we installed without disturbing operator-set props.
|
||||
+ proto, protoErr := s.getEffectiveDrbdProtocol(ctx, volId)
|
||||
+ if protoErr != nil {
|
||||
+ s.log.WithError(protoErr).WithField("volume", volId).
|
||||
+ Warn("failed to determine effective DRBD protocol; skipping Protocol=C override for dual-attach")
|
||||
+ } else if proto == "A" || proto == "B" {
|
||||
+ s.log.WithFields(logrus.Fields{
|
||||
+ "volume": volId,
|
||||
+ "protocol": proto,
|
||||
+ }).Info("installing Protocol=C override on resource-definition for dual-attach")
|
||||
+
|
||||
+ rdProps[linstor.PropertyDrbdNetProtocol] = "C"
|
||||
+ rdProps[linstor.PropertyCsiProtocolOverride] = "yes"
|
||||
+ }
|
||||
+
|
||||
+ err = s.client.ResourceDefinitions.Modify(ctx, volId, lapi.GenericPropsModify{
|
||||
+ OverrideProps: rdProps,
|
||||
+ })
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -774,11 +798,26 @@ func (s *Linstor) Detach(ctx context.Context, volId, node string) error {
|
||||
}
|
||||
|
||||
if resInUse == 1 {
|
||||
- rdPropsModify := lapi.GenericPropsModify{DeleteProps: []string{
|
||||
- linstor.PropertyAllowTwoPrimaries,
|
||||
- }}
|
||||
+ deleteProps := []string{linstor.PropertyAllowTwoPrimaries}
|
||||
+
|
||||
+ // Drop the Protocol=C override only if Attach installed it (gated by
|
||||
+ // our marker), so an operator-set Protocol property on the
|
||||
+ // resource-definition is preserved.
|
||||
+ rd, getErr := s.client.ResourceDefinitions.Get(ctx, volId)
|
||||
+ if getErr != nil {
|
||||
+ log.WithError(getErr).Warn("failed to fetch resource-definition props; skipping Protocol=C override removal")
|
||||
+ } else if rd.Props[linstor.PropertyCsiProtocolOverride] != "" {
|
||||
+ log.Info("removing Protocol=C override from resource-definition")
|
||||
|
||||
- err = s.client.ResourceDefinitions.Modify(ctx, volId, rdPropsModify)
|
||||
+ deleteProps = append(deleteProps,
|
||||
+ linstor.PropertyDrbdNetProtocol,
|
||||
+ linstor.PropertyCsiProtocolOverride,
|
||||
+ )
|
||||
+ }
|
||||
+
|
||||
+ err = s.client.ResourceDefinitions.Modify(ctx, volId, lapi.GenericPropsModify{
|
||||
+ DeleteProps: deleteProps,
|
||||
+ })
|
||||
if err != nil {
|
||||
return nil404(err)
|
||||
}
|
||||
@@ -805,6 +844,33 @@ func (s *Linstor) Detach(ctx context.Context, volId, node string) error {
|
||||
return nil404(s.client.Resources.Delete(ctx, volId, node))
|
||||
}
|
||||
|
||||
+// getEffectiveDrbdProtocol returns the DRBD network protocol ("A", "B" or "C")
|
||||
+// that LINSTOR will hand to drbdadm for the given resource. Resource-definition
|
||||
+// properties take precedence; otherwise the value is inherited from the
|
||||
+// resource-group. An empty string is returned when neither level sets the
|
||||
+// property (LINSTOR's compiled-in default is "C").
|
||||
+func (s *Linstor) getEffectiveDrbdProtocol(ctx context.Context, volId string) (string, error) {
|
||||
+ rd, err := s.client.ResourceDefinitions.Get(ctx, volId)
|
||||
+ if err != nil {
|
||||
+ return "", err
|
||||
+ }
|
||||
+
|
||||
+ if v, ok := rd.Props[linstor.PropertyDrbdNetProtocol]; ok {
|
||||
+ return v, nil
|
||||
+ }
|
||||
+
|
||||
+ if rd.ResourceGroupName == "" {
|
||||
+ return "", nil
|
||||
+ }
|
||||
+
|
||||
+ rg, err := s.client.ResourceGroups.Get(ctx, rd.ResourceGroupName)
|
||||
+ if err != nil {
|
||||
+ return "", err
|
||||
+ }
|
||||
+
|
||||
+ return rg.Props[linstor.PropertyDrbdNetProtocol], nil
|
||||
+}
|
||||
+
|
||||
// CapacityBytes returns the amount of free space in the storage pool specified by the params and topology.
|
||||
func (s *Linstor) CapacityBytes(ctx context.Context, storagePools []string, overProvision *float64, segments map[string]string) (int64, error) {
|
||||
log := s.log.WithField("storage-pools", storagePools).WithField("segments", segments)
|
||||
diff --git a/pkg/linstor/const.go b/pkg/linstor/const.go
|
||||
index 9a5f79c..c8bc9c3 100644
|
||||
--- a/pkg/linstor/const.go
|
||||
+++ b/pkg/linstor/const.go
|
||||
@@ -44,6 +44,19 @@ const (
|
||||
// PropertyAllowTwoPrimaries is DRBD option to allow second primary. Mainly used for live-migration.
|
||||
PropertyAllowTwoPrimaries = lc.NamespcDrbdNetOptions + "/allow-two-primaries"
|
||||
|
||||
+ // PropertyDrbdNetProtocol is the DRBD network replication protocol option
|
||||
+ // (A = async, B = semi-sync, C = sync). DRBD requires Protocol C whenever
|
||||
+ // allow-two-primaries is enabled, otherwise drbdadm adjust fails with
|
||||
+ // "Protocol C required" (errno 139).
|
||||
+ PropertyDrbdNetProtocol = lc.NamespcDrbdNetOptions + "/protocol"
|
||||
+
|
||||
+ // PropertyCsiProtocolOverride marks a resource-connection where this driver
|
||||
+ // has installed a temporary Protocol=C override to make a Protocol-A/B
|
||||
+ // resource compatible with allow-two-primaries during live migration. The
|
||||
+ // marker lets us safely remove only the overrides we set, without touching
|
||||
+ // connection-level overrides installed by the operator.
|
||||
+ PropertyCsiProtocolOverride = lc.NamespcAuxiliary + "/csi-protocol-override"
|
||||
+
|
||||
// CreatedForTemporaryDisklessAttach marks a resource as temporary, i.e. it should be removed after it is no longer
|
||||
// needed.
|
||||
CreatedForTemporaryDisklessAttach = "temporary-diskless-attach"
|
||||
Loading…
Add table
Add a link
Reference in a new issue