backport of commit 9b74e11f064ecc53a53f13e82419927b533a9e4a (#18589)
Co-authored-by: Daniel Bennett <dbennett@hashicorp.com>
This commit is contained in:
parent
84b6321235
commit
3cc387749e
|
@ -2428,18 +2428,10 @@ func (s *StateStore) UpsertCSIVolume(index uint64, volumes []*structs.CSIVolume)
|
||||||
old.Provider != v.Provider {
|
old.Provider != v.Provider {
|
||||||
return fmt.Errorf("volume identity cannot be updated: %s", v.ID)
|
return fmt.Errorf("volume identity cannot be updated: %s", v.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fields that are safe to change while volume is being used.
|
|
||||||
if err := old.UpdateSafeFields(v); err != nil {
|
|
||||||
return fmt.Errorf("unable to update in-use volume: %w", err)
|
|
||||||
}
|
|
||||||
v = old
|
|
||||||
v.ModifyIndex = index
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
v.CreateIndex = index
|
v.CreateIndex = index
|
||||||
v.ModifyIndex = index
|
|
||||||
}
|
}
|
||||||
|
v.ModifyIndex = index
|
||||||
|
|
||||||
// Allocations are copy on write, so we want to keep the Allocation ID
|
// Allocations are copy on write, so we want to keep the Allocation ID
|
||||||
// but we need to clear the pointer so that we don't store it when we
|
// but we need to clear the pointer so that we don't store it when we
|
||||||
|
|
|
@ -813,9 +813,15 @@ func (v *CSIVolume) Merge(other *CSIVolume) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MountOptions can be updated so long as the volume isn't in use,
|
// MountOptions can be updated so long as the volume isn't in use
|
||||||
// but the caller will reject updating an in-use volume
|
if v.InUse() {
|
||||||
v.MountOptions = other.MountOptions
|
if !v.MountOptions.Equal(other.MountOptions) {
|
||||||
|
errs = multierror.Append(errs, errors.New(
|
||||||
|
"can not update mount options while volume is in use"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.MountOptions = other.MountOptions
|
||||||
|
}
|
||||||
|
|
||||||
// Secrets can be updated freely
|
// Secrets can be updated freely
|
||||||
v.Secrets = other.Secrets
|
v.Secrets = other.Secrets
|
||||||
|
@ -833,20 +839,6 @@ func (v *CSIVolume) Merge(other *CSIVolume) error {
|
||||||
return errs.ErrorOrNil()
|
return errs.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSafeFields updates fields that may be mutated while the volume is in use.
|
|
||||||
func (v *CSIVolume) UpdateSafeFields(other *CSIVolume) error {
|
|
||||||
if v == nil || other == nil {
|
|
||||||
return errors.New("unexpected nil volume (this is a bug)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand operation can sometimes happen while in-use.
|
|
||||||
v.Capacity = other.Capacity
|
|
||||||
v.RequestedCapacityMin = other.RequestedCapacityMin
|
|
||||||
v.RequestedCapacityMax = other.RequestedCapacityMax
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request and response wrappers
|
// Request and response wrappers
|
||||||
type CSIVolumeRegisterRequest struct {
|
type CSIVolumeRegisterRequest struct {
|
||||||
Volumes []*CSIVolume
|
Volumes []*CSIVolume
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
|
"github.com/shoenig/test"
|
||||||
"github.com/shoenig/test/must"
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -626,7 +627,7 @@ func TestCSIVolume_Merge(t *testing.T) {
|
||||||
update: &CSIVolume{},
|
update: &CSIVolume{},
|
||||||
expected: "volume topology request update was not compatible with existing topology",
|
expected: "volume topology request update was not compatible with existing topology",
|
||||||
expectFn: func(t *testing.T, v *CSIVolume) {
|
expectFn: func(t *testing.T, v *CSIVolume) {
|
||||||
require.Len(t, v.Topologies, 1)
|
must.Len(t, 1, v.Topologies)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -646,8 +647,8 @@ func TestCSIVolume_Merge(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: "volume topology request update was not compatible with existing topology",
|
expected: "volume topology request update was not compatible with existing topology",
|
||||||
expectFn: func(t *testing.T, v *CSIVolume) {
|
expectFn: func(t *testing.T, v *CSIVolume) {
|
||||||
require.Len(t, v.Topologies, 1)
|
must.Len(t, 1, v.Topologies)
|
||||||
require.Equal(t, "R1", v.Topologies[0].Segments["rack"])
|
must.Eq(t, "R1", v.Topologies[0].Segments["rack"])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -675,6 +676,20 @@ func TestCSIVolume_Merge(t *testing.T) {
|
||||||
},
|
},
|
||||||
expected: "volume topology request update was not compatible with existing topology",
|
expected: "volume topology request update was not compatible with existing topology",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid mount options while in use",
|
||||||
|
v: &CSIVolume{
|
||||||
|
// having any allocs means it's "in use"
|
||||||
|
ReadAllocs: map[string]*Allocation{
|
||||||
|
"test-alloc": {ID: "anything"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: &CSIVolume{
|
||||||
|
MountOptions: &CSIMountOptions{
|
||||||
|
MountFlags: []string{"any flags"}},
|
||||||
|
},
|
||||||
|
expected: "can not update mount options while volume is in use",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "valid update",
|
name: "valid update",
|
||||||
v: &CSIVolume{
|
v: &CSIVolume{
|
||||||
|
@ -704,7 +719,7 @@ func TestCSIVolume_Merge(t *testing.T) {
|
||||||
},
|
},
|
||||||
MountOptions: &CSIMountOptions{
|
MountOptions: &CSIMountOptions{
|
||||||
FSType: "ext4",
|
FSType: "ext4",
|
||||||
MountFlags: []string{"noatime"},
|
MountFlags: []string{"noatime", "another"},
|
||||||
},
|
},
|
||||||
RequestedTopologies: &CSITopologyRequest{
|
RequestedTopologies: &CSITopologyRequest{
|
||||||
Required: []*CSITopology{
|
Required: []*CSITopology{
|
||||||
|
@ -725,20 +740,26 @@ func TestCSIVolume_Merge(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expectFn: func(t *testing.T, v *CSIVolume) {
|
||||||
|
test.Len(t, 2, v.RequestedCapabilities,
|
||||||
|
test.Sprint("should add 2 requested capabilities"))
|
||||||
|
test.Eq(t, []string{"noatime", "another"}, v.MountOptions.MountFlags,
|
||||||
|
test.Sprint("should add mount flag"))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tc = tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := tc.v.Merge(tc.update)
|
err := tc.v.Merge(tc.update)
|
||||||
|
if tc.expectFn != nil {
|
||||||
|
tc.expectFn(t, tc.v)
|
||||||
|
}
|
||||||
if tc.expected == "" {
|
if tc.expected == "" {
|
||||||
require.NoError(t, err)
|
must.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
if tc.expectFn != nil {
|
must.Error(t, err)
|
||||||
tc.expectFn(t, tc.v)
|
must.ErrorContains(t, err, tc.expected)
|
||||||
}
|
|
||||||
require.Error(t, err, tc.expected)
|
|
||||||
require.Contains(t, err.Error(), tc.expected)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue