Skip to content

Commit a60f88e

Browse files
Enhance RFS volume support: add bandwidth/iops update handling (#54)
1 parent 7fc2408 commit a60f88e

File tree

11 files changed

+280
-90
lines changed

11 files changed

+280
-90
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ sudo: true
1919
before_install:
2020
- openssl aes-256-cbc -K $encrypted_7937b810c182_key -iv $encrypted_7937b810c182_iv
2121
-in ./e2e/config/secret.txt.enc -out secret.txt -d || true
22-
- sudo add-apt-repository ppa:masterminds/glide -y && sudo apt-get update -q
23-
- sudo apt-get install glide -y
2422
- sudo apt-get install bc
2523

2624
before_script:

common/vpcclient/models/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package models
1919

2020
const (
2121
// APIVersion is the target RIaaS API spec version
22-
APIVersion = "2023-07-11"
22+
APIVersion = "2025-08-05"
2323

2424
// APIGeneration ...
2525
APIGeneration = 1

common/vpcclient/models/share.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Share struct {
3030
Size int64 `json:"size,omitempty"`
3131
Iops int64 `json:"iops,omitempty"`
3232
EncryptionKey *EncryptionKey `json:"encryption_key,omitempty"`
33+
Bandwidth int32 `json:"bandwidth,omitempty"`
3334
ResourceGroup *ResourceGroup `json:"resource_group,omitempty"`
3435
InitialOwner *InitialOwner `json:"initial_owner,omitempty"`
3536
Profile *Profile `json:"profile,omitempty"`

common/vpcclient/models/share_target.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type ShareTarget struct {
3434
VPC *provider.VPC `json:"vpc,omitempty"`
3535
//EncryptionInTransit
3636
TransitEncryption string `json:"transit_encryption,omitempty"`
37+
AccessProtocol string `json:"access_protocol,omitempty"`
3738
VirtualNetworkInterface *VirtualNetworkInterface `json:"virtual_network_interface,omitempty"`
3839
//Share ID this target is associated to
3940
ShareID string `json:"-"`

common/vpcclient/riaas/riaas.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func New(config Config) (*Session, error) {
6969
queryValues := url.Values{
7070
"version": []string{backendAPIVersion},
7171
"generation": []string{strconv.Itoa(apiGen)},
72+
"maturity": []string{"beta"},
7273
}
7374

7475
riaasClient := client.New(ctx, config.baseURL(), queryValues, config.httpClient(), config.ContextID, config.ResourceGroup)

file/provider/create_volume.go

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ import (
2828
)
2929

3030
const (
31-
minSize = 10 //10 GB
32-
maxSize = 16000 //16 TB
33-
customProfile = "custom-iops"
34-
dp2Profile = "dp2"
31+
minSize = 10 //10 GB
32+
RFSProfile = "rfs"
3533
)
3634

3735
// CreateVolume creates file share
@@ -40,27 +38,37 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo
4038
defer vpcs.Logger.Debug("Exit from CreateVolume method...")
4139
defer metrics.UpdateDurationFromStart(vpcs.Logger, "CreateVolume", time.Now())
4240

41+
var iops int64
42+
var bandwidth int32
4343
vpcs.Logger.Info("Basic validation for CreateVolume request... ", zap.Reflect("RequestedVolumeDetails", volumeRequest))
44-
resourceGroup, iops, err := validateVolumeRequest(volumeRequest)
44+
resourceGroup, iops, bandwidth, err := validateVolumeRequest(volumeRequest)
4545
if err != nil {
4646
return nil, err
4747
}
48+
4849
vpcs.Logger.Info("Successfully validated inputs for CreateVolume request... ")
4950

51+
// Set zone if provided
52+
var zone *models.Zone
53+
if volumeRequest.Az != "" {
54+
zone = &models.Zone{
55+
Name: volumeRequest.Az,
56+
}
57+
}
58+
5059
// Build the share template to send to backend
5160
shareTemplate := &models.Share{
5261
Name: *volumeRequest.Name,
5362
Size: int64(*volumeRequest.Capacity),
5463
InitialOwner: (*models.InitialOwner)(volumeRequest.InitialOwner),
5564
Iops: iops,
65+
Bandwidth: bandwidth,
5666
AccessControlMode: volumeRequest.AccessControlMode,
5767
ResourceGroup: &resourceGroup,
5868
Profile: &models.Profile{
5969
Name: volumeRequest.VPCVolume.Profile.Name,
6070
},
61-
Zone: &models.Zone{
62-
Name: volumeRequest.Az,
63-
},
71+
Zone: zone,
6472
}
6573

6674
// Check for VPC ID, SubnetID or PrimaryIPID either of the one is mandatory for VolumeAccessPoint/FileShareTarget creation
@@ -87,6 +95,12 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo
8795
shareTargetTemplate.TransitEncryption = volumeRequest.TransitEncryption
8896
}
8997

98+
// Set access_protocol and transit_encryption ONLY for 'rfs' profile
99+
if volumeRequest.VPCVolume.Profile != nil && volumeRequest.VPCVolume.Profile.Name == RFSProfile {
100+
shareTargetTemplate.AccessProtocol = "nfs4"
101+
shareTargetTemplate.TransitEncryption = "none"
102+
}
103+
90104
volumeAccessPointList := make([]models.ShareTarget, 1)
91105
volumeAccessPointList[0] = shareTargetTemplate
92106

@@ -101,6 +115,7 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo
101115

102116
vpcs.Logger.Info("Calling VPC provider for volume creation...")
103117
var volume *models.Share
118+
104119
err = retry(vpcs.Logger, func() error {
105120
volume, err = vpcs.Apiclient.FileShareService().CreateFileShare(shareTemplate, vpcs.Logger)
106121
return err
@@ -118,6 +133,7 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo
118133
if err != nil {
119134
return nil, userError.GetUserError("VolumeNotInValidState", err, volume.ID)
120135
}
136+
121137
vpcs.Logger.Info("Volume got valid (stable) state", zap.Reflect("VolumeDetails", volume))
122138

123139
// Converting share to lib volume type
@@ -135,44 +151,47 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo
135151
}
136152

137153
// validateVolumeRequest validating volume request
138-
func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup, int64, error) {
154+
func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup, int64, int32, error) {
139155
resourceGroup := models.ResourceGroup{}
140156
var iops int64
141157
iops = 0
158+
var bandwidth int32
159+
bandwidth = 0
142160

143161
// Volume name should not be empty
144162
if volumeRequest.Name == nil {
145-
return resourceGroup, iops, userError.GetUserError("InvalidVolumeName", nil, nil)
163+
return resourceGroup, iops, bandwidth, userError.GetUserError("InvalidVolumeName", nil, nil)
146164
} else if len(*volumeRequest.Name) == 0 {
147-
return resourceGroup, iops, userError.GetUserError("InvalidVolumeName", nil, *volumeRequest.Name)
165+
return resourceGroup, iops, bandwidth, userError.GetUserError("InvalidVolumeName", nil, *volumeRequest.Name)
148166
}
149167

150-
// Capacity should not be empty
151168
if volumeRequest.Capacity == nil {
152-
return resourceGroup, iops, userError.GetUserError("VolumeCapacityInvalid", nil, nil)
169+
return resourceGroup, iops, bandwidth, userError.GetUserError("VolumeCapacityInvalid", nil, nil)
153170
} else if *volumeRequest.Capacity < minSize {
154-
return resourceGroup, iops, userError.GetUserError("VolumeCapacityInvalid", nil, *volumeRequest.Capacity)
171+
return resourceGroup, iops, bandwidth, userError.GetUserError("VolumeCapacityInvalid", nil, *volumeRequest.Capacity)
155172
}
156173

157174
// Read user provided error, no harm to pass the 0 values to RIaaS in case of tiered profiles
158175
if volumeRequest.Iops != nil {
159176
iops = ToInt64(*volumeRequest.Iops)
160177
}
161-
if volumeRequest.VPCVolume.Profile == nil {
162-
return resourceGroup, iops, userError.GetUserError("VolumeProfileEmpty", nil)
178+
179+
if volumeRequest.Bandwidth != 0 {
180+
bandwidth = volumeRequest.VPCVolume.Bandwidth
163181
}
164-
if volumeRequest.VPCVolume.Profile.Name != customProfile && volumeRequest.VPCVolume.Profile.Name != dp2Profile && iops > 0 {
165-
return resourceGroup, iops, userError.GetUserError("VolumeProfileIopsInvalid", nil)
182+
183+
if volumeRequest.VPCVolume.Profile == nil {
184+
return resourceGroup, iops, bandwidth, userError.GetUserError("VolumeProfileEmpty", nil)
166185
}
167186

168187
// validate and add resource group ID or Name whichever is provided by user
169188
if volumeRequest.VPCVolume.ResourceGroup == nil {
170-
return resourceGroup, iops, userError.GetUserError("EmptyResourceGroup", nil)
189+
return resourceGroup, iops, bandwidth, userError.GetUserError("EmptyResourceGroup", nil)
171190
}
172191

173192
// validate and add resource group ID or Name whichever is provided by user
174193
if len(volumeRequest.VPCVolume.ResourceGroup.ID) == 0 && len(volumeRequest.VPCVolume.ResourceGroup.Name) == 0 {
175-
return resourceGroup, iops, userError.GetUserError("EmptyResourceGroupIDandName", nil)
194+
return resourceGroup, iops, bandwidth, userError.GetUserError("EmptyResourceGroupIDandName", nil)
176195
}
177196

178197
if len(volumeRequest.VPCVolume.ResourceGroup.ID) > 0 {
@@ -183,7 +202,7 @@ func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup,
183202
resourceGroup.Name = volumeRequest.VPCVolume.ResourceGroup.Name
184203
}
185204

186-
return resourceGroup, iops, nil
205+
return resourceGroup, iops, bandwidth, nil
187206
}
188207

189208
func setENIParameters(shareTarget *models.ShareTarget, volumeRequest provider.Volume) {

file/provider/create_volume_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,144 @@ func TestCreateVolume(t *testing.T) {
401401
assert.NotNil(t, err)
402402
},
403403
},
404+
{
405+
testCaseName: "No Bandwidth",
406+
profileName: "rfs",
407+
baseVolume: &models.Share{
408+
ID: "vol-no-bw",
409+
Name: "volume-no-bandwidth",
410+
Status: models.StatusType("stable"),
411+
Size: int64(1024),
412+
Zone: &models.Zone{Name: "zone1"},
413+
},
414+
providerVolume: provider.Volume{
415+
VolumeID: "vol-no-bw",
416+
Name: String("volume-no-bandwidth"),
417+
Capacity: Int(1024),
418+
VPCVolume: provider.VPCVolume{
419+
Profile: &provider.Profile{Name: "rfs"},
420+
ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"},
421+
},
422+
},
423+
},
424+
{
425+
testCaseName: "Min Bandwidth and Min Size",
426+
profileName: "rfs",
427+
baseVolume: &models.Share{
428+
ID: "vol-min-bw",
429+
Name: "volume-min-bandwidth",
430+
Status: models.StatusType("stable"),
431+
Size: int64(10),
432+
Bandwidth: int32(25),
433+
Zone: &models.Zone{Name: "zone1"},
434+
},
435+
providerVolume: provider.Volume{
436+
VolumeID: "vol-min-bw",
437+
Name: String("volume-min-bandwidth"),
438+
Capacity: Int(10),
439+
VPCVolume: provider.VPCVolume{
440+
Profile: &provider.Profile{Name: "rfs"},
441+
Bandwidth: int32(25),
442+
ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"},
443+
},
444+
},
445+
verify: func(t *testing.T, volumeResponse *provider.Volume, err error) {
446+
assert.NotNil(t, volumeResponse)
447+
assert.Nil(t, err)
448+
},
449+
},
450+
{
451+
testCaseName: "Valid Bandwidth and Invalid Size",
452+
profileName: "rfs",
453+
baseVolume: &models.Share{
454+
ID: "vol-valid-bw",
455+
Name: "volume-valid-bandwidth",
456+
Status: models.StatusType("stable"),
457+
Size: int64(34000),
458+
Bandwidth: int32(8192),
459+
Zone: &models.Zone{Name: "zone1"},
460+
},
461+
providerVolume: provider.Volume{
462+
VolumeID: "vol-valid-bw",
463+
Name: String("volume-valid-bandwidth"),
464+
Capacity: Int(34000),
465+
VPCVolume: provider.VPCVolume{
466+
Profile: &provider.Profile{Name: "rfs"},
467+
Bandwidth: int32(8192),
468+
ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"},
469+
},
470+
},
471+
verify: func(t *testing.T, volumeResponse *provider.Volume, err error) {
472+
assert.NotNil(t, volumeResponse)
473+
assert.Nil(t, err)
474+
},
475+
},
476+
{
477+
testCaseName: "Invalid Bandwidth and Valid Size",
478+
profileName: "rfs",
479+
baseVolume: &models.Share{
480+
ID: "vol-invalid-bw",
481+
Name: "volume-invalid-bandwidth",
482+
Status: models.StatusType("stable"),
483+
Size: int64(1000),
484+
Bandwidth: int32(9000),
485+
Zone: &models.Zone{Name: "zone1"},
486+
},
487+
providerVolume: provider.Volume{
488+
VolumeID: "vol-invalid-bw",
489+
Name: String("volume-invalid-bandwidth"),
490+
Capacity: Int(1000),
491+
VPCVolume: provider.VPCVolume{
492+
Profile: &provider.Profile{Name: "rfs"},
493+
Bandwidth: int32(9000),
494+
ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"},
495+
},
496+
},
497+
verify: func(t *testing.T, volumeResponse *provider.Volume, err error) {
498+
assert.NotNil(t, volumeResponse)
499+
assert.Nil(t, err)
500+
},
501+
},
502+
{
503+
testCaseName: "Zero Bandwidth",
504+
profileName: "rfs",
505+
providerVolume: provider.Volume{
506+
VolumeID: "vol-zero-bw",
507+
Name: String("volume-zero-bandwidth"),
508+
Capacity: Int(100),
509+
VPCVolume: provider.VPCVolume{
510+
Profile: &provider.Profile{Name: "rfs"},
511+
Bandwidth: 0,
512+
ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"},
513+
},
514+
},
515+
expectedErr: "{Code:ErrorUnclassified, Type:InvalidRequest, Description: bandwidth must be between '1' Mbps and '8192' Mbps for the specified size of 10 GB. }",
516+
expectedReasonCode: "ErrorUnclassified",
517+
verify: func(t *testing.T, volumeResponse *provider.Volume, err error) {
518+
assert.Nil(t, volumeResponse)
519+
assert.NotNil(t, err)
520+
},
521+
},
522+
{
523+
testCaseName: "Invalid Bandwidth - 9000",
524+
profileName: "rfs",
525+
providerVolume: provider.Volume{
526+
VolumeID: "vol-invalid-bw",
527+
Name: String("volume-invalid-bandwidth"),
528+
Capacity: Int(100),
529+
VPCVolume: provider.VPCVolume{
530+
Profile: &provider.Profile{Name: "rfs"},
531+
Bandwidth: int32(9000),
532+
ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"},
533+
},
534+
},
535+
expectedErr: "{Code:ErrorUnclassified, Type:InvalidRequest, Description: bandwidth must be between '1' Mbps and '8192' Mbps for the specified size of 10 GB. }",
536+
expectedReasonCode: "ErrorUnclassified",
537+
verify: func(t *testing.T, volumeResponse *provider.Volume, err error) {
538+
assert.Nil(t, volumeResponse)
539+
assert.NotNil(t, err)
540+
},
541+
},
404542
}
405543

406544
for _, testcase := range testCases {

0 commit comments

Comments
 (0)