diff --git a/test/volume_test.go b/test/volume_test.go index c833f82..9d03ea9 100644 --- a/test/volume_test.go +++ b/test/volume_test.go @@ -200,3 +200,16 @@ func TestGetUnderVolumeIdSuccess(t *ltesting.T) { t.Log("Result: ", volume) t.Log("PASS") } + +func TestMigrateBlockVolume(t *ltesting.T) { + vtNvme := "vtype-7a7a8610-34f5-11ee-be56-0242ac120002" + vtSsd := "vtype-61c3fc5b-f4e9-45b4-8957-8aa7b6029018" + t.Log(vtSsd, vtNvme) + + vngcloud := validSdkConfig() + opt := v2.NewMigrateBlockVolumeByIdRequest("vol-ef605a82-270d-48f1-b763-f5338ae9f162", vtSsd).WithConfirm(true) + sdkerr := vngcloud.VServerGateway().V2().VolumeService().MigrateBlockVolumeById(opt) + + t.Log("Error: ", sdkerr) + t.Log("PASS") +} diff --git a/vngcloud/sdk_error/error_codes.go b/vngcloud/sdk_error/error_codes.go index f4c19af..9f6084a 100644 --- a/vngcloud/sdk_error/error_codes.go +++ b/vngcloud/sdk_error/error_codes.go @@ -60,6 +60,14 @@ const ( EcVServerVolumeInProcess = ErrorCode("VngCloudVServerVolumeInProcess") EcVServerVolumeUnchanged = ErrorCode("VngCloudVServerVolumeUnchanged") EcVServerVolumeMustSameZone = ErrorCode("VngCloudVServerVolumeMustSameZone") + EcVServerVolumeMigrateMissingInit = ErrorCode("VngCloudVServerVolumeMigrateMissingInit") + EcVServerVolumeMigrateNeedProcess = ErrorCode("VngCloudVServerVolumeMigrateNeedProcess") + EcVServerVolumeMigrateNeedConfirm = ErrorCode("VngCloudVServerVolumeMigrateNeedConfirm") + EcVServerVolumeMigrateBeingProcess = ErrorCode("VngCloudVServerVolumeMigrateBeingProcess") + EcVServerVolumeMigrateBeingFinish = ErrorCode("VngCloudVServerVolumeMigrateBeingFinish") + EcVServerVolumeMigrateProcessingConfirm = ErrorCode("VngCloudVServerVolumeMigrateProcessingConfirm") + EcVServerVolumeMigrateBeingMigrating = ErrorCode("VngCloudVServerVolumeMigrateBeingMigrating") + EcVServerVolumeMigrateInSameZone = ErrorCode("VngCloudVServerVolumeMigrateInSameZone") ) // Billing diff --git a/vngcloud/sdk_error/volume.go b/vngcloud/sdk_error/volume.go index 8380043..af53fd0 100644 --- a/vngcloud/sdk_error/volume.go +++ b/vngcloud/sdk_error/volume.go @@ -17,6 +17,14 @@ const ( // "Cannot get volume type with id vtype-6790f903-38d2-454d-919e-5b49184 patternVolumeInProcess = "is in-process" patternVolumeUnchaged = "volume size or volume type must be changed" patternVolumeMustSameZone = "new volume type must be same zone" + patternVolumeMigrateMissingInit = "the action must be init-migrate or migrate or confirm-migrate" + patternVolumeMigrateNeedProcess = "this volume cannot initialize migration because state is ready to migrate difference" + patternVolumeMigrateNeedConfirm = "this volume cannot initialize migration because state is confirm final migration" + patternVolumeMigrateBeingProcess = "this volume cannot initialize migration because state is migrating difference" + patternVolumeMigrateBeingMigrating = "this volume cannot initialize migration because state is migrating" + patternVolumeMigrateBeingFinish = "this volume cannot migrate difference data because state is confirm final migration" + patternVolumeMigrateProcessingConfirm = "this volume cannot initialize migration because state is processing to confirm" + patternVolumeMigrateInSameZone = "new volume type must be different zone" ) var ( @@ -159,3 +167,125 @@ func WithErrorVolumeMustSameZone(perrResp IErrorRespone) func(sdkError ISdkError } } } + +func WithErrorVolumeMigrateMissingInit(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateMissingInit) { + sdkError.WithErrorCode(EcVServerVolumeMigrateMissingInit). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeMigrateNeedProcess(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateNeedProcess) { + sdkError.WithErrorCode(EcVServerVolumeMigrateNeedProcess). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeMigrateNeedConfirm(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateNeedConfirm) { + sdkError.WithErrorCode(EcVServerVolumeMigrateNeedConfirm). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeMigrateBeingProcess(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateBeingProcess) { + sdkError.WithErrorCode(EcVServerVolumeMigrateBeingProcess). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeMigrateBeingFinish(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateBeingFinish) { + sdkError.WithErrorCode(EcVServerVolumeMigrateBeingFinish). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeMigrateProcessingConfirm(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateProcessingConfirm) { + sdkError.WithErrorCode(EcVServerVolumeMigrateProcessingConfirm). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +// + +func WithErrorVolumeMigrateBeingMigrating(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateBeingMigrating) { + sdkError.WithErrorCode(EcVServerVolumeMigrateBeingMigrating). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeMigrateInSameZone(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeMigrateInSameZone) { + sdkError.WithErrorCode(EcVServerVolumeMigrateInSameZone). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} diff --git a/vngcloud/services/common/common.go b/vngcloud/services/common/common.go index a8e0c4a..5faa669 100644 --- a/vngcloud/services/common/common.go +++ b/vngcloud/services/common/common.go @@ -34,3 +34,8 @@ func (s *Paging) SetSize(psize int) *Paging { s.Size = psize return s } + +type Tag struct { + Key string `json:"key"` + Value string `json:"value"` +} diff --git a/vngcloud/services/volume/ivolume.go b/vngcloud/services/volume/ivolume.go index e486bf9..928902e 100644 --- a/vngcloud/services/volume/ivolume.go +++ b/vngcloud/services/volume/ivolume.go @@ -14,6 +14,7 @@ type IVolumeServiceV2 interface { GetBlockVolumeById(popts lsvolumeSvcV2.IGetBlockVolumeByIdRequest) (*lsentity.Volume, lserr.ISdkError) ResizeBlockVolumeById(popts lsvolumeSvcV2.IResizeBlockVolumeByIdRequest) (*lsentity.Volume, lserr.ISdkError) GetUnderBlockVolumeId(popts lsvolumeSvcV2.IGetUnderBlockVolumeIdRequest) (*lsentity.Volume, lserr.ISdkError) + MigrateBlockVolumeById(popts lsvolumeSvcV2.IMigrateBlockVolumeByIdRequest) lserr.ISdkError // Snapshot ListSnapshotsByBlockVolumeId(popts lsvolumeSvcV2.IListSnapshotsByBlockVolumeIdRequest) (*lsentity.ListSnapshots, lserr.ISdkError) diff --git a/vngcloud/services/volume/v2/blockvolume.go b/vngcloud/services/volume/v2/blockvolume.go index 3d031d9..4ec69f6 100644 --- a/vngcloud/services/volume/v2/blockvolume.go +++ b/vngcloud/services/volume/v2/blockvolume.go @@ -129,3 +129,46 @@ func (s *VolumeServiceV2) GetUnderBlockVolumeId(popts IGetUnderBlockVolumeIdRequ return resp.ToEntityVolume(), nil } + +func (s *VolumeServiceV2) MigrateBlockVolumeById(popts IMigrateBlockVolumeByIdRequest) lserr.ISdkError { + url := migrateBlockVolumeByIdUrl(s.VServerClient, popts) + errResp := lserr.NewErrorResponse(lserr.NormalErrorType) + req := lsclient.NewRequest(). + WithOkCodes(204). + WithJsonBody(popts.ToRequestBody()). + WithJsonError(errResp) + + if _, sdkErr := s.VServerClient.Put(url, req); sdkErr != nil { + sdkErr = lserr.SdkErrorHandler(sdkErr, errResp, + lserr.WithErrorVolumeMigrateInSameZone(errResp), + lserr.WithErrorVolumeMigrateMissingInit(errResp), + lserr.WithErrorVolumeMigrateNeedProcess(errResp), + lserr.WithErrorVolumeMigrateNeedConfirm(errResp), + lserr.WithErrorVolumeMigrateBeingProcess(errResp), + lserr.WithErrorVolumeMigrateProcessingConfirm(errResp), + lserr.WithErrorVolumeMigrateBeingMigrating(errResp), // should under WithErrorVolumeMigrateBeingProcess + lserr.WithErrorVolumeMigrateBeingFinish(errResp), + lserr.WithErrorVolumeNotFound(errResp)). + WithKVparameters( + "projectId", s.getProjectId(), + "volumeId", popts.GetBlockVolumeId()) + + if popts.IsConfirm() { + switch sdkErr.GetErrorCode() { + case lserr.EcVServerVolumeMigrateMissingInit: + popts = popts.WithAction(InitMigrateAction) + return s.MigrateBlockVolumeById(popts) + case lserr.EcVServerVolumeMigrateNeedProcess: + popts = popts.WithAction(ProcessMigrateAction) + return s.MigrateBlockVolumeById(popts) + case lserr.EcVServerVolumeMigrateNeedConfirm: + popts = popts.WithAction(ConfirmMigrateAction) + return s.MigrateBlockVolumeById(popts) + } + } + + return sdkErr + } + + return nil +} diff --git a/vngcloud/services/volume/v2/blockvolume_request.go b/vngcloud/services/volume/v2/blockvolume_request.go index 533d164..3ad3881 100644 --- a/vngcloud/services/volume/v2/blockvolume_request.go +++ b/vngcloud/services/volume/v2/blockvolume_request.go @@ -49,12 +49,24 @@ func NewGetUnderVolumeIdRequest(pvolumeId string) IGetUnderBlockVolumeIdRequest return opt } +func NewMigrateBlockVolumeByIdRequest(pvolumeId, pvolumeType string) IMigrateBlockVolumeByIdRequest { + opt := new(MigrateBlockVolumeByIdRequest) + opt.BlockVolumeId = pvolumeId + opt.VolumeTypeId = pvolumeType + opt.Action = InitMigrateAction + return opt +} + const ( CreateFromNew = CreateVolumeFrom("NEW") CreateFromSnapshot = CreateVolumeFrom("SNAPSHOT") AesXtsPlain64_128 = EncryptType("aes-xts-plain64_128") AesXtsPlain64_256 = EncryptType("aes-xts-plain64_256") + + InitMigrateAction = MigrateAction("INIT-MIGRATE") + ProcessMigrateAction = MigrateAction("MIGRATE") + ConfirmMigrateAction = MigrateAction("CONFIRM-MIGRATE") ) type CreateBlockVolumeRequest struct { @@ -100,7 +112,18 @@ type GetUnderBlockVolumeIdRequest struct { lscommon.BlockVolumeCommon } +type MigrateBlockVolumeByIdRequest struct { + Action MigrateAction `json:"action"` + ConfirmMigrate bool + Tags []lscommon.Tag `json:"tags"` + VolumeTypeId string `json:"volumeTypeId"` + Auto bool + + lscommon.BlockVolumeCommon +} + type ( + MigrateAction string CreateVolumeFrom string EncryptType string @@ -258,3 +281,43 @@ func (s *ResizeBlockVolumeByIdRequest) GetSize() int { func (s *ResizeBlockVolumeByIdRequest) GetVolumeTypeId() string { return s.VolumeTypeID } + +func (s *MigrateBlockVolumeByIdRequest) ToRequestBody() interface{} { + return s +} + +func (s *MigrateBlockVolumeByIdRequest) WithTags(ptags ...string) IMigrateBlockVolumeByIdRequest { + if s.Tags == nil { + s.Tags = make([]lscommon.Tag, 0) + } + + if len(ptags)%2 != 0 { + ptags = append(ptags, "none") + } + + for i := 0; i < len(ptags); i += 2 { + s.Tags = append(s.Tags, lscommon.Tag{Key: ptags[i], Value: ptags[i+1]}) + } + + return s +} + +func (s *MigrateBlockVolumeByIdRequest) WithAction(paction MigrateAction) IMigrateBlockVolumeByIdRequest { + switch paction { + case InitMigrateAction, ProcessMigrateAction, ConfirmMigrateAction: + s.Action = paction + default: + s.Action = InitMigrateAction + } + + return s +} + +func (s *MigrateBlockVolumeByIdRequest) WithConfirm(pconfirm bool) IMigrateBlockVolumeByIdRequest { + s.ConfirmMigrate = pconfirm + return s +} + +func (s *MigrateBlockVolumeByIdRequest) IsConfirm() bool { + return s.ConfirmMigrate +} diff --git a/vngcloud/services/volume/v2/irequest.go b/vngcloud/services/volume/v2/irequest.go index 189dcc5..55557a9 100644 --- a/vngcloud/services/volume/v2/irequest.go +++ b/vngcloud/services/volume/v2/irequest.go @@ -61,3 +61,12 @@ type IDeleteSnapshotByIdRequest interface { type IGetUnderBlockVolumeIdRequest interface { GetBlockVolumeId() string } + +type IMigrateBlockVolumeByIdRequest interface { + GetBlockVolumeId() string + ToRequestBody() interface{} + WithTags(ptags ...string) IMigrateBlockVolumeByIdRequest + WithAction(paction MigrateAction) IMigrateBlockVolumeByIdRequest + WithConfirm(pconfirm bool) IMigrateBlockVolumeByIdRequest + IsConfirm() bool +} diff --git a/vngcloud/services/volume/v2/url.go b/vngcloud/services/volume/v2/url.go index fc98c32..e013b79 100644 --- a/vngcloud/services/volume/v2/url.go +++ b/vngcloud/services/volume/v2/url.go @@ -81,3 +81,12 @@ func getUnderBlockVolumeIdUrl(psc lsclient.IServiceClient, popts IGetUnderBlockV "mapping", ) } + +func migrateBlockVolumeByIdUrl(psc lsclient.IServiceClient, popts IMigrateBlockVolumeByIdRequest) string { + return psc.ServiceURL( + psc.GetProjectId(), + "volumes", + popts.GetBlockVolumeId(), + "change-device-type", + ) +}