diff --git a/.travis.yml b/.travis.yml index f0465a7f..7f97918c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,6 @@ sudo: true before_install: - openssl aes-256-cbc -K $encrypted_7937b810c182_key -iv $encrypted_7937b810c182_iv -in ./e2e/config/secret.txt.enc -out secret.txt -d || true -- sudo add-apt-repository ppa:masterminds/glide -y && sudo apt-get update -q -- sudo apt-get install glide -y - sudo apt-get install bc before_script: diff --git a/common/vpcclient/models/constants.go b/common/vpcclient/models/constants.go index eea24675..8e5af152 100644 --- a/common/vpcclient/models/constants.go +++ b/common/vpcclient/models/constants.go @@ -19,7 +19,7 @@ package models const ( // APIVersion is the target RIaaS API spec version - APIVersion = "2023-07-11" + APIVersion = "2025-08-05" // APIGeneration ... APIGeneration = 1 diff --git a/common/vpcclient/models/share.go b/common/vpcclient/models/share.go index d6e6df78..69c67892 100644 --- a/common/vpcclient/models/share.go +++ b/common/vpcclient/models/share.go @@ -30,6 +30,7 @@ type Share struct { Size int64 `json:"size,omitempty"` Iops int64 `json:"iops,omitempty"` EncryptionKey *EncryptionKey `json:"encryption_key,omitempty"` + Bandwidth int32 `json:"bandwidth,omitempty"` ResourceGroup *ResourceGroup `json:"resource_group,omitempty"` InitialOwner *InitialOwner `json:"initial_owner,omitempty"` Profile *Profile `json:"profile,omitempty"` diff --git a/common/vpcclient/models/share_target.go b/common/vpcclient/models/share_target.go index 60ae4e52..73078b55 100644 --- a/common/vpcclient/models/share_target.go +++ b/common/vpcclient/models/share_target.go @@ -34,6 +34,7 @@ type ShareTarget struct { VPC *provider.VPC `json:"vpc,omitempty"` //EncryptionInTransit TransitEncryption string `json:"transit_encryption,omitempty"` + AccessProtocol string `json:"access_protocol,omitempty"` VirtualNetworkInterface *VirtualNetworkInterface `json:"virtual_network_interface,omitempty"` //Share ID this target is associated to ShareID string `json:"-"` diff --git a/common/vpcclient/riaas/riaas.go b/common/vpcclient/riaas/riaas.go index d6bfe73f..e02b586f 100644 --- a/common/vpcclient/riaas/riaas.go +++ b/common/vpcclient/riaas/riaas.go @@ -69,6 +69,7 @@ func New(config Config) (*Session, error) { queryValues := url.Values{ "version": []string{backendAPIVersion}, "generation": []string{strconv.Itoa(apiGen)}, + "maturity": []string{"beta"}, } riaasClient := client.New(ctx, config.baseURL(), queryValues, config.httpClient(), config.ContextID, config.ResourceGroup) diff --git a/file/provider/create_volume.go b/file/provider/create_volume.go index 0e874288..b6101ff2 100644 --- a/file/provider/create_volume.go +++ b/file/provider/create_volume.go @@ -28,10 +28,8 @@ import ( ) const ( - minSize = 10 //10 GB - maxSize = 16000 //16 TB - customProfile = "custom-iops" - dp2Profile = "dp2" + minSize = 10 //10 GB + RFSProfile = "rfs" ) // CreateVolume creates file share @@ -40,27 +38,37 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo defer vpcs.Logger.Debug("Exit from CreateVolume method...") defer metrics.UpdateDurationFromStart(vpcs.Logger, "CreateVolume", time.Now()) + var iops int64 + var bandwidth int32 vpcs.Logger.Info("Basic validation for CreateVolume request... ", zap.Reflect("RequestedVolumeDetails", volumeRequest)) - resourceGroup, iops, err := validateVolumeRequest(volumeRequest) + resourceGroup, iops, bandwidth, err := validateVolumeRequest(volumeRequest) if err != nil { return nil, err } + vpcs.Logger.Info("Successfully validated inputs for CreateVolume request... ") + // Set zone if provided + var zone *models.Zone + if volumeRequest.Az != "" { + zone = &models.Zone{ + Name: volumeRequest.Az, + } + } + // Build the share template to send to backend shareTemplate := &models.Share{ Name: *volumeRequest.Name, Size: int64(*volumeRequest.Capacity), InitialOwner: (*models.InitialOwner)(volumeRequest.InitialOwner), Iops: iops, + Bandwidth: bandwidth, AccessControlMode: volumeRequest.AccessControlMode, ResourceGroup: &resourceGroup, Profile: &models.Profile{ Name: volumeRequest.VPCVolume.Profile.Name, }, - Zone: &models.Zone{ - Name: volumeRequest.Az, - }, + Zone: zone, } // 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 shareTargetTemplate.TransitEncryption = volumeRequest.TransitEncryption } + // Set access_protocol and transit_encryption ONLY for 'rfs' profile + if volumeRequest.VPCVolume.Profile != nil && volumeRequest.VPCVolume.Profile.Name == RFSProfile { + shareTargetTemplate.AccessProtocol = "nfs4" + shareTargetTemplate.TransitEncryption = "none" + } + volumeAccessPointList := make([]models.ShareTarget, 1) volumeAccessPointList[0] = shareTargetTemplate @@ -101,6 +115,7 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo vpcs.Logger.Info("Calling VPC provider for volume creation...") var volume *models.Share + err = retry(vpcs.Logger, func() error { volume, err = vpcs.Apiclient.FileShareService().CreateFileShare(shareTemplate, vpcs.Logger) return err @@ -118,6 +133,7 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo if err != nil { return nil, userError.GetUserError("VolumeNotInValidState", err, volume.ID) } + vpcs.Logger.Info("Volume got valid (stable) state", zap.Reflect("VolumeDetails", volume)) // Converting share to lib volume type @@ -135,44 +151,47 @@ func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeRespo } // validateVolumeRequest validating volume request -func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup, int64, error) { +func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup, int64, int32, error) { resourceGroup := models.ResourceGroup{} var iops int64 iops = 0 + var bandwidth int32 + bandwidth = 0 // Volume name should not be empty if volumeRequest.Name == nil { - return resourceGroup, iops, userError.GetUserError("InvalidVolumeName", nil, nil) + return resourceGroup, iops, bandwidth, userError.GetUserError("InvalidVolumeName", nil, nil) } else if len(*volumeRequest.Name) == 0 { - return resourceGroup, iops, userError.GetUserError("InvalidVolumeName", nil, *volumeRequest.Name) + return resourceGroup, iops, bandwidth, userError.GetUserError("InvalidVolumeName", nil, *volumeRequest.Name) } - // Capacity should not be empty if volumeRequest.Capacity == nil { - return resourceGroup, iops, userError.GetUserError("VolumeCapacityInvalid", nil, nil) + return resourceGroup, iops, bandwidth, userError.GetUserError("VolumeCapacityInvalid", nil, nil) } else if *volumeRequest.Capacity < minSize { - return resourceGroup, iops, userError.GetUserError("VolumeCapacityInvalid", nil, *volumeRequest.Capacity) + return resourceGroup, iops, bandwidth, userError.GetUserError("VolumeCapacityInvalid", nil, *volumeRequest.Capacity) } // Read user provided error, no harm to pass the 0 values to RIaaS in case of tiered profiles if volumeRequest.Iops != nil { iops = ToInt64(*volumeRequest.Iops) } - if volumeRequest.VPCVolume.Profile == nil { - return resourceGroup, iops, userError.GetUserError("VolumeProfileEmpty", nil) + + if volumeRequest.Bandwidth != 0 { + bandwidth = volumeRequest.VPCVolume.Bandwidth } - if volumeRequest.VPCVolume.Profile.Name != customProfile && volumeRequest.VPCVolume.Profile.Name != dp2Profile && iops > 0 { - return resourceGroup, iops, userError.GetUserError("VolumeProfileIopsInvalid", nil) + + if volumeRequest.VPCVolume.Profile == nil { + return resourceGroup, iops, bandwidth, userError.GetUserError("VolumeProfileEmpty", nil) } // validate and add resource group ID or Name whichever is provided by user if volumeRequest.VPCVolume.ResourceGroup == nil { - return resourceGroup, iops, userError.GetUserError("EmptyResourceGroup", nil) + return resourceGroup, iops, bandwidth, userError.GetUserError("EmptyResourceGroup", nil) } // validate and add resource group ID or Name whichever is provided by user if len(volumeRequest.VPCVolume.ResourceGroup.ID) == 0 && len(volumeRequest.VPCVolume.ResourceGroup.Name) == 0 { - return resourceGroup, iops, userError.GetUserError("EmptyResourceGroupIDandName", nil) + return resourceGroup, iops, bandwidth, userError.GetUserError("EmptyResourceGroupIDandName", nil) } if len(volumeRequest.VPCVolume.ResourceGroup.ID) > 0 { @@ -183,7 +202,7 @@ func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup, resourceGroup.Name = volumeRequest.VPCVolume.ResourceGroup.Name } - return resourceGroup, iops, nil + return resourceGroup, iops, bandwidth, nil } func setENIParameters(shareTarget *models.ShareTarget, volumeRequest provider.Volume) { diff --git a/file/provider/create_volume_test.go b/file/provider/create_volume_test.go index 6828b5d7..df3084c8 100644 --- a/file/provider/create_volume_test.go +++ b/file/provider/create_volume_test.go @@ -401,6 +401,144 @@ func TestCreateVolume(t *testing.T) { assert.NotNil(t, err) }, }, + { + testCaseName: "No Bandwidth", + profileName: "rfs", + baseVolume: &models.Share{ + ID: "vol-no-bw", + Name: "volume-no-bandwidth", + Status: models.StatusType("stable"), + Size: int64(1024), + Zone: &models.Zone{Name: "zone1"}, + }, + providerVolume: provider.Volume{ + VolumeID: "vol-no-bw", + Name: String("volume-no-bandwidth"), + Capacity: Int(1024), + VPCVolume: provider.VPCVolume{ + Profile: &provider.Profile{Name: "rfs"}, + ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"}, + }, + }, + }, + { + testCaseName: "Min Bandwidth and Min Size", + profileName: "rfs", + baseVolume: &models.Share{ + ID: "vol-min-bw", + Name: "volume-min-bandwidth", + Status: models.StatusType("stable"), + Size: int64(10), + Bandwidth: int32(25), + Zone: &models.Zone{Name: "zone1"}, + }, + providerVolume: provider.Volume{ + VolumeID: "vol-min-bw", + Name: String("volume-min-bandwidth"), + Capacity: Int(10), + VPCVolume: provider.VPCVolume{ + Profile: &provider.Profile{Name: "rfs"}, + Bandwidth: int32(25), + ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"}, + }, + }, + verify: func(t *testing.T, volumeResponse *provider.Volume, err error) { + assert.NotNil(t, volumeResponse) + assert.Nil(t, err) + }, + }, + { + testCaseName: "Valid Bandwidth and Invalid Size", + profileName: "rfs", + baseVolume: &models.Share{ + ID: "vol-valid-bw", + Name: "volume-valid-bandwidth", + Status: models.StatusType("stable"), + Size: int64(34000), + Bandwidth: int32(8192), + Zone: &models.Zone{Name: "zone1"}, + }, + providerVolume: provider.Volume{ + VolumeID: "vol-valid-bw", + Name: String("volume-valid-bandwidth"), + Capacity: Int(34000), + VPCVolume: provider.VPCVolume{ + Profile: &provider.Profile{Name: "rfs"}, + Bandwidth: int32(8192), + ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"}, + }, + }, + verify: func(t *testing.T, volumeResponse *provider.Volume, err error) { + assert.NotNil(t, volumeResponse) + assert.Nil(t, err) + }, + }, + { + testCaseName: "Invalid Bandwidth and Valid Size", + profileName: "rfs", + baseVolume: &models.Share{ + ID: "vol-invalid-bw", + Name: "volume-invalid-bandwidth", + Status: models.StatusType("stable"), + Size: int64(1000), + Bandwidth: int32(9000), + Zone: &models.Zone{Name: "zone1"}, + }, + providerVolume: provider.Volume{ + VolumeID: "vol-invalid-bw", + Name: String("volume-invalid-bandwidth"), + Capacity: Int(1000), + VPCVolume: provider.VPCVolume{ + Profile: &provider.Profile{Name: "rfs"}, + Bandwidth: int32(9000), + ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"}, + }, + }, + verify: func(t *testing.T, volumeResponse *provider.Volume, err error) { + assert.NotNil(t, volumeResponse) + assert.Nil(t, err) + }, + }, + { + testCaseName: "Zero Bandwidth", + profileName: "rfs", + providerVolume: provider.Volume{ + VolumeID: "vol-zero-bw", + Name: String("volume-zero-bandwidth"), + Capacity: Int(100), + VPCVolume: provider.VPCVolume{ + Profile: &provider.Profile{Name: "rfs"}, + Bandwidth: 0, + ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"}, + }, + }, + expectedErr: "{Code:ErrorUnclassified, Type:InvalidRequest, Description: bandwidth must be between '1' Mbps and '8192' Mbps for the specified size of 10 GB. }", + expectedReasonCode: "ErrorUnclassified", + verify: func(t *testing.T, volumeResponse *provider.Volume, err error) { + assert.Nil(t, volumeResponse) + assert.NotNil(t, err) + }, + }, + { + testCaseName: "Invalid Bandwidth - 9000", + profileName: "rfs", + providerVolume: provider.Volume{ + VolumeID: "vol-invalid-bw", + Name: String("volume-invalid-bandwidth"), + Capacity: Int(100), + VPCVolume: provider.VPCVolume{ + Profile: &provider.Profile{Name: "rfs"}, + Bandwidth: int32(9000), + ResourceGroup: &provider.ResourceGroup{ID: "rg-1", Name: "rg1"}, + }, + }, + expectedErr: "{Code:ErrorUnclassified, Type:InvalidRequest, Description: bandwidth must be between '1' Mbps and '8192' Mbps for the specified size of 10 GB. }", + expectedReasonCode: "ErrorUnclassified", + verify: func(t *testing.T, volumeResponse *provider.Volume, err error) { + assert.Nil(t, volumeResponse) + assert.NotNil(t, err) + }, + }, } for _, testcase := range testCases { diff --git a/file/provider/list_volumes_test.go b/file/provider/list_volumes_test.go index 1c75ab5d..f555ae36 100644 --- a/file/provider/list_volumes_test.go +++ b/file/provider/list_volumes_test.go @@ -65,12 +65,13 @@ func TestListVolumes(t *testing.T) { Limit: 50, Shares: []*models.Share{ { - ID: "16f293bf-test-4bff-816f-e199c0c65db5", - Name: "test-volume-name1", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone"}, + ID: "16f293bf-test-4bff-816f-e199c0c65db5", + Name: "test-volume-name1", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone"}, + Profile: &models.Profile{Name: "dp2"}, }, }, }, @@ -101,19 +102,21 @@ func TestListVolumes(t *testing.T) { Limit: 50, Shares: []*models.Share{ { - ID: "16f293bf-test-4bff-816f-e199c0c65db5", - Name: "test-volume-name1", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone-1"}, + ID: "16f293bf-test-4bff-816f-e199c0c65db5", + Name: "test-volume-name1", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone-1"}, + Profile: &models.Profile{Name: "dp2"}, }, { - ID: "23b154fr-test-4bff-816f-f213s1y34gj8", - Name: "test-volume-name2", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone-2"}, + ID: "23b154fr-test-4bff-816f-f213s1y34gj8", + Name: "test-volume-name2", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone-2"}, + Profile: &models.Profile{Name: "dp2"}, }, }, }, @@ -149,19 +152,21 @@ func TestListVolumes(t *testing.T) { Limit: 50, Shares: []*models.Share{ { - ID: "16f293bf-test-4bff-816f-e199c0c65db5", - Name: "test-volume-name1", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone-1"}, + ID: "16f293bf-test-4bff-816f-e199c0c65db5", + Name: "test-volume-name1", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone-1"}, + Profile: &models.Profile{Name: "dp2"}, }, { - ID: "23b154fr-test-4bff-816f-f213s1y34gj8", - Name: "test-volume-name2", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone-2"}, + ID: "23b154fr-test-4bff-816f-f213s1y34gj8", + Name: "test-volume-name2", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone-2"}, + Profile: &models.Profile{Name: "dp2"}, }, }, }, @@ -178,19 +183,21 @@ func TestListVolumes(t *testing.T) { Limit: 1, Shares: []*models.Share{ { - ID: "16f293bf-test-4bff-816f-e199c0c65db5", - Name: "test-volume-name1", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone-1"}, + ID: "16f293bf-test-4bff-816f-e199c0c65db5", + Name: "test-volume-name1", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone-1"}, + Profile: &models.Profile{Name: "dp2"}, }, { - ID: "23b154fr-test-4bff-816f-f213s1y34gj8", - Name: "test-volume-name2", - Status: models.StatusType("OK"), - Size: int64(10), - Iops: int64(1000), - Zone: &models.Zone{Name: "test-zone-2"}, + ID: "23b154fr-test-4bff-816f-f213s1y34gj8", + Name: "test-volume-name2", + Status: models.StatusType("OK"), + Size: int64(10), + Iops: int64(1000), + Zone: &models.Zone{Name: "test-zone-2"}, + Profile: &models.Profile{Name: "dp2"}, }, }, }, diff --git a/file/provider/util.go b/file/provider/util.go index 3c54ac0a..7847bdd1 100644 --- a/file/provider/util.go +++ b/file/provider/util.go @@ -79,6 +79,9 @@ var skipErrorCodes = map[string]bool{ "internal_error": false, "invalid_route": true, "service_error": false, + "shares_bad_field_for_rfs_profile": true, + "shares_profile_bandwidth_not_allowed": true, + "shares_bandwidth_invalid": true, } // retry ... @@ -278,31 +281,33 @@ func FromProviderToLibVolume(vpcVolume *models.Share, logger *zap.Logger) (libVo return } - if vpcVolume.Zone == nil { - logger.Info("Volume zone is empty") - return - } - logger.Debug("Volume details of VPC client", zap.Reflect("models.Volume", vpcVolume)) volumeCap := int(vpcVolume.Size) iops := strconv.Itoa(int(vpcVolume.Iops)) + bandwidth := vpcVolume.Bandwidth var createdDate time.Time if vpcVolume.CreatedAt != nil { createdDate = *vpcVolume.CreatedAt } libVolume = &provider.Volume{ - VolumeID: vpcVolume.ID, - Provider: VPC, - Capacity: &volumeCap, - Iops: &iops, + VolumeID: vpcVolume.ID, + Provider: VPC, + Capacity: &volumeCap, + Iops: &iops, + VPCVolume: provider.VPCVolume{ + Bandwidth: bandwidth, + }, VolumeType: VolumeType, CreationTime: createdDate, } + + // Zone can be nil for some profiles (e.g., RFS) if vpcVolume.Zone != nil { libVolume.Az = vpcVolume.Zone.Name } + libVolume.CRN = vpcVolume.CRN var respAccessPointlist = []provider.VolumeAccessPoint{} diff --git a/pkg/ibmcloudprovider/volume_provider.go b/pkg/ibmcloudprovider/volume_provider.go index 43c84e38..7a8e0523 100644 --- a/pkg/ibmcloudprovider/volume_provider.go +++ b/pkg/ibmcloudprovider/volume_provider.go @@ -59,7 +59,7 @@ func NewIBMCloudStorageProvider(clusterVolumeLabel string, k8sClient *k8s_utils. conf.VPC.APIVersion = fmt.Sprintf("%d-%02d-%02d", dateTime.Year(), dateTime.Month(), dateTime.Day()) } else { logger.Warn("Failed to parse VPC_API_VERSION, setting default value") - conf.VPC.APIVersion = "2023-07-11" // setting default values + conf.VPC.APIVersion = "2025-08-05" // setting default values } var clusterInfo utilsConfig.ClusterConfig diff --git a/samples/main.go b/samples/main.go index 216fa1bc..5d81e31f 100644 --- a/samples/main.go +++ b/samples/main.go @@ -33,6 +33,7 @@ import ( userError "github.com/IBM/ibmcloud-volume-interface/lib/utils" "github.com/IBM/ibmcloud-volume-interface/provider/local" "github.com/IBM/secret-utils-lib/pkg/k8s_utils" + utils "github.com/IBM/secret-utils-lib/pkg/utils" uid "github.com/gofrs/uuid" ) @@ -80,6 +81,7 @@ func main() { // Load config file k8sClient, _ := k8s_utils.FakeGetk8sClientSet() + _ = k8s_utils.FakeCreateSecret(k8sClient, utils.DEFAULT, "./samples/sample-secret-config.toml") conf, err := config.ReadConfig(k8sClient, logger) if err != nil { logger.Fatal("Error loading configuration") @@ -96,6 +98,8 @@ func main() { VPCConfig: conf.VPC, ServerConfig: conf.Server, } + // enabling VPC provider as default + vpcFileConfig.VPCConfig.Enabled = true providerRegistry, err := provider_file_util.InitProviders(vpcFileConfig, &k8sClient, logger) @@ -210,24 +214,40 @@ func main() { } else if choiceN == 13 { fmt.Println("You selected choice to Create VPC volume") volume := &provider.Volume{} - volumeName := "" - volume.VolumeType = "vpc-share" /* volume.VolumeEncryptionKey = &provider.VolumeEncryptionKey{ CRN: "crn:v1:bluemix:public:kms:us-south:a/b661e758bf044d2d928fef7900d5130b:f5be30ff-b5d8-4edd-9f5a-cc313b3584db:key:8e282989-19e8-4415-b6e3-b9d2ec1911d0", } */ - resourceGroup := "" - zone := "us-south-3" - volSize := 0 - volume.Az = zone - volume.VPCVolume.ResourceGroup = &provider.ResourceGroup{} - profile := "tier-10iops" - fmt.Printf("\nPlease enter profile name supported profiles are [tier-10iops, tier-5iops, tier-3iops]: ") + var ( + profile string + zone string + volumeName string + resourceGroup string + volSize int + iops, bandwidth int + ) + + fmt.Printf("\nPlease enter profile name (supported: dp2, rfs): ") _, _ = fmt.Scanf("%s", &profile) volume.VPCVolume.Profile = &provider.Profile{Name: profile} + fmt.Printf("Enter zone: ") + _, _ = fmt.Scanf("%s", &zone) + volume.Az = zone + + // Always prompt for IOPS + fmt.Printf("\nEnter IOPS (optional, 0 to skip): ") + _, _ = fmt.Scanf("%d", &iops) + iopsStr := fmt.Sprintf("%d", iops) + volume.Iops = &iopsStr + + // Always prompt for Bandwidth + fmt.Printf("\nEnter Bandwidth (optional, 0 to skip): ") + _, _ = fmt.Scanf("%d", &bandwidth) + volume.Bandwidth = int32(bandwidth) + fmt.Printf("\nPlease enter volume name: ") _, _ = fmt.Scanf("%s", &volumeName) volume.Name = &volumeName @@ -238,12 +258,11 @@ func main() { fmt.Printf("\nPlease enter resource group ID:") _, _ = fmt.Scanf("%s", &resourceGroup) + if volume.VPCVolume.ResourceGroup == nil { + volume.VPCVolume.ResourceGroup = &provider.ResourceGroup{} + } volume.VPCVolume.ResourceGroup.ID = resourceGroup - fmt.Printf("\nPlease enter zone: ") - _, _ = fmt.Scanf("%s", &zone) - volume.Az = zone - //volume.SnapshotSpace = &volSize //volume.VPCVolume.Tags = []string{"Testing VPC Volume"} volumeObj, errr := sess.CreateVolume(*volume) @@ -495,12 +514,13 @@ func main() { _, _ = fmt.Scanf("%d", &capacity) share.VolumeID = volumeID share.Capacity = capacity + // Call ExpandVolume expandedVolumeSize, er11 := sess.ExpandVolume(*share) if er11 == nil { ctxLogger.Info("Successfully expanded volume ================>", zap.Reflect("Volume ID", expandedVolumeSize)) } else { er11 = updateRequestID(er11, requestID) - ctxLogger.Info("failed to expand================>", zap.Reflect("Volume ID", volumeID), zap.Reflect("Error", er11)) + ctxLogger.Info("Failed to expand =================>", zap.Reflect("Volume ID", volumeID), zap.Reflect("Error", er11)) } fmt.Printf("\n\n") } else {