Skip to content

Commit 073fb16

Browse files
kvapsclaude
andcommitted
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 <[email protected]> Signed-off-by: Andrei Kvapil <[email protected]>
1 parent 52edffb commit 073fb16

4 files changed

Lines changed: 140 additions & 58 deletions

File tree

‎packages/system/linstor/Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ include ../../../hack/common-envs.mk
55
include ../../../hack/package.mk
66

77
LINSTOR_VERSION ?= 1.33.2
8-
LINSTOR_CSI_VERSION ?= v1.10.5
8+
LINSTOR_CSI_VERSION ?= v1.10.6
99

1010
image: image-piraeus-server image-linstor-csi
1111

‎packages/system/linstor/images/linstor-csi/Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM golang:1.25 AS builder
22

3-
ARG VERSION=v1.10.5
3+
ARG VERSION=v1.10.6
44
ARG LINSTOR_WAIT_UNTIL_VERSION=v0.3.1
55
ARG TARGETARCH
66
ARG TARGETOS

‎packages/system/linstor/images/linstor-csi/patches/001-relocate-after-clone-restore.diff‎

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,7 @@
11
diff --git a/pkg/client/linstor.go b/pkg/client/linstor.go
2-
index f544493..98e7fde 100644
2+
index ac4651d..46d565d 100644
33
--- a/pkg/client/linstor.go
44
+++ b/pkg/client/linstor.go
5-
@@ -181,7 +181,7 @@ func LogLevel(s string) func(*Linstor) error {
6-
func (s *Linstor) ListAllWithStatus(ctx context.Context) ([]volume.VolumeStatus, error) {
7-
var vols []volume.VolumeStatus
8-
9-
- resourcesByName := make(map[string][]lapi.Resource)
10-
+ resourcesByName := make(map[string][]lapi.ResourceWithVolumes)
11-
12-
resDefs, err := s.client.ResourceDefinitions.GetAll(ctx, lapi.RDGetAllRequest{WithVolumeDefinitions: true})
13-
if err != nil {
14-
@@ -194,7 +194,7 @@ func (s *Linstor) ListAllWithStatus(ctx context.Context) ([]volume.VolumeStatus,
15-
}
16-
17-
for i := range allResources {
18-
- resourcesByName[allResources[i].Name] = append(resourcesByName[allResources[i].Name], allResources[i].Resource)
19-
+ resourcesByName[allResources[i].Name] = append(resourcesByName[allResources[i].Name], allResources[i])
20-
}
21-
22-
for _, rd := range resDefs {
235
@@ -462,6 +462,14 @@ func (s *Linstor) Clone(ctx context.Context, vol, src *volume.Info, params *volu
246
return err
257
}
@@ -35,7 +17,7 @@ index f544493..98e7fde 100644
3517
logger.Debug("reconcile extra properties")
3618

3719
err = s.client.ResourceDefinitions.Modify(ctx, vol.ID, lapi.GenericPropsModify{OverrideProps: vol.Properties})
38-
@@ -1280,6 +1288,14 @@ func (s *Linstor) VolFromSnap(ctx context.Context, snap *volume.Snapshot, vol *v
20+
@@ -1297,6 +1305,14 @@ func (s *Linstor) VolFromSnap(ctx context.Context, snap *volume.Snapshot, vol *v
3921
return err
4022
}
4123

@@ -50,7 +32,7 @@ index f544493..98e7fde 100644
5032
logger.Debug("reconcile extra properties")
5133

5234
err = s.client.ResourceDefinitions.Modify(ctx, vol.ID, lapi.GenericPropsModify{OverrideProps: vol.Properties})
53-
@@ -1470,9 +1486,8 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
35+
@@ -1487,9 +1503,8 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
5436
return fmt.Errorf("snapshot '%s' not deployed on any node", snap.Name)
5537
}
5638

@@ -62,7 +44,7 @@ index f544493..98e7fde 100644
6244
for _, snapNode := range snap.Nodes {
6345
if err := s.NodeAvailable(ctx, snapNode); err != nil {
6446
logger.WithField("selected node candidate", snapNode).WithError(err).Debug("node is not available")
65-
@@ -1480,13 +1495,23 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
47+
@@ -1497,13 +1512,23 @@ func (s *Linstor) reconcileSnapshotResources(ctx context.Context, snapshot *volu
6648
}
6749

6850
if slices.Contains(preferredNodes, snapNode) {
@@ -92,7 +74,7 @@ index f544493..98e7fde 100644
9274
}
9375

9476
if selectedNode == "" {
95-
@@ -1679,6 +1704,114 @@ func (s *Linstor) reconcileResourcePlacement(ctx context.Context, vol *volume.In
77+
@@ -1696,6 +1721,114 @@ func (s *Linstor) reconcileResourcePlacement(ctx context.Context, vol *volume.In
9678
return nil
9779
}
9880

@@ -207,40 +189,8 @@ index f544493..98e7fde 100644
207189
// FindSnapsByID searches the snapshot in the backend
208190
func (s *Linstor) FindSnapsByID(ctx context.Context, id string) ([]*volume.Snapshot, error) {
209191
snapshotId, err := volume.ParseSnapshotId(id)
210-
@@ -2173,7 +2306,7 @@ func (s *Linstor) Status(ctx context.Context, volId string) ([]string, *csi.Volu
211-
"volume": volId,
212-
}).Debug("getting assignments")
213-
214-
- ress, err := s.client.Resources.GetAll(ctx, volId)
215-
+ ress, err := s.client.Resources.GetResourceView(ctx, &lapi.ListOpts{Resource: []string{volId}})
216-
if err != nil {
217-
return nil, nil, fmt.Errorf("failed to list resources for '%s': %w", volId, err)
218-
}
219-
@@ -2261,13 +2394,20 @@ func GetSnapshotRemoteAndReadiness(snap *lapi.Snapshot) (string, bool, error) {
220-
return "", slices.Contains(snap.Flags, lapiconsts.FlagSuccessful), nil
221-
}
222-
223-
-func NodesAndConditionFromResources(ress []lapi.Resource) ([]string, *csi.VolumeCondition) {
224-
+func NodesAndConditionFromResources(ress []lapi.ResourceWithVolumes) ([]string, *csi.VolumeCondition) {
225-
var allNodes, abnormalNodes []string
226-
227-
for i := range ress {
228-
res := &ress[i]
229-
230-
- allNodes = append(allNodes, res.NodeName)
231-
+ // A resource is a CSI publish target if any of its volumes were created
232-
+ // by ControllerPublishVolume, identified by the temporary-diskless-attach property.
233-
+ if slices.ContainsFunc(res.Volumes, func(v lapi.Volume) bool {
234-
+ createdFor, ok := v.Props[linstor.PropertyCreatedFor]
235-
+ return ok && createdFor == linstor.CreatedForTemporaryDisklessAttach
236-
+ }) {
237-
+ allNodes = append(allNodes, res.NodeName)
238-
+ }
239-
240-
if res.State == nil {
241-
abnormalNodes = append(abnormalNodes, res.NodeName)
242192
diff --git a/pkg/volume/parameter.go b/pkg/volume/parameter.go
243-
index 39acd95..aed18ab 100644
193+
index 39acd95..54d1dfb 100644
244194
--- a/pkg/volume/parameter.go
245195
+++ b/pkg/volume/parameter.go
246196
@@ -50,6 +50,7 @@ const (
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
diff --git a/pkg/client/linstor.go b/pkg/client/linstor.go
2+
index ac4651d..9d75c79 100644
3+
--- a/pkg/client/linstor.go
4+
+++ b/pkg/client/linstor.go
5+
@@ -653,11 +653,35 @@ func (s *Linstor) Attach(ctx context.Context, volId, node string, rwxBlock bool)
6+
}
7+
8+
if otherResInUse > 0 && rwxBlock {
9+
- rdPropsModify := lapi.GenericPropsModify{OverrideProps: map[string]string{
10+
+ rdProps := map[string]string{
11+
linstor.PropertyAllowTwoPrimaries: "yes",
12+
- }}
13+
+ }
14+
15+
- err = s.client.ResourceDefinitions.Modify(ctx, volId, rdPropsModify)
16+
+ // DRBD requires Protocol C whenever allow-two-primaries is enabled.
17+
+ // If the resource is configured with Protocol A or B (typically
18+
+ // through its resource-group), drbdadm adjust on the satellites
19+
+ // fails with "Protocol C required" (errno 139) and live migration
20+
+ // cannot proceed. Override Protocol to C on the resource-definition
21+
+ // itself so it applies to every connection (including diskless
22+
+ // peers, e.g. a TieBreaker). The marker lets Detach revert exactly
23+
+ // the override we installed without disturbing operator-set props.
24+
+ proto, protoErr := s.getEffectiveDrbdProtocol(ctx, volId)
25+
+ if protoErr != nil {
26+
+ s.log.WithError(protoErr).WithField("volume", volId).
27+
+ Warn("failed to determine effective DRBD protocol; skipping Protocol=C override for dual-attach")
28+
+ } else if proto == "A" || proto == "B" {
29+
+ s.log.WithFields(logrus.Fields{
30+
+ "volume": volId,
31+
+ "protocol": proto,
32+
+ }).Info("installing Protocol=C override on resource-definition for dual-attach")
33+
+
34+
+ rdProps[linstor.PropertyDrbdNetProtocol] = "C"
35+
+ rdProps[linstor.PropertyCsiProtocolOverride] = "yes"
36+
+ }
37+
+
38+
+ err = s.client.ResourceDefinitions.Modify(ctx, volId, lapi.GenericPropsModify{
39+
+ OverrideProps: rdProps,
40+
+ })
41+
if err != nil {
42+
return "", err
43+
}
44+
@@ -774,11 +798,26 @@ func (s *Linstor) Detach(ctx context.Context, volId, node string) error {
45+
}
46+
47+
if resInUse == 1 {
48+
- rdPropsModify := lapi.GenericPropsModify{DeleteProps: []string{
49+
- linstor.PropertyAllowTwoPrimaries,
50+
- }}
51+
+ deleteProps := []string{linstor.PropertyAllowTwoPrimaries}
52+
+
53+
+ // Drop the Protocol=C override only if Attach installed it (gated by
54+
+ // our marker), so an operator-set Protocol property on the
55+
+ // resource-definition is preserved.
56+
+ rd, getErr := s.client.ResourceDefinitions.Get(ctx, volId)
57+
+ if getErr != nil {
58+
+ log.WithError(getErr).Warn("failed to fetch resource-definition props; skipping Protocol=C override removal")
59+
+ } else if rd.Props[linstor.PropertyCsiProtocolOverride] != "" {
60+
+ log.Info("removing Protocol=C override from resource-definition")
61+
62+
- err = s.client.ResourceDefinitions.Modify(ctx, volId, rdPropsModify)
63+
+ deleteProps = append(deleteProps,
64+
+ linstor.PropertyDrbdNetProtocol,
65+
+ linstor.PropertyCsiProtocolOverride,
66+
+ )
67+
+ }
68+
+
69+
+ err = s.client.ResourceDefinitions.Modify(ctx, volId, lapi.GenericPropsModify{
70+
+ DeleteProps: deleteProps,
71+
+ })
72+
if err != nil {
73+
return nil404(err)
74+
}
75+
@@ -805,6 +844,33 @@ func (s *Linstor) Detach(ctx context.Context, volId, node string) error {
76+
return nil404(s.client.Resources.Delete(ctx, volId, node))
77+
}
78+
79+
+// getEffectiveDrbdProtocol returns the DRBD network protocol ("A", "B" or "C")
80+
+// that LINSTOR will hand to drbdadm for the given resource. Resource-definition
81+
+// properties take precedence; otherwise the value is inherited from the
82+
+// resource-group. An empty string is returned when neither level sets the
83+
+// property (LINSTOR's compiled-in default is "C").
84+
+func (s *Linstor) getEffectiveDrbdProtocol(ctx context.Context, volId string) (string, error) {
85+
+ rd, err := s.client.ResourceDefinitions.Get(ctx, volId)
86+
+ if err != nil {
87+
+ return "", err
88+
+ }
89+
+
90+
+ if v, ok := rd.Props[linstor.PropertyDrbdNetProtocol]; ok {
91+
+ return v, nil
92+
+ }
93+
+
94+
+ if rd.ResourceGroupName == "" {
95+
+ return "", nil
96+
+ }
97+
+
98+
+ rg, err := s.client.ResourceGroups.Get(ctx, rd.ResourceGroupName)
99+
+ if err != nil {
100+
+ return "", err
101+
+ }
102+
+
103+
+ return rg.Props[linstor.PropertyDrbdNetProtocol], nil
104+
+}
105+
+
106+
// CapacityBytes returns the amount of free space in the storage pool specified by the params and topology.
107+
func (s *Linstor) CapacityBytes(ctx context.Context, storagePools []string, overProvision *float64, segments map[string]string) (int64, error) {
108+
log := s.log.WithField("storage-pools", storagePools).WithField("segments", segments)
109+
diff --git a/pkg/linstor/const.go b/pkg/linstor/const.go
110+
index 9a5f79c..c8bc9c3 100644
111+
--- a/pkg/linstor/const.go
112+
+++ b/pkg/linstor/const.go
113+
@@ -44,6 +44,19 @@ const (
114+
// PropertyAllowTwoPrimaries is DRBD option to allow second primary. Mainly used for live-migration.
115+
PropertyAllowTwoPrimaries = lc.NamespcDrbdNetOptions + "/allow-two-primaries"
116+
117+
+ // PropertyDrbdNetProtocol is the DRBD network replication protocol option
118+
+ // (A = async, B = semi-sync, C = sync). DRBD requires Protocol C whenever
119+
+ // allow-two-primaries is enabled, otherwise drbdadm adjust fails with
120+
+ // "Protocol C required" (errno 139).
121+
+ PropertyDrbdNetProtocol = lc.NamespcDrbdNetOptions + "/protocol"
122+
+
123+
+ // PropertyCsiProtocolOverride marks a resource-connection where this driver
124+
+ // has installed a temporary Protocol=C override to make a Protocol-A/B
125+
+ // resource compatible with allow-two-primaries during live migration. The
126+
+ // marker lets us safely remove only the overrides we set, without touching
127+
+ // connection-level overrides installed by the operator.
128+
+ PropertyCsiProtocolOverride = lc.NamespcAuxiliary + "/csi-protocol-override"
129+
+
130+
// CreatedForTemporaryDisklessAttach marks a resource as temporary, i.e. it should be removed after it is no longer
131+
// needed.
132+
CreatedForTemporaryDisklessAttach = "temporary-diskless-attach"

0 commit comments

Comments
 (0)